蓝桥杯嵌入式考前模块总结
一.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);//开启ADCr37_value=HAL_ADC_GetValue(&hadc2);//获得valuer37_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");elsesprintf((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;elsekey_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;//长按标志位置1k1++;}}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;}elseled_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;//不合法返回0if(((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}
elsereturn 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执行ERRORsrt_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;//定义时间自然数,可正可负可为0if(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的数量减1cnbr_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");//输出ERRORHAL_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);//PWMHAL_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.1if(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毫秒加80TIM2->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毫秒减80TIM2->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秒
明天就要比赛了,祝大家旗开得胜取得自己满意的成绩,比赛加油!!
相关文章:
蓝桥杯嵌入式考前模块总结
一.RTC 使用RTC直接再cubeMX中配置启动时钟和日历 如第六届省赛 想要让RTC的秒每隔一秒递增1需要在时钟树界面观察RTC的主频 由于RTC时钟主频为32KHZ将异步预分频计数器的值设为31,将同步预分频计数器的值设为999这样就可以将RTC的时钟信号分频为1HZ达到1秒自增的…...
关于举办“2025年第五届全国大学生技术创新创业大赛“的通知
赛事含金量 大赛获奖即可有机会为你的大学里的“创新创业”加分!这是每个大学要求必须修满的学分! 中国“互联网+”大学生创新创业大赛磨刀赛!“挑战杯”中国大学生创业计划大赛必参赛! 国赛获奖,“互联…...
spark安装过程问题
1. Spark-local模式 - 适用于单节点环境,无需启动Hadoop集群。 - 实验步骤包括解压文件、启动Local环境、运行命令行工具、提交测试应用等。 - 通过bin/spark-shell启动本地环境,通过sc.textFile等命令测试功能。 - 提交应用时使用--master loca…...
Ingress蓝绿发布
Ingress蓝绿发布 Ingress常用注解说明yaml资源清单绿色版本yml资源清单蓝色版本yaml资源清单 主Ingress金丝雀Ingress基于客户端请求头的流量切分结果验证 基于客户端来源IP的流量切分结果验证 基于服务权重的流量切分结果验证 基于IP来源区域来切分IP---方案未验证基于User-Ag…...
基于AOP+Log4Net+AutoFac日志框架
1.项目概述 这是一个基于 C# 的 WPF 项目 WpfApp12log4net,它综合运用了依赖注入、日志记录和接口实现等多种技术,同时使用了 Autofac、Castle.Core 和 log4net 等第三方库。 2.配置log4net 新建一个Log4Net.config,配置需要记录的日志信息…...
python推箱子游戏
,--^----------,--------,-----,-------^--,-------- 作者 yty---------------------------^----------_,-------, _________________________XXXXXX XXXXXX XXXXXX ______(XXXXXXXXXXXX(________(------ 0 [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,0,0,0,0,0,0,0,0,0,0,0,…...
华为hcie证书的有效期怎么判断?
在ICT行业,华为HCIE证书堪称含金量极高的“敲门砖”,拥有它往往意味着在职场上更上一层楼。然而,很多人在辛苦考取HCIE证书后,却对其有效期相关事宜一知半解。今天,咱们就来好好唠唠华为HCIE证书的有效期怎么判断这个关…...
关于 Spring Boot 部署到 Docker 容器的详细说明,涵盖核心概念、配置步骤及关键命令,并附上表格总结
以下是关于 Spring Boot 部署到 Docker 容器的详细说明,涵盖核心概念、配置步骤及关键命令,并附上表格总结: 1. Docker 核心概念 概念描述关系镜像(Image)预定义的只读模板,包含运行环境和配置(…...
PowerBI 条形图显示数值和百分比
数据表: 三个度量值 销售额 SUM(销量表[销售量])//注意, 因为Y轴显示的产品,会被筛选,所以用ALLSELECTED来获取当前筛选条件下,Y轴显示的产品 百分比 FORMAT(DIVIDE([销售额],CALCULATE([销售额],ALLSELECTED(销量表[产品编码]))),"0…...
基于YOLOv8的火车轨道检测识别系统:技术实现与应用前景
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 1. 引言:火车轨道检测领域概述 铁路运输作为国民经济的大动脉,其安全运行至关重要…...
css使用mix-blend-mode的值difference实现内容和父节点反色
1. 使用场景 往往开发过程中,经常遇到产品说你这个背景图和文字颜色太接近了,能不能适配下背景图,让用户能够看清具体内容是啥。 这么说吧,这种需求场景非常合理,因为你做开发就是要给用户一个交代,给他们…...
【从零开始学习JVM | 第二篇】HotSpot虚拟机对象探秘
对象的创建 1.类加载检查 虚拟机遇到一条new的指令,首先去检查这个指令的参数能否在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行类的加载过程。 2.分配内存 在类…...
Pytest多环境切换实战:测试框架配置的最佳实践!
你是否也遇到过这种情况:本地测试通过,一到测试环境就翻车?环境变量错乱、接口地址混乱、数据源配置丢失……这些「环境切换」问题简直像定时炸弹,随时引爆你的测试流程! 测试人员每天都跟不同的环境打交道࿰…...
单细胞多组学及空间组学数据分析与应用
一、引言 生命科学研究正处于快速发展的阶段,随着技术的不断革新,对生物系统的理解也在逐步深入到单细胞和空间层面。单细胞多组学及空间组学技术应运而生,它们突破了传统研究手段在细胞异质性和空间结构解析上的局限,为我们打开…...
[ctfshow web入门] web39
信息收集 题目发生了微妙的变化,只过滤flag,include后固定跟上了.php。且没有了echo $flag;,虽说本来就没什么用 if(isset($_GET[c])){$c $_GET[c];if(!preg_match("/flag/i", $c)){include($c.".php");} }else{…...
HarmonyOS-ArkUI 装饰器V2 @ObservedV2与@Trace装饰器
参考文档: 文档中心https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V14/arkts-new-observedv2-and-trace-V14#trace%E8%A3%85%E9%A5%B0%E5%AF%B9%E8%B1%A1%E6%95%B0%E7%BB%84由于V2的装饰器比V1的装饰器更加易用,尽管学习的过程中用到的都是V1的装饰器,但…...
Spring配置方式演进:从XML到注解,构建灵活高效的开发体系
Spring配置方式演进:从XML到注解,构建灵活高效的开发体系 在Spring框架的演进长河中,配置方式始终是开发者需要掌握的核心技能。从早期XML一统天下的严谨规范,到注解驱动的敏捷开发,再到如今Java Config的优雅实践&am…...
基于ImGui+FFmpeg实现播放器
基于ImGuiFFmpeg实现播放器 演示: ImGui播放器 继续研究FFmpeg,之前做了一个SDL的播放器,发现SDL的可视化UI界面的功能稍微差了点,所以今天我们换了一个新的工具,也就是ImGui。 ImGui官方文档:https://g…...
python的web框架flask(hello,world版)
问题 最近需要基于一个开源项目进行二次开发,但是,现在的我主修java,从来没有接触过python的web开发。所以,我现在需要学习一下flask的hello,world。 python版本选择 通过这个Python版本状态页面Status of Python v…...
Java面试39-Zookeeper中的Watch机制的原理
Zookeeper是一个分布式协调组件,为分布式架构下的多个应用组件提供了顺序访问控制能力。它的数据存储采用了类似于文件系统的树形结构,以节点的方式来管理存储在Zookeeper上的数据。 Zookeeper提供了一个Watch机制,可以让客户端感知到Zooke…...
同时打开多个Microchip MPLAB X IDE
0.引用 Microchip 32位MCU CAN驱动图文教程-附源码 - 哔哩哔哩 https://bbs.21ic.com/icview-3391426-1-1.html https://bbs.21ic.com/icview-3393632-1-1.html 1.前言 工作中接触到使用Microchip 的 MPLAB X IDE 开发工具,使用的MCU是Microchip SAMD21J18A MCU…...
达梦数据库使用druid提示:dbType not support : dm
简单处理: 移除wall即可 (但是用druid那都希望能用上它的功能的,不然为什么不用其他没带检查的jdbc呢。) 中等复杂处理: druid 是阿里开源的项目,所以去github上找对应版本的源码下载:https:/…...
[定位器]晶艺LA1823,4.5V~100V, 3.5A,替换MP9487,MP9486A,启烨科技
Features 4.5V to 100V Wide Input Range 3.5A Typical Peak Current Limit Integrated 500mΩ low resistance high side power MOS. Constant On Time Control with Constant Switching Frequency. 180μA Low Quiescent Current 150kHz/240kHz/420kHz Swi…...
难度偏低,25西电人工智能学院821、833、834考研录取情况
1、人工智能学院各个方向 2、人工智能学院近三年复试分数线对比 学长、学姐分析 由表可看出: 1、智能院25年院线相对于24年院线 全部专业下降比较多,其中控制科学与工程下降20分,计算机科学与技术下降20分,计算机技术[专硕]下降…...
使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第七讲)
这一期来讲解与文本框配套使用的键盘,以及键盘如何在项目中开启。 打开GUI_guider软件平台,在左上角点开工程选项,在该栏目的最下方点击系统设置。 随后在系统设置界面中点击项目选项,选择显示键盘。 在该界面中可以设置键盘文字…...
【C#知识点详解】List<T>储存结构详解
今天来介绍一下List内部的存储结构,话不多说直接开始。 内部数据 List内部采用了连续数组的方式存储数据,其中包含了三个重要的成员变量,示例如下: // 用于存储数据的数组 internal T[] _items; // 列表中实际包含数据的数量 int…...
设计模式:代理模式 - 控制访问与增强功能的艺术
一、为什么使用代理模式? 在开发中,你是否遇到过以下问题: • 某些功能调用需要权限校验,但不希望修改核心逻辑? • 某些对象的创建开销过高,希望延迟加载以优化性能? • 在不改变原始类的情…...
C++二分查找
一、模板①:向下取整(mid (l r) >> 1) while (l < r) {int mid l r >> 1; // 等价于 (l r) / 2(向下取整)if (check(mid)) r mid; // 保留左半区else l mid 1; // 舍弃左半区 } 适用场…...
一个Linux/Java乱码问题的解决
公司有个项目采用的是spring-boot启动方式,以前上线正常,前几天重新上线后,突然发现上传的文件名中文乱码了。我了解了一下具体情况,以前是正常的,而且测试环境也都是正常的,就是生产环境本次上线后突发从页…...
【Java设计模式】第11章 装饰者模式讲解
11.1 装饰者模式讲解 定义:动态扩展对象功能,替代继承。类型:结构型模式适用场景: 运行时动态添加功能避免类爆炸(如多层装饰)优点: 比继承更灵活符合开闭原则缺点: 增加类/对象数量调试复杂度高11.2 装饰者模式 Coding // 抽象组件 public abstract class BatterCake…...

