笔者在自己备赛过程中整理记录了一下蓝桥杯嵌入式比赛中需要用到的主要模块的代码和一些注意事项,供大家一起复习和查漏补缺。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------笔者已进入国赛,将会持续对国赛的模块程序和注意事项持续更新....
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------目前笔记已经更新完,笔者最后成绩为嵌入式组国赛二等奖,祝愿之后的朋友们取得更好的成绩!
LED:
//LED_control()放在while(1)里面执行,不能放在中断里 //因为LCD与LED可能同时操作PC口,所以会与LCD显示相冲突, //只通过中断改变led_ctrl的值 void LED_control(u8 led_ctrl) { HAL_GPIO_WritePin(GPIOC, 0XFF00, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, led_ctrl<<8, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); }
点亮&闪烁:
void led_process(void) { if(uwTick-LED_tick<100) return; LED_tick=uwTick; if(key_enable==1) { ledctrl=ledctrl&~(0x01); //熄灭特定位 } else { ledctrl=ledctrl|0x01;//点亮特定位 } if(VR37>1.0f) { ledctrl=ledctrl^0x02;//闪烁特定位 } LED_control(ledctrl); }
点亮特定秒再熄灭:
if(rightchar==1) { num++; if(num==3000) {rightchar=0;num=0;} }
按键:
void keyscan(void) { switch(keystate) { case 1:if((B1==0)||(B2==0)||(B3==0)||(B4==0)) { keystate++; } break; case 2:if(B1==0) {keyvalue=1;key_cnt=1;} //如果有长按需求,则加上此段 else if(B2==0) keyvalue=2; else if(B3==0) keyvalue=3; else if(B4==0) keyvalue=4; keystate++; break; case 3:if((B1==0)||(B2==0)||(B3==0)||(B4==0)) { } else { keystate=1;keyflag=1; key_cnt=0; //如果有长按需求,则加上此段 } break; } } //长按 void SysTick_Handler(void) { if(key_cnt==1) { keypresstime++; } HAL_IncTick(); }
双击:
void key_process(void) { if(uwTick-key_tick<10) return; key_tick=uwTick; key_scan(); if(keyflag==1) { keyflag=0; if(keyvalue==2) { keyvalue=0; // } if(keyvalue==3) { keyvalue=0; // } if(keyvalue==4) { keyvalue=0; // } } if(key1_flag==1) { key1_flag=0; if(keyvalue==1) { keyvalue=0; // } else if(keyvalue==5) { keyvalue=0; // } } } void key_scan(void) { switch(keystate) { case 1: if(B1==0||B2==0||B3==0||B4==0) keystate++;break; case 2:if(B1==0) keyvalue=1; else if(B2==0) keyvalue=2; else if(B3==0) keyvalue=3; else if(B4==0) keyvalue=4; keystate++;break; case 3: if(B1==0||B2==0||B3==0||B4==0) { } else { keystate=1;keyflag=1; if(keyvalue==1) //需要添加部分 (假设只对按键B1判断是否双击) { key_press++; //按键次数加1 } } } } void SysTick_Handler(void) { //需要添加部分 if(key_press!=0) //如果按键次数不为0,立即开始计时 { num++; } if(num>200) //判断200ms后的key_press值 { num=0; if(key_press==1) //如果还为1,证明只按击一次 { key_press=0; key1_flag=1; //执行程序(keyvalue=1) } else if(key_press==2) //如果按下两次 { key_press=0; keyvalue=5; key1_flag=1; //执行程序(keyvalue=5) } else //如果200ms内按下两次以上,则不执行任何功能 { key_press=0; keyvalue=0; } } //需要添加部分 HAL_IncTick(); }
LCD:
两个界面切换 先清屏一次
void LCD_process(void) { if(lcdstate==1) { if(lcdclear==1) //清屏 { lcdclear=0; //清屏 LCD_Clear(Black); //清屏 } sprintf((char*)lcdbuf," Data"); LCD_DisplayStringLine(Line1,lcdbuf); sprintf((char*)lcdbuf," FRQ:%dHZ ",fre); LCD_DisplayStringLine(Line3,lcdbuf); sprintf((char*)lcdbuf," R37:%.2FV",vr37); LCD_DisplayStringLine(Line5,lcdbuf); } else { if(lcdclear==0) //清屏 { lcdclear=1; //清屏 LCD_Clear(Black); //清屏 } sprintf((char*)lcdbuf," Para"); LCD_DisplayStringLine(Line1,lcdbuf); sprintf((char*)lcdbuf," R:%d ",r); LCD_DisplayStringLine(Line3,lcdbuf); } }
LCD高亮某些字符:
如果需要高亮一行中的某些字符,需要用到LCD_DisplayChar() 第一个变量为行,第二个变量为列,所用LCD屏一共20列,一列占320/16=20 最左边是320,所以第i列为320-16*i 第三个变量为ASCII码字符 void highlight(u8 *hour,u8 *minute,u8 *second) { if(light==1)LCD_SetBackColor(Red); //判断以下字符是否高亮 else LCD_SetBackColor(Black); LCD_DisplayChar(Line4,320-16*7,(*hour/10)+'0'); //高亮字符 LCD_DisplayChar(Line4,320-16*8,(*hour%10)+'0'); //高亮字符 LCD_SetBackColor(Black); //以下字符正常 LCD_DisplayChar(Line4,320-16*9,':'); if(light==2)LCD_SetBackColor(Red); //判断以下字符是否高亮 else LCD_SetBackColor(Black); LCD_DisplayChar(Line4,320-16*10,(*minute/10)+'0'); LCD_DisplayChar(Line4,320-16*11,(*minute%10)+'0'); LCD_SetBackColor(Black); LCD_DisplayChar(Line4,320-16*12,':'); if(light==3)LCD_SetBackColor(Red); //判断以下字符是否高亮 else LCD_SetBackColor(Black); LCD_DisplayChar(Line4,320-16*13,(*second/10)+'0'); LCD_DisplayChar(Line4,320-16*14,(*second%10)+'0'); LCD_SetBackColor(Black); }
ADC:
u16 ADC_VALUE; float VR37; void ADC_process(void) { HAL_ADC_Start(&hadc2); ADC_VALUE=HAL_ADC_GetValue(&hadc2); VR37=ADC_VALUE*3.3f/4096.0f; }
同一个ADC双路采集的话需要cubemx配置(number of conversion)为2,以及采样速度
不管几个通道采样速度需要配置尽可能长,最好 640.5Cycles 不然按键会影响ADC采集
ADC输出电压达不到3.3V,原因是HAL_ADC_GetValue()的值最高只能达到4015,所以如果想要达到3.3V,计算时配置4015
PSC: 预分频器(配置成79的话则对80MHZ进行分频(PSC+1),1MHZ每1us一个脉冲)
CNT:定时器中的计数器 (每一个脉冲CNT加一)
ARR:自动重装载寄存器(相当于计数的周期(ARR+1),CNT超过ARR值则溢出,重新开始计数)
CCRx:默认CNT小于CCRx高电平,反之低电平
CCER:配置上升沿还是下降沿(TIM2->CCER|=0X02 下降沿 TIM2->CCER&=~0X02 上升沿 )
PWM波输出:
void PWM_process(void) { HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1); TIM17->ARR=1000000/PWM_fre-1; //ARR-1 if(VR37<=1.0f) { TIM17->CCR1=(TIM17->ARR+1)*0.4f; } else if(VR37>=2.0f) { TIM17->CCR1=(TIM17->ARR+1)*0.8f; } else { TIM17->CCR1=(TIM17->ARR+1)*VR37*0.4f; } duty=TIM17->CCR1*100.0f/(TIM17->ARR+1); }
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1) PWM初始化不要放在while(1)中,放在主函数之前执行一次就好
注:PWM波频率需要改变时,给auto_reload preload ENABLE
(单路)PWM输入捕获:
主函数里: HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); //开启中断 u32 cnt_value; u32 fre; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { cnt_value=TIM3->CNT; //每一个上升沿中断必须马上获取值然后清零,否则检测的频率会有误差 TIM3->CNT=0; //每一个上升沿中断必须马上获取值然后清零,否则检测的频率会有误差 fre=1000000/cnt_value; HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); //input capture start interrupt }
(多路)PWM输入捕获:
主函数:HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim==&htim2) { htim2_value=TIM2->CNT; TIM2->CNT=0; fre_40=1000000/htim2_value; HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); } if(htim==&htim3) { htim3_value=TIM3->CNT; TIM3->CNT=0; fre_39=1000000/htim3_value; HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); } }
(多路)PWM输入捕获频率和占空比:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim==&htim2) { if(tim2_state==0) { htim2_value=TIM2->CNT; //获得整个周期的计数值 fre_40=1000000/htim2_value; duty_40=tim2_highvalue*100.0f/htim2_value; //占空比 TIM2->CNT=0; //重新计数 TIM2->CCER=TIM2->CCER|0X02; //改变为下降沿中断 tim2_state=1; } else if(tim2_state==1) { tim2_highvalue=TIM2->CNT; //获得高电平计数值 TIM2->CCER=TIM2->CCER&~(0X02); //改变为上升沿中断 tim2_state=0; } HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); } if(htim==&htim3) { if(tim3_state==0) { htim3_value=TIM3->CNT; fre_39=1000000/htim3_value; duty_39=tim3_highvalue*100.0f/htim3_value; TIM3->CNT=0; TIM3->CCER=TIM3->CCER|0X02; tim3_state=1; } else if(tim3_state==1) { tim3_highvalue=TIM3->CNT; TIM3->CCER=TIM3->CCER&~(0X02); tim3_state=0; } HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); } }
(多路)扩展板PWM输入捕获频率PULSE:
[一个定时器的两个通道]
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) { if(uhCaptureIndex_ch3 == 0) { /* Get the 1st Input Capture value */ uwIC3Value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_3); uhCaptureIndex_ch3 = 1; } else if(uhCaptureIndex_ch3 == 1) { /* Get the 2nd Input Capture value */ uwIC3Value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_3); /* Capture computation */ if (uwIC3Value2 > uwIC3Value1) { uwDiffCapture_ch3 = (uwIC3Value2 - uwIC3Value1); } else if (uwIC3Value2 < uwIC3Value1) { /* 0xFFFFFFFF is max TIM2_CCRx value */ uwDiffCapture_ch3 = ((0xFFFFFFFF - uwIC3Value1) + uwIC3Value2) + 1; } //需要修改 else { /* If capture values are equal, we have reached the limit of frequency measures */ Error_Handler(); } /* Frequency computation: for this example TIMx (TIM1) is clocked by APB2Clk */ uwFrequency_ch3 = 1000000 / uwDiffCapture_ch3; //需要修改 uhCaptureIndex_ch3 = 0; } } if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { if(uhCaptureIndex == 0) { /* Get the 1st Input Capture value */ uwIC2Value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); uhCaptureIndex = 1; } else if(uhCaptureIndex == 1) { /* Get the 2nd Input Capture value */ uwIC2Value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); /* Capture computation */ if (uwIC2Value2 > uwIC2Value1) { uwDiffCapture = (uwIC2Value2 - uwIC2Value1); } else if (uwIC2Value2 < uwIC2Value1) { /* 0xFFFFFFFF is max TIM2_CCRx value */ uwDiffCapture = ((0xFFFFFFFF - uwIC2Value1) + uwIC2Value2) + 1; } //需要修改 else { /* If capture values are equal, we have reached the limit of frequency measures */ Error_Handler(); } /* Frequency computation: for this example TIMx (TIM1) is clocked by APB2Clk */ uwFrequency = 1000000 / uwDiffCapture; //需要修改 uhCaptureIndex = 0; } } } }
UART:
串口向PC发送数据:
int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1,(u8*)&ch,1,50); return ch; }
prtinf("xxx\r\n"); \r\n 是回车换行
串口接收PC的数据:
(1)单字节:
HAL_UART_Receive_IT(&huart1,&uart_value,1); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(uart_value=='@') { } else if(uart_value=='#') { } else { } HAL_UART_Receive_IT(&huart1,&uart_value,1); }
(2)接收固定长度字节
例:接收固定3个字节数据
u8 uart_data; u8 receive[3]; u8 uart_n; u32 uart_tick; void uart_receive_clear(void) //每50ms清零一次 { if(uwTick-uart_tick<50) return; uart_tick=uwTick; uart_n=0; //个数清零 memset(receive,'\0',sizeof(receive)); //存储数组清零 } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { uart_tick=uwTick; //一旦开始接收1个字节的数据就开始计时 receive[uart_n++]=uart_data; if(uart_n==3) { uart_n=0; printf("%c\r\n",receive[0]); printf("%c\r\n",receive[1]); printf("%c\r\n",receive[2]); } HAL_UART_Receive_IT(&huart1,&uart_data,1); }
RTC:
(1)STM32CubeMX配置:
使能时钟源:
时钟源必须配置成32000分频,即1HZ计一次数
RTC_TimeTypeDef rtc_time; RTC_DateTypeDef rtc_date; void RTC_Process() { HAL_RTC_GetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN); HAL_RTC_GetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN); } //LCD u8 lcdbuf[20]; void lcd_process(void) { sprintf((char*)lcdbuf,"%02d-%02d-%02d",rtc_time.Hours,rtc_time.Minutes,rtc_time.Seconds);
注意:RTC时钟处理必须先得到时间的值再获取日期的值,才能成功获得时间的值
IIC:
EEPROM:
void EEPROM_write(uint8_t add, uint8_t dat) { I2CStart(); I2CSendByte(0xa0); I2CWaitAck(); I2CSendByte(add); I2CWaitAck(); I2CSendByte(dat); I2CWaitAck(); I2CStop(); HAL_Delay(5); //写入间隔5ms } uint8_t EEPROM_read(uint8_t add) { uint8_t data; I2CStart(); I2CSendByte(0xa0); I2CWaitAck(); I2CSendByte(add); I2CWaitAck(); I2CStart(); I2CSendByte(0xa1); I2CWaitAck(); data=I2CReceiveByte(); I2CSendNotAck(); I2CStop(); return data; } //IIC I2CInit(); start_times=EEPROM_read(0x35); EEPROM_write(0X35,start_times+1); //写16位数据 EEPROM_write(0x01,unit1>>8); //高8位 EEPROM_write(0x02,unit1&0xff); //低8位 //读16位数据 unit1=EEPROM_read(0x01)<<8|EEPROM_read(0x02);
void EEPROM_process(void) { if(EEPROM_read(0x99)!=0xaa) //随便定义 { EEPROM_write(0x99,0xaa); EEPROM_write(0x01,10); //设定初值 EEPROM_write(0x02,10); //设定初值 EEPROM_write(0x03,10); //设定初值 EEPROM_write(0x04,10); //设定初值 } else { X_rep=EEPROM_read(0x00); Y_rep=EEPROM_read(0x01); X_price=EEPROM_read(0x02); Y_price=EEPROM_read(0x03); } }
以防读取地址默认数据,如果不符合要求,赋初值,符合要求,正常读取
MCP4017
void MCP4017_write(uint8_t data) { I2CStart(); I2CSendByte(0x5e); I2CWaitAck(); I2CSendByte(data); I2CWaitAck(); I2CStop(); }
MCP4017_write(0x7f); / /7位 0x00~0x7f (0~100kΩ)
数码管:
//dp g f e d c b a 共阴极 0x00熄灭 #define SER_H HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET) //代表数据 #define SER_L HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET) #define RCK_H HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET) //存储数据 #define RCK_L HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET) #define SCK_H HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET) //移位数据 #define SCK_L HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET) u8 seg_buf[3]; u8 seg_code[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; void seg_display(void) { u8 i; for(i=0;i<8;i++) { if(seg_buf[2]&0x80) SER_H; else SER_L; SCK_L; SCK_H; seg_buf[2]=seg_buf[2]<<1; } for(i=0;i<8;i++) { if(seg_buf[1]&0x80) SER_H; else SER_L; SCK_L; SCK_H; seg_buf[1]=seg_buf[1]<<1; } for(i=0;i<8;i++) { if(seg_buf[0]&0x80) SER_H; else SER_L; SCK_L; SCK_H; seg_buf[0]=seg_buf[0]<<1; } RCK_L; RCK_H; }
光敏电阻:
void ADC_PROCESS(void) { HAL_ADC_Start(&hadc2); light_value=HAL_ADC_GetValue(&hadc2); voltage=light_value/4095.0f*3.3f; DO=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3); }
ADC按键:
u32 Akey_value=4095;// Akey_value必须设定初值,保证上电时没有按键按下的效果 //否则如果key_process()在ADC_process()之前的话Akey_value还是0没有更新到断开时的4095 //会造成先按一次按键的效果 void key_scan(void) { switch(keystate) { case 1: if(Akey_value<3950) keystate++;break; case 2:if(Akey_value<200) keyvalue=1; else if(Akey_value<800) keyvalue=2; else if(Akey_value<1400) keyvalue=3; else if(Akey_value<2000) keyvalue=4; else if(Akey_value<2600) keyvalue=5; else if(Akey_value<3200) keyvalue=6; else if(Akey_value<3800) keyvalue=7; else if(Akey_value<3950) keyvalue=8; keystate++;break; case 3: if(Akey_value<3950) { } else { keystate=1;keyflag=1; } break; } }
温度传感器:
float ds18b20_read(void) { u8 high,low; float temp; ow_reset(); ow_byte_wr(0xcc); ow_byte_wr(0x44); ow_reset(); ow_byte_wr(0xcc); ow_byte_wr(0xbe); low=ow_byte_rd(); high=ow_byte_rd(); temp=(high<<8|low)*0.0625f; return temp; }
float temperature; void ds18b20_process(void) { temperature=ds18b20_read(); }
注意事项:
#define delay_us(X) delay((X)*100/5) //需要将例程中的80改成100
while((u16)ds18b20_read()==85){;} //在主函数前加上,防止上电初始85 while (1) { ds18b20_process(); lcd_process(); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }
DTH11温湿度传感器:
dht11_read()函数返回32位数据
void DHT11_Process() { if(uwTick - humTick < 500) return; //500ms读取一次 humTick = uwTick; dht11_val = dht11_read(); } sprintf((char*)display_buf,"T:%d.%d ",(dht11_val>>8)&0xff,dht11_val&0xff);//温度 LCD_DisplayStringLine(Line6 ,(unsigned char *)display_buf); sprintf((char*)display_buf,"H:%d ",dht11_val>>24); //湿度 LCD_DisplayStringLine(Line7 ,(unsigned char *)display_buf);
常见小程序:
循环改值:
if(keyvalue==2) { keyvalue=0; VP1=VP1+0.3f; if(VP1>3.3f){VP1=0.0f;} }
只初始执行一次,之后不再执行
if(lcdclear==0) //定义一个变量 { lcdclear=1; //代码段// }
if(state==1) { state=0; } else { //代码// }
if(state==1) { //代码段// } state=1;
判断浮点数相等边界问题:
浮点数无法判断相等,所以根据想要达到的精度,将浮点数放大相应的倍数,然后同整形数进行比较
if((int)(voltage*100)==300) { LED_CONTROL(0x01); }
实时比较最大值:
if(speed>speedmax) { speedmax=speed; }
注意事项:
(1) 需要开启NVIC中断的模块为:串口接收和PWM输入捕获
(2) 浮点数进行大小比较,运算时需要给数据末尾加上‘f',否则默认为double类型
如:if((vr37>=1.0f)&&(vr37<3.0f))
(3) 注意按键增加或减少数据大小时的边界问题,比如数据不能小于0
(4)串口发送:
hex模式:11,22,33分别为一个字节(16进制), 数据之间隔空格不算发送内容
文本模式:任意一个ASCII码都算一个字节(1,@,w),空格也算一个字节
(5)数字转换为字符:数字+‘0’ 字符转换为数字:字符-‘0’
(6) !代表逻辑取反 ~代表按位取反 Bool变量只能用!取反
(7) 通过形参改变实参需要通过指针来实现 如swap()
(8)如果要求对数据进行小数上的加减操作,数据应选用整型变量,因为浮点数加减存在误差,显示时再除以放大倍数
客观题:
(1)温度升高,二极管正向,反向电流增大
(2) OC门 实现线与功能
(3) F103RBT6 Cortex-M3 G431RBT6 Cortex-M4
(4) OC,OD门 实现电平转换和线与功能