按键、UI 与用户交互
本文档介绍 fw-AD16N_GP-MCU_SDK 中按键扫描、事件识别、按键到 UI 消息的映射,以及低功耗与提示音联动的完整用户交互机制,覆盖从硬件驱动采样到应用层消息消费的端到端链路。
Purpose and Scope
本页面向 mbox_flash 应用(含 linein、loudspeaker、midi_dec、midi_keyboard 等模式)的按键与用户交互子系统,重点说明:
- 按键驱动抽象层(IO / AD / 矩阵 / MIC / 红外 / 触摸 / 低功耗触摸),通过统一的
key_interface_t接口接入; - 按键核心状态机
key_scan():短按、长按、连按(HOLD)、抬键、双击、三击的判定逻辑与时间门槛; - 按键 → 消息转换(
key2msg_emit+key_msg_filter)以及各模式按键映射表(如loudspk_mode_key.c中的 IR 按键表); - 消息投递(
post_msg)与消息池满的处理、按键提示音(d_key_voice_kick)、低功耗目标(key_lp_target)联动。
以下主题属于兄弟页面,不在本页展开:具体模式的应用逻辑(如 loudspeaker 播放控制)、显示/UI 渲染框架、消息系统(msg.h)本身的设计。相关概念见"Related Links"。
概述
在音频嵌入式应用中,用户交互几乎全部通过按键与红外遥控器完成:单击播放/暂停、双击切歌、长按快进/快退、连按连续调音量等。SDK 将这些交互拆成三层:
- 驱动层:各
key_drv_*.c负责从具体硬件(GPIO、ADC、矩阵、MIC、IR、触摸)读取键值,屏蔽硬件差异; - 核心层:
key.c的key_scan()在 10ms 系统 tick 中被周期调用,对键值做去抖计数与时间窗判定,输出标准化的按键状态(KEY_SHORT_UP、KEY_LONG、KEY_HOLD、KEY_DOUBLE等),并通过注册的过滤函数把"状态 + 键值 + 按键类型"翻译成业务消息; - 应用层:各模式的按键映射表决定"哪个键、什么操作"对应哪条
MSG_*消息,消息进入系统消息队列后由各模式 UI/逻辑任务消费,最终驱动界面显示、声音提示和功能执行。
这种分层设计的意图是:按键语义与硬件解耦——同一套状态机可以服务于 IR 遥控器、IO 按键、触摸按键;模式间按键定义可切换——key_table_sel() 可在模式切换时更换过滤函数(即更换映射表),而无需改动驱动与状态机。
架构
flowchart TD
subgraph sg_HW["硬件层"]
HW_IO["IO 按键<br/>key_drv_io.c"]
HW_AD["AD 按键<br/>key_drv_ad.c"]
HW_MATRIX["矩阵按键<br/>key_drv_matrix.c"]
HW_MIC["MIC 按键<br/>key_drv_mic.c"]
HW_IR["红外遥控<br/>key_ir.c"]
HW_TOUCH["触摸<br/>key_touch.c / key_lptouch.c"]
end
subgraph sg_CORE["按键核心层 (bsp/common/key)"]
KEYLIST["key_list[]<br/>key_interface_t 接口数组"]
SCAN["key_scan()<br/>10ms tick 状态机"]
FILTER["key_msg_filter<br/>按键→消息过滤函数"]
EMIT["key2msg_emit()<br/>提示音 + post_msg"]
end
subgraph sg_APP["应用层 (src/mbox_flash)"]
TAB_LOUD["loudspk_mode_key.c<br/>IR/IO 映射表"]
TAB_LINEIN["linein_mode_key.c"]
TAB_MIDI["midi_dec / midi_keyboard<br/>mode_key.c"]
MSGQ["系统消息队列 post_msg"]
APP["模式 UI / 逻辑任务<br/>消费 MSG_* 消息"]
end
subgraph sg_PM["低功耗联动"]
LP["key_lp_target<br/>key_idle_query()"]
VOICE["d_key_voice_kick()<br/>按键提示音"]
end
HW_IO --> KEYLIST
HW_AD --> KEYLIST
HW_MATRIX --> KEYLIST
HW_MIC --> KEYLIST
HW_IR --> KEYLIST
HW_TOUCH --> KEYLIST
KEYLIST --> SCAN
SCAN --> FILTER
FILTER --> EMIT
TAB_LOUD --> FILTER
TAB_LINEIN --> FILTER
TAB_MIDI --> FILTER
EMIT --> VOICE
EMIT --> MSGQ
MSGQ --> APP
SCAN --> LP
KEYLIST --> LP
架构要点:
key_list[]是驱动注册表,各驱动以key_interface_t(含key_init与key_get_value)形式静态注册,由编译宏(KEY_IO_EN等)决定是否编入,见 key.c;key_scan()是唯一的状态机入口,由系统 tick 定时器驱动(tick_timer_sys.c中调用key_scan()),见 tick_timer_sys.c;key_msg_filter是一个可热切换的函数指针:key_table_sel()在模式切换时被调用来更换映射表,从而实现"同一颗芯片、不同模式、不同按键语义";- 映射后的消息经
post_msg()进入系统消息队列,模式逻辑任务统一消费,UI 显示与按键事件解耦。
核心机制
驱动抽象:key_interface_t 与 key_list
所有按键驱动暴露同一接口,核心层通过接口数组轮询,无需感知具体硬件:
typedef struct {
KEY_TYPE key_type;
void (*key_init)(void);
u8(*key_get_value)(void);
} key_interface_t;
Source: key.h
KEY_TYPE 枚举定义了七种按键来源(IO、AD、MATRIX、IR、TOUCH、MIC、LPTOUCH),见 key.h。key_init() 遍历 key_list 逐个调用驱动的初始化函数;get_key_value() 则按数组顺序轮询,返回第一个有效键值,因此数组顺序即扫描优先级,见 key.c 与 key.c。
按键状态机:key_scan()
key_scan() 在 10ms tick 中执行,用静态变量 last_key、key_press_counter 记录按键状态。判定阈值定义在 key.h:
| 宏 | 值 | 含义 |
|---|---|---|
KEY_BASE_CNT | 2 | 基础去抖计数 |
KEY_SHORT_CNT | 3 | 短按判定阈值(3 × 10ms) |
KEY_LONG_CNT | 75 | 长按判定阈值(75 × 10ms) |
KEY_HOLD_CNT | 15 | 连按(HOLD)重复间隔 |
状态机核心逻辑(按下保持计数 → 释放判定):
if (cur_key == last_key) { //长时间按键
if (cur_key == NO_KEY) {
return;
}
key_press_counter++;
if (key_press_counter == KEY_SHORT_CNT) {
key_status = KEY_SHORT;
} else if (key_press_counter == KEY_LONG_CNT) { //长按
key_status = KEY_LONG;
} else if (key_press_counter == (KEY_LONG_CNT + KEY_HOLD_CNT)) { //连按
key_status = KEY_HOLD;
key_press_counter = KEY_LONG_CNT;
} else {
return;
}
} else { //cur_key = NO_KEY, 抬键
last_key = cur_key;
if ((key_press_counter > KEY_SHORT_CNT) && (key_press_counter < KEY_LONG_CNT) && (cur_key == NO_KEY)) { //短按抬起
key_press_counter = 0;
key_status = KEY_SHORT_UP;
} else if ((cur_key == NO_KEY) && (key_press_counter >= KEY_LONG_CNT)) { //长按抬起
key_press_counter = 0;
key_status = KEY_LONG_UP;
} else {
key_press_counter = 0;
return;
}
}
Source: key.c
设计意图:事件发生在"按下满 N 个 tick"与"抬键"两个时刻——KEY_SHORT 用于按下 30ms 时给 UI 的即时反馈;KEY_SHORT_UP 才是短按的"完成"语义;KEY_LONG 触发一次后进入 KEY_HOLD 周期重复,配合 key_press_counter = KEY_LONG_CNT 实现连发;KEY_LONG_UP 表示长按释放。事件产生顺序为"短按抬起/长按 → 连按",注释见 key.c。
按键状态枚举(KEY_STATUS)定义于 key.h:KEY_SHORT(0x0)、KEY_SHORT_UP(0x1)、KEY_LONG(0x2)、KEY_HOLD(0x3)、KEY_LONG_UP(0x4)、KEY_DOUBLE(0x5)、KEY_TRIPLE(0x6)。
双击 / 三击检测
KEY_DOUBLE_CLICK_EN 开启后,key_scan() 在短按抬起时进入"等待第二次点击"窗口:key_press_flag 计数,超过 KEY_DOUBLE_CLICK_CNT(35 × 10ms = 350ms)窗口后按累计 click_cnt 发射 KEY_SHORT_UP(单击)/ KEY_DOUBLE(双击)/ KEY_TRIPLE(三击),见 key.c 与 key.c。窗口内若换了键,则先补发前一键的单击事件,再以新键重新计数。MIC 按键通过 d_check_suport_double_click(k) 宏(mic_key_check_support_double_click)支持按键过滤,其余按键类型默认支持,见 key.c。
按键 → 消息转换
key2msg_emit:事件出口
所有被状态机判定出来的按键事件统一汇聚到 key2msg_emit(),它是"按键世界"与"消息世界"的唯一桥梁:
static void key2msg_emit(u8 key_status, u8 key_num, u8 key_type)
{
key_printf("key_status:%d key_num:%d key_type:%d \n", key_status, key_num, key_type);
if (key_msg_filter) {
u16 msg = key_msg_filter(key_status, key_num, key_type);
if (msg != NO_MSG) {
d_key_voice_kick();
int ret = post_msg(1, msg);
if (ret != 0) {
key_printf("error !!! msg pool full \n");
}
}
}
}
Source: key.c
执行顺序即设计顺序:先查表(key_msg_filter 返回映射消息)→ 非 NO_MSG 才播提示音(d_key_voice_kick())→ 投递消息(post_msg(1, msg),1 为消息优先级)。NO_MSG(0xff)表示"该键/该状态无动作",不投递任何消息。post_msg 返回非 0 表示消息池已满,按键事件被丢弃并打印错误,避免阻塞中断/定时器上下文。
key_table_sel:模式切换映射表
映射函数指针 key_msg_filter 是静态变量,key_table_sel() 通过关中断保护地热切换它:
static u16(*key_msg_filter)(u8, u8, u8) = NULL;
...
void key_table_sel(void *msg_filter)
{
local_irq_disable();
key_msg_filter = msg_filter;
local_irq_enable();
}
模式切换时(如从 loudspeaker 切到 linein),应用调用 key_table_sel(新模式的过滤函数),此后所有按键事件立即按新模式解释——驱动层与状态机完全复用。
模式按键映射表:以 loudspeaker 为例
每个模式目录下的 xxx_mode_key.c 定义若干"键值数组",数组下标即键号,元素即 MSG_* 消息。loudspeaker 模式为 IR 遥控器定义了五张表:短按、长按、连按、长按释放、双击,见 loudspk_mode_key.c:
#define IRFF00_LOUDSPK_SHORT_UP \
/*00*/ MSG_PP,\
/*01*/ MSG_NEXT_WORKMODE,\
/*02*/ MSG_MUTE,\
/*03*/ MSG_PP,\
/*04*/ MSG_PREV_FILE,\
/*05*/ MSG_NEXT_FILE,\
/*06*/ MSG_HW_EQ_SW,\
/*07*/ MSG_VOL_DOWN,\
/*08*/ MSG_VOL_UP,\
/*09*/ MSG_0,\
/*10*/ MSG_NEXT_PLAYMODE,\
/*11*/ MSG_NEXT_DEVICE,\
/*12*/ MSG_1,\
/*13*/ MSG_2,\
/*14*/ MSG_3,\
/*15*/ MSG_4,\
/*16*/ MSG_5,\
/*17*/ MSG_6,\
/*18*/ MSG_7,\
/*19*/ MSG_8,\
/*20*/ MSG_9
Source: loudspk_mode_key.c
短按表把 21 个 IR 键(0~20)映射到播放/暂停(MSG_PP)、模式切换(MSG_NEXT_WORKMODE)、静音(MSG_MUTE)、上下曲(MSG_PREV_FILE/MSG_NEXT_FILE)、EQ 切换(MSG_HW_EQ_SW)、音量(MSG_VOL_UP/MSG_VOL_DOWN)与数字键 0~9。长按表(IRFF00_LOUDSPK_LONG)则把键 4/5 映射为 MSG_MUSIC_FR/MSG_MUSIC_FF(快退/快进),键 7/8 映射为音量连续调节;连按表(IRFF00_LOUDSPK_HOLD)复用同样的快进快退与音量消息实现连续动作;长按释放表(IRFF00_LOUDSPK_LONG_UP)与双击表(IRFF00_LOUDSPK_DOUBLE_KICK)在 loudspeaker 模式下大多为 NO_MSG,见 loudspk_mode_key.c。
同目录下
linein_mode_key.c、midi_dec_mode_key.c、midi_keyboard_mode_key.c采用相同模式定义各自模式下的按键语义(见仓库文件清单),本页不再逐一展开。
核心流程
一次完整的按键交互从硬件采样到 UI 执行的时序如下:
sequenceDiagram
participant TICK as tick_timer_sys (10ms)
participant DRV as key_drv_* / get_key_value
participant SCAN as key_scan() 状态机
participant FILTER as key_msg_filter 映射表
participant EMIT as key2msg_emit()
participant VOICE as d_key_voice_kick()
participant MSGQ as 消息队列 post_msg
participant APP as 模式 UI/逻辑任务
TICK->>SCAN: 每 10ms 调用 key_scan()
SCAN->>DRV: get_key_value() 轮询 key_list
DRV-->>SCAN: key_num / key_type
SCAN->>SCAN: 计数判定: SHORT/LONG/HOLD/UP/DOUBLE
SCAN->>FILTER: key_msg_filter(status, num, type)
FILTER-->>EMIT: MSG_* 或 NO_MSG
alt msg != NO_MSG
EMIT->>VOICE: d_key_voice_kick() 按键音
EMIT->>MSGQ: post_msg(1, msg)
MSGQ-->>APP: 消息被模式任务取出
APP->>APP: 执行功能 + 刷新 UI 显示
else msg == NO_MSG
EMIT-->>EMIT: 丢弃,无动作
end
关键路径说明:
- 系统 10ms tick 定时器驱动
key_scan()(见 tick_timer_sys.c),因此所有时间门槛(3/75/15/35 tick)的单位都是 10ms; - 按下期间
is_key_active被置 35 并递减,作为"系统忙"标志供低功耗查询; - 状态机只在"阈值到达"与"抬键"两个时刻产生事件,避免高频轮询带来的消息风暴;
- 映射表把事件翻译为业务消息,UI 任务只认
MSG_*,不关心按键来源——同一MSG_PP可由 IR 键 0/3、IO 键或触摸键触发。
按键状态机
stateDiagram-v2
[*] --> IDLE
IDLE --> PRESSING: 检测到键值 cur_key != NO_KEY
PRESSING --> SHORT: 计数 == KEY_SHORT_CNT (3)
PRESSING --> SHORT_UP: 释放且 3 < 计数 < 75
PRESSING --> LONG: 计数 == KEY_LONG_CNT (75)
LONG --> HOLD: 计数 == LONG_CNT + HOLD_CNT (90),周期重复
LONG --> LONG_UP: 释放且计数 >= 75
SHORT_UP --> WAIT_DCLICK: KEY_DOUBLE_CLICK_EN 且支持双击
WAIT_DCLICK --> ONE_CLICK: 350ms 窗口内仅 1 次
WAIT_DCLICK --> DOUBLE: 窗口内 2 次
WAIT_DCLICK --> TRIPLE: 窗口内 3 次
SHORT_UP --> IDLE
ONE_CLICK --> IDLE
DOUBLE --> IDLE
TRIPLE --> IDLE
HOLD --> HOLD: 保持按下,每 15 tick 再发
HOLD --> IDLE: 释放
LONG_UP --> IDLE
状态转移依据 key_press_counter 与阈值宏实现,详见 key.c。
低功耗联动
按键子系统同时是低功耗管理的参与方。is_key_active 标志在按键产生事件时被刷新,空闲后递减归零:
static volatile u8 is_key_active = 0;
...
void key_active_set(P33_IO_WKUP_EDGE edge)
{
is_key_active = 35; //35*10Ms
}
u8 key_idle_query(void)
{
return !is_key_active;
}
REGISTER_LP_TARGET(key_lp_target) = {
.name = "key",
.is_idle = key_idle_query,
};
key_scan()中检测到有效键值(key.key_type != NO_KEY)时置is_key_active = 35(约 350ms 活动窗口),无按键时逐 tick 递减,见 key.c;key_lp_target通过REGISTER_LP_TARGET宏注册进低功耗框架,key_idle_query()返回"按键是否空闲",供电源管理决定是否允许进入睡眠;P33 引脚唤醒边沿通过key_active_set()上报,把系统拉回活动状态。
配置选项
按键子系统的功能开关集中在 app_config.h(各模式可覆写),核心层通过条件编译裁剪驱动与特性:
| 配置宏 | 类型 | 默认行为 | 说明 |
|---|---|---|---|
KEY_IO_EN | 布尔 | 取决于板级配置 | 使能 IO 按键驱动 key_drv_io |
KEY_AD_EN | 布尔 | 取决于板级配置 | 使能 ADC 按键驱动 key_drv_ad |
KEY_MATRIX_EN | 布尔 | 取决于板级配置 | 使能矩阵按键驱动 key_drv_matrix |
KEY_MIC_EN | 布尔 | 取决于板级配置 | 使能 MIC 按键驱动 key_drv_mic |
KEY_IR_EN | 布尔 | 取决于板级配置 | 使能红外遥控 key_ir |
KEY_TOUCH_EN | 布尔 | 取决于板级配置 | 使能触摸按键 key_touch |
KEY_LPTOUCH_EN | 布尔 | 取决于板级配置 | 使能低功耗触摸 key_lptouch |
KEY_DOUBLE_CLICK_EN | 布尔 | 视模式而定 | 使能双击/三击检测窗口 |
KEY_DOUBLE_CLICK_CNT | 数值 | 35(350ms) | 双击判定窗口,见 key.h |
KEY_SHORT_CNT | 数值 | 3 | 短按阈值(tick),见 key.h |
KEY_LONG_CNT | 数值 | 75 | 长按阈值(tick),见 key.h |
KEY_HOLD_CNT | 数值 | 15 | HOLD 连发间隔(tick),见 key.h |
KEY_BASE_CNT | 数值 | 2 | 基础去抖计数,见 key.h |
KEY_UART_DEBUG | 布尔 | 关闭 | 使能 key_puts/key_printf 调试输出,见 key.h |
KEY_*_MAX_NUM | 数值 | 8~21 不等 | 各类按键最大个数(如 IR_KEY_MAX_NUM 21、LPTOUCH_KEY_MAX_NUM 8),见 key.h |
说明:驱动使能宏的默认值由各板级
app_config.h决定,本页未逐一读取板级配置;在特定板卡上以实际配置为准。
API 参考
void key_init(void)
初始化所有已使能按键驱动:遍历 key_list[],对每个接口调用 key_init() 回调。
- 参数:无
- 返回:无
- 调用时机:系统启动阶段,见 key.c
void key_scan(void)
按键状态机主入口,须由 10ms tick 定时器周期调用。内部轮询 get_key_value(),完成去抖计数、长按/短按/连按/双击判定,并经由 key2msg_emit() 发射事件。
- 参数:无
- 返回:无
- 注意:不可在非 tick 上下文随意调用,时间门槛以 10ms 为基准,见 key.c
void key_table_sel(void *msg_filter)
热切换按键 → 消息映射过滤函数(模式切换时使用)。关中断保护赋值,防止状态机并发读。
- 参数:
msg_filter— 形如u16 (*)(u8 key_status, u8 key_num, u8 key_type)的过滤函数指针 - 返回:无,见 key.c
key_io_t get_key_value(void)
轮询 key_list[],按注册顺序返回第一个有效键值(含 key_type 与 key_num);全部无效时返回 {NO_KEY, NO_KEY}。
- 返回:
key_io_t,见 key.c
void key_active_set(P33_IO_WKUP_EDGE edge)
外部唤醒回调:将 is_key_active 置 35,通知系统按键活跃。
- 参数:
edge— P33 唤醒边沿类型 - 返回:无,见 key.c
u8 key_idle_query(void)
低功耗查询:返回 !is_key_active,注册为 key_lp_target.is_idle。
- 返回:1 表示按键空闲可休眠,0 表示活跃,见 key.c
使用示例
示例 1:按键状态机与事件出口(核心层)
key_scan() 判定事件后统一走 key2msg_emit(),这是新增按键类型或新交互语义时必须理解的关键路径:
static void key2msg_emit(u8 key_status, u8 key_num, u8 key_type)
{
key_printf("key_status:%d key_num:%d key_type:%d \n", key_status, key_num, key_type);
if (key_msg_filter) {
u16 msg = key_msg_filter(key_status, key_num, key_type);
if (msg != NO_MSG) {
d_key_voice_kick();
int ret = post_msg(1, msg);
if (ret != 0) {
key_printf("error !!! msg pool full \n");
}
}
}
}
Source: key.c
要点:key_msg_filter 返回 NO_MSG(0xff)表示"忽略该事件";返回有效消息则先触发按键音再投递。消息池满时打印 error !!! msg pool full 并丢弃,不阻塞调用上下文。
示例 2:模式按键映射表(应用层)
新模式定义按键语义的标准写法——以 loudspeaker 的 IR 短按表为例,数组下标即 IR 键号(0~20),元素即要投递的业务消息:
#define IRFF00_LOUDSPK_SHORT_UP \
/*00*/ MSG_PP,\
/*01*/ MSG_NEXT_WORKMODE,\
/*02*/ MSG_MUTE,\
/*03*/ MSG_PP,\
/*04*/ MSG_PREV_FILE,\
/*05*/ MSG_NEXT_FILE,\
/*06*/ MSG_HW_EQ_SW,\
/*07*/ MSG_VOL_DOWN,\
/*08*/ MSG_VOL_UP,\
/*09*/ MSG_0,\
/*10*/ MSG_NEXT_PLAYMODE,\
/*11*/ MSG_NEXT_DEVICE
Source: loudspk_mode_key.c
设计意图:把"键号 → 消息"做成纯数据表,过滤函数只需按 key_status 选择对应数组、按 key_num 取元素,即可实现短按/长按/连按/双击各自独立且可读的按键语义。扩展新键或新动作只需改表,无需触碰驱动与状态机。
示例 3:按键接口注册(驱动层抽象)
新增一种按键硬件时,实现 key_interface_t 并在 key_list[] 中按编译宏挂接即可被核心层自动轮询:
typedef struct {
KEY_TYPE key_type;
void (*key_init)(void);
u8(*key_get_value)(void);
} key_interface_t;
Source: key.h
失败模式、边界与并发
消息池满
post_msg(1, msg) 返回非 0 时按键事件被丢弃并打印 error !!! msg pool full(见 key.c)。系统设计上按键事件频率受状态机"阈值到达才发事件"的约束(短按最多一次、HOLD 每 150ms 一次),正常情况下不会打满消息池;若出现该日志,应检查消费端任务是否阻塞。
NO_KEY 与空转
NO_KEY = 0xff 表示无按键。key_scan() 在 cur_key == NO_KEY 且持续无键时直接 return,不产生任何事件(见 key.c),保证空闲时零开销。
并发与中断安全
key_msg_filter的切换用local_irq_disable()/enable()保护(见 key.c),防止状态机(tick 上下文)与模式切换线程并发读写指针;key_scan()的状态变量均为static volatile(key_type、last_key、key_press_counter),确保 tick 中断与主循环读取一致(见 key.c)。
双击窗口竞争
单击事件被延迟到 KEY_DOUBLE_CLICK_CNT(350ms)窗口结束后才发出,意味着"单击响应有 350ms 延迟"是启用双击功能的固有代价;窗口内快速切换按键会补发前一键单击并重置计数(见 key.c)。若产品对单击响应时延敏感且无双击需求,应关闭 KEY_DOUBLE_CLICK_EN。
键值数量上限
各类型按键数量受限:IO/AD/矩阵/触摸各 10、IR 21、MIC 3、LPTOUCH 8(见 key.h)。驱动返回超出上限的键号将导致映射表越界,板级配置时需核对。
性能与运维
- 轮询成本:
key_scan()每 10ms 执行一次,get_key_value()顺序轮询各驱动;未使能的驱动通过编译宏裁剪(#if KEY_*_EN),避免无谓调用; - 代码段划分:
key.c通过#pragma code_seg(".key.text")等将按键代码独立分段(见 key.c),便于链接器做 bank/overlay 优化与低功耗代码裁剪; - 调试手段:打开
KEY_UART_DEBUG可打印key_status/key_num/key_type与消息池满错误(见 key.h),是现场定位"按键无反应"的首选工具; - 低功耗:
key_lp_target的is_idle查询确保按键活跃期间系统不会误入睡眠,P33 唤醒边沿经key_active_set()恢复活动(见 key.c)。
扩展点
- 新增按键硬件类型:实现
key_interface_t(key_init+key_get_value),在key_list[]中按#if宏挂接,并补充KEY_TYPE枚举与KEY_*_MAX_NUM(见 key.h 与 key.c); - 新增交互语义:在
key_scan()状态机中扩展状态判定并在key.h的KEY_STATUS枚举中登记新状态; - 新增模式按键定义:参照
loudspk_mode_key.c定义短按/长按/连按/双击表,模式初始化时调用key_table_sel(过滤函数)切换生效; - 按键音定制:事件出口调用
d_key_voice_kick(),可在该接口中按key_type/key_status播放不同提示音。
测试情况
本仓库未在按键目录中发现独立的单元测试文件;按键行为验证主要依赖板级联调与 KEY_UART_DEBUG 输出(key_status/key_num/key_type 打印)。建议验证用例:短按/长按/连按/双击/三击各状态的时间边界(3、75、90、350 tick)、消息池满场景、模式切换后映射表生效、低功耗下按键唤醒。
Related Links
- 按键驱动实现:
sdk/apps/app/bsp/common/key/key_drv_io.c、key_drv_ad.c、key_drv_matrix.c、key_drv_mic.c、key_ir.c、key_touch.c、key_lptouch.h - 模式按键映射:
sdk/apps/app/src/mbox_flash/loudspeaker/loudspk_mode_key.c、linein/linein_mode_key.c、midi_dec/midi_dec_mode_key.c、midi_keyboard/midi_keyboard_mode_key.c - 系统 tick 驱动:tick_timer_sys.c(
key_scan()调用点) - 消息系统:
sdk/apps/app/bsp/common/include/msg.h(post_msg与消息队列) - 各模式应用逻辑页(linein / loudspeaker / midi_dec / midi_keyboard):负责消费
MSG_*并驱动 UI 显示