hpw421初始版本
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
/*!
|
||||
* \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_OFF(GPIO19)和SPEED(GPIO17)已注释预留
|
||||
* @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);
|
||||
}
|
||||
Reference in New Issue
Block a user