更新按键程序,加入时钟源,加入连击功能

This commit is contained in:
不吃油炸鸡
2025-12-02 19:23:11 +08:00
parent 6a27b28ace
commit e141aa4260
2 changed files with 327 additions and 60 deletions

View File

@@ -1,7 +1,244 @@
#include "key.h"
#include "cmsis_os.h"
/*
* key.c
*
* 规范化按键驱动:
* - 支持多键注册GPIO低/高有效)
* - 去抖、长按、连发
* - 事件队列DOWN/UP/CLICK/LONG/REPEAT
* - 掩码查询当前稳定按下状态,支持多键同时
*
* Created on: 2025-12-02
*/
void Key_Port_Init(void)
#include "key.h"
#include "sys.h"
#include "cmsis_os.h" // 若无OS可移除
#include <string.h>
/* 若有OS则用OS tick否则用HAL tick */
#if OS_SUPPORT
#define key_get_tick() ((uint32_t)osKernelGetTickCount())
#else
#define key_get_tick() ((uint32_t)HAL_GetTick())
#endif
#define TICK_DIFF(now, then) ((uint32_t)((uint32_t)(now) - (uint32_t)(then)))
/* 按键槽结构体 */
typedef struct {
GPIO_TypeDef* port; // gpio port
uint16_t pin; // gpio pin
uint8_t active_high; // 高电平有效
uint8_t registered; // 是否已注册
uint8_t raw_pressed; // 原始按下状态
uint8_t stable_pressed; // 稳定按下状态
uint32_t debounce_tick; // 消抖计时
uint8_t long_reported; // 长按事件是否上报
uint32_t press_tick; // 按下时刻
uint32_t last_repeat_tick; // 上次连发时刻
} key_slot_t;
/* 全局按键管理结构体 */
static struct {
uint8_t mode;
key_timing_t timing;
uint32_t long_enable_mask;
key_slot_t slot[KEY_ID_MAX];
uint32_t raw_mask;
uint32_t stable_mask;
} g_key = {
.mode = (KEY_MODE_REPORT_DOWN_UP | KEY_MODE_CLICK_ON_RELEASE | KEY_MODE_LONG_ONCE),
.timing = { .debounce_ms = 5, .long_ms = 500, .repeat_ms = 150 },
.long_enable_mask = 0,
.raw_mask = 0,
.stable_mask = 0,
};
/**
* @brief 注册GPIO按键
* @param id 按键ID
* @param port GPIO端口
* @param pin GPIO引脚
* @param active_high 引脚高电平有效
*/
bool Key_RegisterGPIO(key_id_t id, GPIO_TypeDef* port, uint16_t pin, uint8_t active_high)
{
if(id >= KEY_ID_MAX || port == NULL) return false;
key_slot_t* k = &g_key.slot[id];
k->port = port;
k->pin = pin;
k->active_high = active_high ? 1 : 0;
k->registered = 1;
k->raw_pressed = 0;
k->stable_pressed = 0;
k->debounce_tick = key_get_tick();
k->long_reported = 0;
k->press_tick = 0;
k->last_repeat_tick = 0;
return true;
}
/**
* @brief 设置按键模式
* @param mode_flags 模式标志位
*/
void Key_SetMode(uint8_t mode_flags) { g_key.mode = mode_flags; }
/**
* @brief 获取按键模式
* @return 当前模式标志位
*/
uint8_t Key_GetMode(void) { return g_key.mode; }
/**
* @brief 设置允许长按/连发的按键掩码
* @param mask 按键掩码
*/
void Key_SetLongEnableMask(uint32_t mask) { g_key.long_enable_mask = mask & KEY_ALL_MASK; }
/**
* @brief 获取允许长按/连发的按键掩码
* @return 按键掩码
*/
uint32_t Key_GetLongEnableMask(void) { return g_key.long_enable_mask; }
/**
* @brief 获取当前稳定按下的按键掩码
* @return 按键掩码
*/
uint32_t Key_GetPressedMask(void) { return g_key.stable_mask; }
/**
* @brief 获取当前原始按下的按键掩码
* @return 按键掩码
*/
uint32_t Key_GetRawMask(void) { return g_key.raw_mask; }
/**
* @brief 设置时序参数
* @param t 指向时序参数结构体的指针
*/
void Key_SetTiming(const key_timing_t* t)
{
if(!t) return;
if(t->debounce_ms) g_key.timing.debounce_ms = t->debounce_ms;
if(t->repeat_ms) g_key.timing.repeat_ms = t->repeat_ms;
if(t->long_ms) g_key.timing.long_ms = t->long_ms;
}
/**
* @brief 获取按键按下状态(考虑高/低有效)
* @param port GPIO端口
* @param pin GPIO引脚
* @param active_high 引脚高电平有效
*/
static inline uint8_t read_pressed(GPIO_TypeDef* port, uint16_t pin, uint8_t active_high)
{
GPIO_PinState s = HAL_GPIO_ReadPin(port, pin);
return active_high ? (s == GPIO_PIN_SET) : (s == GPIO_PIN_RESET);
}
/**
* @brief 扫描一次,若有事件则通过 out_evt 返回
* @param out_evt 指向事件对象的指针(非空)
* @return true 有事件false 无事件
*/
bool Key_Scan(key_event_t* out_evt)
{
if(!out_evt) return false;
uint32_t now = key_get_tick();
/* 采样原始状态 */
uint32_t new_raw_mask = 0;
for(uint8_t i=0; i<KEY_ID_MAX; ++i){
key_slot_t* k = &g_key.slot[i];
if(!k->registered) continue;
uint8_t raw = read_pressed(k->port, k->pin, k->active_high);
if(raw != k->raw_pressed){
k->raw_pressed = raw;
k->debounce_tick = now;
}
if(raw) new_raw_mask |= KEY_BIT(i);
}
g_key.raw_mask = new_raw_mask;
/* 边沿与事件(优先返回最先发现的一个事件) */
for(uint8_t i=0; i<KEY_ID_MAX; ++i){
key_slot_t* k = &g_key.slot[i];
if(!k->registered) continue;
/* 去抖窗口内不改变稳定态 */
if(TICK_DIFF(now, k->debounce_tick) < g_key.timing.debounce_ms){
continue;
}
uint8_t stable_now = k->raw_pressed;
if(stable_now != k->stable_pressed){
/* 稳定边沿 */
k->stable_pressed = stable_now;
if(stable_now){
/* DOWN */
g_key.stable_mask |= KEY_BIT(i);
k->press_tick = now;
k->last_repeat_tick = now;
k->long_reported = 0;
if(g_key.mode & KEY_MODE_REPORT_DOWN_UP){
out_evt->id = (key_id_t)i;
out_evt->type = KEY_EVT_DOWN;
return true;
}
}else{
/* UP */
g_key.stable_mask &= ~KEY_BIT(i);
if(g_key.mode & KEY_MODE_REPORT_DOWN_UP){
out_evt->id = (key_id_t)i;
out_evt->type = KEY_EVT_UP;
return true;
}
if((g_key.mode & KEY_MODE_CLICK_ON_RELEASE) && !k->long_reported){
out_evt->id = (key_id_t)i;
out_evt->type = KEY_EVT_CLICK;
/* 复位长按标记 */
k->long_reported = 0;
return true;
}
/* 复位长按标记 */
k->long_reported = 0;
}
}
}
/* 长按/连发(按下且允许时) */
for(uint8_t i=0; i<KEY_ID_MAX; ++i){
key_slot_t* k = &g_key.slot[i];
if(!k->registered || !k->stable_pressed) continue;
uint32_t bit = KEY_BIT(i);
bool long_allowed = (g_key.long_enable_mask & bit) != 0;
if(!long_allowed) continue;
uint32_t held = TICK_DIFF(now, k->press_tick);
/* 首次长按 */
if(!k->long_reported && (held >= g_key.timing.long_ms)){
k->long_reported = 1;
if(g_key.mode & (KEY_MODE_LONG_ONCE | KEY_MODE_REPEAT)){
out_evt->id = (key_id_t)i;
out_evt->type = KEY_EVT_LONG;
k->last_repeat_tick = now;
return true;
}
}
/* 连发 */
if(k->long_reported && (g_key.mode & KEY_MODE_REPEAT)){
if(TICK_DIFF(now, k->last_repeat_tick) >= g_key.timing.repeat_ms){
k->last_repeat_tick = now;
out_evt->id = (key_id_t)i;
out_evt->type = KEY_EVT_REPEAT;
return true;
}
}
}
return false;
}
/* 初始化GPIO */
static void Key_Port_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
@@ -15,49 +252,26 @@ void Key_Port_Init(void)
HAL_GPIO_Init(KEYB_PORT, &GPIO_InitStruct);
}
// mode: 0 - sigle press, 1 - long press
// return: 0 - no key press, 1 - KEYB, 2 - KEYY, 3 - KEYL, 4 - KEYR, 5 - KEYN
uint8_t KeyScan(uint8_t mode)
/* 注册默认按键 */
static bool Key_RegisterDefaultKeys(void)
{
static uint8_t key_up = 1;
static uint8_t key_down = 0;
uint8_t keyvalue = 0;
if(mode)
{
key_up = 1;
key_down = 0;
}
if( key_up && (!KEYB || !KEYY || !KEYL || !KEYR || !KEYN) )
{
osDelay(3); // ensure the key pressed
if(!KEYB)
key_down = KEYB_NUM;
else if(!KEYY)
key_down = KEYY_NUM;
else if(!KEYL)
key_down = KEYL_NUM;
else if(!KEYR)
key_down = KEYR_NUM;
else if(!KEYN)
key_down = KEYN_NUM;
if(key_down)
key_up = 0;
}
// 松开才有效
if ( key_down && KEYB && KEYY && KEYL && KEYR && KEYN )
{
osDelay(3); // ensure the key
if(KEYB && KEYY && KEYL && KEYR && KEYN)
{
key_up = 1;
keyvalue = key_down;
key_down = 0;
}
}
return keyvalue;
bool ok = true;
ok &= Key_RegisterGPIO(KEY_ID_B, KEYB_PORT, KEYB_PIN, 0);
ok &= Key_RegisterGPIO(KEY_ID_Y, KEYY_PORT, KEYY_PIN, 0);
ok &= Key_RegisterGPIO(KEY_ID_L, KEYL_PORT, KEYL_PIN, 0);
ok &= Key_RegisterGPIO(KEY_ID_R, KEYR_PORT, KEYR_PIN, 0);
ok &= Key_RegisterGPIO(KEY_ID_N, KEYN_PORT, KEYN_PIN, 0);
return ok;
}
/* 初始化按键 */
void Key_Init(void)
{
memset(&g_key.slot[0], 0, sizeof(g_key.slot));
g_key.raw_mask = 0;
g_key.stable_mask = 0;
Key_Port_Init();
Key_RegisterDefaultKeys();
Key_SetMode(KEY_MODE_CLICK_ON_RELEASE | KEY_MODE_LONG_ONCE | KEY_MODE_REPEAT);
Key_SetLongEnableMask(KEY_BIT(KEY_ID_Y) | KEY_BIT(KEY_ID_N));
}

