当前位置:网站首页>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 总结
边栏推荐
- 常用API类及异常体系
- MySql——CRUD
- Single merchant v4.4 has the same original intention and strength!
- 【DesignMode】适配器模式(adapter pattern)
- Room cannot create an SQLite connection to verify the queries
- MySQL之函数
- LeetCode 6006. Take out the least number of magic beans
- FFMPEG关键结构体——AVCodecContext
- Notepad + + regular expression replace String
- 如何解决ecology9.0执行导入流程流程产生的问题
猜你喜欢

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

多线程与高并发(8)—— 从CountDownLatch总结AQS共享锁(三周年打卡)

Search (DFS and BFS)
![N1 # if you work on a metauniverse product [metauniverse · interdisciplinary] Season 2 S2](/img/f3/8e237296f5948dd0488441aa625182.jpg)
N1 # if you work on a metauniverse product [metauniverse · interdisciplinary] Season 2 S2

Room cannot create an SQLite connection to verify the queries

Hudi of data Lake (1): introduction to Hudi

Start from the bottom structure and learn the introduction of fpga---fifo IP core and its key parameters
![[EI conference sharing] the Third International Conference on intelligent manufacturing and automation frontier in 2022 (cfima 2022)](/img/39/9d189a18f3f75110b400506e274391.png)
[EI conference sharing] the Third International Conference on intelligent manufacturing and automation frontier in 2022 (cfima 2022)

如何利用Flutter框架开发运行小程序
![[designmode] composite mode](/img/9a/25c7628595c6516ac34ba06121e8fa.png)
[designmode] composite mode
随机推荐
LeetCode 8. String conversion integer (ATOI)
Extracting profile data from profile measurement
【线上小工具】开发过程中会用到的线上小工具合集
MySql——CRUD
Reading notes of the beauty of programming
Browser local storage
Atcoder beginer contest 258 [competition record]
Notepad + + regular expression replace String
权限问题:source .bash_profile permission denied
Key structure of ffmpeg - avformatcontext
Codeforces gr19 D (think more about why the first-hand value range is 100, JLS yyds)
Chapter 16 oauth2authorizationrequestredirectwebfilter source code analysis
Common API classes and exception systems
Global and Chinese markets of POM plastic gears 2022-2028: Research Report on technology, participants, trends, market size and share
从底层结构开始学习FPGA----FIFO IP核及其关键参数介绍
Hudi of data Lake (1): introduction to Hudi
【DesignMode】组合模式(composite mode)
Recognize the small experiment of extracting and displaying Mel spectrum (observe the difference between different y_axis and x_axis)
硬件及接口学习总结
电机的简介