今天上午刚刚结束了蓝桥杯嵌入式赛道。
源码可以加群获取哦!
我们有几个需要完成的任务:
-
一路模拟信号的采集(0~3.3V)从滑动电阻器采集一路模拟信号
-
四个按键输入用来实现各种功能
-
LED的输出,这里使用锁存器来点亮各路LED
-
两路互补PWM输出,其输出占空比根据模拟信号的值发生变化
题目对按键要求做出了规定,由于涉及到GUI的设计,本来我是决定使用RTOS的,但是听到有朋友说使用FreeRTOS可能导致不计入分数,但是如果裸机开发的话就会导致代码的结构异常的麻烦。
于是我决定在不使用FreeRTOS的情况下,引入FreeRTOS的思想自己制作一个以定时器为核心的状态机。
我们使用定时器1定时20ms来触发,实现各个功能的部署。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ static int num = 0; if(htim == &htim1) { /* 轮询按键采样,按键内容包含是否按下,是否连按1.5s等 */ KeyCallBack(&key4,GPIOA,GPIO_PIN_0); KeyCallBack(&key1,GPIOB,GPIO_PIN_0); KeyCallBack(&key2,GPIOB,GPIO_PIN_1); KeyCallBack(&key3,GPIOB,GPIO_PIN_2); /* 按键1主要用来切换GUI界面以及对于阈值上下限进行校验 */ if(key1.HasPressNum) { if(GUIMODE==0)//参数界面 { if(P[1]>P[0])//参数不合理 { GUIERROR = 1; } else { TrueP[0] = P[0]; TrueP[1] = P[1]; } } GUIMODE=!GUIMODE; key1.HasPressNum = 0; } /* 按键2用来数据选择,修改上限电压还是修改下限电压 */ if(key2.HasPressNum) { if(!GUIMODE) { Select = !Select; } key2.HasPressNum = 0; } /* 按键3主要用于DATA界面下的高速低速模式切换 以及PARA界面下的值增加,每次递增0.3,有上限保护 */ if(key3.HasPressNum) { if(GUIMODE) { SpeedMode=!SpeedMode; FreHasChange = 1; } else { P[Select]+=0.3; if(P[Select]>3.3) { P[Select] = 3.3; } } key3.HasPressNum = 0; } /* 按键4应用于Data界面下的短按解锁以及长按上锁 PARA界面的数据减少,有下限阈值 */ if(key4.HasPressNum) { if(GUIMODE&&!key4.isContinue) { LockMode= 0; } else { P[Select]-=0.3; if(P[Select]<0) { P[Select] = 0; } } key4.HasPressNum = 0; key4.isContinue = 0; } if(key4.isContinue&&GUIMODE) { LockMode = 1; } /* 该标志位用于错误界面显示以及统计错误时长,恢复正常数据 */ if(GUIERROR) { ErrorTime++; if(ErrorTime==150)//3s后 { P[0] = TrueP[0]; P[1] = TrueP[1]; ErrorTime = 0; GUIERROR = 0; GUIMODE = 0; } } /* 该部分用于ADC采样使用软件触发的方式,并且统计平均值 */ ADC_Ave[num%5] = HAL_ADC_GetValue(&hadc1); HAL_ADC_Start(&hadc1); if(num % 5 == 4)//100ms { ADC_Value = 0; for(int i = 0;i<4;i++) { ADC_Value += ADC_Ave[i]; } ADC_Value/=4; ADCf = ADC_Value*3.3/4095; if(ADCf>TrueP[0]||ADCf<TrueP[1]) { ADCError = 1; } else { ADCError = 0; } } /* 该部分用于闪烁标记位的计时翻转 */ if(num % 16 == 15) { if(!GUIMODE) { Shanshuo = !Shanshuo; } num = 0; } } num++; }
我们在定时器中断回调中执行各步骤的任务,包括ADC采样,按键检查,异常状态处理。而主循环中执行GUI的刷新。
这样子的好处是我们可以利用定时器的回调作为心跳,通过各标志位刷新GUI的显示,以及调度各个任务的执行。
并且这样子的好处可以在按键短按和长按的时候不仅做到消抖(20ms)还可以防止检测按键的时候导致LCD卡住。
** 注意要点 **
首先是LED刷新的函数我将其放在了界面刷新的后面。
这里的原因是因为 LED和LCD共用了引脚
,而我们刷新LED利用了锁存器,如果我们将其在中断中执行,会出现LCD正在写命令的时候我们写入了LED,导致LCD刷新出现故障以及LED的其他灯出现闪烁。
if(Select==0&&Shanshuo==1) { sprintf(s," PU = "); } else { sprintf(s," PU = %.1fV ",P[0]); } LCD_DisplayStringLine(Line2,s); memset(s," ",50); if(Select==1&&Shanshuo==1) { sprintf(s," PD = "); } else { sprintf(s," PD = %.1fV ",P[1]); }
在参数设置界面,我们通过一个闪烁标志位:Shanshuo,这个标志位会在判断界面是否属于参数设置界面之后在定时器的中断回调函数中以300ms的速率刷新,这样子就可以实现选中时候的闪烁效果。
sprintf(s," P=%d%c-%d%c ",x,37,100-x,37); //ASCLL码37对应字符%这样子就可以不使用转义字符
显示百分号的时候,本来想使用转义字符的,但是不知道为什么使用转义字符的时候出现了问题,所以这里选择直接使用ASCLL码来进行显示。
这里我们要学会灵活变通,我们不能上网搜索%所对应的ASCLL码,这里我们可以先定义一个变量 char c =
‘%’,然后用整型的方式将c的值显示出来,发现%对应的字符是37,之后就可以显示出来了。
赛后感悟
所以虽然我们没有使用FreeRTOS来直接调度代码任务,但是我们通过定时器来调度任务,利用状态机实现界面各参数的显示就可以流畅的写出代码了。
其实参加这次蓝桥杯还是有不少感悟的,我开始创建工程的时候出现了一些问题,导致将近一个小时的时间都在查LCD的问题(我的任务莫名其妙卡在Init下不去了)然后我新建了一个工程就ok了。
蓝桥杯(虽然很多人说是水赛)但是实际下来还是需要非常深厚的基本功,在无法上网的情况下掌握各个底层驱动的了解,例如本次需要掌握改变定时器的频率,那么到底是利用修改分频系数还是修改计数值,如果修改计数值的话导致计数值过大也有可能出现修改占空比的时候导致超出数据长度。
所以如果不是对STM32有着足够的了解和熟练度是很难的,尤其是几个小时之内(早上九点到下午两点)完成各项指标要求,我的话从十点钟差不多开始去写各部分功能,写到了下午一点(最后一个小时在写注释)
并且其实我们大部分人都存在着眼高手低的问题,可能好多人看到题目之后会觉得非常简单,但是实际上手又会出现许多许多的问题,因此其实总体来看蓝桥杯还是有点难度的,尤其是要求这些时间内能满足这些要求,确实是一个非常大的挑战。