一.RTC
使用RTC直接再cubeMX中配置启动时钟和日历
如第六届省赛
想要让RTC的秒每隔一秒递增1需要在时钟树界面观察RTC的主频
由于RTC时钟主频为32KHZ将异步预分频计数器的值设为31,将同步预分频计数器的值设为999这样就可以将RTC的时钟信号分频为1HZ达到1秒自增的效果
生成代码首先定义时间,日期结构体
RTC_TimeTypeDef T={0};
RTC_DateTypeDef D={0};
然后使用两个函数得到时间和日期就可以啦
void RTC_proc(void)
{
if(uwTick-rtc_tick<100)
return ;
rtc_tick=uwTick;
HAL_RTC_GetTime(&hrtc,&T,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&D,RTC_FORMAT_BIN);
}
放在LCD显示
sprintf((char *)lcd_buf," T:%02d-%02d-%02d ",T.Hours,T.Minutes,T.Seconds);
LCD_DisplayStringLine(Line8,lcd_buf);
RTC很简单只要记得结构体和获取时间,日期就行要注意的是在RTC函数中要先获取时间然后再获取日期,省赛好像只考过两回没有什么大的区别这里只展示第六届省赛
二.ADC
G4板子有两个ADC一般省赛用的较多的是R37,我们这里展示R37,R38的操作和R37一模一样
在cubemx中选择PB15然后选择ADC2——IN15然后将通道15打开就可以了
u32 r37_value=0;
float r37_volt=0;
然后定义两个变量存放测量值和电压值,注意value要定义成u32不然会导致数据放不下而使得结果出现问题
然后就可以写R37函数了
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);//开启ADC校验
HAL_ADC_Start(&hadc2);//开启ADC
r37_value=HAL_ADC_GetValue(&hadc2);//获得value
r37_volt=r37_value*3.3/4096.0;//计算电平
有的省赛题目会要求ADC进行滤波这里我们采用均值滤波
如:
第七届省赛
采用均值滤波法设置一个求和变量
u32 ADC_tick=0;
u32 R37_value=0;
u32 R37_sum=0;
float R37_volt=0;
u8 height=0;
第七届ADC代码展示
void ADC_proc(void)
{
if(uwTick-ADC_tick<1000)
return ;
ADC_tick=uwTick;
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);
HAL_ADC_Start(&hadc2);
R37_value=HAL_ADC_GetValue(&hadc2);
for(int i=0;i<10;i++)
{
R37_sum+=R37_value;
}
R37_volt=(R37_sum/10.0)*3.3/4096.0;
R37_sum=0;
height=R37_volt*(100.0/3.3);
HAL_ADC_Stop(&hadc2);
}
这里的原理就是用求和均值达到滤波的要求
三.LCD
这个不用多说,直接上内容
以第六届代码为例
首先是LCD的初始化
LCD_Init();
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
LCD_Clear(Black);
这里要注意初始化的位置一定要在
/* USER CODE BEGIN 2 */
这里不然的话就会显示白屏
u8 ui=0;
u8 lcd_buf[50];
void LCD_proc(void)
{
if(uwTick-lcd_tick<300)
return ;
lcd_tick=uwTick;
if(ui==0)
{
sprintf((char *)lcd_buf," V1:%4.2fV ",r37_volt);
LCD_DisplayStringLine(Line2,lcd_buf);
sprintf((char *)lcd_buf," K:%.1f ",K_value);
LCD_DisplayStringLine(Line4,lcd_buf);
if(led_mode==0)
sprintf((char *)lcd_buf," LED:%s ","OFF");
else
sprintf((char *)lcd_buf," LED:%s ","ON");
LCD_DisplayStringLine(Line6,lcd_buf);
sprintf((char *)lcd_buf," T:%02d-%02d-%02d ",T.Hours,T.Minutes,T.Seconds);
LCD_DisplayStringLine(Line8,lcd_buf);
LCD_DisplayStringLine(Line9,(unsigned char *)" 1");
}
else if(ui==1)
{
sprintf((char *)lcd_buf," Setting ");
LCD_DisplayStringLine(Line3,lcd_buf);
if(time_light==1)
{
time_light=0;
sprintf((char *)lcd_buf," %02d-%02d-%02d ",T_Start.Hours,T_Start.Minutes,T_Start.Seconds);
}
else
{
time_light=1;
if(time_line==0)
sprintf((char *)lcd_buf," -%02d-%02d ",T_Start.Minutes,T_Start.Seconds);
else if(time_line==1)
sprintf((char *)lcd_buf," %02d- -%02d ",T_Start.Hours,T_Start.Seconds);
else if(time_line==2)
sprintf((char *)lcd_buf," %02d-%02d- ",T_Start.Hours,T_Start.Minutes);
}
LCD_DisplayStringLine(Line5,lcd_buf);
LCD_DisplayStringLine(Line9,(unsigned char *)" 2");
}
}
还需要注意的是题目一般都是从Line1到Line10但是官方给的例程LCD是从Line0到Line9所以题目上让显示第二行的话我们代码应该写Line1这个需要注意不然到时LCD显示会串行
LCD有时候会考到高亮显示
如:第七届省赛
高亮显示就是将要高亮显示的内容换个背景颜色高亮显示完成后再换回原来的颜色
sprintf((char *)lcd_buff," Parameter Setup ");
LCD_DisplayStringLine(Line1,lcd_buff);
if(threshold_line==0)
LCD_SetBackColor(Yellow);
sprintf((char *)lcd_buff," Threshold1:%dcm ",Threshold[0]);
LCD_DisplayStringLine(Line4,lcd_buff);
LCD_SetBackColor(White);
if(threshold_line==1)
LCD_SetBackColor(Yellow);
sprintf((char *)lcd_buff," Threshold2:%dcm ",Threshold[1]);
LCD_DisplayStringLine(Line6,lcd_buff);
LCD_SetBackColor(White);
if(threshold_line==2)
LCD_SetBackColor(Yellow);
sprintf((char *)lcd_buff," Threshold3:%dcm ",Threshold[2]);
LCD_DisplayStringLine(Line8,lcd_buff);
LCD_SetBackColor(White);
以及数字闪烁
拿第六届代码做个示范(第六届没有要求这里是自己加的)
闪烁就是将原本该显示数字的位置拿空格替换掉就可以了注意只替换要闪烁的部分不然会导致一整行都开始左右移动
if(time_line==0)
sprintf((char *)lcd_buf," -%02d-%02d ",T_Start.Minutes,T_Start.Seconds);
else if(time_line==1)
sprintf((char *)lcd_buf," %02d- -%02d ",T_Start.Hours,T_Start.Seconds);
else if(time_line==2)
sprintf((char *)lcd_buf," %02d-%02d- ",T_St
此外
第十三届赛题的LCD密码这里也给大家展示一下
int password[3]={-1,-1,-1};//密码初始显示-1时显示字符@
void LCD_INPUT(void)//LCD输入密码界面
{
sprintf((char *)lcd_buff," PSD ");
LCD_DisplayStringLine(Line1,lcd_buff);
if(ui==0)//处于界面一
{
for(int i=0;i<3;i++)//for循环控制三位密码
{
if(password[i]==-1)//如果密码是初始状态显示字符@
{
sprintf((char *)lcd_buff," B%d:%c ",i+1,'@');
}
else//否则显示对应的数字
{
sprintf((char *)lcd_buff," B%d:%d ",i+1,password[i]);
}
switch(i)//规定每位密码在LCD的那一行显示
{
case 0:
LCD_DisplayStringLine(Line3,lcd_buff);
break;
case 1:
LCD_DisplayStringLine(Line4,lcd_buff);
break;
case 2:
LCD_DisplayStringLine(Line5,lcd_buff);
break;
default:
break;
}
}
}
}
void LCD_OUTPUT(void)
{
if(ui==1)//处于界面二
{
sprintf((char *)lcd_buff," STA ");
LCD_DisplayStringLine(Line1,lcd_buff);
sprintf((char *)lcd_buff," F:%dHz ",FRQ);
LCD_DisplayStringLine(Line3,lcd_buff);
sprintf((char *)lcd_buff," D:%d%% ",(int)(duty*100));
LCD_DisplayStringLine(Line4,lcd_buff);
}
}
四.按键
在cubemx直接将对应引脚设置为GPIO_INPUT
代码里面首先定义几个变量
u8 key_down,key_up,key_value,key_old=0;
按键读取函数
void key_read(void)
{
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET)
key_value=1;
else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET)
key_value=2;
else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET)
key_value=3;
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
key_value=4;
else
key_value=0;
key_down=key_value&(key_value^key_old);
key_up=~key_value&(key_value^key_old);
key_old=key_value;
}
按键执行函数(以第六届代码为例)
void key_proc(void)
{
if(uwTick-key_tick<20)
return ;
key_tick=uwTick;
key_read();
uint16_t b4_sum=0;
uint16_t b4_bclk=0;
if(key_down==1&&ui==0)
{
led_mode = !led_mode;
}
else if(key_down==2)
{
ui=!ui;
LCD_Clear(Black);
T_vaild.Hours=T_Start.Hours;
T_vaild.Minutes=T_Start.Minutes;
T_vaild.Seconds=T_Start.Seconds;
}
else if(key_down==3&&ui==1)
{
time_line++;
if(time_line>2)
time_line=0;
}
else if(key_down==4&&ui==1)
{
b4_bclk++;
if(time_line==0)
{
T_Start.Hours++;
if(b4_bclk==2)
{
T_Start.Hours--;
if(T_Start.Hours<=0)
{
T_Start.Hours=0;
}
}
if(T_Start.Hours>23)
T_Start.Hours=0;
}
else if(time_line==1)
{
T_Start.Minutes++;
if(T_Start.Minutes>60)
T_Start.Minutes=0;
}
else if(time_line==2)
{
T_Start.Seconds++;
if(T_Start.Seconds>60)
T_Start.Seconds=0;
}
}
}
前面都是一些按键简单功能在省赛中有时会考到按键的长短按键
如:第九届省赛
按键长短按可以拿定时器完成这里在后续的定时器部分展示,我们这里使用的是一种比较简单的方法即写一个宏定义规定长按键的时间然后利用系统的滴答定时器完成长短按键
#define long_press_time 800
按键代码
void KEY_proc(void)
{
if(uwTick-key_tick<50)
return ;
key_tick=uwTick;
key_read();
if(key_down==1)
{
s_line=0;
time_line=3;
number++;
if(number%6==0)
number=1;
}
else if(key_down==2)//按键按下
{
k2_press_start_tick=uwTick;//开始计时
key_long_flag=0;//长按键标志位0表示不是长按
}
else if(key_value==2)
{
if(uwTick-k2_press_start_tick>=long_press_time)//计时时间达到我们规定的长按时间
{
time_line=3;
// addre=0x01;
// for(int i=0;i<5;i++)
// {
// EEP_Write(addre++,time[i].hour);
// EEP_Write(addre++,time[i].minue);
// EEP_Write(addre++,time[i].secend);
// }
EEP_Write(0x01,time[1].hour);//储存
EEP_Write(0x02,time[1].minue);
EEP_Write(0x03,time[1].secend);
EEP_Write(0x04,time[2].hour);
EEP_Write(0x05,time[2].minue);
EEP_Write(0x06,time[2].secend);
EEP_Write(0x07,time[3].hour);
EEP_Write(0x08,time[3].minue);
EEP_Write(0x10,time[3].secend);
EEP_Write(0x11,time[4].hour);
EEP_Write(0x12,time[4].minue);
EEP_Write(0x13,time[4].secend);
EEP_Write(0x14,time[5].hour);
EEP_Write(0x15,time[5].minue);
EEP_Write(0x16,time[5].secend);
s_line=0;//设置状态
key_long_flag=1;//标志位置1表示长按
}
}
else if(key_up==2)//短按
{
if(key_long_flag)
{
}
else
{
if(s_line!=1)//不是设置状态
{
time_line=3;//谁也没选中
s_line=1;//变为设置状态
return ;
}
time_line++;
if(time_line>2)//切换设置位置
time_line=0;
}
}
else if(key_down==3)
{
k3_press_start_tick=uwTick;
key_long_flag=0;
}
else if(key_value==3)
{
if(uwTick-k3_press_start_tick>=long_press_time)//长按
{
key_long_flag=1;
if(time_line==0)//长按一直加
{
time[number].hour++;
if(time[number].hour>23)
time[number].hour=0;
}
if(time_line==1)
{
time[number].minue++;
if(time[number].minue>59)
time[number].minue=0;
}
if(time_line==2)
{
time[number].secend++;
if(time[number].secend>59)
time[number].secend=0;
}
}
}
else if(key_up==3)
{
if(key_long_flag)
{
}
else
{
if(time_line==0)//短按加
{
time[number].hour++;
if(time[number].hour>23)
time[number].hour=0;
}
if(time_line==1)
{
time[number].minue++;
if(time[number].minue>59)
time[number].minue=0;
}
if(time_line==2)
{
time[number].secend++;
if(time[number].secend>59)
time[number].secend=0;
}
}
}
else if(key_down==4)
{
k4_press_start_tick=uwTick;
key_long_flag=0;
}
else if(key_value==4)
{
if(uwTick-k4_press_start_tick>=long_press_time)//长按
{
s_line=0;
key_long_flag=1;
}
}
else if(key_up==4)
{
if(key_long_flag)
{
s_line=0;
printf(" s_lin=%d\r\n",s_line);
}
else//短按
{
if(s_line!=2)
{
s_line=2;
printf(" s_lin2=%d\r\n",s_line);
}
else
{
s_line=3;
printf(" s_lin3=%d\r\n",s_line);
}
if(time[number].hour==0&&time[number].minue==0&&time[number].secend==0)
{
s_line=3;
}
}
}
}
还有按键的双击(这个目前为止没有考到过但是以防万一我们在这里简单写一下)
#define double_click_time 1000//定义双击时间
代码展示这里
void KEY_proc(void)
{
if(uwTick-key_tick<50)
return ;
key_tick=uwTick;
key_read();
if(key_down==1)//按键按下
{
k1_press__start_tick=uwTick;//开始计时
key_long_flag=0;//刚开始未达到时间要求
}
else if(key_value==1)//按键1按下
{
if(uwTick-k1_press__start_tick>=long_press_time)//超过时间判定为长按
{
key_long_flag=1;//长按标志位置1
k1++;
}
}
else if(key_up==1)//按键短按
{
k1+=1;
// if(key_long_flag)
// {
//
//
// }
// else
// {
// k1+=1;
//
// }
}
if(key_down==2)
{
k2_press__start_tick=uwTick;
key_long_flag=0;
}
else if(key_value==2)
{
if(uwTick-k2_press__start_tick>=long_press_time)
{
key_long_flag=1;
k2++;
}
}
if(key_down==3)//按键3按下
{
if(uwTick-k3_press__last_tick<=double_click_time)//小于设置时间内如果再次检测到按键按下判定为双击按键
{
if(k1>0)
k1--;
}
k3_press__last_tick=uwTick;//更新时间为了下一次按键双击判断
}
if(key_down==4)
{
if(uwTick-k4_press__last_tick<=double_click_time)
{
if(k2>0)
k2--;
}
k4_press__last_tick=uwTick;
}
}
五.LED
这个只需要注意别忘了PD2锁存引脚就行,可以在配置完引脚后现将LED引脚都拉高
以第六届代码为例(这里闪烁使用的是滴答定时器)
void led_disp(u8 led)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,0XFF00,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,led<<8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
void led_proc(void)
{
if(uwTick-led_tick<200)
return ;
led_tick=uwTick;
// if(led_num !=0)
// led_num=0;
// else
// led_num=0xff;
if(r37_volt>3.3f*K_value&&led_mode==1)
{
led_num ^=0x01;
}
else
led_num&=~0x01;
led_disp(led_num);
}
六.E2PROM
这个可以去芯片手册里找到E2PROM读写图照着图打代码就可以了
第六届的E2PROM就很典型这里我们展示第六届E2PROM代码
首先先去芯片手册中找到读写时序图
因为这个也是官方提供例程的所以我们直接添加官方例程然后再自己的.C文件中直接先读写函数就可以了
void EEP_write(uint8_t add,uint8_t date)
{
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(add);
I2CWaitAck();
I2CSendByte(date);
I2CWaitAck();
I2CStop();
HAL_Delay(5);
}
E2PROM写函数既要地址有需要数据所以要有两个参数,写函数需要9行,0XA0是写操作这个要在芯片手册里看现在直接记住就行
uint8_t EEP_read(uint8_t add)
{
uint8_t data;
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(add);
I2CWaitAck();
I2CStart();
I2CSendByte(0xA1);
I2CWaitAck();
data=I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return data;
}
E2PROM读刚开始需要定义一个变量存放读到的数据最后要放回数据值一般为13行0XA1是读操作直接记住就行
这里给大家放一下第六届E2PROM要求
这里要求将串口修改的K值存储所以存储代码和后面的串口放在一起展示
这里要只要我们还需要初始I2C
I2CInit();
if(EEP_read(123) !=123)
{
EEP_write(123,123);
K_value=0.1;
EEP_write(0x01,K_value*10);
}
else
K_value=EEP_read(0x01)/10.0;
这里if的作用是判断是否是第一次写入如果是第一次写入(也就是123!=123这里123是自己定义的也可以是其他数字)那就写入,不过不是第一次写入就读取
这里要注意EEP——witre要写入整型(也位传入写浮点型但是我不会那种)所以在写入时给k*10转换成整型然后再读出k的值后再除以10变回原来的值
七.串口
这个在cubemx中只需要配置好引脚然后再改一下波特率就可以了波特率看赛题一般为9600,要注意的是记得勾选接收中断
这里串口展示我们选择第六届串口以及第十二届串口
首先我们现将串口的重定向函数加到代码中,这个重定向函数不用记忆到时可以在keil中找到
查找步骤:
点击列出主题,选择第一个双击就可以找到啦
第六届串口模块:
定时上报电压功能:
void tx_proc(void)
{
if(uwTick-tx_tick<1000)
return ;
tx_tick=uwTick;
if(T.Hours==T_vaild.Hours&&T.Minutes==T_vaild.Minutes&&T.Seconds==T_vaild.Seconds)
printf("%.2f+%.1f+%02d%02d%02d\n",r37_volt,K_value,T_vaild.Hours,T_vaild.Minutes,T_vaild.Seconds);
}
记得要在重定向函数中添加串口发送函数
struct __FILE
{
int handle;
};
FILE __stdout;
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,50);
return ch;
}
在写接收函数之前要在初始化位置打开接收中断
HAL_UART_Receive_IT(&huart1,&rx_data,1);
定义变量
u8 rx_data,rx_pointer;
u8 rx_buff[30];
u32 rx_tick=0;
设置K值
memset(rx_buff,0,sizeof(rx_buff));
这个函数作用就是将缓存区中的内容清零,这样这次接收的东西不会影响下次接收的内容
第十二届串口模块:
赛题要求如下:
这届串口功能很难,我在写这个代码时参考了一位国一大佬的源码这个在博客十二届省赛中有大佬文章链接大家感兴趣的可以去看看
重定向和上面的第六届的一样这里就不给大家写了
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
超时时间不想定义为50的话可以定义为最大超时时间
由于串口功能过于复杂这里关于串口写了好几个函数
1.判断合法性
unsigned char isRxCplt()//判断串口是否合法
{
if(rx_dex!=22)//如果字符不是22位
return 0;//不合法返回0
if(((rx_buf[0]=='C')||(rx_buf[0]=='V'))&&(rx_buf[1]=='N')&&(rx_buf[2]=='B')&&(rx_buf[3]=='R')&&(rx_buf[4]==':')&&(rx_buf[9]==':'))//判断字符是否符合题目格式
{
unsigned char i;
for(i=10;i<22;i++)
{
if((rx_buf[i]>'9')||(rx_buf[i]<'0'))//判断时间是否合法
return 0;//不合法返回0
}
return 1;//合法返回1
}
else
return 0;//不合法
}
2.判断车辆是否已经停在停车场
unsigned char isexis(unsigned char *str)
{
unsigned char i;
for(i=0;i<8;i++)//一共八辆车从第一辆开始比对
{
if(strcmp((char *)str,(char *)car_data[i].id)==0)//判断传入的字符串是否与车辆id相同相同则表示车辆已经在停车场中
return i;
}
return 0xFF;//如果不相同则没有在停车场中
}
3.判断停车场是否有空位置
unsigned char isempty()
{
unsigned char i;
for(i=0;i<8;i++)//一次检索八个位置
{
if(car_data[i].empty==0)//之前设置的标志位标志位为0表示这个位置为空
return i;
}
return 0xFF;//没有空闲位置,停车场满了
}
4.车辆进出停车场信息储存
void srt_tran(unsigned char*d_str,unsigned char*str,unsigned char num,unsigned char lenth)//将str中的部分信息提取到d_str中存储起来,num表示源码位置,length表示要截取字符串的长度
{
unsigned char i;
for(i=0;i<lenth;i++)//依次存储八辆车的信息
{
d_str[i]=str[num+i];//存储
d_str[lenth]='\0';//将字符串的最后一位设置为\0作为字符串的结束
}
}
5.总的串口功能
void UART_proc(void)
{
static __IO uint32_t UART_tick;
unsigned char tx[30];
if(uwTick-UART_tick<100)
return ;
UART_tick=uwTick;
unsigned char car_id[5],car_type[5],year,month,day,hour,min,sec;//定义车辆信息用于进行提取存储
// if(!isRxCplt())
// {
// sprintf((char *)tx,"Error\r\n");
// HAL_UART_Transmit(&huart1,tx,strlen(tx),50);
// memset(&rx_buf,0,sizeof(rx_buf));
// rx_dex=0;
// return ;
// }
if(isRxCplt())//数据合法
{
year=(rx_buf[10]-'0')*10+(rx_buf[11]-'0');//由于是字符串所以需要转化成数字
month=(rx_buf[12]-'0')*10+(rx_buf[13]-'0');
day=(rx_buf[14]-'0')*10+(rx_buf[15]-'0');
hour=(rx_buf[16]-'0')*10+(rx_buf[17]-'0');
min=(rx_buf[18]-'0')*10+(rx_buf[19]-'0');
sec=(rx_buf[20]-'0')*10+(rx_buf[21]-'0');
if((month>12)||(day>31)||(hour>23)||(min>59)||(sec>59))//时间不合法
goto SEND_ERROR;//这是一个跳转指令,不合法就跳转到ERROR执行ERROR
srt_tran(car_id,rx_buf,5,4);//提取id信息
srt_tran(car_type,rx_buf,0,4);//提取车辆类型信息
if(isexis(car_id)==0xFF)//如果车辆不存在
{
unsigned char in_close=isempty();//判断是否有空闲位置,有空闲位置close等于空闲位置
if(in_close==0xFF)//没有空闲位置
goto SEND_ERROR;//跳转
srt_tran(car_data[in_close].type,car_type,0,4);//提取车辆类型到空闲位置中表示车辆停进去了
srt_tran(car_data[in_close].id,car_id,0,4);//提取车辆id到位置信息中
car_data[in_close].year=year;//将停车时间赋值给位置信息的时间
car_data[in_close].month=month;
car_data[in_close].day=day;
car_data[in_close].hour=hour;
car_data[in_close].min=min;
car_data[in_close].sec=sec;
car_data[in_close].empty=1;//空闲标志位置1表示有车停入
if(car_data[in_close].type[0]=='C')//如果车辆类型是C那么C的数量加一
cnbr_number++;
if(car_data[in_close].type[0]=='V')//同理
vnbr_number++;
idle_numer--;//一共八个停车位置有车停进总位置要减1
}
else if(isexis(car_id)!=0xFF)//如果车辆之前已经在停车场了,就表示要出停车场
{
unsigned char out_locate=isexis(car_id);//获取车辆的位置信息
signed int time;//定义时间自然数,可正可负可为0
if(strcmp((char *)car_type,(char *)car_data[out_locate].type)!=0)//比对车辆编号与车辆类型是否一致
goto SEND_ERROR;//不一致出错跳转
time=(year-car_data[out_locate].year)*365*24*60*60+(month-car_data[out_locate].month)*31*24*60*60+(day-car_data[out_locate].day)*24*60*60+(hour-car_data[out_locate].hour)*60*60+(min-car_data[out_locate].min)*60+(sec-car_data[out_locate].sec);//将总体的时间转化为秒用于计算费用
if(time<0)//时间小于0,时间不合法
goto SEND_ERROR;//跳转
time=(time+3599)/3600;//不足一个小时按一个小时计算
sprintf((char *)tx,"%s:%s:%d:%.2f\r\n",car_data[out_locate].type,car_data[out_locate].id,time,(car_data[out_locate].type[0]=='C'?time*cnbr:time*vnbr));
HAL_UART_Transmit(&huart1,tx,strlen(tx),50);
if(car_data[out_locate].type[0]=='C')//如果出停车的车辆类型是C,那么停车场内C的数量减1
cnbr_number--;
else if(car_data[out_locate].type[0]=='V')//同理
vnbr_number--;
memset(&car_data[out_locate],0,sizeof(car_data[out_locate]));//清空这个车位的车辆信息方便下一辆车记录
}
}
if(rx_dex>0&&!isRxCplt())//数据不合法
{
printf("Error\r\n");//输出EEOR
}
goto CLEAR;//跳转到清除语句
SEND_ERROR://错误语句
sprintf((char *)tx,"Error\r\n");//输出ERROR
HAL_UART_Transmit(&huart1,tx,strlen(tx),50);//串口发送
CLEAR://清除语句
memset(&rx_buf,0,sizeof(rx_buf));//清除缓存区内的内容
rx_dex=0;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef*huart)
{
if(rx_dex< sizeof(rx_buf))
{
rx_buf[rx_dex++]=rx;
}
else
{
memset(rx_buf,0,sizeof(rx_buf));
rx_dex=0;
}
HAL_UART_Receive_IT(&huart1,&rx,1);
}
八.PWM
以第九届省赛为例
PWM函数
void RWM_proc(void)
{
if(s_line==2)
{
HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);
}
else
{
HAL_TIM_PWM_Stop(&htim16,TIM_CHANNEL_1);
}
}
这道题只要求打开PWM和关闭PWM比较单一
所以我们展示一下第十四届PWM功能(输入输出都有)
赛题
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);//PWM
HAL_TIM_IC_Start_IT(&htim17,TIM_CHANNEL_1);//输入捕获
由于频率部分和ADC部分密切相关我们这里一起展示
高低频转换计算速度以及更新最大值
void ADC_frq()
{
velo=(frq1*2*3.14*R)/(100*K);//将PA7测量的频率值转化为速度
duty=((double)TIM2->CCR2/(TIM2->ARR+1))*100;//直接用寄存器计算PWM的占空比
ADC_frq1=getADC(&hadc2)*0.375-0.275;//根据图上的信息表示ADC的值与频率的关系
if(ADC_frq1<0.1) ADC_frq1=0.1;//看图ADC值小于0.1时等于0.1
if(ADC_frq1>0.85) ADC_frq1=0.85;//同理
if(fre_flag == 0 || fre_flag == 2)//低频高频时更新CCR的值用于更新占空比
{
TIM2->CCR2 = ADC_frq1 * (TIM2->ARR + 1);
}
}
void HL_MAN()
{
if(fre_flag==0)//低频
{
if(max_L<velo) max_L=velo;//更新速度最大值
}
if(fre_flag==2)//高频
{
if(max_H<velo) max_H=velo;//更新速度最大值
}
}
步进值
uint16_t fre4000=4000;
uint16_t fre8000=8000;
if(htim->Instance==TIM6)
{
if(fre_flag==1)//转化过程中
{
count++;//计时
fre4000+=80;//频率每100毫秒加80
TIM2->CNT=0;//定时器计数器清零
TIM2->ARR = 1000000/fre4000 -1;//更新自动重装载器的值
TIM2->CCR2 = ADC_frq1 * (TIM2->ARR +1);//更新CCR的值
if(count>=50)//5秒时间达到
{
count=0;//计数清零
fre_flag=2;//转化成高频
fre4000=4000;//低频值重新值为4000
}
}
if(fre_flag==3)//高频
{
count++;
fre8000-=80;//每100毫秒减80
TIM2->CNT=0;
TIM2->ARR = 1000000/fre8000 -1;
TIM2->CCR2 = ADC_frq1 * (TIM2->ARR +1);
if(count>=50)
{
count=0;
fre_flag=0;
fre8000=8000;
}
}
}
}
uint16_t ccrl_val1a=0,ccrl_val1b=0;
uint32_t frq1=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM3)
{
ccrl_val1a=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2)+1;//捕获频率值
__HAL_TIM_SetCounter(htim, 0);//计数器清零
frq1=(80000000 / 80) /ccrl_val1a;//计算频率值
HAL_TIM_IC_Start(htim,TIM_CHANNEL_2);//开启PWM捕获
}
}
步进值就不在这里给大家解释了大家看这两位位大佬的文章
【蓝桥杯嵌入式】蓝桥杯嵌入式第十四届省赛程序真题,真题分析与代码讲解_蓝桥杯 嵌入式-CSDN博客
第十四届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)_蓝桥杯嵌入式14届省赛-CSDN博客
九.定时器
在第九届省赛里面涉及到了一个进行一秒递减的定时器我们在这里展示一下
首先现在cubeMX里面配置一个定时器7
在使用定时器前现在初始化部分将定时器打开
HAL_TIM_Base_Start_IT(&htim7);
然后再it.C函数中找到定时器中断回调函数
添加进我们的.C文件中直接编写就行啦
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==htim7.Instance)
{
time_cnt++;
if(time_cnt==1000)
{
time_cnt=0;
if(s_line==2)
{
if(time[number].secend>0)
{
time[number].secend--;
}
else if(time[number].minue>0)
{
time[number].secend=59;
time[number].minue--;
}
else if(time[number].hour>0)
{
time[number].minue=59;
time[number].secend=59;
time[number].hour--;
}
else
{
s_line=0;
}
}
}
}
}
这里为什么CNT等于1000时是1秒我们做一个解释
等cnt等于1000时刚好达到1秒