当前位置:网站首页>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 总结
边栏推荐
- Go learning --- read INI file
- Notepad + + regular expression replace String
- Data analysis thinking analysis methods and business knowledge -- analysis methods (II)
- Spark SQL空值Null,NaN判断和处理
- QT -- thread
- [online chat] the original wechat applet can also reply to Facebook homepage messages!
- Leetcode:20220213 week race (less bugs, top 10% 555)
- FFT 学习笔记(自认为详细)
- [designmode] composite mode
- NSSA area where OSPF is configured for Huawei equipment
猜你喜欢

权限问题:source .bash_profile permission denied

FFMPEG关键结构体——AVFrame

Mysql - CRUD

【DesignMode】组合模式(composite mode)

How to solve the problems caused by the import process of ecology9.0

Determinant learning notes (I)

MySQL之函数

FFT learning notes (I think it is detailed)

notepad++正则表达式替换字符串

Classic CTF topic about FTP protocol
随机推荐
[designmode] composite mode
行列式学习笔记(一)
FFMPEG关键结构体——AVFrame
如何利用Flutter框架开发运行小程序
提升工作效率工具:SQL批量生成工具思想
LeetCode 6004. Get operands of 0
[designmode] Decorator Pattern
Mysql - CRUD
Go learning - dependency injection
STM32 configuration after chip replacement and possible errors
What are the functions of Yunna fixed assets management system?
Pointer pointer array, array pointer
Location based mobile terminal network video exploration app system documents + foreign language translation and original text + guidance records (8 weeks) + PPT + review + project source code
Power Query数据格式的转换、拆分合并提取、删除重复项、删除错误、转置与反转、透视和逆透视
【QT】Qt使用QJson生成json文件并保存
数据分析思维分析方法和业务知识——分析方法(三)
[QT] QT uses qjson to generate JSON files and save them
Determinant learning notes (I)
硬件及接口学习总结
How to use the flutter framework to develop and run small programs