目录
题目
效果
第十四届蓝桥杯省赛嵌入式组
分析
这届省赛的题目感觉比较简单,没涉及到通讯、存储等知识的考察。类似ADC电压采集、定时器输入捕获测频率、LCD等开发板都带有基本测试历程,看一下直接拷贝就行。所以我想说的不多,大致以下几点:
1.按键的长按。如果你之前检测按键采用的方法是直接读取引脚电平或者延时消抖,那处理长按这种情况就大概没有那么从容,以后再来个双击检测更是雪上加霜...所以从最初的检测短按开始就要养成良好的习惯。正确的做法是用定时中断检测按键。
首先我们定义一个按键结构体:
typedef struct
{
bool gpio; //读取按键引脚电平,初步判断按键是否被按下
uint8_t state; //记录按键状态
char kind; //判断长按还是短按,其值为s或l或n(short、long、nothing)
uint16_t count; //用来计数,判断长短按
}Key;
Key key[4];
如注释,结构体Key中的gpio用于读取按键引脚电平,初步判断按键是否被按下;state用于记录按键状态;kind用于判断长按还是短按,其值为s或l或n(short、long、nothing);count用来计数,判断长短按。
接着我们开启一个10ms的定时中断,每次进中断时,先会依次读取四个按键的引脚电平,然后进行初步判断,若被检测到被按下(低电平)则将按键状态state置1,下一次进中断时(10ms后)若按键依然检测到被按下,则将按键状态state置2,下一步进行长短按的甄别,即若之后进的200次中断里(即2s)按键都被检测按下,则判断为长按,否则判断为短按。每次四个按键都会进行如此的判断。如下:
void Key_Interrupt(void)
{
key[0].gpio=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key[1].gpio=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key[2].gpio=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key[3].gpio=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
for(uint8_t i=0;i<4;i++)
{
switch(key[i].state)
{
case 0: //初步判断按键是否被按下
if(key[i].gpio==0) //按下
{
key[i].state=1;
key[i].count=0;
}
break;
case 1: //10ms消抖,最终判断按键是否被按下
if(key[i].gpio==0) key[i].state=2; //按下
else key[i].state=0; //松手
break;
case 2: //1.2s判断长按短按
if(key[i].gpio==1) //松手
{
if(key[i].count<200) key[i].kind='s';
if(key[i].count>200) key[i].kind='l';
key[i].state=0;
key[i].count=0;
}
else key[i].count++; //按着就计数
break;
default:break;
}
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM4)
{
Key_Interrupt();
}
}
2.题目要求PA1引脚输出的pwm频率要可以在5s内均匀地从4kHz变化到8kHz,且步进值不大于200Hz。因为我本身就开了一个10ms的定时中断判断按键,所以当需要pwm频率改变时,我就让它每次进中断的改变一点点,例如每次改变10,那么共需要改变4000/10=400次,也就是需要进400次中断,400×10ms=4s<5s,符合要求。在Key_Interrupt()中for循环的下方加上如下代码:
if(frq_convert==1) key[1].kind='s'; //防止转换中key2被长按
if(display==0&&key[1].kind=='s') //界面1变更频率模式
{
frq_convert=1; //转换中置1
key[0].kind='n'; //转换其间所有按键均不生效
key[2].kind='n';
key[3].kind='n';
if(++LED2_Blink>10) //每0.1s闪烁一次
{
LED2_Toggle();
LED2_Blink=0;
}
if(pwm_mode=='H') //高频模式转为低频模式
{
frq_output-=10; //10ms变更一次,每次变更10,一共变更4000,共4s
arr=(uint16_t)(1000000/frq_output);
if(frq_output<=4000) //完成转换
{
frq_output=4000;
arr=20000;
pwm_mode='L';
key[1].kind='n';
frq_convert=0; //转换完成后置0
}
__HAL_TIM_SET_AUTORELOAD(&htim2,arr); //设置arr,及设置频率
}
if(pwm_mode=='L') //低频模式转为高频模式
{
frq_output+=10; //10ms变更一次,每次变更10,一共变更4000,共4s
arr=(uint16_t)(1000000/frq_output);
if(frq_output>=8000) //完成转换
{
frq_output=8000;
arr=10000;
pwm_mode='H';
key[1].kind='n';
frq_convert=0; //转换完成后置0
}
__HAL_TIM_SET_AUTORELOAD(&htim2,arr); //设置arr,及设置频率
}
if(frq_convert==0)
{
++n; //转换次数加一
LED2_OFF(); //转换完成LED2熄灭
LED2_Blink=0;
}
}
3.能够判断长按后就可以对pwm占空比进行锁定了。定义一个锁定标志位,默认为0,在界面1下显示占空比时先判断下锁定标志位是否为0,若是则读取ADC控制占空比;如果检测到Key4长按,则将锁定标志位置1,此时判断条件不再成立,if中的语句不再执行,所以占空比一直保留上次锁定前的数值;如果检测到key4短按,判断下是否在界面1下锁定标志位是否为1,若是则将标志位转为0取消锁定。
4.主循环中执行两个函数:
while (1)
{
key_circulate();
DI();
}
这两个函数是对按键和LCD显示界面的判断,分别长这样:
void key_circulate(void) //主函数按键循环
{
if(key[0].kind=='s')
{
LCD_Clear(Blue); //清除上个界面的遗留
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
if(display==0)
{
display=1;
LED1_OFF();
}
else if(display==1) display=2;
else if(display==2)
{
display=0;
LED1_ON();
}
key[0].kind='n';
}
if(key[1].kind=='s')
{
// if(display==0) 频率更改在中断里进行
if(display==1)
{
if(RK_Select==0) RK_Select=1;
else if(RK_Select==1) RK_Select=0;
}
key[1].kind='n';
}
if(key[2].kind=='s')
{
if(display==1)
{
if(RK_Select==0)
{
++R;
if(R>10) R=1;
}
if(RK_Select==1)
{
++K;
if(K>10) K=1;
}
}
key[2].kind='n';
}
if(key[3].kind=='s') //key3短按
{
if(display==0)
{
if(pwm_lock==1)
{
pwm_lock=0;
LED3_OFF();
}
}
if(display==1)
{
if(RK_Select==0)
{
--R;
if(R<1) R=10;
}
if(RK_Select==1)
{
--K;
if(K<1) K=10;
}
}
key[3].kind='n';
}
if(key[3].kind=='l') //key3长按
{
if(display==0)
{
pwm_lock=1;
LED3_ON();
}
key[3].kind='n';
}
}
void DI(void) //Display Interface
{
switch(display)
{
case 0:
{
velocity=frq_input*6.28*R/100/K;
LCD_DisplayStringLine(Line1, (uint8_t *)" DATA ");
sprintf(buf," M=%c ",pwm_mode);
LCD_DisplayStringLine(Line3, (uint8_t *)buf);
if(pwm_lock==0)
{
ccr=(0.0003*Get_ADCvalue(&hadc2)-0.2755)*arr;
if(ccr>=0.85*arr) ccr=0.85*arr;
if(ccr<=0.1*arr) ccr=0.1*arr;
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,ccr);
}
sprintf(buf," P=%d%% ",(int)ccr*100/arr);
LCD_DisplayStringLine(Line4, (uint8_t *)buf);
sprintf(buf," V=%.1f ",velocity);
LCD_DisplayStringLine(Line5, (uint8_t *)buf);
break;
}
case 1:
{
LCD_DisplayStringLine(Line1, (uint8_t *)" PARA ");
sprintf(buf," R=%d ",R);
LCD_DisplayStringLine(Line3, (uint8_t *)buf);
sprintf(buf," K=%d ",K);
LCD_DisplayStringLine(Line4, (uint8_t *)buf);
break;
}
case 2:
{
LCD_DisplayStringLine(Line1, (uint8_t *)" RECD ");
sprintf(buf," N=%d ",n);
LCD_DisplayStringLine(Line3, (uint8_t *)buf);
MH=8000*6.28*R/100/K;
sprintf(buf," MH=%.1f ",MH);
LCD_DisplayStringLine(Line4, (uint8_t *)buf);
ML=4000*6.28*R/100/K;
sprintf(buf," ML=%.1f ",ML);
LCD_DisplayStringLine(Line5, (uint8_t *)buf);
break;
}
default:break;
}
}
就是按照题目要求进行判断来实现功能的切换和界面的显示。
5.吐槽一下 这个带锁存器的LED我是真的无语。。。从未见过如此难用的LED,单操作一个的时候要打开锁存器PD2,但是一旦打开所有的LED就都会被置低电平(亮),所以想要操作多个LED的亮灭是真的费心。。。不过也可能是我的操作方法有误,欢迎小伙伴们教授我正确的操作方法!
源码
完整工程已经上传到CSDN,点击下方链接即可,免费下载,欢迎批评指正。