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

发布者:温暖心情最新更新时间:2024-04-07 来源: elecfans关键字:STM32  按键消抖 手机看文章 扫描二维码
随时随地手机看文章

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


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 pressrn');

}

}

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 releasern');

}

}

break;

default:break;

}

if (g_keyStatus != g_lastKeyStatus)

{

g_lastKeyStatus = g_keyStatus;

printf('new key status:%d(%s)rn', g_keyStatus, key_status_name[g_keyStatus]);

}

}


int main(void)

{

delay_init();     //延时函数初始化   

KEY_Init();

uart_init(115200);

printf('hellorn');

while(1)

{

key_status_check();

delay_ms(10);

}

}

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


2.3 使用测试

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

我的测试输出信息如下:

pYYBAGMSCpeADWb3AACpThu0Gj0100.png

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

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


3 总结

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


关键字:STM32  按键消抖 引用地址:STM32按键消抖——入门状态机思维

上一篇:STM32CUBEMX(11)--外部中断(EXTI)
下一篇:stm32定时器如何获取高电平脉宽的时间

推荐阅读最新更新时间:2024-11-02 08:42

STM32启动文件(Reset_Handler函数)
该文件主要实现目的: 设置初始SP 设置初始PC=Reset_Handler 设置向量表入口地址,并初始化向量表 调用SystemInit,把系统时钟配制成72M,SystemInit在库文件system_stm32f10.c定义 调转到 标号_main,最终来到C程序文件 ; Reset handler Reset_Handler PROC EXPORT Reset_Handler IMPORT __main IMPORT SystemInit LDR R0, =SystemInit BLX R0
[单片机]
STM32-初学者必知
STM32的核心Cortex-M3处理器是一个标准化的微控制器结构,希望思考一下,何为标准化?简言之,Cortex-M3处理器拥有32位CPU,并行总线结构,嵌套中断向量控制单元,调试系统以及标准的存储映射。 嵌套中断向量控制器(Nested Vector Interrupt Controller,简称NVIC)是Cortex-M3处理器中一个比较关键的组件,它为基于Cortex-M3的微控制器提供了标准的中断架构和优秀的中断响应能力,为超过240个中断源提供专门的中断入口,而且可以赋予每个中断源单独的优先级。利用NVIC从可以达到极快的中断响应速度,从收到中断请求到执行中断服务的第一条指令仅需12个周期。这种极快的响应速度一方面得
[单片机]
在Mac OSX中开发STM32程序
在Mac OSX下编写STM32程序: 1、下载stm32的gcc软件包,参考下面URL: 2、安装moxa NPORT 5110在虚拟Windows中; 3、安装ST Flash Loader 软件; 需要注意的: 1、Mac OSX 需要10.5; 2、NPORT装完要关机重启;
[单片机]
stm32程序中的assert_param()的说明
子程序都有assert_param(....),如下:这句到底有什么用呢???把它删了应该可以的吗?? void TIM1_TimeBaseInit(u16 TIM1_Prescaler, TIM1_CounterMode_TypeDef TIM1_CounterMode, u16 TIM1_Period, u8 TIM1_RepetitionCounter) { assert_param(IS_TIM1_COUNTER_MODE_OK(TIM1_CounterMode)); ...... } 答: 这是断言,可以删掉,只在编译的时候防
[单片机]
STM32利用SPI读写SD卡的程序详解
关于SD卡的基础知识这里不做过多陈述,如果有对这方面感兴趣的朋友可以直接百度一下,有很多讲SD卡的文章,这里主要是针对SD卡的读写程序实现做一些详细说明。 SD卡的读写驱动程序是运用FATFS的基础,学了FATFS就可以在SD卡上创建文件夹及文件了。 我们先从main文件了解一下程序的执行流程 int main(void) { u16 i; USART1_Config(); for(i=0;i 1536;i++) send_data ='D'; switch(SD_Init()) { case 0: USART1_Puts( \r\nSD Card Init
[单片机]
stm32关闭中断
我只试了一下 NVIC_SETFAULTMASK(); //关闭总中断 NVIC_RESETFAULTMASK();//开放总中断 可以开、关中断 但是汇编 asm( CPSID I ); //关中断 asm( CPSIE I ); //开中断 编译过不了 我使用的是KEIL 3.50 今天找答案的时候找到这里的,发现还有自己的回复,还有自己的一个问题,下面把答案给附上: 项目中包含 STM32F10x_StdPeriph_Lib_V3.1.2\Libraries\CMSIS\Core\CM3\core_cm3.h STM32F10x_StdPeriph_Lib_V3.1.2\Libraries\CMSI
[单片机]
STM32开发笔记16: 使用静态库加快Keil编译速度
单片机型号:STM32L053R8T6 使用Stm32CubeMx生成的源文件数量比较多,编译的时候会占用很长的时间,我们仔细观察这些文件除了main.c外,其它的文件我们基本上是调用其中的函数,并不需要对其进行改动。所以,可以仿照Freescale MQX的设计方法,分层进行编译,下图是在进行项目设计时候的层次结构。 第0层用于存储Stm32CubeMx生成的工成文件,第2层处理除main.c以外的所有文件,第3层加入应用层的逻辑结构。 第0层的处理方法,大家参考Stm32CubeMx的使用方法,就可以了。 第1层的处理方法,也非常简单,可通过以下步骤完成: 1、在工程目录结构
[单片机]
<font color='red'>STM32</font>开发笔记16: 使用静态库加快Keil编译速度
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved