** 01. **
按键消抖
在嵌入式学习的过程中,按键作为简单模块很早就出现在了众多学习者的学习进程中,学习按键的使用中必不可少的会接触到按键消抖。
由于按键存在机械抖动,在按下和释放的时候会存在大约5~20ms的机械抖动,这些机械抖动可能会导致按键的误判。
上图是大部分初学者接触到的按键检测流程,在释放和按下的瞬间都对其进行一定程度的延时,并且利用while等待按键释放。
这种轮询检测按键的方式具有实现简单的优点,没有涉及到过多复杂的功能,非常适合新手们学习。
但是这种轮询方式也有非常多的缺点,例如检测按键利用while等待按键释放,那么如果我们长按的时候整个系统就会卡死等待按键释放,这就是一种非阻塞式的按键检测。
并且这种方式无法轻易实现如长按检测,连按检测,双击检测等功能的实现。为了让按键检测功能具有更多的可拓展性,本期我们介绍利用定时器实现按键检测的方式。
** 02. **
状态机
在介绍定时器的按键检测之前,我们简单的聊一聊状态机的编程思维。状态机(FSM)是指在其生命周期内根据不同的事件而发生的状态变迁,在有限个状态以及在这些状态之间的转移和动作等行为
具体些来说,按键按下是一种状态,不同的按键按下也属于不同的状态。按键按下的方式不同(长按短按)也是不同的状态。所谓按键的状态机就是这些状态之间的切换和各种状态对应的动作实现。
在裸机编程中,系统的工作空间可以分为循环和中断。
根据这种运行状态,我们可以设置一个/多个标志位,让系统在主循环中根据这些标志位来判断是否该执行某些任务(函数)。
在中断中,我们就可以通过对相关的按键检测来改变这些标志位。
这种根据系统状态(各个标志位)来决定系统运行状态的编程思维就是状态机被广泛应用在编程领域中。
同样的,这种思维也被广泛的应用在GUI界面设计中。
** 03. **
代码实现
为了实现按键检测,这里我们引入定时器。例如设置50ms触发的定时器,实现利用定时器定时扫描按键状态。
利用定时器的周期性,本身就是一种效果非常好的消抖行为。并且我们可以统计按下按钮(例如按下是高电平即统计高电平)次数,来判断我们按下按钮的时间实现长按的状态。
在这个的基础上,以STM32为例我们编写相关的按键功能
#ifndef __KEY_H#define __KEY_H#include "main.h"// 按键状态枚举typedef enum { KEY_STATE_RELEASE, // 释放状态 KEY_STATE_PRESS, // 按下状态 KEY_STATE_LONG_PRESS, // 长按状态 KEY_STATE_REPEAT // 连按状态} KeyState;
// 按键结构体typedef struct { GPIO_TypeDef* GPIOx; // GPIO端口 uint16_t GPIO_Pin; // GPIO引脚 KeyState State; // 当前按键状态 uint32_t PressTime; // 按下时长计数 uint32_t RepeatCount; // 连按计数 uint8_t isPressed; // 是否处于按下状态} Key_TypeDef;
// 配置参数#define KEY_LONG_PRESS_TIME 100 // 长按时间阈值(100*20ms = 2s)#define KEY_REPEAT_TIME 25 // 连按时间阈值(25*20ms = 500ms)#define KEY_SCAN_PERIOD 20 // 扫描周期(ms)
// 扫描函数,在定时器中断函数中调用void KEY_Scan(Key_TypeDef* key);#endif
.h文件中我们定义了一个枚举变量,用来返回按键的状态值,有释放,按下,长按,连按等状态。
按键结构体定义中定义相关的变量,例如引脚端口号和引脚号等。
#include "key.h"void KEY_Scan(Key_TypeDef* key){ uint8_t currentLevel = HAL_GPIO_ReadPin(key->GPIOx, key->GPIO_Pin); // 检测到按键按下 if(currentLevel == GPIO_PIN_RESET) // 假设按键按下为低电平 { if(key->isPressed == 0) { key->isPressed = 1; key->State = KEY_STATE_PRESS; key->PressTime = 0; } else { key->PressTime++; // 长按检测 if(key->PressTime >= KEY_LONG_PRESS_TIME) { key->State = KEY_STATE_LONG_PRESS; // 连按检测 if(key->PressTime >= KEY_LONG_PRESS_TIME + KEY_REPEAT_TIME) { key->State = KEY_STATE_REPEAT; key->RepeatCount++; key->PressTime = KEY_LONG_PRESS_TIME; // 重置计时以便继续触发连按 } } } } // 按键释放 else { if(key->isPressed == 1) { key->isPressed = 0; key->State = KEY_STATE_RELEASE; key->RepeatCount = 0; key->PressTime = 0; } }}
在.c文件中,我们定义好按键检测的功能。接着只需要在main.c中初始化结构体,以及在定时器中断服务函数中调用即可。