关于状态机编程,以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条回答
shr5791
1楼-- · 2019-07-21 03:55
回复【2楼】miaoguoqiang:
---------------------------------
如果你想按键按下触发,那更简单了,把最后一个状态拿掉,中间修改一下,将case循环回去即可:

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:    //按键确认状态
        {
           KeyStatus=KEY_SEARCH_STATUS;  //按键返回至查询状态            
             if(HasKeyPressed )      //如果有按键按下
             {
                  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;
                  return KeyCurPress; //返回当前按键 
             }
             return 0;
        }
        default: return 0;
   }
}
FreeRTOS
2楼-- · 2019-07-21 04:33
 精彩回答 2  元偷偷看……
miaoguoqiang
3楼-- · 2019-07-21 06:39
这样就只有释放才能执行一个按键的功能
miaoguoqiang
4楼-- · 2019-07-21 08:15
 精彩回答 2  元偷偷看……
shr5791
5楼-- · 2019-07-21 10:15
回复【4楼】miaoguoqiang:
---------------------------------
这要看你一个while(1)的执行周期了,如果短(比如原子的这个实验,while就很短),那就不需要了,如果 while(1)的执行周期很长,就应该定时扫描。不丢键就行。这样就能实时扫描了。
luofeng
6楼-- · 2019-07-21 12:26
2楼没说错啊,状态机不就是通过程序每执行一次调用一次key_scan()或是定时中断等来达到delay延时的效果,可以再加一个状态来添加长按的效果!

一周热门 更多>