• STM32按键消抖——入门状态机思维


    在嵌入式软件开发中,状态机编程是一个十分重要的编程思想,它也是嵌入式开发中一个常用的编程框架。掌握了状态机编程思想,可以更加逻辑清晰的实现复杂的业务逻辑功能。

    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个状态。为了在调试时,能够把对应状态名称以字符串的形式打印出来,这里使用宏定义的一个小技巧:

    #符号+自定义的枚举名称
    
    • 1

    即可自动转变为字符串形式,再将这些字符串放到const char* key_status_name[]数组中,便可通过数组的形式访问这些状态的字符串名称形式。

    此外,为了不重复书写枚举名称与对应的枚举字符串(#+枚举名称),进一步使用宏定义的方式,只定义一次状态,然后通过下面两条宏定义,实现对枚举项枚举项对应的字符串的分别获取:

    #define ENUM_ITEM(ITEM) ITEM,
    #define ENUM_STRING(ITEM) #ITEM,
    
    • 1
    • 2

    具体是宏定义、枚举定义与枚举名称数组声明如下:

    #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)
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    宏定义不便理解的,可以将宏定义分别带入,转为最终的结果,理解替代后的具体形式,比如下面的宏定义带入替换示意:

    /*
    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,
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    注:本例程需要使用一个按键,需要初始化对应的GPIO,这里不再贴代码。

    2.3 使用测试

    将完整的代码编译后烧录到板子中,连接串口,按下与松开按键,观察串口输出信息。

    我的测试输出信息如下:

    前两次拨动按键模拟按钮抖动的情况,可以看到串口打印出两次从松开到按下抖动的状态切换。

    然后是按下按键,再松开按键,可以看到状态的变化:松开 -> 按下抖动 -> 按下 -> 松开抖动 -> 松开

    3 总结

    本篇介绍了嵌入式软件开发中常用的状态机编程实现,并通过按键消抖实例,以常用的switch-case形式,实现了对应的状态机编程代码实现,并通过测试,串口打印对应状态,分析状态机的状态跳转过程。

  • 相关阅读:
    CentOS7安装Oracle-19c
    Java中的抽象方法、抽象类和接口知识总结
    JAVA动漫周边产品销售管理系统计算机毕业设计Mybatis+系统+数据库+调试部署
    算法----组合总和(Kotlin)-面试真题
    【数据结构】栈的顺序表实现
    java+php+python的公文审批系统-办公系统
    Nginx:过滤模块的实现
    Linux centos环境 安装谷歌浏览器
    1-(4-氨基苯)-1,2,2-三苯乙烯|AIE聚集诱导发光|1-(4-Aminophenyl)-1,2,2-triphenylethene
    arduino 记录
  • 原文地址:https://blog.csdn.net/hbsyaaa/article/details/125494192