STM32按键状态机3——增加双击与功能优化

举报
码农爱学习 发表于 2022/07/11 22:50:14 2022/07/11
【摘要】 本篇在前两篇按键状态机的基础上,继续介绍增加按键的双击功能,并解决之前状态存在的两个问题,通过实测验证,演示短按、长按、双击的使用效果。最后对代码结构进行优化,使其更符合实际开发应用。

上篇文章,介绍了将按键检测增加长按功能,并将按下抖动与松开抖动共用一个抖动状态来表示,其状态图如下:

仔细研究这个状态图,其它还存在一些问题:

  • 短按状态,只要按下去,不需要等按键再释放,就会触发短按事件。对于需要按下再松开作为一次短按的应用来说,此状态图也不满足需求

  • 长按状态,必须先经过短按状态,即长按按键,会先触发一个短按,再触发一个长按。如果实际应用中需要分别使用短按和长按,则此状态图不满足要求


本篇,就来解决上述两个问题,并再增加一个按键双击检测,实现一个功能更全面的按键检测。


1 增加双击检测

增加一个双击检测,需要增加两个状态:

  • 等待再次按下

  • 确认第2次按下

同时,之前的“短按状态”和“长按状态”分别改为“确认按下”和“确认长按”。

1.1 状态图修改

修改后的状态图如下,有以下几点需要注意:

  • 确认按下”不是短按触发的条件,需要等松开后,经消抖进入到“等待再次按下”一段时间后(200ms),没有再次被按下,才触发短按事件,这样就解决了本篇开头提到的第1个问题

  • 确认按下”不是短按触发的条件,另一个用途是,当此状态继续保持按下状态一段时间后(1s),则会单独触发长按事件,同时进入到“确认长按”状态,这样就解决了本篇开头提到的第2个问题

  • 对于双击事件的检测,首先按下按键进入“确认按下”状态,然后在1s内松开进入“等待再次按下”状态,接着在200ms内再次按下进入“确认第2次按下”状态,然后在1s内松开,即可触发双击事件,并同时进入“稳定松开”状态

  • 注意,在“确认第2次按下”状态下,如果在1s内没有松开,也会进入到“确认长按”状态

1.2 程序编写

根据状态图,修改对应的状态机逻辑,修改后的代码如下:

