当前位置:网站首页>STM32按键消抖——入门状态机思维
STM32按键消抖——入门状态机思维
2022-07-06 00:23:00 【华为云】
1 状态机思想
状态机,或称有限状态机FSM(Finite State Machine),是一种重要的编程思想。
状态机有3要素:状态、事件与响应
事件:发生了什么事?
响应:此状态下发生了这样的事,系统要如何处理?
状态机编程前,首先要根据需要实现的功能,整理出一个对应的状态转换图(状态机图),然后就可以根据这个状态转换图,套用状态机编程模板,实现对应是状态机代码了。
状态机编程主要有 3 种方法:switch-case 法、表格驱动法、函数指针法,本篇先介绍最简单也最易理解的switch-case 法。
2 状态机实例
下面以按键消抖功能,来介绍switch-case 法的状态机编程思路。
2.1 按钮消抖状态转换图
状态机机编程前,首先要明确的对应功能的状态机需要几个状态,本例的按键功能,只检测最基础的按下与松开状态(暂不实现长按、双击等状态),并增加对应的按钮去抖功能,因此,需要用到4个状态:
稳定松开状态
按下抖动状态
稳定按下状态
松开抖动状态
对应的状态转换图如下:
由于按键通常处于松开状态,这里让状态机的初始化状态为松开状态,然后在这4个状态中来回切换。
图中的VT代表按键检测到电平,VT=0即检测到低电平,可能是按键按下,由初始的“稳定松开”状态转为“按下抖动”状态
当持续检测到低电平(VT=0)一段时间后,认为消抖完成,由“按下抖动”状态转为“稳定按下”状态
在“按下抖动”状态时,在指定的一段时间内,再次检测到高电平(VT=1),说明确实是按钮抖动(比如按键被快速拨动了一下又弹起,或强烈震动导致的按键抖动),则由“按下抖动”状态转为“稳定松开”状态
2.2 编程实现
2.2.1 状态定义
对应上面的按钮状态图,可以知道需要用到4个状态:
稳定松开状态(KS_RELEASE)
按下抖动状态(KS_PRESS_SHAKE)
稳定按下状态(KS_PRESS)
松开抖动状态(KS_RELEASE_SHAKE)
这里使用枚举来定义这4个状态。为了在调试时,能够把对应状态名称以字符串的形式打印出来,这里使用宏定义的一个小技巧:
#符号+自定义的枚举名称
即可自动转变为字符串形式,再将这些字符串放到const char* key_status_name[]数组中,便可通过数组的形式访问这些状态的字符串名称形式。
此外,为了不重复书写枚举名称与对应的枚举字符串(#+枚举名称),进一步使用宏定义的方式,只定义一次状态,然后通过下面两条宏定义,实现对枚举项和枚举项对应的字符串的分别获取:
#define ENUM_ITEM(ITEM) ITEM,#define ENUM_STRING(ITEM) #ITEM,
具体是宏定义、枚举定义与枚举名称数组声明如下:
#define ENUM_ITEM(ITEM) ITEM,#define ENUM_STRING(ITEM) #ITEM,#define KEY_STATUS_ENUM(STATUS) \ STATUS(KS_RELEASE) /*稳定松开状态*/ \ STATUS(KS_PRESS_SHAKE) /*按下抖动状态*/ \ STATUS(KS_PRESS) /*稳定按下状态*/ \ STATUS(KS_RELEASE_SHAKE) /*松开抖动状态*/ \ STATUS(KS_NUM) /*状态总数(无效状态)*/ \ typedef enum{ KEY_STATUS_ENUM(ENUM_ITEM)}KEY_STATUS;const char* key_status_name[] = { KEY_STATUS_ENUM(ENUM_STRING)};
宏定义不便理解的,可以将宏定义分别带入,转为最终的结果,理解替代后的具体形式,比如下面的宏定义带入替换示意:
/*KEY_STATUS_ENUM(STATUS) --> STATUS(KS_RELEASE) ... STATUS(KS_NUM)KEY_STATUS_ENUM(ENUM_ITEM)--> ENUM_ITEM(KS_RELEASE) ... ENUM_ITEM(KS_NUM)--> KS_RELEASE, ... KS_NUM,KEY_STATUS_ENUM(ENUM_STRING)--> ENUM_STRING(KS_RELEASE) ... ENUM_STRING(KS_NUM)--> #KS_RELEASE, ... #KS_NUM,*/
2.2.2 状态机实现
下面是状态机的具体实现:
状态机函数key_status_check在一个循环中,被每隔10ms调用一次
定义一个g_keyStatus表示状态机所处的状态
在每个循环中,switch根据当前的状态,执行对应状态所需要执行的逻辑
定义一个g_DebounceCnt用于消抖时间计算,当持续进入消抖状态,每次循环(10ms)中将此值加1,持续一定次数(5次,即50ms),认为是稳定的按下或松开,消抖完成,跳转到稳定方向或稳定松开状态
在每个状态的执行逻辑中,当检测到某些条件满足时,跳转到其它的状态
通过状态的不断跳转,实现状态机的运行
此外,为方便观察状态机中状态的变化,定义了一个g_lastKeyStatus表示前一状态,当状态发生变化时,可以将状态名称打印出来
KEY_STATUS g_keyStatus = KS_RELEASE; //当前按键的状态KEY_STATUS g_lastKeyStatus = KS_NUM; //上一状态int g_DebounceCnt = 0; //消抖时间计数void key_status_check(){ switch(g_keyStatus) { //按键释放(初始状态) case KS_RELEASE: { //检测到低电平,先进行消抖 if (KEY0 == 0) { g_keyStatus = KS_PRESS_SHAKE; g_DebounceCnt = 0; } } break; //按下抖动 case KS_PRESS_SHAKE: { g_DebounceCnt++; //确实是抖动 if (KEY0 == 1) { g_keyStatus = KS_RELEASE; } //消抖完成 else if (g_DebounceCnt == 5) { g_keyStatus = KS_PRESS; printf("=====> key press\r\n"); } } break; //稳定按下 case KS_PRESS: { //检测到高电平,先进行消抖 if (KEY0 == 1) { g_keyStatus = KS_RELEASE_SHAKE; g_DebounceCnt = 0; } } break; //松开抖动 case KS_RELEASE_SHAKE: { g_DebounceCnt++; //确实是抖动 if (KEY0 == 0) { g_keyStatus = KS_PRESS; } //消抖完成 else if (g_DebounceCnt == 5) { g_keyStatus = KS_RELEASE; printf("=====> key release\r\n"); } } break; default:break; } if (g_keyStatus != g_lastKeyStatus) { g_lastKeyStatus = g_keyStatus; printf("new key status:%d(%s)\r\n", g_keyStatus, key_status_name[g_keyStatus]); }}int main(void){ delay_init(); //延时函数初始化 KEY_Init(); uart_init(115200); printf("hello\r\n"); while(1) { key_status_check(); delay_ms(10); }}
注:本例程需要使用一个按键,需要初始化对应的GPIO,这里不再贴代码。
2.3 使用测试
将完整的代码编译后烧录到板子中,连接串口,按下与松开按键,观察串口输出信息。
我的测试输出信息如下:
前两次拨动按键模拟按钮抖动的情况,可以看到串口打印出两次从松开到按下抖动的状态切换。
然后是按下按键,再松开按键,可以看到状态的变化:松开 -> 按下抖动 -> 按下 -> 松开抖动 -> 松开
3 总结
边栏推荐
- Tools to improve work efficiency: the idea of SQL batch generation tools
- Priority queue (heap)
- [Chongqing Guangdong education] reference materials for Zhengzhou Vocational College of finance, taxation and finance to play around the E-era
- 行列式学习笔记(一)
- [designmode] Decorator Pattern
- anconda下载+添加清华+tensorflow 安装+No module named ‘tensorflow‘+KernelRestarter: restart failed,内核重启失败
- Leetcode:20220213 week race (less bugs, top 10% 555)
- Determinant learning notes (I)
- Atcoder beginer contest 254 [VP record]
- 建立时间和保持时间的模型分析
猜你喜欢
FPGA内部硬件结构与代码的关系
Notepad++ regular expression replacement string
Ffmpeg learning - core module
数据分析思维分析方法和业务知识——分析方法(二)
Classical concurrency problem: the dining problem of philosophers
权限问题:source .bash_profile permission denied
NSSA area where OSPF is configured for Huawei equipment
LeetCode 1598. Folder operation log collector
Wechat applet -- wxml template syntax (with notes)
Go learning - dependency injection
随机推荐
2022-02-13 work record -- PHP parsing rich text
【NOI模拟赛】Anaid 的树(莫比乌斯反演,指数型生成函数,埃氏筛,虚树)
小程序技术优势与产业互联网相结合的分析
The difference of time zone and the time library of go language
Notepad++ regular expression replacement string
建立时间和保持时间的模型分析
LeetCode 1189. Maximum number of "balloons"
免费的聊天机器人API
Date类中日期转成指定字符串出现的问题及解决方法
《编程之美》读书笔记
Set data real-time update during MDK debug
7.5模拟赛总结
7.5 decorator
Gavin teacher's perception of transformer live class - rasa project actual combat e-commerce retail customer service intelligent business dialogue robot system behavior analysis and project summary (4
DEJA_VU3D - Cesium功能集 之 055-国内外各厂商地图服务地址汇总说明
Pointer pointer array, array pointer
[designmode] Decorator Pattern
Hardware and interface learning summary
GD32F4xx uIP协议栈移植记录
LeetCode 6005. The minimum operand to make an array an alternating array