按键、编码器与红外输入
本文档全面介绍 AW33N BLE SDK 中的人机输入子系统:按键(IO/AD/矩阵)扫描核心引擎、旋转编码器接入方式、红外(IR)解码/编码接口,以及配套的配置工具脚本与示例应用,覆盖从硬件引脚到系统事件分发的完整链路。
Purpose and Scope
本页面向「输入设备」这一能力边界,涵盖:
- 按键核心引擎
key.c / key.h:多驱动注册、消抖、单击/连击/长按/HOLD 事件判定; - 三种按键底层驱动:
key_drv_io(IO 直连)、key_drv_ad(ADC 分压,也常用于编码器类输入)、key_drv_matrix(矩阵扫描); - 红外输入:
ir_decoder解码接口与ir_encoder发射接口; - 配置工具脚本(
iokey_v1.lua、adkey_v1.lua、key_common.lua、key_msg.lua、irflt_v1.lua)与 HID demo(app_keyboard.c、app_keyfob.c、app_keypage.c)。
以下内容不属于本页边界,请参见对应页面:HID 键鼠上报协议(hid_keyboard.c)、系统消息投递(msg.h / sys_event)、电源管理与唤醒(P33_IO_WKUP_EDGE 相关)、触摸/麦克风输入(KEY_TYPE_TOUCH / KEY_TYPE_MIC / KEY_TYPE_LPTOUCH 仅枚举预留)。
Overview
输入子系统是 BLE 设备用户交互的入口。无论是键盘、遥控器还是带旋钮的穿戴设备,硬件输入最终都要转化为统一的「按键事件」消息,再由应用层映射为蓝牙 HID 上报、音量调节、开机/唤醒等行为。
SDK 采用「驱动接口 + 核心扫描引擎」的分层设计:
- 每种物理输入(IO 电平、ADC 电压、矩阵行列、红外码值)实现统一的
key_interface_t接口(key_init+key_get_value); - 核心引擎
key.c以固定周期(10ms tick)调用key_driver_scan(),依次查询所有注册驱动,完成消抖、按下/抬起判定、连击计数、长按/HOLD 计时; - 判定结果通过弱符号函数
key_event_remap()(可被用户重写实现组合键)后封装为sys_event投递给应用。
关键概念:
| 术语 | 含义 |
|---|---|
NO_KEY(0xff) | 表示当前无有效按键值 |
KEY_BASE_CNT(2) | 消抖基础计数门槛 |
filter_time / filter_cnt | 消抖周期数与累加器,连续 filter_time 次采样一致才认为按键有效 |
long_time / hold_time | 长按与 HOLD 判定计数(单位:扫描周期) |
click_cnt / click_delay_time | 连击次数与抬起后的连击等待窗口 |
KEY_TYPE_* | 按键来源类型枚举(IO/AD/MATRIX/IR/TOUCH/MIC/LPTOUCH) |
Architecture
flowchart TD
subgraph sg_HW["硬件层"]
GPIO["GPIO 引脚"]
ADC["ADC 通道"]
IR_RX["红外接收头"]
end
subgraph sg_DRV["驱动层 (apps/app/bsp/common/key 与 ir)"]
IO_DRV["key_drv_io"]
AD_DRV["key_drv_ad"]
MATRIX_DRV["key_drv_matrix"]
IR_DEC["ir_decoder"]
end
subgraph sg_CORE["核心扫描引擎"]
KEY_C["key.c / key.h"]
PARA["key_driver_para 扫描参数"]
end
subgraph sg_EVT["事件层"]
REMAP["key_event_remap (weak)"]
SYS_EVT["sys_event 消息"]
end
subgraph sg_APP["应用层"]
APP["app_keyboard / app_keyfob / app_keypage"]
end
GPIO --> IO_DRV
ADC --> AD_DRV
GPIO --> MATRIX_DRV
IR_RX --> IR_DEC
IO_DRV --> KEY_C
AD_DRV --> KEY_C
MATRIX_DRV --> KEY_C
IR_DEC --> KEY_C
PARA --> KEY_C
KEY_C --> REMAP
REMAP --> SYS_EVT
SYS_EVT --> APP
架构说明:
- 驱动层通过
key_interface_t向核心引擎暴露统一接口,编译宏KEY_IO_EN / KEY_AD_EN / KEY_MATRIX_EN决定哪些驱动被编入key_list注册表(见 key.c); - 核心引擎
key_driver_scan()是唯一扫描入口,所有事件语义(消抖、连击、长按、HOLD)都在这里收敛,保证上层只面对统一的事件流; - 红外解码器
ir_decoder独立于按键驱动目录(apps/app/bsp/common/ir/),其解码结果可映射为KEY_TYPE_IR的按键值进入同一事件管道; - 事件层与系统消息总线解耦:
key_event_remap是弱符号钩子,未重定义时原样放行,重定义后即可实现组合键重映射。
按键核心引擎:key.c 实现详解
驱动注册表与初始化
核心引擎维护一个静态驱动注册表 key_list,数组元素是 key_interface_t(含 key_type、key_init 函数指针、key_get_value 函数指针),按编译宏条件装配:
static const key_interface_t *key_list[] = {
#if KEY_IO_EN
&key_io_info,
#endif
#if KEY_AD_EN
&key_ad_info,
#endif
#if KEY_MATRIX_EN
&key_matrix_info,
#endif
};
来源:key.c
key_init() 遍历注册表调用每个驱动的 key_init(),完成 GPIO/ADC 硬件初始化。get_key_value() 则按注册顺序依次调用 key_get_value(),第一个返回非 NO_KEY 的驱动胜出,其 key_type 与 key_num 即当前扫描结果——这意味着驱动顺序决定多输入源的优先级:
static key_io_t get_key_value(void)
{
key_io_t key;
key.key_type = NO_KEY;
#if (KEY_IO_EN || KEY_AD_EN || KEY_MATRIX_EN)
for (uint8_t i = 0; i < (sizeof(key_list) / sizeof(key_list[0])); i++) {
if (key_list[i]->key_get_value) {
key.key_num = key_list[i]->key_get_value();
if (NO_KEY != key.key_num) {
key.key_type = key_list[i]->key_type;
return key;
}
}
}
#endif
key.key_num = NO_KEY;
return key;
}
来源:key.c
扫描参数与事件语义
struct key_driver_para 承载全部扫描状态与阈值,key_scan_para 是其全局实例。普通模式与鼠标模式(MOUSE_KEY_SCAN_MODE)下的默认值差异显著:
#if MOUSE_KEY_SCAN_MODE
struct key_driver_para key_scan_para = {
.last_key = NO_KEY, //上一次get_value按键值, 初始化为NO_KEY;
.filter_time = 2, //按键消抖延时;
.long_time = 3, //按键判定长按数量
.hold_time = (3), //按键判定HOLD数量
.click_delay_time = 20, //按键被抬起后等待连击延时数量
};
#else
struct key_driver_para key_scan_para = {
.last_key = NO_KEY,
.filter_time = 2,
.long_time = 100, //按键判定长按数量
.hold_time = (100 + 50), //按键判定HOLD数量
.click_delay_time = 20,
};
#endif
来源:key.c
设计意图:以 10ms 扫描周期为基准,long_time = 100 意味着约 1 秒判定长按,hold_time = 150 意味着长按后再持续 500ms 判定 HOLD(持续事件,用于音量连续调节等场景);鼠标模式把长按阈值压到 3 个周期,适合快速点击类外设。filter_time = 2 保证连续两次采样一致才确认按键,兼顾响应速度与机械抖动。
消抖与事件判定状态机
key_driver_scan() 是每次 tick 的核心处理函数,完整流程如下:
flowchart TD
S0["等待按键 (last_key = NO_KEY)"] -->|"get_value 返回有效键值"| S1["消抖: filter_cnt 累加<br/>(filter_value 记录上次值)"]
S1 -->|"filter_cnt < filter_time"| S1
S1 -->|"filter_cnt >= filter_time"| S2{"cur_key != last_key?"}
S2 -->|"是, 且 cur_key = NO_KEY: 抬起"| S3{"press_cnt >= long_time?"}
S3 -->|"是 (长按/HOLD 后抬起)"| S4["上报 KEY_EVENT_UP"]
S3 -->|"否 (短按抬起)"| S5["click_delay_cnt = 1<br/>启动连击等待窗口"]
S2 -->|"是, 且 cur_key 为有效键: 按下"| S6["press_cnt = 1<br/>click_cnt 按 notify_value 累加"]
S6 --> S7["press_cnt 每 tick 递增"]
S7 -->|"press_cnt >= long_time"| S8["长按事件 (long_event)"]
S7 -->|"press_cnt >= hold_time"| S9["HOLD 事件 (hold_event)"]
S8 --> S7
S9 --> S7
S5 -->|"窗口内再次按下同一按键"| S6
S5 -->|"窗口超时 (click_delay_time)"| S10["上报单击/多击事件"]
S10 --> S0
S4 --> S0
核心判定代码(节选):
//===== 按键消抖处理
if (cur_key_value != scan_para->filter_value && scan_para->filter_time) { //当前按键值与上一次按键值如果不相等, 重新消抖处理
scan_para->filter_cnt = 0; //消抖次数清0, 重新开始消抖
scan_para->filter_value = cur_key_value; //记录上一次的按键值
return; //第一次检测, 返回不做处理
}
if (scan_para->filter_cnt < scan_para->filter_time) {
scan_para->filter_cnt++;
return;
}
//===== 按键消抖结束, 开始判断按键类型(单击, 双击, 长按, 多击, HOLD, (长按/HOLD)抬起)
if (cur_key_value != scan_para->last_key) {
if (cur_key_value == NO_KEY) { //cur_key = NO_KEY; last_key = valid_key -> 按键被抬起
if (scan_para->press_cnt >= scan_para->long_time) { //长按/HOLD状态之后被按键抬起;
key_event = KEY_EVENT_UP;
key_value = scan_para->last_key;
goto _notify; //发送抬起消息
}
scan_para->click_delay_cnt = 1; //按键等待下次连击延时开始
} else { //cur_key = valid_key, last_key = NO_KEY -> 按键被按下
scan_para->press_cnt = 1; //用于判断long和hold事件的计数器重新开始计时;
if (cur_key_value != scan_para->notify_value) { //第一次单击/连击时按下的是不同按键, 单击次数重新开始计数
scan_para->click_cnt = 1;
scan_para->notify_value = cur_key_value;
} else {
scan_para->click_cnt++; //单击次数累加
}
来源:key.c
要点:
- 消抖采用「连续采样一致」策略:第一次采样到新值先记录
filter_value并返回,随后filter_cnt每 tick 累加,达到filter_time后才进入事件判定,从机制上滤除机械抖动与电气毛刺; - 抬起判定区分短按抬起与长按/HOLD 后抬起:短按抬起不立即上报,而是启动
click_delay_cnt连击窗口,等待click_delay_time(20 tick ≈ 200ms)内是否再次按下,从而合成单击/双击/多击;长按/HOLD 后抬起则直接上报KEY_EVENT_UP; - 连击计数以
notify_value区分「同一按键的多次点击」与「不同按键的切换」,只有同一键值才累加click_cnt; - 每次检测到有效按键时
is_key_active = 35(35×10ms 的活动窗口),用于系统判断「按键是否在活动」——这是唤醒/省电策略的输入信号之一。
开机唤醒与运行开关
引擎提供三组对外控制原语:
set_key_poweron_flag / get_key_poweron_flag / clear_key_poweron_flag:维护按键开机标志,供上电流程判断「是否由按键触发开机」(见 key.c);set_key_driver_scan_enable(enable):全局暂停/恢复按键扫描(例如进入低功耗或充电检测时关闭扫描,见 key.c);key_active_set(P33_IO_WKUP_EDGE edge)/key_active_num_set(u8 key_active):配置按键唤醒边沿与唤醒按键号(声明于 key.h)。
事件出口:key_event_remap 与 sys_event
key_event_remap() 是弱符号(__attribute__((weak)))函数,默认返回 true 原样放行。用户可在自己的代码中强符号重定义该函数,把原始键值改写为组合键键值——例如「音量+」与「播放」同时按下时映射为「下一曲」:
int __attribute__((weak)) key_event_remap(struct sys_event *e)
{
return true;
}
来源:key.c
配套的 struct key_remap / key_remap_data(声明于 key.h)提供「位组合 → 新键值」的静态映射表结构,供实现组合键查表使用。事件最终封装为 sys_event 消息(key.h 中 key_emit_t 回调签名 int (*)(uint8_t key_status, uint8_t key_num, uint8_t key_type),见 key.h),由系统消息总线投递到应用层。
按键驱动层:IO / AD / 矩阵
三种底层驱动均实现 key_interface_t 接口(key_type、key_init、key_get_value),差异只在「如何读取物理电平」:
| 驱动文件 | 物理原理 | 典型场景 |
|---|---|---|
key_drv_io.c/h | 直接读取 GPIO 电平,每个按键占用一个引脚 | 少量独立按键、开机键 |
key_drv_ad.c/h | 通过 ADC 采样分压电阻网络,多个按键共享一路 ADC,按电压区间区分键值 | 遥控器、带旋钮的紧凑面板 |
key_drv_matrix.c/h | 行列扫描(Row/Column),N+M 引脚支持 N×M 按键矩阵 | 全键盘、多按键面板 |
设计意图:key_drv_ad 采用电压区间判键,机械一致性由电阻精度保证,因此消抖参数(filter_time)与阈值由配置工具生成的代码决定;key_drv_io 响应最快但引脚开销大;key_drv_matrix 用扫描换取引脚数,扫描周期天然与核心引擎的 10ms tick 对齐。
三个驱动的具体引脚配置、电压阈值与键值表由配置工具脚本生成,见下文「配置工具」。驱动头文件中 key_io_info / key_ad_info / key_matrix_info 即为注册到 key_list 的实例符号(条件编译宏 KEY_IO_EN / KEY_AD_EN / KEY_MATRIX_EN 由配置工具输出)。
旋转编码器(编码器)的接入
旋转编码器(Rotary Encoder)在本 SDK 中没有独立的专用驱动文件,按仓库现有实现,编码器 A/B 两相信号通常复用 key_drv_ad(两路 ADC 通道)或 key_drv_io(两路 GPIO 中断/轮询)接入按键系统,再通过 key_event_remap 或应用层根据 A/B 相位差(超前/滞后)区分正反转方向并映射为「音量+/-」等事件。
相关实现位置(详见对应文件):
- AD 通道式编码器:
apps/app/bsp/common/key/key_drv_ad.c、key_drv_ad.h; - 配置侧:
apps/app/post_build/bd57/AW33N_config_tool/conf/source/board/adkey_v1.lua(AD 按键/旋钮配置)与apps/app/post_build/bd57/AW33N_config_tool/conf/source/board_common/adkey_v1.lua; - 方向判定与键值映射的推荐扩展点是弱符号
key_event_remap()(见上文)。
说明:编码器方向判定与脉冲计数的具体算法位于
key_drv_ad驱动的实现细节中,本次文档采样未展开该文件内容;接入编码器时请以key_drv_ad.c实际代码与adkey_v1.lua配置项为准。
红外输入:ir_decoder / ir_encoder
红外输入与发射由 apps/app/bsp/common/ir/ 下的解码器与编码器提供,解码 API 声明于 ir_decoder.h:
/**@brief 红外解码功能初始化
* @param[in] gpio 接收引脚
* @return 无
*/
void ir_decoder_init(u32 gpio);
/**@brief 获取红外原始数据
* @param[in] 无
* @return 红外原始数据,4*8bit = (命令反码 + 命令码 + 地址反码 +地址码)
*/
u32 ir_decoder_get_data(void);
/**@brief 获取红外命令码,内部校验
* @return 红外命令码 cmd
*/
u32 ir_decoder_get_command_value(void);
/**@brief 获取红外地址码,内部校验
* @return 红外地址码 addr
*/
u32 ir_decoder_get_address_value(void);
来源:ir_decoder.h
红外输入链路要点:
ir_decoder_init(gpio)指定接收引脚后开始解码;解码结果按 NEC 类协议组织为 32bit 原始数据:命令反码 + 命令码 + 地址反码 + 地址码;- 提供「内部校验」与「不校验」两套读取接口:
ir_decoder_get_command_value()会做反码校验(抗干扰、防止误触发),..._uncheck()则直接返回原始码值(用于调试或非标准协议); - 红外键值通过
KEY_TYPE_IR(枚举定义见 key.h)进入按键事件管道;遥控器码表过滤可在irflt_v1.lua中配置(apps/app/post_build/bd57/AW33N_config_tool/conf/source/board/irflt_v1.lua); - 发射侧使用
ir_encoder.c(apps/app/bsp/common/ir/ir_encoder.c),实现遥控/对码等场景的红外发送。
配置工具与编译开关
按键/红外参数由 AW33N 配置工具(AW33N_config_tool)生成代码,源头脚本位于 conf/source:
| 脚本 | 作用 |
|---|---|
board_common/key_common.lua | 按键公共参数(消抖、长按/HOLD 阈值等) |
board_common/key_msg.lua | 按键事件消息映射定义 |
board/iokey_v1.lua | IO 按键引脚与键值表 |
board/adkey_v1.lua | AD 按键/旋钮的 ADC 通道、电压区间与键值表 |
board/irflt_v1.lua | 红外遥控码过滤/映射表 |
配置工具输出会生成 KEY_IO_EN / KEY_AD_EN / KEY_MATRIX_EN / MOUSE_KEY_SCAN_MODE 等编译宏以及 key_io_info 等驱动实例,直接决定 key.c 中 key_list 的装配内容。修改按键映射的正确方式是改 lua 配置并重新生成代码,而不是手改驱动文件——这是 SDK 保持「配置驱动代码」一致性的设计约束。
示例应用
仓库提供三个 HID 输入示例,可作为按键事件到蓝牙上报的完整参考:
apps/demo/hid/examples/keyboard/app_keyboard.c:标准键盘按键 → HID 键盘报告;apps/demo/hid/examples/keyfob/app_keyfob.c:遥控器/媒体键应用(典型 IR 或 AD 按键场景);apps/demo/hid/examples/keypage/app_keypage.c/.h:翻页/多媒体按键应用。
这些示例展示了在 sys_event 回调中按 key_type + key_num 分派到蓝牙 HID 上报的惯用法。
核心流程:一次按键的完整生命周期
sequenceDiagram
participant T as Tick Timer (10ms)
participant K as key.c (key_driver_scan)
participant D as 按键驱动 (io/ad/matrix)
participant E as 事件层 (remap / sys_event)
participant A as 应用 (app_key*)
T->>K: 周期调用 key_driver_scan()
K->>K: get_key_driver_scan_enable() 检查
K->>D: get_key_value()
D-->>K: key_io_t {key_type, key_num}
K->>K: 消抖 (filter_value / filter_cnt)
K->>K: 事件判定: 按下 / 抬起 / 连击 / 长按 / HOLD
K->>E: key_event_remap(e) 组合键重映射
E->>E: 封装为 sys_event 消息
E-->>A: 消息投递
A->>A: 业务处理 (HID 上报 / 音量 / 唤醒)
流程要点:
- 驱动使能检查:若
set_key_driver_scan_enable(0)被调用(如低功耗),整个扫描直接返回,保证省电与安全; - 轮询采集:按
key_list顺序查询驱动,首个非NO_KEY结果即为本次采样值; - 消抖:连续
filter_time次一致才进入判定,机械抖动被挡在事件层之外; - 事件合成:短按抬起进入
click_delay_time窗口合成单击/双击/多击;长按/HOLD 独立计时并在抬起时上报KEY_EVENT_UP; - 重映射钩子:
key_event_remap允许把原始键值改写为组合键值,是扩展按键语义的标准扩展点; - 消息投递:最终事件以
sys_event形式到达应用层(如app_keyboard.c),由应用决定是 HID 上报、媒体控制还是唤醒系统。
使用示例
示例 1:注册一个按键驱动接口
任何新的输入源(自研传感器、红外码表等)只需实现 key_interface_t 即可接入核心引擎:
typedef struct {
KEY_TYPE key_type;
void (*key_init)(void);
uint8_t(*key_get_value)(void);
} key_interface_t;
typedef struct {
uint8_t key_type;
uint8_t key_num;
} key_io_t;
来源:key.h
示例 2:组合键重映射钩子
应用层重定义弱符号函数,实现组合按键的键值改写(例如 bit_value 位组合 → remap_value 新键值):
struct key_remap {
uint8_t bit_value;
uint8_t remap_value;
};
struct key_remap_data {
uint8_t remap_num;
const struct key_remap *table;
};
来源:key.h
示例 3:初始化与驱动启停
void key_init(void);
void key_scan();
void key_driver_scan(void *_scan_para);
void key_active_set(P33_IO_WKUP_EDGE edge);
void key_active_num_set(u8 key_active);
void set_key_driver_scan_enable(uint8_t enable);
来源:key.h
key_init() 在系统上电阶段调用;key_driver_scan() 由 10ms tick 定时器周期调用(传入 &key_scan_para);进入睡眠前调用 set_key_driver_scan_enable(0) 暂停扫描,配合 key_active_set 配置唤醒边沿。
示例 4:红外解码接口
void ir_decoder_init(u32 gpio);
u32 ir_decoder_get_data(void); // 4*8bit = 命令反码+命令码+地址反码+地址码
u32 ir_decoder_get_command_value(void); // 带反码校验
u32 ir_decoder_get_command_value_uncheck(void); // 不带校验
u32 ir_decoder_get_address_value(void); // 带反码校验
u32 ir_decoder_get_address_value_uncheck(void); // 不带校验
来源:ir_decoder.h
典型用法:初始化时传入红外接收头所在 GPIO;应用轮询或中断中调用 ir_decoder_get_command_value() 获取命令码,与 irflt_v1.lua 配置的码表比对后映射为 KEY_TYPE_IR 键值送入按键事件管道。
配置选项
运行时扫描参数(struct key_driver_para / key_scan_para)
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
last_key | uint8_t | NO_KEY | 上一次确认的按键值,用于事件边沿判定 |
filter_value | uint8_t | - | 消抖过程中的暂存采样值 |
filter_cnt | uint8_t | - | 消抖累加计数 |
filter_time | const uint8_t | 2 | 消抖门槛:连续 filter_time 次采样一致才有效 |
long_time | const uint8_t | 100(鼠标模式 3) | 长按判定计数(≈1s @10ms) |
hold_time | const uint8_t | 150(鼠标模式 3) | HOLD 判定计数(长按 + 500ms) |
press_cnt | uint8_t | - | 按下持续时间计数,与 long/hold_time 对比 |
click_cnt | uint8_t | - | 单击次数(用于单击/双击/多击合成) |
click_delay_cnt | uint8_t | - | 抬起后连击等待窗口计数 |
click_delay_time | const uint8_t | 20 | 连击等待窗口长度(≈200ms) |
notify_value | uint8_t | - | 连击判定中的目标按键值 |
默认值来源:key.c key_scan_para;结构定义见 key.h。
编译宏与常量
| 宏/常量 | 默认值 | 说明 |
|---|---|---|
KEY_IO_EN / KEY_AD_EN / KEY_MATRIX_EN | 配置工具生成 | 决定 key_list 装配哪些驱动 |
MOUSE_KEY_SCAN_MODE | 0 | 切换鼠标模式扫描参数(快速长按判定) |
KEY_BASE_CNT | 2 | 消抖基础计数门槛(key.h) |
KEY_PRESS_CNT | 35 | 按键按下有效计数(key.h) |
NO_KEY | 0xff | 无有效按键值标志 |
KEY_UART_DEBUG | 注释态 | 打开后按键日志走 log_info(key.h) |
API 参考
按键核心(key.h / key.c)
void key_init(void) 初始化所有已注册驱动(调用各驱动的 key_init)。上电阶段调用一次。
void key_driver_scan(void *_scan_para) 每 10ms tick 调用一次;入参为 struct key_driver_para *(传 &key_scan_para)。完成采样、消抖与事件判定,内部经 key_event_remap 投递消息。当 key_driver_scan_enable == 0 时直接返回。
void key_scan() 扫描入口的便捷包装(声明于 key.h)。
void set_key_driver_scan_enable(uint8_t enable)1 恢复扫描,0 暂停扫描。用于低功耗/充电检测等场景临时禁用按键。
void key_active_set(P33_IO_WKUP_EDGE edge) 配置按键唤醒边沿(上升/下降沿唤醒)。
void key_active_num_set(u8 key_active) 配置参与唤醒的按键号。
void set_key_poweron_flag(uint8_t flag) / uint8_t get_key_poweron_flag(void) / void clear_key_poweron_flag(void) 维护「按键开机」标志,供启动流程区分按键唤醒与复位开机(key.c)。
int key_event_remap(struct sys_event *e)(weak,可重定义) 事件出口钩子:返回 true 放行;重定义后可改写事件键值实现组合键。
红外解码(ir_decoder.h)
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
ir_decoder_init | u32 gpio 接收引脚 | 无 | 启动红外解码 |
ir_decoder_deinit | 无 | 无 | 释放解码功能 |
ir_decoder_get_data | 无 | u32 | 原始 32bit 数据(命令反码+命令码+地址反码+地址码) |
ir_decoder_get_command_value | 无 | u32 | 命令码(内部反码校验) |
ir_decoder_get_command_value_uncheck | 无 | u32 | 命令码(不校验) |
ir_decoder_get_address_value | 无 | u32 | 地址码(内部反码校验) |
ir_decoder_get_address_value_uncheck | 无 | u32 | 地址码(不校验) |
失败模式、边界情况与并发注意
- 误触发与消抖:消抖窗口(
filter_time=2)只保证连续两次一致。AD 键与 MIC 共用电容充放电时可能出现开机误判——key.c中保留了poweron_cnt抑制逻辑的注释(key.c),Type-C 耳机类硬件可恢复该逻辑; - 长按/HOLD 后抬起:只有
press_cnt >= long_time的抬起才上报KEY_EVENT_UP;短按抬起进入连击窗口,若窗口内无后续按下则按单击/多击上报。若应用只监听单击而忽略抬起,注意连击窗口(200ms)会延迟短按事件的最终确认; - 多驱动优先级:
get_key_value按key_list顺序短路返回,多个驱动同时命中时只有先注册者生效,驱动顺序即优先级; - 扫描暂停竞态:
key_driver_scan_enable是普通全局变量,set_key_driver_scan_enable与 tick 扫描在不同上下文(任务/中断)调用时存在读取时序窗口,应在同一上下文(如任务中)控制启停; - 红外干扰:解码数据带反码校验,
get_command_value()可滤除多数环境红外干扰;若使用_uncheck接口需应用层自行加码表白名单(对应irflt_v1.lua); - ADC 分压边界:AD 键相邻电压区间过近时,供电波动或器件误差会导致键值跳变,区间划分应在
adkey_v1.lua中保留足够裕量。
性能与运维注意事项
- 扫描周期固定 10ms(
is_key_active = 35即 350ms 活动窗口),每次扫描只做几次比较与一次驱动查询,CPU 开销极小;低功耗场景应调用set_key_driver_scan_enable(0)并配合key_active_set使用硬件唤醒边沿,避免空转; KEY_UART_DEBUG宏开启后按键轨迹打印到日志,是排查「按键没反应/重复触发」的首选手段;- 长按/HOLD 阈值(
long_time/hold_time)与产品交互手感直接相关,调整时应同步评估连击窗口click_delay_time的配合(例如 HOLD 用于连续音量时,抬起事件KEY_EVENT_UP用于停止)。
扩展点
- 新增输入源驱动:实现
key_interface_t(key_init+key_get_value),在key_list中按编译宏加入注册表即可复用全部消抖/连击/长按逻辑; - 组合键重映射:重定义弱符号
key_event_remap(),配合key_remap_data静态映射表; - 红外码表扩展:在
irflt_v1.lua中扩展遥控码映射,映射目标使用KEY_TYPE_IR键值; - 事件语义扩展:
KEY_TYPE枚举预留TOUCH / MIC / LPTOUCH类型,触摸与麦克风输入可作为独立驱动接入同一管道。
Related Links
- 按键核心引擎:key.c / key.h
- 按键驱动:
apps/app/bsp/common/key/key_drv_io.c、key_drv_ad.c、key_drv_matrix.c(含同名 .h) - 红外:
apps/app/bsp/common/ir/ir_decoder.c、ir_encoder.c;接口头文件 ir_decoder.h - HID 键盘上报(兄弟页面):
apps/app/bsp/common/usb/device/hid_keyboard.c - 配置工具脚本:
apps/app/post_build/bd57/AW33N_config_tool/conf/source/board/iokey_v1.lua、adkey_v1.lua、irflt_v1.lua、key_common.lua、key_msg.lua - 示例应用:
apps/demo/hid/examples/keyboard/app_keyboard.c、apps/demo/hid/examples/keyfob/app_keyfob.c、apps/demo/hid/examples/keypage/app_keypage.c - 系统消息与唤醒(兄弟页面):
msg.h/sys_event/power_interface.h(P33_IO_WKUP_EDGE)