第一个示例是通过计时器来判断按键状态的稳定性,而第二个示例则是通过延时函数和状态机来实现按键消抖。
这两种方法在消抖效果方面是相似的,但具体实现方式略有差异。使用计时器进行消抖可以更精确地控制时间间隔,避免了延时过程中其他任务的干扰,因此比较常用。而使用延时函数和状态机的方式虽然稍微复杂一些,但易于理解和实现,并且可以根据需要进行灵活调整,适用范围较广。
1.常见的消抖方法是在短时间内连续检测按键状态,并判断是否稳定稳定下来,如果检测到按键状态稳定,才认为按键真正被按下。
以下是一个简单的按键消抖的示例代码:
// 定义按键IO口的抽象数据类型
typedef struct {
GPIO_TypeDef *GPIOx; // GPIO组指针
uint16_t GPIO_Pin; // GPIO引脚编号
} KeyTypeDef;
// 按键初始化
void Key_Init(KeyTypeDef *key) {
// 配置GPIO方向为输入模式
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = key->GPIO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(key->GPIOx, &GPIO_InitStruct);
}
// 检测按键状态是否稳定
int Debounce_Key(KeyTypeDef *key) {
int key_val = 1; // 假设按键弹起
int toggle_cnt = 0; // 计数器,用于记录连续检测到的状态翻转次数
int i;
for (i = 0; i < 10; ++i) { // 连续检测10次按键状态
if (HAL_GPIO_ReadPin(key->GPIOx, key->GPIO_Pin) == 0) { // 如果检测到按键被按下
++toggle_cnt;
if (toggle_cnt >= 2) { // 如果连续检测到状态翻转超过2次,则说明按键状态已经稳定
key_val = 0; // 将按键值标记为按下
break;
}
} else {
toggle_cnt = 0; // 重置状态翻转计数器
}
HAL_Delay(10); // 延迟10毫秒
}
return key_val;
}
int main(void)
{
KeyTypeDef key;
key.GPIOx = GPIOA;
key.GPIO_Pin = GPIO_PIN_0;
Key_Init(&key);
while (1) {
int key_val = Debounce_Key(&key);
if (key_val == 0) {
// 按键被按下了,执行相应的操作
...
}
}
}
在上述代码中,Key_Init()
初始化按键的GPIO方向为输入模式,并启用上拉电阻。Debounce_Key()
函数用于检测按键状态是否稳定下来,通过连续检测10次按键状态,以及统计状态翻转次数,来判断按键是否真的被按下。如果检测到按键被按下,就将按键值标记为0,并返回该值。在主函数中,不断调用 Debounce_Key()
函数检测按键状态,如果检测到按键被按下,就执行相应的操作。
按键在按下或松开的瞬间,由于机械性能等因素会导致按键状态发生多次切换,从而引起连续发出多个按键事件。这种现象被称为按键抖动(Bounce),解决办法是对按键进行消抖操作,将抖动的过程消除掉,只保留按下和松开时的状态。
2.软件消抖主要是利用延时函数和状态机实现,具体流程如下:
- 检测按键是否按下;
- 若按键按下,则进入延时流程,等待固定时间;
- 再次检测按键状态,若相同则确定按下有效,否则重新进入延时流程。
代码示例如下(以STM32 HAL库为例):
#include "main.h"
#include <stdbool.h>
#define DEBOUNCE_TIME 10 // 延时时间,单位 ms
#define BUTTON_GPIO_PORT GPIOA
#define BUTTON_GPIO_PIN GPIO_PIN_0
typedef enum {
KEY_STATE_RELEASED = 0,
KEY_STATE_PRESSED = 1
} KeyState; // 定义按键状态枚举类型
bool debounce(KeyState *state) {
static uint8_t cnt = 0; // 计数器
bool ret = false; // 返回值
if (HAL_GPIO_ReadPin(BUTTON_GPIO_PORT, BUTTON_GPIO_PIN) == GPIO_PIN_RESET) {
*state = KEY_STATE_PRESSED;
if (++cnt >= DEBOUNCE_TIME) {
ret = true;
}
} else {
*state = KEY_STATE_RELEASED;
cnt = 0; // 计数器清零
}
return ret;
}
int main(void) {
HAL_Init();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = BUTTON_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(BUTTON_GPIO_PORT, &GPIO_InitStruct);
KeyState state = KEY_STATE_RELEASED;
while (1) {
if (debounce(&state)) {
if (state == KEY_STATE_PRESSED) {
// 进行按键处理操作
}
}
}
}
以上代码中,定义了DEBOUNCE_TIME变量作为设定的延时时间,BUTTON_GPIO_PORT和BUTTON_GPIO_PIN分别指示按键所连接的GPIO端口和引脚号。在主函数中,定义了KeyState类型的state变量作为按键状态标志,并通过轮询方式不断调用debounce函数进行消抖检测。
在debounce函数中,通过HAL_GPIO_ReadPin函数获取按键状态,然后根据状态的变化来更新计数器和状态标志,并返回消抖完成标志。当返回标志为真时,说明按键已经消抖完成,可以执行相应的按键处理操作。