一、简介
1.1 功能需求
- 短按:按键按下时间 < 1s,触发一次短按事件;
- 长按:按键按下时间 ≥ 1s,触发一次长按事件;
- 3秒内统计按键次数:在3秒内统计短按次数(长按不计入)。
1.2 软件设计思路
- 使用 状态机 检测按键状态(按下、释放、长按、短按);
- 使用 滴答定时器 记录按下时间;
- 使用 软件定时器 实现3秒统计窗口;
二、代码
2.1 按键驱动头文件 key.h
c
#ifndef __KEY_H
#define __KEY_H
#include "main.h" // HAL 头文件,提供 GPIO 口与 HAL_GetTick
#include <stdbool.h>
/* 用户配置:引脚、电平、长按阈值、统计窗口时长 */
/* BUTTON_USER定义 */
#define RCC_BUTTON_USER_CLK RCC_PERIPH_CLK_GPIOB
#define BUTTON_USER_PORT GPIOB
#define BUTTON_USER_PIN GPIO_PIN_10
#define KEY_PRESSED_LEVEL 0 // 0 = 低电平为按下
#define KEY_LONG_MS 1000 // ≥1 s 算长按
#define KEY_STAT_WINDOW_MS 3000 // 3 s 统计窗口
/* 状态机状态 */
typedef enum {
KEY_STA_IDLE = 0,
KEY_STA_PRESSED,
KEY_STA_SHORT,
KEY_STA_LONG
} KeyStateEnum;
/* 按键结构体 */
typedef struct {
/* 底层硬件信息 */
GPIO_t *port;
uint16_t pin;
uint8_t pressedLevel; // 按下时的电平值
uint32_t longThd; // 长按阈值 ms
uint32_t winThd; // 统计窗口 ms
/* 运行期状态 */
KeyStateEnum state;
uint32_t tPress; // 本次按下时刻
uint32_t tRelease; // 本次释放时刻
uint8_t shortCnt; // 窗口内短按计数
uint32_t winStart; // 统计窗口起始时刻
bool winActive; // 统计窗口是否打开
} Key_t;
extern Key_t gKey;
void KEY_Init(Key_t *k);
void KEY_Scan(Key_t *k);
void KEY_Process(Key_t *k);
#endif
2.2 按键驱动源文件 key.c
c
#include "key.h"
#include <stdio.h>
/**
* @brief EXTI初始化
* @retval 无
*/
void KeyExtiInit(void)
{
std_exti_init_t exti_init_config = {0};
std_gpio_init_t button_init_config = {0};
/* 使能BUTTON_USER对应的GPIO时钟 */
std_rcc_gpio_clk_enable(RCC_BUTTON_USER_CLK);
/* 配置BUTTON_USER的GPIO */
button_init_config.pin = BUTTON_USER_PIN;
button_init_config.mode = GPIO_MODE_INPUT;
button_init_config.pull = GPIO_PULLUP;
std_gpio_init(BUTTON_USER_PORT, &button_init_config);
/* 配置BUTTON_USER的EXTI */
exti_init_config.line_id = BUTTON_USER_EXTI_LINE;
exti_init_config.mode = EXTI_MODE_INTERRUPT;
exti_init_config.trigger = EXTI_TRIGGER_FALLING;
exti_init_config.gpio_id = BUTTON_USER_EXTI_PORT;
std_exti_init(&exti_init_config);
/* 配置中断优先级 */
NVIC_SetPriority(EXTI4_15_IRQn, NVIC_PRIO_0);
/* 使能中断 */
NVIC_EnableIRQ(EXTI4_15_IRQn);
}
Key_t gKey;
/* 读引脚,返回 true = 当前处于按下状态 */
static bool KEY_Read(const Key_t *k) {
return std_gpio_get_input_pin(k->port, k->pin) == k->pressedLevel;
}
/* 初始化,把用户配置填进去 */
void KEY_Init(Key_t *k) {
k->port = BUTTON_USER_PORT;
k->pin = BUTTON_USER_PIN;
k->pressedLevel= KEY_PRESSED_LEVEL; // 0 = 低电平为按下
k->longThd = KEY_LONG_MS; // ≥1 s 算长按
k->winThd = KEY_STAT_WINDOW_MS; // 3 s 统计窗口
k->state = KEY_STA_IDLE;
k->tPress = 0; // 本次按下时刻
k->tRelease = 0; // 本次释放时刻
k->shortCnt = 0; // 窗口内短按计数
k->winStart = 0; // 统计窗口起始时刻
k->winActive = false; // 统计窗口是否打开
}
/* 状态机扫描,1 ms 周期调用 */
void KEY_Scan(Key_t *k)
{
static bool lastRaw = false;
bool raw = KEY_Read(k);
uint32_t now = osKernelGetTickCount();
switch (k->state)
{
case KEY_STA_IDLE:
if (raw && !lastRaw) // 刚按下
{
k->tPress = now;
k->state = KEY_STA_PRESSED;
}
break;
case KEY_STA_PRESSED: // 释放
if (!raw)
{
k->tRelease = now;
if ((k->tRelease - k->tPress) < k->longThd)
k->state = KEY_STA_SHORT;
else
k->state = KEY_STA_IDLE;
}
else if ((now - k->tPress) >= k->longThd)
{
k->state = KEY_STA_LONG;
}
break;
case KEY_STA_SHORT:
/* 打开或延续统计窗口 */
if (!k->winActive)
{
k->winActive = true;
k->winStart = now;
k->shortCnt = 1;
}
else
{
k->shortCnt++;
}
k->state = KEY_STA_IDLE;
break;
case KEY_STA_LONG:
/* 长按事件触发,不计入统计 */
k->state = KEY_STA_IDLE;
break;
}
lastRaw = raw;
}
/* 业务层:3 s 到点后打印统计值,并重置窗口 */
void KEY_Process(Key_t *k)
{
if (!k->winActive) return;
uint32_t now = osKernelGetTickCount();
if ((now - k->winStart) >= k->winThd)
{
printf("3 s 内短按次数:%d\n", k->shortCnt);
k->state = KEY_STA_IDLE;
k->tPress = 0; // 本次按下时刻
k->tRelease = 0; // 本次释放时刻
k->shortCnt = 0; // 窗口内短按计数
k->winStart = 0; // 统计窗口起始时刻
k->winActive = false; // 统计窗口是否打开
if(system_info.flags.key_isr_flag == 1)
{
if(KeyIsPressed() == false)
{
printf("KeyIsPressed-2 \r\n");
system_info.flags.key_isr_flag = 0;
}
}
}
}
2.3 主循环中调用
c
void AppTaskStart(void *argument)
{
const uint16_t usFrequency = 10; /* 延迟周期 */
uint32_t tick;
/* 获取当前时间 */
tick = osKernelGetTickCount();
KEY_Init(&gKey);
while(1)
{
KEY_Scan(&gKey); // 10 ms 周期
KEY_Process(&gKey);
/* 相对延迟 */
tick += usFrequency;
osDelayUntil(tick);
}
}
2.4 示例输出(串口):
