公共组件与第三方协议
本页面向 AC630N 蓝牙 SDK 中 apps/common 目录下的公共组件(按键驱动、代码切换、光电鼠标传感器、升级组件)与第三方协议栈(杰理 JL RCSP、透传 demo、AT 命令、HOGP、SIG Mesh)的完整实现说明。
Purpose and Scope
本页覆盖 apps/common 中所有被各 apps/<APP_CASE>(如 hid)工程共享的可复用组件与第三方协议实现,包括:
- 公共组件:
key/(按键驱动框架)、code_switch/(代码切换/升级模式选择)、optical_mouse_sensor/(光电鼠标传感器管理)、update/(OTA 升级入口)。 - 第三方协议:
third_party_profile/jieli/(杰理 RCSP 蓝牙配对/升级协议、SPP/BLE 透传 demo、AT 命令、HOGP)、third_party_profile/sig_mesh/(蓝牙 Mesh)。
不在本页范围:各具体应用工程(如 apps/hid、apps/classic 等)的业务逻辑、apps/app_cfg 的板级配置与 apps/debug.c 的调试框架、SDK 底层(include_lib 中的蓝牙协议栈、VM/FS 驱动、系统事件/定时器内核)——这些属于各自独立页面的话题。本页聚焦于这些组件如何被组装、如何与底层交互、如何被配置裁剪。
Overview
在 AC630N 蓝牙 SDK 中,apps/common 是应用层与底层 SDK 之间的共享中间层。它解决三类问题:
- 硬件抽象复用:不同应用(HID 键盘、音频、遥控器)都使用同一套按键扫描、消抖、事件判定逻辑(
key_driver),通过注册不同的get_value()实现(IO 口、ADC、滑动、红外、触摸、旋转编码)来适配不同硬件。 - 协议接入标准化:杰理私有协议(RCSP)与标准协议(SIG Mesh、HOGP)被封装成可裁剪的
third_party_profile组件,通过apps/hid/Makefile等构建脚本按需编入,避免未用代码进入固件。 - 配置与升级通道:
custom_cfg.c通过config.dat文件 + VM 存储保存可远程修改的配置(广播包、设备名、PIN 码、链接密钥等),配合 RCSP 升级通道实现"空中改配置/升级"。
构建层面,apps/hid/Makefile 通过 -I$(ROOT)/apps/common/ 及 objs += 列表把上述组件逐个加入编译,因此裁剪组件的入口就是各应用工程的 Makefile,这也是理解整个 apps/common 的关键线索。
Architecture
下图展示了 apps/common 公共组件与第三方协议在整体软件栈中的位置及其与底层 SDK 的依赖关系:
flowchart TD
subgraph sg_App["应用层 apps/<APP_CASE>"]
App["app 工程 (如 hid)"]
end
subgraph sg_Common["公共组件 apps/common"]
Key["key 按键驱动<br/>key_driver / iokey / adkey"]
CodeSwitch["code_switch 代码切换"]
OMS["optical_mouse_sensor<br/>OMSensor_manage / hal3205"]
Update["update 升级组件"]
end
subgraph sg_Third["第三方协议 apps/common/third_party_profile"]
Jieli["jieli 杰理协议族<br/>RCSP / 透传 / AT / HOGP"]
Mesh["sig_mesh 蓝牙 Mesh"]
end
subgraph sg_SDK["SDK 底层"]
Event["system/event 事件系统"]
Timer["system/timer 定时器"]
BT["蓝牙协议栈 (SPP/BLE)"]
VM["VM 存储 / FS 文件系统"]
end
App --> Key
App --> CodeSwitch
App --> OMS
App --> Update
App --> Jieli
App --> Mesh
Key --> Event
Key --> Timer
Jieli --> BT
Jieli --> VM
Jieli --> Update
Mesh --> BT
Update --> VM
各节点职责与连线说明:
key组件是纯定时器驱动的扫描框架:系统定时器周期性调用key_driver_scan(),扫描结果通过sys_event(system/event.h)上报给应用,不直接触碰蓝牙协议栈。code_switch与update服务于固件升级与多代码区切换,被 RCSP 升级流程(rcsp_user_update、rcsp_ch_loader_download)调用。jieli协议族直接依赖底层 SPP/BLE 协议栈与 VM/FS,是唯一横跨"应用 ⇄ 协议栈 ⇄ 存储"的组件;sig_mesh则完全运行在 BLE 之上。optical_mouse_sensor是独立的外设驱动层(HAL3205 传感器),通过OMSensor_manage向应用提供鼠标位移/按键数据,典型用于 HID 鼠标应用。
构建证据见 apps/hid/Makefile,其中明确列出了 apps/common/、apps/common/include/、third_party_profile/jieli/JL_rcsp/ 等头文件搜索路径以及 code_switch/include/、optical_mouse_sensor/include/ 等子模块路径。
公共组件:按键驱动子系统
组件结构与设计意图
按键驱动位于 apps/common/key/,由多个文件组成:key_driver.c(扫描框架与事件判定)、iokey.c(IO 口按键)、adkey.c(ADC 按键)、slidekey.c(滑动按键)、irkey.c(红外按键)、touch_key.c(触摸按键)、rdec_key.c(旋转编码器按键)。构建时通过 apps/hid/Makefile 一并编入:
objs += \
$(ROOT)/apps/common/key/iokey.o \
$(ROOT)/apps/common/key/adkey.o \
$(ROOT)/apps/common/key/key_driver.o
Source: apps/hid/Makefile
设计上采用策略模式:struct key_driver_para 描述一次扫描所需的全部状态与回调(get_value() 读键值、filter_time 消抖时间、long_time 长按阈值、press_cnt/click_cnt 计数等),key_driver_scan() 只负责"周期性读取 → 消抖 → 判定事件类型 → 上报"。具体按键类型(IO/ADC/红外/触摸)通过不同的 get_value() 实现注入,框架代码无需改动即可适配新硬件。
扫描与消抖机制
key_driver_scan() 由系统定时器按固定周期(约 10ms)调用,核心代码如下:
static void key_driver_scan(void *_scan_para)
{
struct key_driver_para *scan_para = (struct key_driver_para *)_scan_para;
u8 key_event = 0;
u8 cur_key_value = NO_KEY;
u8 key_value = 0;
struct sys_event e;
static u8 poweron_cnt = 0;
cur_key_value = scan_para->get_value();
...
//===== 按键消抖处理
if (cur_key_value != scan_para->filter_value && scan_para->filter_time) { //当前按键值与上一次按键值如果不相等, 重新消抖处理, 注意filter_time != 0;
scan_para->filter_cnt = 0; //消抖次数清0, 重新开始消抖
scan_para->filter_value = cur_key_value; //记录上一次的按键值
return; //第一次检测, 返回不做处理
} //当前按键值与上一次按键值相等, filter_cnt开始累加;
if (scan_para->filter_cnt < scan_para->filter_time) {
scan_para->filter_cnt++;
return;
}
//===== 按键消抖结束, 开始判断按键类型(单击, 双击, 长按, 多击, HOLD, (长按/HOLD)抬起)
Source: apps/common/key/key_driver.c
消抖算法要点:
- 两段式消抖:第一段比较
cur_key_value与filter_value,不一致则重置filter_cnt并记录新值后立即返回;一致则进入第二段,累加filter_cnt直到达到filter_time。这保证按键电平在连续filter_time个周期内稳定才被确认,滤除机械抖动。 - 防误判开关:代码中保留了一段注释掉的"上电前 250 个周期不做按键处理"逻辑,用于滤掉 adkey 与 mic 连在一起时电容充放电导致的开机按键误判(典型 Type-C 耳机场景),说明该框架曾为特定硬件形态做过针对性处理。
- 按下/抬起状态机:
cur_key_value != scan_para->last_key时判定状态翻转——从有效键值变为NO_KEY视为抬起(若press_cnt >= long_time则上报KEY_EVENT_UP),从NO_KEY变为有效键值视为按下(重置press_cnt,并通过notify_value判断是否同一按键以累加click_cnt,实现单击/双击/多击)。
事件判定与上报流程
判定完成后,按键事件通过系统事件(sys_event)上报,上报前经过一个可重映射的弱函数钩子:
//=======================================================//
// 按键值重新映射函数:
// 用户可以实现该函数把一些按键值重新映射, 可用于组合键的键值重新映射
//=======================================================//
int __attribute__((weak)) key_event_remap(struct sys_event *e)
{
return true;
}
Source: apps/common/key/key_driver.c
key_event_remap 是弱符号(weak symbol),SDK 默认实现直接返回 true(不重映射);应用工程若需实现组合键(如 FN+音量键映射为其他功能),只需在业务代码中定义同名强符号函数即可覆盖,无需修改公共组件——这是 SDK 典型的扩展点设计。
按键扫描核心流程
flowchart TD
Start([定时器触发 key_driver_scan]) --> GetVal["scan_para->get_value() 读取当前按键值"]
GetVal --> Active["更新 is_key_active 活跃计数"]
Active --> Debounce{"cur != filter_value<br/>且 filter_time != 0 ?"}
Debounce -->|"是 (电平变化)"| Reset["filter_cnt = 0<br/>记录 filter_value 后返回"]
Debounce -->|"否 (电平稳定)"| Cnt{"filter_cnt < filter_time ?"}
Cnt -->|"是"| Inc["filter_cnt++ 返回"]
Cnt -->|"否 (消抖完成)"| Judge{"cur != last_key ?"}
Judge -->|"抬起 (cur = NO_KEY)"| Up{"press_cnt >= long_time ?"}
Up -->|"是"| EvtUp["上报 KEY_EVENT_UP"]
Up -->|"否"| Click["click_delay_cnt = 1<br/>等待连击窗口"]
Judge -->|"按下 (cur = 有效键)"| Press["press_cnt = 1<br/>同键 click_cnt++ / 异键重置"]
Click --> Wait([等待连击延时])
EvtUp --> Notify["判定 单击/双击/长按/HOLD 事件类型"]
Press --> Notify
Notify --> Remap["key_event_remap() 弱函数重映射"]
Remap --> Post["sys_event_notify 发送按键事件"]
Post --> End([进入下一扫描周期])
Wait --> End
设计意图说明:
- 消抖在框架内完成而非各驱动内完成,保证所有按键类型行为一致;
filter_time == 0时跳过消抖,供对响应速度敏感的场景使用。 is_key_active声明为volatile u8,因为它在定时器中断上下文中被读写,应用代码(如待机判断)也可能读取,volatile 防止编译器缓存优化导致读取到过期值。- 长按(
press_cnt >= long_time)后抬起才上报KEY_EVENT_UP,而 HOLD 事件在长按期间持续上报;KEY_EVENT_CLICK_ONLY_SUPPORT与ALL_KEY_EVENT_CLICK_ONLY宏(见 key_driver.c)用于让某些按键(如 SPI LCD 场景下)只响应单击,简化事件处理。
公共组件:代码切换与光电鼠标传感器
code_switch(代码切换)
apps/common/code_switch/code_switch.c 实现多代码区/多固件切换能力,其头文件路径 code_switch/include/ 被显式加入各工程搜索路径(见 apps/hid/Makefile)。该组件与 update/ 升级组件配合:升级下载完成后,通过 code_switch 将启动标志/跳转地址写入指定区域,实现复位后从新固件启动;它也是 RCSP 远程升级(rcsp_user_update)的落地执行者。代码切换通常需要把关键代码段放入固定地址(#pragma code_seg 一类段重定位),保证切换逻辑在任意固件版本下都可执行。
optical_mouse_sensor(光电鼠标传感器)
该组件为 HID 鼠标类应用提供外设抽象,包含两层:
OMSensor_manage.c:传感器管理/调度层,负责初始化、周期性读取位移与按键、向应用层上报鼠标数据。hal3205/hal3205.c:HAL3205 传感器芯片驱动(芯片级寄存器读写)。
构建证据见 apps/hid/Makefile 与对象列表:
objs += \
$(ROOT)/apps/common/optical_mouse_sensor/OMSensor_manage.o \
$(ROOT)/apps/common/optical_mouse_sensor/hal3205/hal3205.o \
Source: apps/hid/Makefile
分层意图:hal3205 只负责"如何操作这颗芯片",OMSensor_manage 负责"何时操作、数据给谁",替换传感器型号时只需新增一个 hal 层驱动并保持管理接口不变。
第三方协议族(third_party_profile)
apps/common/third_party_profile/ 按厂商/标准组织,分为 jieli/(杰理私有协议)与 sig_mesh/(蓝牙 SIG Mesh 标准协议)两大块。下图展示协议栈分层关系:
flowchart LR
subgraph sg_App2["应用层"]
User["用户 app 业务 (hid/classic 等)"]
end
subgraph sg_Proto2["第三方协议层 apps/common/third_party_profile"]
RCSP["JL RCSP<br/>rcsp_bluetooth"]
Trans["透传 demo<br/>spp_trans_data / le_trans_data"]
AT["AT 命令<br/>spp_at_com / le_at_com"]
HOGP["HOGP 键盘<br/>le_hogp"]
Mesh["SIG Mesh<br/>access / adv_core"]
end
subgraph sg_Stack2["SDK 协议栈"]
SPP["SPP 串口仿真"]
BLE["BLE GATT"]
HIDP["HID over GATT"]
end
User --> RCSP
User --> Trans
User --> AT
User --> HOGP
User --> Mesh
RCSP --> SPP
RCSP --> BLE
RCSP --> Trans
Trans --> SPP
Trans --> BLE
AT --> SPP
AT --> BLE
HOGP --> HIDP
HIDP --> BLE
Mesh --> BLE
JL RCSP(杰理遥控/配对/升级协议)
目录 jieli/JL_rcsp/ 是杰理私有 RCSP(Remote Control/SPP)协议实现,核心文件:
rcsp_bluetooth.c:RCSP 与蓝牙协议栈的桥接,管理 SPP/BLE 连接的建立、数据收发与协议分发。rcsp_updata/rcsp_user_update.c:用户区固件升级,通过 RCSP 通道接收升级包并写入 Flash。rcsp_updata/rcsp_ch_loader_download.c:芯片 Bootloader(loader)下载,用于引导区程序更新。jieli/hid_user.c:HID 应用与 RCSP 的用户层适配。
编译入口(见 apps/hid/Makefile):
objs += \
$(ROOT)/apps/common/third_party_profile/common/custom_cfg.o \
$(ROOT)/apps/common/third_party_profile/jieli/JL_rcsp/rcsp_bluetooth.o \
$(ROOT)/apps/common/third_party_profile/jieli/JL_rcsp/rcsp_updata/rcsp_user_update.o \
$(ROOT)/apps/common/third_party_profile/jieli/JL_rcsp/rcsp_updata/rcsp_ch_loader_download.o \
Source: apps/hid/Makefile
RCSP 协议栈通过 RCSP_BTMATE_EN / RCSP_ADV_EN 宏选择 BTMATE 或 ADV 变体(见 custom_cfg.c),并据此引入不同的用户升级头文件(rcsp_user_update.h 或 rcsp_adv_user_update.h)——协议实现与配置存储紧耦合,custom_cfg 即为 RCSP 提供可空中修改的配置项。
SPP/BLE 透传与 AT 命令
trans_data_demo/ 提供最简数据透传示例:spp_trans_data.c(SPP 通道)、le_trans_data.c(BLE 通道)、spp_user.c(SPP 用户回调)。spp_at_com.c 与 le_at_com.c 则在透传基础上实现 AT 命令解析,允许手机/上位机通过串口仿真通道下发 AT+... 指令控制设备(如改名字、查电量)。这些 demo 是接入 RCSP 前理解"字节流如何从协议栈到业务"的最佳范例,其 include 路径 third_party_profile/jieli/trans_data_demo/ 已加入构建(见 apps/hid/Makefile)。
HOGP 与 SIG Mesh
le_hogp.c:HID over GATT Profile 实现,使设备可作 BLE 键盘/鼠标(与hid_user.c的 USB/HID 路径互补)。le_client_demo.c提供 BLE 客户端示例。sig_mesh/:SIG Mesh 标准协议实现,access.c为模型访问层(model access),adaptation/ble_core/adv_core.c将 Mesh 广播承载(PB-ADV)适配到底层 BLE 广播。
裁剪原则:sig_mesh 与 jieli 协议族互不依赖,应用工程只需在 Makefile 中删除对应 .o 与 -I 路径即可移除协议,减少固件体积——协议组件全部按"需要才编译"设计。
公共组件:自定义配置(custom_cfg)
apps/common/third_party_profile/common/custom_cfg.c 是 RCSP 生态的配置持久化层:它把"可被手机 App 远程修改的配置项"统一读写到 SD 卡文件 config.dat(配合 VM 存储),并自动附加 CRC 校验。配置文件路径与 API 族选择如下:
#define RES_CUSTOM_CFG_FILE SDFILE_RES_ROOT_PATH"config.dat"
//配置:VM的接口采用692X还是693X
#define VM_API_AC692X 0
#define VM_API_AC693X 1
#define USE_VM_API_SEL VM_API_AC693X
//配置:是否在总是擦除EXIF区域然后重写,还是只有在EXIF信息有变化时才擦除
#define ALWAYS_ERASE_EXIF_AREA 0
#define ONLY_DIFF_ERASE_EXIF_AREA 1
#define EXIF_ERASE_CONFIG ALWAYS_ERASE_EXIF_AREA
//配置:文件操作接口是用692X还是693X
#define SDFILE_API_AC692X 0
#define SDFILE_API_AC693X 1
#define USE_FS_API_SEL SDFILE_API_AC693X
Source: apps/common/third_party_profile/common/custom_cfg.c
设计意图:SDK 同时兼容 AC692X/AC693X 两代芯片,VM 与文件系统 API 签名不同,因此用编译期宏 USE_VM_API_SEL / USE_FS_API_SEL 在编译期选择 API 族,避免运行期分支开销并保证链接正确性;EXIF_ERASE_CONFIG 则控制擦写策略(总是擦除 vs 仅差异擦除),在 Flash 寿命与实现复杂度之间取舍。
配置项的数据结构
所有配置项都遵循统一的 {u16 crc, u16 len, u8 data[]} 头+载荷 布局,读写时按结构体偏移量定位字段:
#define MEMBER_OFFSET_OF_STRUCT(type, member) ((u32)&(((type *)0)->member))
#define MEMBER_SIZE_OF_STRUCT(type,member) ((u16)sizeof(((type *)0)->member))
typedef struct _adv_data_t {
u16 crc;
u16 len;
u8 data[31];
} adv_data_cfg_t;
typedef struct _ble_name_t {
u16 crc;
u16 len;
u8 data[31 - 2];
} ble_name_t;
typedef struct _bt_name_t {
u16 crc;
u16 len;
u8 data[31];
} bt_name_t;
typedef struct _bt_pin_code_t {
u16 crc;
u16 len;
char data[4];
} bt_pin_code_t;
Source: apps/common/third_party_profile/common/custom_cfg.c
同一文件中还有 ver_info_cfg_t(固件版本信息,载荷为 update_file_id_t)、reset_io_info_cfg_t(复位 IO)、pilot_lamp_io_info_cfg_t(指示灯 IO,4 字节)、link_key_info_cfg_t(链接密钥,载荷为 update_file_link_key_t)、last_device_connect_linkkey_cfg_t(上次连接设备密钥,16 字节)等结构(见 custom_cfg.c)。
模式分析:crc + len + data 是典型的"自描述记录"格式——len 使旧固件可跳过新版本 App 写入的更长载荷,crc 在读取时校验完整性,损坏则回退默认值;MEMBER_OFFSET_OF_STRUCT / MEMBER_SIZE_OF_STRUCT 让配置项可按字段增量读写(配合 EXIF_ERASE_CONFIG 的差异擦除策略),避免每次全量重写浪费 Flash 寿命。
配置选项
以下配置项均来自实际源码,按所属组件分组:
| 组件 | 配置宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
| key | KEY_EVENT_CLICK_ONLY_SUPPORT | int | 1 | 是否支持某些按键只响应单击事件 |
| key | ALL_KEY_EVENT_CLICK_ONLY | int | TCFG_SPI_LCD_ENABLE 时 1,否则 0 | 全部按键只响应单击(SPI LCD 场景) |
| key | MOUSE_KEY_SCAN_MODE | int | — | 鼠标扫描模式:按下即上报,不等待长按抬起 |
| key | TCFG_IRSENSOR_ENABLE | int | — | 是否启用红外传感器(引入 irSensor/ir_manage.h) |
| key | struct key_driver_para.filter_time | u8 | 由驱动注册时设定 | 消抖周期数;0 表示不消抖 |
| key | struct key_driver_para.long_time | u8 | 由驱动注册时设定 | 长按/HOLD 判定阈值(扫描周期数) |
| code_switch | code_switch 相关接口 | — | — | 多代码区切换,配合 update 组件 |
| RCSP | RCSP_BTMATE_EN | int | — | 启用 BTMATE 版 RCSP 协议与 rcsp_user_update.h |
| RCSP | RCSP_ADV_EN | int | — | 启用 ADV 版 RCSP 协议与 rcsp_adv_user_update.h |
| custom_cfg | RES_CUSTOM_CFG_FILE | string | SDFILE_RES_ROOT_PATH"config.dat" | 自定义配置文件路径 |
| custom_cfg | USE_VM_API_SEL | int | VM_API_AC693X | 选择 AC692X 或 AC693X 的 VM API |
| custom_cfg | USE_FS_API_SEL | int | SDFILE_API_AC693X | 选择 AC692X 或 AC693X 的文件系统 API |
| custom_cfg | EXIF_ERASE_CONFIG | int | ALWAYS_ERASE_EXIF_AREA | EXIF 区域擦写策略(总是擦除/仅差异擦除) |
| custom_cfg | CUSTOM_CFG_DEBUG_EN | int | 1 | 使能 cfg_puts/cfg_printf/cfg_printf_buf 调试输出 |
构建裁剪选项(各应用 Makefile):
| 配置点 | 默认值 | 说明 |
|---|---|---|
includes += -I$(ROOT)/apps/common/... | 按工程设定 | 头文件搜索路径,决定组件是否可见 |
objs += $(ROOT)/apps/common/.../xxx.o | 按工程设定 | 对象文件列表,未列入的组件不参与链接 |
API 参考
int key_event_remap(struct sys_event *e)(弱函数)
按键事件上报前调用的重映射钩子。
- 参数:
e— 待上报的系统事件(含按键值与事件类型)。 - 返回:
true表示继续正常上报;返回false可吞掉事件。 - 说明:
__attribute__((weak))弱符号,默认实现直接返回true;应用可定义同名强符号实现组合键重映射,覆盖公共组件行为。
static void key_driver_scan(void *_scan_para)
按键扫描主函数,由系统定时器周期性调用。
- 参数:
_scan_para—struct key_driver_para *,包含get_value()、filter_value、filter_time、filter_cnt、last_key、press_cnt、click_cnt、notify_value、long_time、click_delay_cnt等扫描状态。 - 行为:读取键值 → 消抖 → 判定按下/抬起 → 判定单击/双击/长按/HOLD → 经
key_event_remap后通过sys_event上报。 - 返回:无(void)。
custom_cfg 配置结构体族
| 类型 | 载荷字段 | 用途 |
|---|---|---|
adv_data_cfg_t | u8 data[31] | BLE 广播数据包配置 |
ble_name_t | u8 data[29] | BLE 设备名配置 |
bt_name_t | u8 data[31] | 经典蓝牙设备名配置 |
bt_pin_code_t | char data[4] | 蓝牙 PIN 码配置 |
ver_info_cfg_t | update_file_id_t data | 固件版本信息 |
reset_io_info_cfg_t | u8 data | 复位 IO 配置 |
pilot_lamp_io_info_cfg_t | u8 data[4] | 指示灯 IO 配置 |
link_key_info_cfg_t | update_file_link_key_t data | 配对链接密钥 |
last_device_connect_linkkey_cfg_t | u8 data[16] | 上次连接设备链接密钥 |
所有结构统一为 {u16 crc; u16 len; ...} 头 + 载荷布局,供 RCSP 远程读写。
故障模式、边界情况与并发
按键抖动与误判
- 消抖窗口不足:若
filter_time设置过小,机械抖动会被当作真实按键,产生连发;框架要求filter_time != 0才启用消抖,0 值场景(快速响应)由调用方自行承担抖动风险。 - 上电误判:adkey 与麦克风共用引脚时,电容充放电会导致开机瞬间误判按键;
key_driver_scan中保留的poweron_cnt注释代码正是该问题的修复预案(上电前 N 周期忽略按键)。 - 长按抬起事件:只有
press_cnt >= long_time后的抬起才上报KEY_EVENT_UP(key_driver.c);短按抬起不直接发 UP,而是进入连击等待窗口(click_delay_cnt = 1),因此单击事件存在上报延迟(等待确认不是双击),交互设计需考虑该延迟。
配置损坏与 Flash 寿命
- CRC 校验失败:
custom_cfg各配置项带u16 crc头,读取时校验失败应回退默认配置;config.dat为外部文件,可能被拔卡/断电写坏,恢复策略依赖该 CRC 机制。 - Flash 擦写:
EXIF_ERASE_CONFIG默认ALWAYS_ERASE_EXIF_AREA(总是擦除后重写),频繁远程改配置会加速 Flash 磨损;ONLY_DIFF_ERASE_EXIF_AREA选项通过MEMBER_OFFSET_OF_STRUCT/MEMBER_SIZE_OF_STRUCT按字段比较,仅在变化时擦写,是延长寿命的推荐选项。 - 版本兼容:
len字段使旧固件可跳过新载荷;新 App 写入更长数据时旧固件只读自身认识的字段,避免结构体越界。
并发与中断上下文
- 按键扫描运行在定时器中断上下文,与业务线程共享
struct key_driver_para状态;is_key_active被声明为volatile以保证跨上下文可见性。业务代码不应直接修改扫描状态,只能通过key_event_remap或事件处理回调介入。 - RCSP 升级(
rcsp_user_update/rcsp_ch_loader_download)涉及擦写 Flash 的长时间临界区,升级期间蓝牙连接与按键扫描必须暂停或降级处理,避免擦写过程中被中断破坏数据;具体协调逻辑位于 RCSP 升级模块内部。 code_switch涉及代码段跳转,必须在关闭中断、确认新固件 CRC 有效后执行,防止跳到损坏固件导致变砖。
协议冲突
jieli协议族(RCSP/透传/AT)与sig_mesh均占用 BLE/SPP 链路,若同时使能需确认通道与 UUID 不冲突;le_at_com与spp_at_com的 AT 解析器若与 RCSP 数据流混用同一连接,需在协议分发层(rcsp_bluetooth.c)区分帧头,否则会产生串包。
性能与运维考量
- 扫描周期:按键扫描约 10ms 一次,每次仅做比较与计数,CPU 开销极低;
filter_time与long_time均以扫描周期为单位(如 35 对应 350ms),调参即改变手感与响应。 - 编译期裁剪:
apps/hid/Makefile的includes/objs列表是固件体积的主要开关——不用的协议(Mesh、AT、透传)从 Makefile 移除即可显著减小 ROM 占用;custom_cfg的CUSTOM_CFG_DEBUG_EN关闭后,cfg_printf系列退化为空宏,减少调试输出开销。 - 日志分级:key 组件通过
LOG_TAG "[KEY]"与LOG_ERROR_ENABLE/LOG_DEBUG_ENABLE/LOG_INFO_ENABLE控制输出级别(key_driver.c),量产固件应关闭 DEBUG/DUMP 级别以降低串口与时间开销。 - 升级运维:RCSP 提供
rcsp_user_update(用户区)与rcsp_ch_loader_download(Bootloader)两条升级通道,配合update组件与config.dat可实现"远程改配置 + 远程升级固件"的完整运维闭环。
扩展点
- 新增按键类型:实现
get_value()并注册struct key_driver_para(参照iokey.c/adkey.c等既有驱动),框架自动获得消抖与事件判定能力。 - 组合键/键值重映射:覆盖弱函数
key_event_remap(struct sys_event *e),在事件上报前改写键值或吞掉事件。 - 新增传感器型号:仿照
hal3205/增加 hal 层驱动,保持OMSensor_manage接口不变。 - 新协议接入:在
third_party_profile/下新增目录并仿照jieli/sig_mesh提供 Makefile 片段(-I+.o),由各应用工程选择性编入。 - 配置项扩展:在
custom_cfg.c中按{crc, len, data}模板新增结构体并注册到读写表中,App 侧即可通过 RCSP 远程读写。
测试情况说明
在本次探索范围内(apps/common 源码与 apps/hid/Makefile),未发现独立的单元测试工程;按键与协议组件的验证主要依赖目标板运行验证(如 HID 键盘的按键手感、RCSP 升级流程的实机测试)。key_driver.c 中保留的条件编译与注释代码(poweron_cnt 防误判、MOUSE_KEY_SCAN_MODE)表明其验证方式为实机迭代调参,测试覆盖需由各应用工程自行补充。
Related Links
- apps/hid/Makefile — 公共组件与第三方协议的编译裁剪入口
- apps/common/key/key_driver.c — 按键驱动框架核心实现
- apps/common/third_party_profile/common/custom_cfg.c — RCSP 配置持久化实现
- 相关目录:
apps/common/code_switch/、apps/common/optical_mouse_sensor/、apps/common/third_party_profile/jieli/JL_rcsp/、apps/common/third_party_profile/sig_mesh/ - 相邻页面:应用工程(apps/hid 等)的业务逻辑、SDK 底层协议栈与系统服务(event/timer/VM/FS)请参见对应目录的独立文档