在编写STM32等单片机程序的时候,我们经常会面对一些看似很寻常的需求,但是实现起来颇为困难,有的需求也许用一些单片机自带的外设可以很轻松解决,但是当面对一个很复杂的任务或者一个外设资源很稀少的系统,在一些无关紧要的地方浪费单片机资源是一种很不明智的选择。本系列帖子记录了笔者在对STM32进行开发的时候对代码结构与单片机资源分配的所思所想。
算法思想对于编程人员非常重要,嵌入式开发人员将算法融入得当,可以节省很多宝贵的单片机资源,让单片机的运行更加轻便,同时大大减少了运行时出现bug的可能性,同时更少依赖单片机资源意味着代码的可移植性更高。
一.需求描述:
在嵌入式相关技能赛中,我们经常能遇见这种题目:按下一次按键触发一个事件,再按下一次按键触发一个事件。在这里我们简化为这样的模型,按一次按键灯亮,再按一次按键灯熄灭,如此循环。
分析:
这里的按下按键在单片机开发中是分为很多个过程:按下,按住,松手,用更加专业的语言来描述就是:高电平,下降沿,低电平,上升沿,高电平(假设这里按键是接地),一共有五个过程,暂时不考虑按键的抖动,要实现题目的功能,我们只能让这五个步骤触发一次事件。
正常的步骤是,按下按键触发中断,利用外部中断记录按键上升沿来改变LED灯的状态,这样最简单也最方便。但是,假如当前的中断线被其他外设占据了怎么办呢,这就是一个比较麻烦的问题(很多同好可能想到用while死循环跳过长按按键,这是非常不可取的,这意味着当你按下按键时程序就卡死在了死循环里面,在大型工程是致命的)
所以如果想要不使用任何外设(外部中断,定时器)的情况下单单依靠代码完成这一操作,还是有一点问题,笔者将自己思考这个问题的思路记录如下(为了简单易懂,就使用HAL库来表示代码):
方法一(不太好用):
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)
{
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==1)
{
keystatus=1;
}
}
if(keystatus==1)
{
keystatus=0;
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_10);//意思是翻转该引脚的电平
}
这个方法的核心思想是:简单粗暴地利用if去检测上升沿,代码比较简单,但是稳定性很差,假如主循环中有一些需要花时间处理的事件,或者触发了需要花时间处理的中断,按键就会变得不灵敏,因为如果无法在按下按键的瞬间进入第一个if,那么这一次按键就无法触发事件。
方法二(改进版):
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)
{
keystatus=1;
}
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==1&&keystatus==1)
{
keystatus=0;
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_10);
}
既然上一个方法有瑕疵,那么就将进入if的逻辑改变。我的要求是每次按按键只会进入一次Pin脚翻转的判断,那么就将限制条件(检测到上升沿)放到反转引脚的if条件中,这样,当我按下按键时,低电平会触发按键状态标志位改变,然后由按键状态标志位和高电平同时控制是否翻转引脚电平。
采取这种方法的好处是将限制条件集中到一个地方,只有在同时满足:1.触发过低电平;2.当前按键状态是松开的;这两个条件的情况下才会让电平翻转。
方法三(更加粗暴):
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)
{
keystatus++;
if(keystatus==1)
{
k=!k;
}
}
if(k==0)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_10,GPIO_PIN_SET);
keystatus=0;
}
else if(k==1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_10,GPIO_PIN_RESET);
keystatus=0;
}
这种方法一般是最简单直接的方法,因为这种按键问题的模型一般比较简单,但是细想可能会想得很复杂。这里也记录一下这种方法。这种方法和上面的不同在于并不是检测上升沿,而是从标志位下手。定义两个标志位,一个是翻转标志位,一个是让翻转标志位变化的标志位。这样只需要控制翻转标志位每次按按键只变化一次,而对Pin脚的电平翻转不做限制。但是代码太臃肿,不适合很复杂的题目或者工程。
二.总结:
这个题目的要点就在于理清思路,拿到这种需求的第一时间是想好怎么实现最终的目标,首先要想到,如果每次按下按键就会触发一个不同的事件,那么触发这个事件的条件一定要是按按键这个过程比较有标志性的动作,比如下降沿或者上升沿,或者最简单粗暴的,独立设置一个标志位,让这个标志位变化的某一个不可逆的瞬间作为这个标志性的动作。对于这种比较单一的模式逆向思考会更加简单。
虽然这个题目非常简单,但是要很快地反应过来还是需要一定的编程思维,将自己的思路记录下来可以很方便的复盘自己思考这种题目的思路。
(本系列的所有帖子都是鄙人对编程中一些临时想到的问题的归纳总结,如果记录中有任何错误疏漏,欢迎各位大佬移步评论区指正,或者有任何更加简单巧妙的方法也欢迎大家移步评论区讨论)