关于状态机编程,以ALIENTEK V2.0为平台

2019-07-21 01:00发布

  最近看到几个关于状态机编程的帖子,发现大家更多的是在辩论状态机编程好不好。这里,我以原子提供的《实验按键输入》为基础,着重以key.c中的按键扫描函数KEY_Scan()为例,写一个的基于状态机形式的KEY_Scan(),来扫描KEY_UPKEY0KEY1KEY2,希望能起到抛砖引玉的作用。工程项目文件我打包挂在下面,大家可以下载到自己的板子上运行,效果很好。 一般情况下,在按键扫描函数中最让人蛋疼的就是按键消抖延时。这是可靠程序所必须的。否则,按键一次,MCU读到的却是N多次。但是用延时来给按键消抖,代价是很大的。对于我们用户来说,KEY_Scan()中的“delay_ms(10); //抖动延时”并无大碍,但是对于STM32这样的72MHzMCU来说,delay_ms(10)是很漫长的。所以,如果你的系统里需要数码管显示数据,同时还要实时扫描按键,而你的KEY_Scan()中含有类似delay_ms(10)这样的语句的话,那么数码管的数据显示肯定会有问题,除非你搭载了uC/OS-II。除此之外,“delay_ms(10)”至少还有以下两点不足: 第一,如果需要实时扫描按键,那么扫描时间间隔一定要小于按键消抖延时,否则,会丢键; 第二,由于按键本身的机械性能不同,对某些按键而言,delay_ms(10)可能因延时过长而丢键,也可能因延时不足而一键多发。 归根结底,delay_ms(10)对要求不太严格的系统是可以用的,而且效果也不错。但是还是治标不治本。而状态机的优势就是不用delay_ms(10)这种方法。 下面,以状态机的方法,来写一个KEY_Scan()函数(其实状态机的方法真的用的很广,尤其是在多任务运行的情况下) 用状态机思想去进行单片机编程,比较通用的方法就是用switch分支语句来进行状态的跳转,不仅编程模式相对固定,而且程序可读性也很好。就像下面这种框架:        switch(KeyStatus)     {         case KEY_SEARCH_STATUS:   //按键查询状态         { ……             KeyStatus=KEY_ACK_STATUS;  //按键下一个状态为确认状态 ……         }           case KEY_ACK_STATUS:  //按键确认状态         {              ……              KeyStatus=KEY_REALEASE_STATUS;  //按键下一个状态为释放状态              ……         }           case KEY_REALEASE_STATUS:    //按键释放状态         {             ……             KeyStatus=KEY_SEARCH_STATUS;  //按键下一个状态为释放状态             ……         }         default: break;     }   key.c中,状态机形式的KEY_Scan()如下所示,注意,整个函数是没有delay的。另外,原子提供的KEY_Scan()通过入口参数是可以选择是否支持长按的,这里我只把注意力集中于状态机上,你也可以实现类似选择是否支持长按,加一个静态变量来标识按键的状态即可,并不难。工程文件在下面。   u8 KEY_Scan(void) {              static u8 KeyStatus=KEY_SEARCH_STATUS; //静态变量,保存按键状态     static u8 KeyCurPress=0; //静态变量,保存当前按键的键值            u8 KeyValue=0;       if(KEY0==0||KEY1==0||KEY2==0||KEY3==1)   //如果有按键按下     {
        if(KEY0==0) KeyValue=KEY_RIGHT;         else if(KEY1==0) KeyValue=KEY_DOWN;         else if(KEY2==0) KeyValue=KEY_LEFT;         else if(KEY3==1) KeyValue=KEY_UP;     }        switch(KeyStatus)     {         case KEY_SEARCH_STATUS:   //按键查询状态         {
             if(KeyValue)   //如果有按键按下(暂时不管到底是哪个Key按下)                   KeyStatus=KEY_ACK_STATUS;  //则按键下一个状态为确认状态              return 0;         }           case KEY_ACK_STATUS:    //按键确认状态         {              if(!KeyValue)      //如果没有按键按下                   KeyStatus=KEY_SEARCH_STATUS;   //按键状态返回为查询状态              else //否则有按键按下              {                   if(KEY0==0) KeyCurPress=KEY_RIGHT;                   else if(KEY1==0) KeyCurPress=KEY_DOWN;                   else if(KEY2==0) KeyCurPress=KEY_LEFT;                   else if(KEY3==1) KeyCurPress=KEY_UP;                   KeyStatus=KEY_REALEASE_STATUS; //按键下一个状态为释放状态              }              return 0;         }           case KEY_REALEASE_STATUS:      //按键释放状态         {             if(!KeyValue)      //按键释放             {                 KeyStatus=KEY_SEARCH_STATUS;  //按键下一个状态为释放状态                 return KeyCurPress;  //返回当前按键             }             return 0;            }         default: return 0;     } }

修改:发现
KEY_Scan() 修改为以下代码更简洁(对应修改后的工程项目文件也在下面):

u8 KEY_Scan(void) {              static u8 KeyStatus=KEY_SEARCH_STATUS; //静态变量,保存按键状态     static u8 KeyCurPress=0; //静态变量,保存当前按键的键值            u8  HasKeyPressed ;       /* 如果有按键按下,HasKeyPressed置1.否则,置0 */     HasKeyPressed=(KEY0==0||KEY1==0||KEY2==0||KEY3==1)?1:0;        switch(KeyStatus)     {         case KEY_SEARCH_STATUS:   //按键查询状态         {
             if( HasKeyPressed )   //如果有按键按下(暂时不管到底是哪个Key按下)                   KeyStatus=KEY_ACK_STATUS;  //则按键下一个状态为确认状态              return 0;         }           case KEY_ACK_STATUS:    //按键确认状态         {              if(! HasKeyPressed )      //如果没有按键按下                   KeyStatus=KEY_SEARCH_STATUS;   //按键状态返回为查询状态              else //否则有按键按下              {                   if(KEY0==0) KeyCurPress=KEY_RIGHT;                   else if(KEY1==0) KeyCurPress=KEY_DOWN;                   else if(KEY2==0) KeyCurPress=KEY_LEFT;                   else if(KEY3==1) KeyCurPress=KEY_UP;                   KeyStatus=KEY_REALEASE_STATUS; //按键下一个状态为释放状态              }              return 0;         }           case KEY_REALEASE_STATUS:      //按键释放状态         {             if(! HasKeyPressed )      //按键释放             {                 KeyStatus=KEY_SEARCH_STATUS; //按键下一个状态为释放状态                 return KeyCurPress;//返回当前按键             }             return 0;            }         default: return 0;     } }
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
26条回答
hwl1023
2019-07-22 15:33
回复【13楼】hwl1023:
---------------------------------
比如,你的MCU上电后第一步就是要判断按键值,然后才根据按键值去执行不同的操作,这就是你说的情况了吧。如下:

u8 KeyVal=0;

main()
{
        while( ( KeyVal = Key_Scan() ) ==0 );  //其实这句中的while不也是个循环么,依然会消抖
        
        switch(KeyVal )
        {
                 //根据 KeyVal 去执行不同的操作
        }
}

一周热门 更多>