0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

码农爱学习 来源:码农爱学习 作者:码农爱学习 2022-09-02 21:54 次阅读

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

1 状态机思想

状态机,或称有限状态机FSM(Finite State Machine),是一种重要的编程思想。

状态机有3要素:状态事件响应

状态:系统处在什么状态?

事件:发生了什么事?

响应:此状态下发生了这样的事,系统要如何处理?

状态机编程前,首先要根据需要实现的功能,整理出一个对应的状态转换图(状态机图),然后就可以根据这个状态转换图,套用状态机编程模板,实现对应是状态机代码了。

状态机编程主要有 3 种方法:switch-case 法表格驱动法函数指针法,本篇先介绍最简单也最易理解的switch-case 法

2 状态机实例

下面以按键消抖功能,来介绍switch-case 法的状态机编程思路。

2.1 按钮消抖状态转换图

状态机机编程前,首先要明确的对应功能的状态机需要几个状态,本例的按键功能,只检测最基础的按下与松开状态(暂不实现长按、双击等状态),并增加对应的按钮去抖功能,因此,需要用到4个状态:

稳定松开状态

按下抖动状态

稳定按下状态

松开抖动状态

对应的状态转换图如下:

poYBAGMSCi2AVy31AAA8AKcyDe0236.png

由于按键通常处于松开状态,这里让状态机的初始化状态为松开状态,然后在这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 使用测试

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

我的测试输出信息如下:

pYYBAGMSCpeADWb3AACpThu0Gj0100.png

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

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

3 总结

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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6011

    文章

    44171

    浏览量

    624533
  • 嵌入式
    +关注

    关注

    5017

    文章

    18502

    浏览量

    293422
  • STM32
    +关注

    关注

    2251

    文章

    10731

    浏览量

    350592
  • 状态机
    +关注

    关注

    2

    文章

    487

    浏览量

    27283
收藏 人收藏

    评论

    相关推荐

    STM32按键状态机2——状态简化与增加长按功能

    本篇继续介绍状态机的使用,在上篇的基础上,通过简化按键逻辑,并增加按键长按功能,进一步介绍状态图的修改与
    的头像 发表于 09-03 21:26 3581次阅读
    <b class='flag-5'>STM32</b><b class='flag-5'>按键</b><b class='flag-5'>状态机</b>2——<b class='flag-5'>状态</b>简化与增加长按功能

    【连载视频教程(八)】小梅哥FPGA设计思想与验证方法视频教程之基于状态机的独立按键

    ,主要通过独立按键这样一个实验,来进一步举例讲解状态机的设计思想,独立按键
    发表于 09-29 14:19

    利用状态机按键程序

    与过程分离. 把和按键状态相关的东西统统塞到结构里, 把的代码放在一个函数中.//key.h 头文件
    发表于 02-18 19:58

    怎样去设计一个基于stm32标准库独立按键的多按键状态机

    写在前面  一般引用都写在最后,但是这篇博文对我这个状态机的影响很大,我这里有许多借鉴他的思维。所以写在前面,如有侵权立即删除简单按键检测  一开始学习单片的时候我接触到
    发表于 12-09 07:48

    STM32单片按键和FPGA按键的相关资料分享

    写在前面:STM32单片按键和FPGA按键
    发表于 01-18 06:39

    请问按键状态机的长按和短按测试该怎样去实现呢

    什么是按键呢?按键状态机的长按和短按测试该怎样去实现呢?
    发表于 01-20 06:45

    如何用状态机法实现单片矩阵键盘的控制

    看了蓝桥杯单片的培训视频,发现用状态机法实现矩阵键盘时左边两列没有用,这是因为P36、P37口实际是P42、P44口,所以我们在用行列扫描法时会出错。但习惯了
    发表于 02-17 07:47

    基于FPGA的按键电路设计

    采用了VHDL语言编程的设计方法,通过FPGA来实现按键的硬件电路。论述了基于计数器、RS触发器和状态机3种方法来实现按键
    发表于 12-05 14:13 224次下载

    利用状态机按键程序

    利用状态机按键程序讲解,很好的资料下载吧。
    发表于 01-11 09:32 30次下载

    VHDL—按键

    达到去抖动的目的。本例中用状态机实现了电路:端口描述:clk 输入检测时钟;reset 复位信号;din 原始按键信号输入; dout 去抖动输出信号。
    发表于 11-11 17:17 2次下载

    使用状态机按键处理方法电子钟程序免费下载

    之前的电子钟程序中,用的按键处理方法是10ms的延时,这种方法效率比较低所以现在利用状态机原理重写一下,效率很高啊 4个独立按键中用到
    发表于 07-09 17:41 8次下载
    使用<b class='flag-5'>状态机</b>的<b class='flag-5'>按键</b><b class='flag-5'>消</b><b class='flag-5'>抖</b>处理方法电子钟程序免费下载

    使用51单片实现按键的资料和程序免费下载

    抖动是机械按键存在的现象,是必须要进行处理的。一般处理有两种方式:一种是硬件,另一种是软件。硬件
    发表于 07-05 17:41 5次下载
    使用51单片<b class='flag-5'>机</b>实现<b class='flag-5'>按键</b><b class='flag-5'>消</b><b class='flag-5'>抖</b>的资料和程序免费下载

    基于stm32标准库独立按键的多按键状态机的实现

    写在前面  一般引用都写在最后,但是这篇博文对我这个状态机的影响很大,我这里有许多借鉴他的思维。所以写在前面,如有侵权立即删除简单按键检测  一开始学习单片的时候我接触到
    发表于 11-26 13:36 24次下载
    基于<b class='flag-5'>stm32</b>标准库独立<b class='flag-5'>按键</b>的多<b class='flag-5'>按键</b><b class='flag-5'>状态机</b>的实现

    基于STM32按键的防和松开处理:状态机

    用延时和while();去处理按键很浪费资源,这里我们用定时器来做一个按键的处理-状态机;typedef enum {KEY_RELEASED,KEY_PRESSED,KEY_PROCESSED
    发表于 12-09 09:21 7次下载
    基于<b class='flag-5'>STM32</b><b class='flag-5'>按键</b>的防<b class='flag-5'>抖</b>和松开处理:<b class='flag-5'>状态机</b>

    状态机法实现蓝桥杯单片矩阵键盘的控制

    看了蓝桥杯单片的培训视频,发现用状态机法实现矩阵键盘时左边两列没有用,这是因为P36、P37口实际是P42、P44口,所以我们在用行列扫描法时会出错。但习惯了
    发表于 12-20 19:42 6次下载
    用<b class='flag-5'>状态机</b><b class='flag-5'>消</b><b class='flag-5'>抖</b>法实现蓝桥杯单片<b class='flag-5'>机</b>矩阵键盘的控制