在嵌入式软件开发中,状态机编程是一个十分重要的编程思想,它也是嵌入式开发中一个常用的编程框架。掌握了状态机编程思想,可以更加逻辑清晰的实现复杂的业务逻辑功能。
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) /*状态总数(无效状态)*/ ypedef 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);}}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);}}break;default:break;}if (g_keystatus != g_lastkeystatus){g_lastkeystatus = g_keystatus;printf(new key status:%d(%s), g_keystatus, key_status_name[g_keystatus]);}}int main(void){delay_init(); //延时函数初始化 key_init();uart_init(115200);printf(hello);while(1){key_status_check();delay_ms(10);}} 注:本例程需要使用一个按键,需要初始化对应的gpio,这里不再贴代码。
2.3 使用测试
将完整的代码编译后烧录到板子中,连接串口,按下与松开按键,观察串口输出信息。
我的测试输出信息如下:
前两次拨动按键模拟按钮抖动的情况,可以看到串口打印出两次从松开到按下抖动的状态切换。
然后是按下按键,再松开按键,可以看到状态的变化:松开 -> 按下抖动 -> 按下 -> 松开抖动 -> 松开
3 总结
本篇介绍了嵌入式软件开发中常用的状态机编程实现,并通过按键消抖实例,以常用的switch-case形式,实现了对应的状态机编程代码实现,并通过测试,串口打印对应状态,分析状态机的状态跳转过程。
华为nova青春版真机开箱评测:主打颜值和拍照两大卖点!
业界首款100G CFP2光模块
华为深耕物联网,推进5G发展进程
全波整流电路和桥式整流电路的特点与区别
尼得科(Nidec)的双DD直驱变频电机被TCL科技集团的 新产品“双子舱洗衣机Q10”采用
嵌入式软件开发中常用的状态机编程实现
pcb电路怎样抗干扰
5G赋能,中兴全力推动兆丰机电数字化转型
力合微诚邀您相约能源电子博览会
电路的互易定理
制造企业如何快速实现数字化转型?
基于工业互联网平台的工业机器人故障检测方案规划
特斯拉Model3中方形扁卷绕硬壳LFP电池拆解分析
介绍智慧安全用电管理平台在城市建筑方面中的应用
海尔HOPE开放创新平台宣布与硅谷高创会达成合作
一体化IoT平台 实现智能家居合而为一
如何看待SSD的高温问题,多角度解答M.2固态硬盘高温问题
Light Reading专访华为沈城营:携手迈向NFV下一个黄金十年
聚焦TCL春季发布会:引发行业地震的X11G电视背后蕴藏着重磅科技!
c语言编写的源程序可以直接运行吗