Files
hpw421/project/example/ble/ble_peripheral/app/src/key.c
T
2026-06-06 16:49:48 +08:00

405 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*!
* \file key.c
*
* \brief Target key implementation
*
* \copyright Revised BSD License, see section \ref LICENSE.
*次
* \code 版本:只有单击,双击,四击,六击功能,三击,五击没有。大于等于六击强制六击,清零计数,连按八次触发双击
*
* _ __ _ ________ _
* | |/ /(_)___ / ____/ /_ (_)___
* | // / __ \/ / / __ \/ / __ \
* / |/ / / / / /___/ / / / / /_/ /
* /_/|_/_/_/ /_/\____/_/ /_/_/ .___/
* /_/
* (C) 2022-2025 XinChip
*
* \endcode
*
* \author ( XinChip ) Alex-J
*
* \author ( XinChip )
*/
/*-----------------------------------------------------------------------------------
INCLUDE HEADE FILES
------------------------------------------------------------------------------------*/
#include "xc_drv_gpio.h" // GPIO驱动接口
#include "xc60xx.h" // 芯片寄存器定义
#include "key.h" // 按键模块头文件
#include "message.h" // 消息定义
#include "rgblight.h" // RGB灯控制
/*------------------------------------------------------------------------------------
Macros
-------------------------------------------------------------------------------------*/
/* 按键按下检测宏:GPIO读取为低电平时表示按键按下(按键接地,默认上拉) */
#define IS_KEY_DOWN(n) (!(xc_gpio_read_pin(n)))
/*------------------------------------------------------------------------------------
Global Variables
-------------------------------------------------------------------------------------*/
static uint16_t last_key_val = NO_KEY; // 上一轮扫描到的按键值(用于检测按键状态变化)
static uint16_t key_down_count = 0; // 按键按下持续时间计数(每10ms+1)
static uint16_t key_up_count = 0; // 按键抬起持续时间计数(每10ms+1)
KeyMessageCallback key_callback = NULL; // 按键消息回调函数指针
/* 全局连击状态变量:记录连续点击的次数和超时状态 */
KeyClickState_t g_key_click_state = {
.click_count = 0, // 初始化连击次数为0
.click_interval_cnt = 0, // 初始化间隔计数为0
.last_click_key = NO_KEY, // 初始化上次按键为无按键
.is_waiting_click = 0 // 初始化为非等待状态
};
/*------------------------------------------------------------------------------------
Func Prototype
-------------------------------------------------------------------------------------*/
static uint16_t app_key_click_detection(uint16_t key_val); // 连击检测函数声明
static void key_click_state_reset(void); // 连击状态重置函数声明
static uint16_t get_click_event(uint16_t click_cnt, uint16_t key_val); // 连击事件转换函数
/*------------------------------------------------------------------------------------
Functions
-------------------------------------------------------------------------------------*/
/**
* @brief key_click_state_reset
* @details 重置连击状态结构体的所有字段为初始值
* 在连击超时确定最终结果后、或长按触发后、或无效按键时调用
* @param void
* @retval void
*/
static void key_click_state_reset(void)
{
g_key_click_state.click_count = 0; // 清零连击次数
g_key_click_state.click_interval_cnt = 0; // 清零间隔计数
g_key_click_state.last_click_key = NO_KEY; // 清零上次按键编号
g_key_click_state.is_waiting_click = 0; // 退出等待连击状态
}
/**
* @brief get_click_event
* @details 根据连击次数转换为对应事件(核心修复:明确计数边界)
* @param click_cnt 连击次数
* @param key_val 按键编号
* @retval 组合后的按键事件值
*/
static uint16_t get_click_event(uint16_t click_cnt, uint16_t key_val)
{
uint16_t event = NO_KEY;
if (click_cnt == 1)
{
event = key_val | KEY_SHORT_UP; // 单击
}
else if (click_cnt == 2)
{
event = key_val | KEY_DOUBLE_CLICK; // 双击
}
else if (click_cnt == 4)
{
event = key_val | KEY_QUAD_CLICK; // 四击
}
else if (click_cnt >= 6)
{
event = key_val | KEY_SIX_CLICK; // 六击(6次及以上统一判定为六击)
}
return event;
}
/**
* @brief app_key_register_callback
* @details 注册按键消息回调函数,当按键事件触发时通过该回调通知上层
* @param callback 回调函数指针
* @retval void
*/
void app_key_register_callback(KeyMessageCallback callback)
{
key_callback = callback; // 保存回调函数指针
}
/**
* @brief app_key_hold_detection
* @details 按键按住检测:处理按键持续按下的情况
* 通过key_down_count计时,区分短按下和长按下事件
* @param curren_key_val 当前扫描到的按键值
* @retval key_num 按键事件值(带事件类型的组合键值)
*/
__RAM_CODE uint16_t app_key_hold_detection(uint16_t curren_key_val)
{
uint16_t key_num = NO_KEY; // 初始化返回值为无按键
if (key_down_count < 0xff) // 防止计数器溢出
key_down_count++; // 按下持续时间+1(每10ms递增)
/* 短按下判定:按下时间达到消抖阈值(20ms) */
if (key_down_count == KEY_DOWN_TIMER)
{
key_num = (curren_key_val | KEY_SHORT_DOWN); // 生成短按下事件
}
/* 长按下判定:按下时间达到长按阈值(600ms) */
else if(key_down_count == KEY_LONG_TIMER)
{
key_num = (curren_key_val | KEY_LONG_DOWN); // 生成长按下事件
key_click_state_reset(); // 长按触发时重置连击状态(长按不算连击)
}
key_up_count = 0; // 按键未抬起,清零抬起计数器
return key_num; // 返回按键事件
}
/**
* @brief app_new_key_detection
* @details 新按键检测:处理按键状态变化(按键释放、按键切换等)
* 当按键状态改变时(按键释放或切换到另一个按键),判断之前的按键事件类型
* @param curren_key_val 当前扫描到的按键值
* @retval key_num 按键事件值
*/
__RAM_CODE uint16_t app_new_key_detection(uint16_t curren_key_val)
{
uint16_t key_num = NO_KEY; // 初始化返回值为无按键
/* 按键已释放(当前无按键按下),开始计数抬起时间 */
if (curren_key_val == NO_KEY && last_key_val != NO_KEY)
{
key_up_count++; // 抬起持续时间+1(每10ms递增)
/* 抬起消抖完成判定:抬起时间达到消抖阈值(20ms),确认按键已稳定释放 */
if (key_up_count >= KEY_UP_TIMER)
{
/* 情况1:之前是长按(按下时长 >= 600ms),现在松手 */
if (key_down_count >= KEY_LONG_TIMER)
{
key_num = (last_key_val | KEY_LONG_UP); // 生成长按抬起事件
key_click_state_reset(); // 长按抬起后重置连击状态
}
/* 情况2:之前是短按(按下时长在20ms~600ms之间),进入连击检测 */
else if (key_down_count >= KEY_DOWN_TIMER && key_down_count < KEY_LONG_TIMER)
{
/* 调用连击检测函数:记录本次松手并判断是否需要等待更多连击 */
key_num = app_key_click_detection(last_key_val);
}
// 重置计数器
key_down_count = 0;
key_up_count = 0;
last_key_val = NO_KEY;
}
}
/* 按键切换检测:在当前按键未完全释放时,按下了另一个按键 */
if (curren_key_val != NO_KEY && curren_key_val != last_key_val)
{
/* 切换时先结算上一个按键:如果是长按则发长按抬起事件 */
if (last_key_val != NO_KEY)
{
if (key_down_count >= KEY_LONG_TIMER)
{
key_num = (last_key_val | KEY_LONG_UP);
key_click_state_reset();
}
/* 切换时上一个按键是短按:走连击检测 */
else if (key_down_count >= KEY_DOWN_TIMER && key_down_count < KEY_LONG_TIMER)
{
key_num = app_key_click_detection(last_key_val);
}
}
last_key_val = curren_key_val;
key_down_count = 0;
key_up_count = 0;
}
return key_num;
}
/**
* @brief app_key_click_detection
* @details 连击检测核心函数:修复计数逻辑,明确边界
* 每次短按松手时调用,累加click_count计数
* - 第1次松手:进入等待状态
* - 第2次松手:暂存,超时后判定双击
* - 第4次松手:暂存,超时后判定四击
* - 第6次及以上:直接判定六击(防止计数溢出)
* @param key_val 当前松手的按键编号
* @retval key_num 需要立即返回的按键事件(仅在按键切换时返回)
*/
static uint16_t app_key_click_detection(uint16_t key_val)
{
uint16_t key_num = NO_KEY;
if (key_val == NO_KEY)
{
key_click_state_reset();
return NO_KEY;
}
/* 情况1:首次点击(之前没有在等待连击) */
if (g_key_click_state.is_waiting_click == 0)
{
g_key_click_state.click_count = 1;
g_key_click_state.last_click_key = key_val;
g_key_click_state.is_waiting_click = 1;
g_key_click_state.click_interval_cnt = 0;
return NO_KEY;
}
/* 情况2:按下了不同的按键(按键切换) */
if (g_key_click_state.last_click_key != key_val)
{
/* 立即结算上一个按键的连击结果 */
key_num = get_click_event(g_key_click_state.click_count, g_key_click_state.last_click_key);
/* 重置状态,并开始跟踪新按键的第1次松手 */
key_click_state_reset();
g_key_click_state.click_count = 1;
g_key_click_state.last_click_key = key_val;
g_key_click_state.is_waiting_click = 1;
g_key_click_state.click_interval_cnt = 0;
return key_num;
}
/* 情况3:同一按键再次松手,累加点击次数 */
g_key_click_state.click_count++;
g_key_click_state.click_interval_cnt = 0;
/* 核心修复:6次及以上直接判定六击,防止无限累加 */
if (g_key_click_state.click_count >= 6)
{
key_num = get_click_event(6, key_val); // 强制六击
key_click_state_reset(); // 重置状态
return key_num;
}
return NO_KEY;
}
/**
* @brief key_check_process
* @details 按键检测主流程:根据当前按键和上一轮按键是否相同,分发到不同处理函数
* @param curren_key_val 当前扫描到的按键值
* @retval key_num 按键事件值
*/
__RAM_CODE uint16_t key_check_process(uint16_t curren_key_val)
{
uint16_t key_num = NO_KEY;
/* 当前按键与上一轮相同且非NO_KEY:按键持续按下中 */
if (curren_key_val == last_key_val && last_key_val != NO_KEY)
{
key_num = app_key_hold_detection(curren_key_val);
}
/* 按键状态发生变化(按下/抬起/切换) */
else
{
// 新按键按下(从无到有)
if (curren_key_val != NO_KEY && last_key_val == NO_KEY)
{
last_key_val = curren_key_val;
key_down_count = 0;
key_up_count = 0;
}
key_num = app_new_key_detection(curren_key_val);
}
return key_num;
}
/**
* @brief gpio_key_check
* @details GPIO按键扫描:读取所有按键GPIO的电平状态,返回当前被按下的按键编号
* 当前仅启用MODE键(GPIO18),ON_OFFGPIO19)和SPEEDGPIO17)已注释预留
* @param void
* @retval key_val 当前按下的按键编号,无按键时返回NO_KEY
*/
uint16_t gpio_key_check(void)
{
uint16_t key_val = NO_KEY;
/* 检测MODE键(GPIO18)是否按下 */
if (IS_KEY_DOWN(KEY_MODE))
{
key_val = REC_KEY_UP;
}
/* ON_OFF键和SPEED键检测(预留,当前未启用) */
// else if ( IS_KEY_DOWN(KEY_ON_OFF) )
// {
// key_val = REC_KEY;
// }
// else if ( IS_KEY_DOWN(KEY_SPEED) )
// {
// key_val = REC_KEY_DOWN;
// }
return key_val;
}
/**
* @brief app_key_scan
* @details 按键扫描主函数:每10ms调用一次
* 核心修复:
* 1. 连击计时仅在无按键按下时递增(按下阶段停止计时)
* 2. 超时判定时严格按计数边界生成事件
* @param void
* @retval void
*/
void app_key_scan(void)
{
uint16_t key_num = NO_KEY;
uint16_t key_val = NO_KEY;
/* 步骤1:读取GPIO获取当前按键状态 */
key_val = gpio_key_check();
/* 步骤2:进行按键消抖和事件判定 */
key_num = key_check_process(key_val);
/* 步骤3:如果有需要立即发出的事件,通过回调通知上层 */
if (key_num != NO_KEY && key_callback != NULL)
{
key_callback(key_num);
}
/* 步骤4:连击超时判定 —— 仅在无按键按下时计时 */
if (g_key_click_state.is_waiting_click && key_val == NO_KEY)
{
g_key_click_state.click_interval_cnt++;
/* 超时判定:距离最后一次松手已超过200ms */
if (g_key_click_state.click_interval_cnt >= KEY_CLICK_TIMEOUT)
{
/* 根据最终连击次数确定事件类型 */
key_num = get_click_event(g_key_click_state.click_count, g_key_click_state.last_click_key);
/* 通过回调发送最终判定的事件 */
if (key_num != NO_KEY && key_callback != NULL)
{
key_callback(key_num);
}
/* 发送完毕后重置状态 */
key_click_state_reset();
}
}
}
/**
* @brief app_key_init
* @details 按键模块初始化:配置按键GPIO为上拉输入模式,并注册按键回调函数
* @param void
* @retval void
*/
void app_key_init(void)
{
GPIO_InitCfg_t GPIO_InitCfg = { 0 };
GPIO_InitCfg.Mux = GPIO_Mux0;
GPIO_InitCfg.FunSel = GPIO_Dx;
GPIO_InitCfg.Pull = GPIO_PULLUP;
GPIO_InitCfg.Dir = GPIO_DIR_INPUT;
GPIO_InitCfg.Int = NOT_INT;
GPIO_InitCfg.Pin = GPIO_18;
xc_gpio_init(&GPIO_InitCfg);
app_key_register_callback(app_key_callback_handler);
}