/*! * \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); }