【stm32单片机基础】按键状态机实现长按和短按
【stm32单片机基础】按键状态机
文章目录
- 【stm32单片机基础】按键状态机
- 前言
- 一、按键的消抖
- 二、按键状态机实现
- 0.状态机模式
- 1.单个按键检测
- 2.单个按键实现长按和短按
- 三、长按和短按测试示例
- 四 、多按键检测
- 按键处理经典例程:
- 总结
前言
在单片机的教学例程中,常使用delay延迟的方式消除按键抖动,而delay延迟的方式使CPU处于空等的状态,不能进行其他任务,直到结束delay延时函数,这种阻塞的方式不利于多任务的情形。本文将使用非阻塞的方式消抖,并采用状态机的模式编写按键处理函数。
一、按键的消抖
按键消抖:通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,因而在闭合及断开的瞬间均伴随有一连串的抖动,按键抖动会引起一次按键被误读多次。
抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。
软件消抖:硬件方法将导致系统硬件电路设计复杂化,常采用软件方法进行消抖。
软件方法去抖,即检测出键闭合后执行一个延时程序,5ms~10ms的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。
二、按键状态机实现
0.状态机模式
简单理解为:将一个事件划分为有限个状态,满足相应的条件,在有限个状态之间跳转;可以使用状态图来描述事件处理过程,这种方式使得程序逻辑思路更加清晰严谨。以按键为例,按键检测的过程可以分为三个状态:按键检测状态、按键确认状态、按键释放状态;而在这三个状态之间跳转的条件为当前状态下按键的值。
在单片机中实现状态机最常用的语句便是switch case语句。
【状态机中如何非阻塞消抖】:使用定时器中断,定时每10ms执行一次switch case语句,即两个状态之间跳转的时间为10ms,这样便代替了delay延时。当定时中断发生时,才跳转到中断服务函数执行。
1.单个按键检测
独立按键电路
单个按键的状态转移图如下:
S1状态为按键检测,S2为按键确认,S3为释放按键;状态跳转条件为当前状态下读取到的按键高低电平Key,当Result为1时,表示按键已经成功按下。
单个按键检测的代码实现:
#ifdef SingleKeyEvent typedef enum
{KEY_CHECK = 0,KEY_COMFIRM = 1,KEY_RELEASE = 2
}KEY_STATE;KEY_STATE KeyState =KEY_CHECK; // 初始化按键状态为检测状态
u8 g_KeyFlag = 0; // 按键有效标志,0: 按键值无效; 1:按键值有效
/*** 单个按键检测事件* 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动;* 状态机使用switch case语句实现状态之间的跳转* */
void Key_Scan(void)
{switch (KeyState){//按键未按下状态,此时判断Key的值case KEY_CHECK: if(!Key) {KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态}break;//按键按下状态:case KEY_COMFIRM:if(!Key) //查看当前Key是否还是0,再次确认是否按下{KeyState = KEY_RELEASE; //进入下一个释放状态g_KeyFlag = 1; //按键有效值为1, 按键确认按下,按下就执行按键任务; } else //当前Key值为1,确认为抖动,则返回上一个状态{KeyState = KEY_CHECK; //返回上一个状态} break;//按键释放状态case KEY_RELEASE:if(Key) //当前Key值为1,说明按键已经释放,返回开始状态{ KeyState = KEY_CHECK;// g_KeyFlag = 1; //如果置于此,则在按键释放状态后,才执行按键任务;} break;default: break;}
}#endif
2.单个按键实现长按和短按
单个按键实现短按和长按是个很常用的方式,区别单个按键是否是长按和短按依靠检测按键按下的持续时间。
此处将短按时间T设为 10ms < T <1 s;长按时间的T设置为:T > 1s.
在上面的按键实现基础上可继续实现长按和短按判断,具体程序如下:
代码如下(示例):
#ifdef SingleKey_LongShort_Event
/*** 单个按键检测短按和长按事件* 短按:时间 10ms < T < 1 s, 长按:时间 T >1 s* 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动;* 状态机使用switch case语句实现状态之间的跳转* lock变量用于判断是否是第一次进行按键确认状态* :按键释放后才执行按键事件*/
void Key_Scan(void)
{static u8 TimeCnt = 0;static u8 lock = 0;switch (KeyState){//按键未按下状态,此时判断Key的值case KEY_CHECK: if(!Key) {KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态 }TimeCnt = 0; //计数复位lock = 0;break;case KEY_COMFIRM:if(!Key) //查看当前Key是否还是0,再次确认是否按下{if(!lock) lock = 1;TimeCnt++; } else {if(lock) // 不是第一次进入, 释放按键才执行{/*按键时长判断*/if(TimeCnt > 100) // 长按 1 s{g_KeyActionFlag = LONG_KEY;TimeCnt = 0; }else // Key值变为了1,说明此处动作为短按{g_KeyActionFlag = SHORT_KEY; // 短按}/*按键时长判断*/KeyState = KEY_RELEASE; // 需要进入按键释放状态 }else // 当前Key值为1,确认为抖动,则返回上一个状态{KeyState = KEY_CHECK; // 返回上一个状态}} break;case KEY_RELEASE:if(Key) //当前Key值为1,说明按键已经释放,返回开始状态{ KeyState = KEY_CHECK; }break;default: break;}
}
#endif
按键释放之后,才检测不太合理,做如下调整,长按事件需要达到时长就执行,短按可以在按键释放后执行。
/*** 单个按键检测短按和长按事件* 短按:时间 10ms < T < 1 s, 长按:时间 T >1 s* 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动;* 状态机使用switch case语句实现状态之间的跳转* lock变量用于判断是否是第一次进行按键确认状态* :长按键事件提前执行,短按键事件释放后才执行*/
void Key_Scan(void)
{static u8 TimeCnt = 0;static u8 lock = 0;switch (KeyState){//按键未按下状态,此时判断Key的值case KEY_CHECK: if(!Key) {KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态 }TimeCnt = 0; //计数复位lock = 0;break;case KEY_COMFIRM:if(!Key) //查看当前Key是否还是0,再次确认是否按下{if(!lock) lock = 1;TimeCnt++; /*按键时长判断*/if(TimeCnt > 100) // 长按 1 s{g_KeyActionFlag = LONG_KEY;TimeCnt = 0; lock = 0; //重新检查KeyState = KEY_RELEASE; // 需要进入按键释放状态} } else {if(1==lock) // 不是第一次进入, 释放按键才执行{g_KeyActionFlag = SHORT_KEY; // 短按KeyState = KEY_RELEASE; // 需要进入按键释放状态 }else // 当前Key值为1,确认为抖动,则返回上一个状态{KeyState = KEY_CHECK; // 返回上一个状态}} break;case KEY_RELEASE:if(Key) //当前Key值为1,说明按键已经释放,返回开始状态{ KeyState = KEY_CHECK; }break;default: break;}
}
三、长按和短按测试示例
头文件
/*** 使用定时器来轮询Key_Scan()函数,定时节拍为2ms,* 状态转换时间为10ms,即每次进入switch case语句的时间差为10ms* 利用该10ms的间隙跳过按键抖动*/
#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h"//按键对应的IO管脚 KEY1 PA.15
#define KEY_IO_RCC RCC_APB2Periph_GPIOA
#define KEY_IO_PORT GPIOA
#define KEY_IO_PIN GPIO_Pin_15//Key: 1:高电平,按键未按下, 0:低电平,按键按下
#define Key GPIO_ReadInputDataBit(KEY_IO_PORT,KEY_IO_PIN)typedef enum
{KEY_CHECK = 0,KEY_COMFIRM = 1,KEY_RELEASE = 2
}KEY_STATE;typedef enum
{NULL_KEY = 0,SHORT_KEY =1,LONG_KEY
}KEY_TYPE;//extern u8 g_KeyFlag;
//extern KEY_TYPE g_KeyActionFlag; //单个按键事件
//#define SingleKeyEvent//单个按键实现长按和短按
#define SingleKey_LongShort_Event 1
void Key_Init(void);
void Key_Scan(void);
main函数
KEY_STATE KeyState =KEY_CHECK; // 初始化按键状态为检测状态
u8 g_KeyFlag = 0; // 按键有效标志,0: 按键值无效; 1:按键值有效KEY_TYPE g_KeyActionFlag; //用于区别长按和短按int main()
{Key_Init();Timer_init(19,7199);//10Khz的计数频率,计数到20为2mswhile(1){switch(g_KeyActionFlag){case SHORT_KEY:/*执行短按对应的事件*/g_KeyActionFlag = 0; //状态回到空break;case LONG_KEY:/*执行长按对应的事件*/g_KeyActionFlag = 0; //状态回到空default: break;}}
}void TIM3_IRQHandler(void) //TIM3 每 2ms 中断一次
{ static u8 cnt;if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 {cnt++;if(cnt>5) // 每10ms 执行一次按键扫描程序{Key_Scan();cnt = 0;}TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位:TIM 中断源 }
}
四 、多按键检测
同样的,多按键也是一样的;
示例如下:
Buttion.h 头文件中只需要把Key做下修改;
#define KEY0 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)//读取按键0
#define KEY1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)//读取按键1
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键2
#define Key (KEY0 && KEY1 && (!WK_UP))typedef enum
{KEY_CHECK = 0,KEY_COMFIRM = 1,KEY_RELEASE = 2,
}KEY_STATE;//对应的按键值,
typedef enum
{KEY_NULL = 0,KEY_0,KEY_1,KEY_WK_UP
}KEY_VALUE;
对应的状态机中对按键值进行区分即可:
void Key_Scan(void)
{switch (KeyState){//按键未按下状态,此时判断Key的值case KEY_CHECK: if(!Key) {KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态}break;//按键按下状态:case KEY_COMFIRM:if(!Key) //查看当前Key是否还是0,再次确认是否按下{KeyState = KEY_RELEASE; //进入下一个释放状态//g_KeyFlag = 1; //按键有效值为1, 按键确认按下,按下就执行按键任务; // 多按键判断if(0 == KEY0)g_Key = KEY_0;else if(0 == KEY1)g_Key = KEY_1;else if(1 == WK_UP)g_Key = KEY_WK_UP;} else //当前Key值为1,确认为抖动,则返回上一个状态{KeyState = KEY_CHECK; //返回上一个状态} break;//按键释放状态case KEY_RELEASE:if(Key) //当前Key值为1,说明按键已经释放,返回开始状态{ KeyState = KEY_CHECK;} break;default: break;}
}
main函数中也是一样,使用switch case 语句执行按键事件即可:
extern KEY_VALUE g_Key;switch(g_Key){case KEY_0:/* KEY 0 按键事件 */g_Key=KEY_NULL;break;case KEY_1:/* KEY 1 按键事件 */g_Key=KEY_NULL;break;case KEY_WK_UP:/* KEY_WK_UP 按键事件 */g_Key=KEY_NULL;break;default: break;}
按键处理经典例程:
以下是按键处理的经典程序:这是一个论坛中某位博主写的,在csdn这个blog中的也对这种方法进行了归纳:
[独立按键] - 1: 单击,双击,三击以及N击
按键处理的核心就是把driver层和中间层进行拆分,driver层只需要判断长按,短按和无按下这三种状态,中间层再在上面进行处理,即可处理更多种情况;
示例场景:一个按键需要通过长按和短按实现三种功能;短按的时候,在2 秒之内,继续短按,即转变为其他功能,长按的时候则是另外一种功能。对应具体需求就是:首先短按1下开机,开机之后,如果2秒之内继续短按,则短按切换为设置键;超过2秒之后,短按1下,则关机;
这种情况下,程序可以如下:
driver 层不需要更改,只需要对中间层进行处理;
//============================ key.c ===================#define KEY_INPUT P1.0 // 按键IO#define KEY_STATE_0 0 // 按键状态
#define KEY_STATE_1 1
#define KEY_STATE_2 2
#define KEY_STATE_3 3#define LONG_KEY_TIME 300 // LONG_KEY_TIME*10MS = 3S
#define SINGLE_KEY_TIME 3 // SINGLE_KEY_TIME*10MS = 30MS#define N_KEY 0 // no click
#define S_KEY 1 // single click
#define L_KEY 10 // long pressunsigned char key_driver(void)
{ static unsigned char key_state = 0; // 按键状态变量static unsigned int key_time = 0; // 按键计时变量unsigned char key_press, key_return; key_return = N_KEY; // 清除 返回按键值key_press = KEY_INPUT; // 读取当前键值switch (key_state) { case KEY_STATE_0: // 按键状态0:判断有无按键按下if (!key_press) // 有按键按下{key_time = 0; // 清零时间间隔计数key_state = KEY_STATE_1; // 然后进入 按键状态1} break;case KEY_STATE_1: // 按键状态1:软件消抖(确定按键是否有效,而不是误触)。按键有效的定义:按键持续按下超过设定的消抖时间。if (!key_press) {key_time++; // 一次10msif(key_time>=SINGLE_KEY_TIME) // 消抖时间为:SINGLE_KEY_TIME*10ms = 30ms;{key_state = KEY_STATE_2; // 如果按键时间超过 消抖时间,即判定为按下的按键有效。按键有效包括两种:单击或者长按,进入 按键状态2, 继续判定到底是那种有效按键}} else key_state = KEY_STATE_0; // 如果按键时间没有超过,判定为误触,按键无效,返回 按键状态0,继续等待按键break; case KEY_STATE_2: // 按键状态2:判定按键有效的种类:是单击,还是长按if(key_press) // 如果按键在 设定的长按时间 内释放,则判定为单击{ key_return = S_KEY; // 返回 有效按键值:单击key_state = KEY_STATE_0; // 返回 按键状态0,继续等待按键} else{key_time++; if(key_time >= LONG_KEY_TIME) // 如果按键时间超过 设定的长按时间(LONG_KEY_TIME*10ms=200*10ms=2000ms), 则判定为 长按{key_return = L_KEY; // 返回 有效键值值:长按key_state = KEY_STATE_3; // 去状态3,等待按键释放}}break;case KEY_STATE_3: // 等待按键释放if (key_press) {key_state = KEY_STATE_0; // 按键释放后,进入 按键状态0 ,进行下一次按键的判定} break; default: // 特殊情况:key_state是其他值得情况,清零key_state。这种情况一般出现在 没有初始化key_state,第一次执行这个函数的时候key_state = KEY_STATE_0;break;}return key_return; // 返回 按键值
}
中间层接收到driver层的返回数据,再根据功能需求编写处理函数;
void key_event_handle(void)
{static uint8_t long_click_cnt = 0;static uint8_t short_click_cnt = 0;volatile static uint32_t prev_cnt, cur_cnt = 0;switch(key_return){case S_KEY: // 短按 开关{/* turn on */if(power_state != ON){prev_cnt = get_tim3_systick();power_state = ON;printf("turn on!! \r\n"); } else{cur_cnt = get_tim3_systick(); // 在定时器3中获取最新计数值if((cur_cnt - prev_cnt) < 2000) // 两次单击时间小于2s{short_click_cnt++;short_click_cnt = short_click_cnt%2;/* 根据按键的次数依次执行不同的动作 */switch(short_click_cnt){case 0:printf("enter case 0 \r\n");/*此处可调用需要执行的函数*/ break;case 1:printf("enter case 1 \r\n");/*此处可调用需要执行的函数*/ break;default:break;} }else // 超过2秒,turn offs{printf("turn off \r\n");power_state = OFF;}/* 更新计数值 */prev_cnt = cur_cnt;}}break;case L_KEY: {if(power_state){long_click_cnt++;if(long_click_cnt > 50) // 500ms 进行一次调用{ long_click_cnt = 0;printf("enter long press mode\r\n");/*此处调用长按处理函数*/}}}break;default:break;}
}
总结
以上便是对按键状态机程序的总结,对长按和短按的判断实现……
【stm32单片机基础】按键状态机实现长按和短按相关推荐
- stm32单片机基础知识总结(三)
今天天津下了好大的雪,我想这应该是2022年的第一场雪.虽然已经进入了春天,但是能下这么大的雪,对于大多数北方人来说,并不会感到意外.大家都知道有个词叫做"倒春寒",就是说春天来临 ...
- STM32单片机基础之蜂鸣器
说明: 这里说明一下,STM32有很多文件,我这里上传的只是部分配置文件,不是所有的文件. 代码: main.c 文件#include "system.h" #include &q ...
- STM32单片机基础19——使用SDMMC接口读写SD卡数据
本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的硬件SDMMC外设读取SD卡数据. 1. 准备工作 硬件准备 开发板 首先需要准备一个开发板,这里我准备的是STM32L4 ...
- STM32单片机基础12——使用通用定时器产生PWM驱动蜂鸣器
本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的通用定时器外设,产生PWM驱动无源蜂鸣器. 1. 准备工作 硬件准备 开发板 首先需要准备一个开发板,这里我准备的是STM ...
- STM32单片机基础17——使用硬件SPI驱动TFT-LCD(ST7789)
本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的硬件SPI外设与ST7789通信,驱动16bit TFT-LCD 屏幕. 0. 前言 学习 SPI 外设驱动LCD屏幕没有 ...
- 基于STM32单片机的远程智能浇花花盆GSM短信浇水补光方案原理图程序设计
硬件电路的设计 (末尾附文件) 3.1系统的功能分析及体系结构设计 3.1.1系统功能分析 本设计由STM32F103C8T6单片机核心电路+温湿度传感器DHT11电路+LCD1602液晶显示电路+继 ...
- STM32 单片机 程序 指纹 门禁锁 RC522 射频卡 发短信 报警
功能按这个做: 功能设计如下: 硬件:单片机+显示器+指纹模块+继电器+按键+振动模块+发短信模块+人体红外+RC522射频卡刷卡模块+震动模块 1 开机后,检测指纹模块是否存在,如不存在,屏幕会有相 ...
- 【ESP32】按键驱动,长按、短按,可设置多个按键
文章目录 一.ESP32 二.实现 1.源文件 2.头文件 三.调用 总结 一.ESP32 之后的项目要用到ESP32,对按键驱动进行移植 二.实现 1.源文件 支持短按.长按和多个按键. 我在这里设 ...
- STM32 -- 实现按键的长按与短按检测(其他单片机可移植)
一 前言 今天在逛博客的时候,偶然看到了一篇关于按键检测的文章,兴趣使然自己尝试了一番,写了一些代码去验证自己的思路,通过验证完美实现了长按和短按检测,后续有时间的我也会更新一下连按检测等 二 思路 ...
- 51单片机——按键,定时器消抖,短按,长按,三行代码,状态机
0. 序 今天太晚了,先不写介绍了,代码中注释非常详细,很方便移植 双击,三击等操作,可以在状态机的短按操作中做更改. 组合键需要重新构思,在扫描中确定被按下和被抬起的是哪个键. 1. 分析 (1)按 ...
最新文章
- Libre OJ 「BalticOI 2013」非回文数 数位dp
- python拨号_python 拨号代码(win10 系统亲测有效)
- 禁止访问 共享计算机,win7如何禁止局域网用户访问电脑
- 80后偷偷“变老”的20种表现
- 彭博社“机器学习基础”视频教程已经全部搬到腾讯视频
- Flutter AnimatedSwitcher 实现优美的图片切换动画
- 如何设置mysql远程访问
- Go bufio.Reader 结构+源码详解
- Android Webview实现文件下载功能
- android studio读写txt,Android Studio从.txt文件读取/写入,保存路径?
- 计算机开机切换用户界面,win7开机登录界面怎么设置win7更换开机画面壁纸解决办法...
- 超分辨网络SRCNN的Pytorch实现
- C语言short精解
- 阿里的世界版图——“风清扬”的全球梦
- linux克隆机器IP问题
- 介绍中国传统节日的网页html,介绍中国传统节日
- 下月起你的到手工资将有重大变化! 图解2019年个税怎么算
- L9110 L9110S SOP8 马达驱动 IC 芯片 H桥全桥 贴片
- CC2540\CC2541 资料整理
- 对于微信支付宝支付的总结
热门文章
- react 函数组件使用了 hook 后闪屏问题的分析与解决
- python 身高预测
- 浏览器与HTTP网络协议缓存原理分析 转自网界网:http://news.cnw.com.cn/news-china/htm2015/20151027_322909.shtml
- drf接收、处理上传图片并写入文件
- 女性三围--表单只能输入数字和英文输入下的逗号
- VB 提示框MsgBox用法
- 对《骨骼运动变换的数学计算过程详解》一文的理解
- 怎么设置html页面背景图片大小怎么设置,css怎么设置背景图片大小?
- Attention机制原理
- Xcode6的新特性、