View File

@@ -5,43 +5,96 @@
extern "C" {
#endif
#include "main.h"
#pragma once
// boot, yes, right, left, no
#define KEYB_NUM 1
#define KEYY_NUM 2
#define KEYL_NUM 3
#define KEYR_NUM 4
#define KEYN_NUM 5
#include "main.h"
#include <stdint.h>
#include <stdbool.h>
// 时间参数单位ms
#define KEY_LONG_THRESHOLD_MS 500 // 长按判定时间
#define KEY_REPEAT_INTERVAL_MS 150 // 连发间隔时间
#define KEY_DEBOUNCE_MS 3 // 消抖时间
/* 掩码工具(位索引即 key_id_t */
#define KEY_BIT(id) (1U << (uint8_t)(id))
#define KEY_ALL_MASK ((uint32_t)((KEY_ID_MAX >= 32) ? 0xFFFFFFFFu : ((1u << KEY_ID_MAX) - 1)))
//KEYB
#define KEYB_PORT GPIOA
#define KEYB_PIN GPIO_PIN_0
#define KEYB HAL_GPIO_ReadPin(KEYB_PORT,KEYB_PIN)
//KEYN
#define KEYN_PORT GPIOA
#define KEYN_PIN GPIO_PIN_4
#define KEYN HAL_GPIO_ReadPin(KEYN_PORT,KEYN_PIN)
//KEYL
#define KEYL_PORT GPIOA
#define KEYL_PIN GPIO_PIN_3
#define KEYL HAL_GPIO_ReadPin(KEYL_PORT,KEYL_PIN)
//KEYR
#define KEYR_PORT GPIOA
#define KEYR_PIN GPIO_PIN_2
#define KEYR HAL_GPIO_ReadPin(KEYR_PORT,KEYR_PIN)
//KEYY
#define KEYY_PORT GPIOA
#define KEYY_PIN GPIO_PIN_1
#define KEYY HAL_GPIO_ReadPin(KEYY_PORT,KEYY_PIN)
void Key_Port_Init(void);
uint8_t KeyScan(uint8_t mode);
/**
* @brief 按键ID与掩码位索引一致
*/
typedef enum {
KEY_ID_B = 0, // KEYB
KEY_ID_Y, // KEYY
KEY_ID_L, // KEYL
KEY_ID_R, // KEYR
KEY_ID_N, // KEYN
KEY_ID_MAX
} key_id_t;
/**
* @brief 事件类型
*/
typedef enum {
KEY_EVT_NONE = 0,
KEY_EVT_DOWN = 1, // 去抖后稳定按下
KEY_EVT_UP = 2, // 去抖后稳定释放
KEY_EVT_CLICK = 3, // 短按(一次按下->释放,且未触发长按)
KEY_EVT_LONG = 4, // 达到长按阈值触发一次
KEY_EVT_REPEAT = 5 // 长按连发周期事件
} key_evt_type_t;
/**
* @brief 事件对象
*/
typedef struct {
key_id_t id;
key_evt_type_t type;
} key_event_t;
/**
* @brief 扫描模式(可组合)
*/
typedef enum {
KEY_MODE_REPORT_DOWN_UP = 0x01, // 上报 DOWN/UP
KEY_MODE_CLICK_ON_RELEASE = 0x02, // 释放时若未长按,上报 CLICK
KEY_MODE_LONG_ONCE = 0x04, // 达到阈值上报一次 LONG
KEY_MODE_REPEAT = 0x08 // 达到阈值后周期性上报 REPEAT
} key_mode_t;
/**
* @brief 时序参数单位ms可运行时修改
*/
typedef struct {
uint16_t debounce_ms; // 消抖时间,建议 3~20
uint16_t long_ms; // 长按判定时间,建议 400~800
uint16_t repeat_ms; // 连发间隔,建议 80~200
} key_timing_t;
void Key_Init(void);
bool Key_Scan(key_event_t* out_evt);
#ifdef __cplusplus
}