void key_status_check()
{
    switch(g_keyStatus)
    {
        //按键释放(初始状态)
        case KS_RELEASE:
        {
            //检测到低电平,先进行消抖
            if (KEY0 == 0)
            {
                g_keyStatus = KS_SHAKE;
            }
        }
        break;
        
        //抖动
        case KS_SHAKE:
        {
            if (KEY0 == 1) 
            {
                //从松开状态来的抖动
                if (KS_RELEASE == g_lastKeyStatus)
                {
                    g_keyStatus = KS_RELEASE;
                }
                //从等待再次按下状态来的抖动
                else if (KS_WAIT_PRESS_AGAIN == g_lastKeyStatus)
                {
                    g_keyStatus = KS_WAIT_PRESS_AGAIN;
                }
                //从确认按下状态来
                else if (KS_AFFIRM_SHORT_PRESS == g_lastKeyStatus)
                {
                    g_WaitPressAgainCnt = 0;
                    g_keyStatus = KS_WAIT_PRESS_AGAIN;
                }
                //从确认再次按下状态来
                else if (KS_AFFIRM_PRESS_AGAIN == g_lastKeyStatus)
                {
                    printf("=====> key double press\r\n");
                    g_keyStatus = KS_RELEASE;
                }
                //从确认长按状态来
                else if (KS_AFFIRM_LONG_PRESS == g_lastKeyStatus)
                {
                    g_keyStatus = KS_RELEASE;
                }
                else
                {
                    printf("err!\r\n");
                }
            }
            else
            {
                //从确认按下状态来的抖动
                if (KS_AFFIRM_SHORT_PRESS == g_lastKeyStatus)
                {
                    g_keyStatus = KS_AFFIRM_SHORT_PRESS;
                }
                //从第2次按下状态来的抖动
                else if (KS_AFFIRM_PRESS_AGAIN == g_lastKeyStatus)
                {
                    g_keyStatus = KS_AFFIRM_PRESS_AGAIN;
                }
                //从确认长按状态来的抖动
                else if (KS_AFFIRM_LONG_PRESS == g_lastKeyStatus)
                {
                    g_keyStatus = KS_AFFIRM_LONG_PRESS;
                }
                //从松开状态而来
                else if (KS_RELEASE == g_lastKeyStatus)
                {
                    g_PressTimeCnt = 0;
                    g_keyStatus = KS_AFFIRM_SHORT_PRESS;
                    //printf("=====> key short press\r\n");
                }
                //从等待再次看下(的松开)状态而来
                else if (KS_WAIT_PRESS_AGAIN == g_lastKeyStatus)
                {
                    g_Press2TimeCnt = 0;
                    g_keyStatus = KS_AFFIRM_PRESS_AGAIN;
                }
                else
                {
                    printf("err!\r\n");
                }
            }
        }
        break;
        
        //确认按下
        case KS_AFFIRM_SHORT_PRESS:
        {
            //检测到高电平,先进行消抖
            if (KEY0 == 1)
            {
                g_keyStatus = KS_SHAKE;
            }
            else
            {
                if (g_LongPressTimeCnt % 20 == 0) //每隔1000ms打印一次
                {
                    printf("=====> key long press:%d\r\n", g_LongPressTimeCnt/20);
                    
                    keyEvent = KE_LONG_PRESS;
                }
                g_LongPressTimeCnt++;
            }
        }
        break;
        
        //等待再次按下
        case KS_WAIT_PRESS_AGAIN:
        {
            //检测到低电平,先进行消抖
            if (KEY0 == 0)
            {
                g_keyStatus = KS_SHAKE;
            }
            
            g_WaitPressAgainCnt++;
            if (g_WaitPressAgainCnt == 4) //200ms没有再次按下
            {
                printf("=====> key single press\r\n");
                g_keyStatus = KS_RELEASE;
            }
        }
        break;
        
        //确认第2次按下
        case KS_AFFIRM_PRESS_AGAIN:
        {
            //检测到高电平,先进行消抖
            if (KEY0 == 1)
            {
                g_keyStatus = KS_SHAKE;
            }
            
            g_Press2TimeCnt++;
            if (g_Press2TimeCnt == 20) //1000ms
            {
                g_LongPressTimeCnt = 0;
                g_keyStatus = KS_AFFIRM_LONG_PRESS;
            }
        }
        break;
        
        //确认长按
        case KS_AFFIRM_LONG_PRESS:
        {
            //检测到高电平,先进行消抖
            if (KEY0 == 1)
            {
                g_keyStatus = KS_SHAKE;
            }
            
            g_LongPressTimeCnt++;
            if (g_LongPressTimeCnt % 20 == 0) //每隔1000ms打印一次
            {
                printf("=====> key long press:%d\r\n", g_LongPressTimeCnt/20);
            }
        }
        break;
        
        default:break;
    }
    
    if (g_keyStatus != g_nowKeyStatus)
    {
        g_lastKeyStatus = g_nowKeyStatus;
        g_nowKeyStatus = g_keyStatus;
        //printf("new key status:%d(%s)\r\n", g_keyStatus, key_status_name[g_keyStatus]);
    }
}

最后注释掉的一句是调试打印,调试时可打开,方便观察状态变化

1.3 测试

短按、长按、双击的测试结果如下:

还有从确认第2次按下状态到达的长按状态:



2 功能优化

上面的代码实现,是在主函数中,每50ms延时执行一次状态机循环(主函数代码如下),仅用做演示按键状态机的运行机制。

int main(void)
{   
    delay_init();
    KEY_Init();
    uart_init(115200);
​
    printf("hello\r\n");
    
    while(1)
    {
        key_status_check();
        delay_ms(50);
    }
}

实际开发中,按键检测程序,应该作为一个独立的模块运行,当检测到某一按键状态触发时,通知应用程序来使用。

对于stm32裸机开发来说,可以将按键状态机放到一个定时器中断服务函数中运行,当检测到某一按键状态触发后,通知应用程序:

//主函数
int main(void)
{   
    delay_init();
    KEY_Init();
    uart_init(115200);
    TIM3_Int_Init(500-1,7200-1); //调用定时器使得50ms产生一个中断
​
    printf("hello\r\n");
    
    while(1)
    {
    }
}
​
//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
        
        KEY_EVENT keyEvent = key_status_check();
        switch (keyEvent)
        {
            case KE_SHORT_PRESS:  printf("检测到单击\r\n"); break;
            case KE_DOUBLE_PRESS: printf("检测到双击\r\n"); break;
            case KE_LONG_PRESS:   printf("检测到长按\r\n"); break;
            default:break;
        }
    }
}


3 总结

本篇在前两篇按键状态机的基础上,继续介绍增加按键的双击功能,并解决之前状态存在的两个问题,通过实测验证,演示短按、长按、双击的使用效果。最后对代码结构进行优化,使其更符合实际开发应用。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。