HID 蓝牙应用模块
AW33N BLE SDK 中基于 BLE HID over GATT Profile(HOGP)实现 HID 设备应用的完整模块,覆盖键盘、鼠标、遥控器等产品形态,包含应用入口、分支选择、状态机调度、HID GATT 服务定义与板级配置。
Purpose and Scope
本页介绍 apps/demo/hid 工程所构成的 HID 蓝牙应用模块,内容包括:
- 应用入口
app_main()的启动流程与资源分配(栈、堆、蓝牙 RAM、中断优先级) - 基于编译宏的应用分支选择机制(键盘 / 鼠标 / 遥控器 / keyfob / keypage / idle)
- 应用状态机与系统事件分发机制(
list_for_each_app_main+state_machine+event_handler) - BLE HID GATT 服务的 UUID 定义(
standard_hid.h,HID Service 0x1812 及其特征) - 板级配置目录结构与多种产品形态(demo / mouse / mouse_m143 / mouse_single / rc)
- 与 HID 相关的 USB 设备类和 RCSP HID 交互模块的边界说明
USB HID 设备类(usb/device/hid_keyboard.c 等)与 RCSP(Jieli 私有遥控协议)本身属于独立能力,本页仅说明它们与 BLE HID 应用的关系;各自的详细实现请参见对应的 USB 设备章节与 RCSP 章节。BLE 协议栈底层的 GATT 服务注册、连接管理与 SMP 配对不在本页展开。
Overview
什么是 HID over BLE(HOGP)
HID(Human Interface Device,人机交互设备)是传统 USB 的设备类别,用于键盘、鼠标、遥控器等输入设备。BLE 通过 HID over GATT Profile(HOGP) 将 HID 报告(Report)承载在 GATT 服务之上,使低功耗蓝牙设备也能以标准 HID 报告格式向主机(手机、PC、平板)上报按键、指针位移和媒体控制事件。
在 AW33N SDK 中,HID 蓝牙应用模块位于 apps/demo/hid/,其核心设计是:
- 单工程多形态:同一套
app_main.c通过CONFIG_APP_*编译宏切换出hid_key、hid_rc、mouse_dual、mouse_low_latency、keyfob、keypage等不同应用,避免为每种产品维护独立工程; - 应用框架驱动:应用模块通过
struct application+struct application_operation(含state_machine与event_handler回调)注册到全局链表,由main_application_operation_state按名称查找并驱动; - 标准 GATT 服务:
standard_hid.h集中定义 HID Service(0x1812)及其特征/描述符 UUID,供 BLE 协议栈注册标准 HOGP 服务使用。
模块组成
| 组成部分 | 路径 | 职责 |
|---|---|---|
| 应用入口 | apps/demo/hid/app_main.c | 系统启动、资源分配、应用分支选择、状态机/事件分发 |
| HID GATT 定义 | apps/demo/hid/include/standard_hid.h | BLE HID 服务与特征 UUID 宏 |
| 板级配置 | apps/demo/hid/board/bd57/ | 外设、BLE 参数、产品形态配置(demo/mouse/rc) |
| 工程文件 | apps/demo/hid/board/bd57/AW33N_hid.cbp | Code::Blocks 工程描述 |
| USB HID 类(相关) | apps/app/bsp/common/usb/device/hid_*.c | USB 侧 HID 设备类,与 BLE HID 共用报告概念 |
| RCSP HID(相关) | apps/app/bsp/common/third_party_profile/jieli/JL_rcsp/rcsp_hid/ | Jieli 私有遥控协议对 HID 的封装 |
Architecture
下图展示了 HID 蓝牙应用模块的整体架构与数据/控制流:
flowchart TD
subgraph sg_Board["板级配置 board/bd57"]
BoardCfg["board_aw33n_*_cfg.h<br/>外设与BLE参数"]
BoardInit["board_aw33n_*.c<br/>板级初始化"]
Modules["app_modules.h<br/>模块注册"]
end
subgraph sg_App["应用层 apps/demo/hid"]
Main["app_main()<br/>启动入口"]
Branch{"CONFIG_APP_*<br/>分支选择"}
HidKey["hid_key<br/>ACTION_HID_MAIN"]
HidRc["hid_rc<br/>ACTION_REMOTE_CONTROL"]
Mouse["mouse_dual / mouse_low_latency<br/>ACTION_MOUSE_MAIN"]
OtherApp["keyfob / keypage / idle"]
SM["state_machine 状态机<br/>list_for_each_app_main"]
Event["event_handler<br/>系统事件分发"]
end
subgraph sg_Profile["BLE HID 协议层"]
StdHid["standard_hid.h<br/>HID GATT UUID"]
Gatt["BLE 协议栈 GATT<br/>HOGP 服务"]
end
subgraph sg_Related["相关能力(独立页面)"]
UsbHid["USB HID 设备类<br/>hid_keyboard / hid_mouse / hid_media"]
RcspHid["RCSP HID<br/>rcsp_hid_inter"]
end
Main --> Branch
Branch --> HidKey
Branch --> HidRc
Branch --> Mouse
Branch --> OtherApp
HidKey --> SM
HidRc --> SM
Mouse --> SM
OtherApp --> SM
SM --> Event
SM --> StdHid
StdHid --> Gatt
BoardCfg --> Main
BoardInit --> Main
Modules --> SM
Gatt -.->|"HID 报告上报"| UsbHid
RcspHid -.->|"私有协议封装"| HidRc
架构说明:
- 应用层(
apps/demo/hid):app_main()是唯一入口,先完成更新检查、电源检测,再通过main_app_get_name()根据CONFIG_APP_*编译宏确定要运行的应用名称,最后由main_application_operation_state()在已注册的应用链表中查找匹配项并调用其state_machine回调。这一"按名查找 + 状态机驱动"的模式与 Android Intent/Activity 思想类似:应用名相当于 Intent 的 name,struct intent携带 action,框架负责路由。 - 板级配置(
board/bd57):每个产品形态(demo、mouse、mouse_m143、mouse_single、rc)都有独立的board_aw33n_*_cfg.h与board_aw33n_*.c,用于配置外设(按键、LED、传感器)与 BLE 参数;app_modules.h负责向框架注册应用模块。 - 协议层:
standard_hid.h提供标准 HOGP 服务所需的全部 UUID 宏,协议栈据此在 GATT 上暴露 HID Service、Report Map、Report、Protocol Mode 等特征。 - 相关能力:USB HID 类与 BLE HID 在"报告"概念上同源(同为 HID Report 描述符),但走完全不同的传输路径;RCSP HID 则是 Jieli 私有遥控协议对 HID 操作的封装,仅与遥控器应用(
hid_rc)相关。
启动流程与资源分配
内存布局定义
HID 应用工程在 app_main.c 顶部通过段属性(sec_used)静态分配系统关键内存,这部分直接决定了 SDK 可用的堆栈与蓝牙 RAM 容量:
//*********************************************************************************//
// sdk 堆栈、堆、蓝牙ram的分配定义 //
//*********************************************************************************//
int _sstack_top[1] sec_used(.sstack_top);//fixed
int _ustack_top[1] sec_used(.ustack_top);//fixed
//for ld.c link
static int _sstack_space[SYS_STACK_SIZE_ALL / 4] sec_used(.sstack);
static int _ustack_space[USR_STACK_SIZE_ALL / 4] sec_used(.ustack);
static int _sys_heap_space[SYS_HEAP_SIZE / 4] sec_used(.sec_sys_heap);//最少占用
static int _bt_nk_ram_min[BT_NK_RAM_SIZE_ALL / 4] sec_used(.sec_bt_nk_ram);//最少占用
static int _bt_nv_ram_min[BT_NV_RAM_SIZE_ALL / 4] sec_used(.sec_bt_nv_ram);//最少占用
来源:app_main.c
设计意图:SYS_STACK_SIZE_ALL、USR_STACK_SIZE_ALL、SYS_HEAP_SIZE、BT_NK_RAM_SIZE_ALL、BT_NV_RAM_SIZE_ALL 等宏由板级配置(如 board_aw33n_demo_cfg.h)或全局构建配置定义。NK RAM 与 NV RAM 是蓝牙协议栈的两类内存池(NK 为常驻 RAM,NV 为可掉电保存区),app_main() 启动时会打印它们的起始地址与大小(nk_malloc/nv_malloc 日志),用于联调时确认内存分配是否合理。这些段名(.sstack、.ustack、.sec_sys_heap、.sec_bt_nk_ram、.sec_bt_nv_ram)必须与链接脚本(ld)中的段定义一致,改动内存大小时需同步检查 ld 文件。
中断优先级配置
BLE 协议栈对实时性敏感,app_main.c 集中定义了 BT 相关中断的优先级,这是 HID 低延迟上报(尤其鼠标)性能的基础:
//BT
const int IRQ_BT_TIMEBASE_IP = 6; //BT TIMEBASE
const int IRQ_BLE_EVENT_IP = 5; //BT RX_EVT
const int IRQ_BLE_RX_IP = 5; //BT RX
const int IRQ_BTSTACK_MSG_IP = 4; //BT STACK
const int IRQ_BREDR_IP = 3; //no use
const int IRQ_BT_RXMCH_IP = 3; //no use
const int IRQ_AES_IP = 3; //aes
来源:app_main.c
设计意图:BLE 接收事件(IRQ_BLE_EVENT_IP/IRQ_BLE_RX_IP,优先级 5)高于协议栈消息(IRQ_BTSTACK_MSG_IP,优先级 4),确保射频数据不被协议栈处理延迟阻塞;IRQ_BREDR_IP/IRQ_BT_RXMCH_IP 标注 "no use",因为该工程是纯 BLE 应用,经典蓝牙(BR/EDR)链路被关闭。HID 上报的端到端时延与这条中断链的响应速度直接相关。
启动入口 app_main()
app_main() 是整个 HID 应用的 C 入口,顺序执行:OTA 升级状态检查 → 开机键检测 → 低压检测 → 进入应用状态机:
void app_main()
{
#if UPDATE_V2_EN
/* 初始化检查升级状态, 测试盒升级后要关机, APP-OTA升级后要开机*/
update_success_boot_check();
#endif
log_info(">>>>>>>>>>>>>>>>>>app_main...\n");
log_info("nk_malloc: %08x,%04x, nv_malloc: %08x,%04x", NK_RAM_MALLOC_START_ADDR, NK_RAM_MALLOC_SIZE,
NV_RAM_MALLOC_START_ADDR, NV_RAM_MALLOC_SIZE);
#if TCFG_POWER_ON_NEED_KEY
check_power_on_key();
#endif
#if TCFG_SYS_LVD_EN
app_power_vbat_check();
#endif
main_application_operation_state(NULL, APP_STA_START);
}
来源:app_main.c
关键点:
update_success_boot_check():区分"测试盒升级后关机"与"APP-OTA 升级后开机"两种升级收尾行为,避免升级成功后错误掉电;check_power_on_key():当TCFG_POWER_ON_NEED_KEY使能时,开机后需持续按住电源键等待开机确认,若按键松开则进入软关机(power_set_soft_poweroff),防止误上电;app_power_vbat_check():TCFG_SYS_LVD_EN使能时做低电压检测,电压不足直接保护关机;- 最后以
APP_STA_START状态调用main_application_operation_state()启动所选应用的状态机。
应用分支选择机制
HID 模块最大的设计特点是一份入口代码,多产品形态。main_app_get_name() 依据编译宏决定运行哪个应用:
static void main_app_get_name(struct intent *it)
{
init_intent(it);
// 选择应用分支
#if(CONFIG_APP_KEYBOARD)
it->name = "hid_key";
it->action = ACTION_HID_MAIN;
#elif(CONFIG_APP_KEYFOB)
it->name = "keyfob";
it->action = ACTION_KEYFOB;
#elif(CONFIG_APP_KEYPAGE)
it->name = "keypage";
it->action = ACTION_KEYPAGE;
#elif(CONFIG_APP_REMOTE_CONTROL)
it->name = "hid_rc";
it->action = ACTION_REMOTE_CONTROL;
#elif(CONFIG_APP_MOUSE_DUAL)
it->name = "mouse_dual";
it->action = ACTION_MOUSE_MAIN;
#elif(CONFIG_APP_MOUSE_LOW_LATENCY)
it->name = "mouse_low_latency";
it->action = ACTION_MOUSE_MAIN;
#elif(CONFIG_APP_IDLE)
it->name = "idle";
it->action = ACTION_IDLE_MAIN;
#else
ASSERT(0, "no app!!!");
#endif
}
来源:app_main.c
设计意图:
- 编译期裁剪:
CONFIG_APP_*是编译宏,未被选择的代码路径根本不会链接进固件,天然实现代码裁剪与 ROM/RAM 节省; - Intent 建模:
struct intent携带name(应用标识)与action(行为意图),是 SDK 应用框架的"启动参数"对象;ACTION_HID_MAIN表示键盘主界面,ACTION_MOUSE_MAIN表示鼠标主界面,action 可在应用内部驱动界面/功能切换; - 强制唯一性:
#if / #elif链保证同一时间只有一个应用被选择,#else ASSERT(0, "no app!!!")在没有配置任何应用时于编译/运行期报错,防止"空工程"上线。
状态机与事件分发框架
应用注册与查找
main_application_operation_state() 遍历框架维护的应用链表,按名称匹配后调用其 state_machine 回调:
static struct application *main_application_operation_state(struct application *app, enum app_state state)
{
struct intent it;
const struct application *dev = NULL;
main_app_get_name(&it);
log_info("run app>>> %s", it.name);
list_for_each_app_main(dev) {
if (memcmp(dev->name, it.name, strlen(it.name)) == 0) {
if (dev->ops) {
dev->ops->state_machine(app, state, &it);
}
} else {
log_info("no app run");
}
}
return NULL;
}
来源:app_main.c
设计意图:list_for_each_app_main(dev) 遍历由 app_modules.h(板级目录下)注册的 struct application 链表。每个应用实现 struct application_operation,其中 state_machine 负责状态迁移(如 APP_STA_START → 初始化 → 运行),event_handler 负责处理外部系统事件。名称匹配使用 memcmp + strlen 而非 strcmp,属于嵌入式风格的长度受限比较。
系统事件分发
蓝牙协议栈、按键、电源等模块产生的事件通过消息队列投递到应用层,由 main_sys_event_msg_handle() 统一分发:
void main_sys_event_msg_handle(int *msg)
{
// putchar('$');
struct sys_event *event_ptr = (struct sys_event *)msg[1];
const struct application_operation *ops_ptr = (const struct application_operation *)msg[2];
ops_ptr->event_handler(NULL, event_ptr);
event_pool_free(event_ptr);
}
来源:app_main.c
设计意图:msg[1] 是指向 struct sys_event 的指针(事件负载),msg[2] 指向目标应用的 application_operation(回调表)。分发后必须调用 event_pool_free(event_ptr) 归还事件池内存——这是典型的内存池复用设计,避免高频事件(按键、BLE 连接状态变化)导致的堆碎片与泄漏。所有系统事件都走这条统一管道,应用无需关心事件来源是 BLE、按键还是电源。
BLE HID GATT 服务定义
standard_hid.h 是 BLE HID 应用与协议栈之间的"契约"文件,集中定义 HOGP 标准服务所需的全部 16 位 UUID:
/*******************************************************************/
/*
*-------------------hid for ble
*/
#define HID_UUID_16 0x1812
#define HID_INFORMATION_UUID_16 0x2A4A
#define HID_REPORT_MAP_UUID_16 0x2A4B
#define HID_CONTROL_POINT_UUID_16 0x2A4C
#define HID_REPORT_UUID_16 0x2A4D
#define PROTOCOL_MODE_UUID_16 0x2A4E
#define HID_REPORT_REFERENCE_UUID_16 0x2908
这些 UUID 与蓝牙 SIG 标准 HID Service(0x1812)完全一致,各特征用途如下:
| UUID | 宏名 | 角色 | 说明 |
|---|---|---|---|
| 0x1812 | HID_UUID_16 | 服务 | HID Service,HOGP 的 GATT 服务根 |
| 0x2A4A | HID_INFORMATION_UUID_16 | 特征 | HID Information:报告 HID 版本、国家码、标志位 |
| 0x2A4B | HID_REPORT_MAP_UUID_16 | 特征 | Report Map:HID 报告描述符(决定按键/指针的报文格式) |
| 0x2A4C | HID_CONTROL_POINT_UUID_16 | 特征 | Control Point:主机下发 Suspend/Exit Suspend 控制 |
| 0x2A4D | HID_REPORT_UUID_16 | 特征 | Report:承载输入/输出/特征报告,键盘按键、鼠标位移都经此上报 |
| 0x2A4E | PROTOCOL_MODE_UUID_16 | 特征 | Protocol Mode:Boot Protocol / Report Protocol 切换 |
| 0x2908 | HID_REPORT_REFERENCE_UUID_16 | 描述符 | Report Reference:关联 Report 特征与报告 ID/类型 |
设计意图:将 UUID 与业务代码解耦。应用/协议栈代码引用宏名而非裸数字,既避免魔法数,也便于跨工程复用——apps/demo/transfer/include/standard_hid.h 存在同一文件,说明该定义被多个 demo 工程共享。头文件中 USB HID 的包含被注释(// #include "usb/device/hid.h"),表明 BLE HID 应用刻意不依赖 USB 设备栈,两者通过各自的协议栈独立工作,仅在报告内容层面保持一致。
板级配置与产品形态
apps/demo/hid/board/bd57/ 是 HID 模块的板级支持目录,采用"一板多配置"策略,每个产品形态对应一组 board_aw33n_*_cfg.h + board_aw33n_*.c:
| 配置组 | 文件 | 目标产品 |
|---|---|---|
| demo | board_aw33n_demo.c / board_aw33n_demo_cfg.h | 通用 HID demo 板 |
| mouse | board_aw33n_mouse.c / board_aw33n_mouse_cfg.h | 双模鼠标(对应 mouse_dual) |
| mouse_m143 | board_aw33n_mouse_m143.c / board_aw33n_mouse_m143_cfg.h | M143 传感器鼠标 |
| mouse_single | board_aw33n_mouse_single.c / board_aw33n_mouse_single_cfg.h | 单连接鼠标(对应 mouse_low_latency) |
| rc | board_aw33n_rc.c / board_aw33n_rc_cfg.h | 遥控器(对应 hid_rc) |
配套文件还包括:
board_config.h:板级总配置入口;app_modules.h:向应用框架注册应用模块(list_for_each_app_main的数据来源);AW33N_hid.cbp:Code::Blocks 工程文件,IDE 构建入口;Makefile:命令行构建脚本;board_aw33n_*_global_build_cfg.h:全局构建开关(如内存大小、协议栈裁剪宏)。
设计意图:把"产品差异"全部收敛到 board/bd57 一层,应用层 app_main.c 与 standard_hid.h 对产品差异无感知。新增一款 HID 产品时,只需在 board 目录新增一组 cfg/c 文件,并在 main_app_get_name() 的分支链中补充一个 CONFIG_APP_* 分支,应用框架代码无需改动。这是典型的"平台 + 产品配置"分层思想。
与 USB HID、RCSP HID 的关系
BLE HID 应用模块并非孤立存在,SDK 中还有两处 HID 相关能力:
- USB HID 设备类(
apps/app/bsp/common/usb/device/hid_keyboard.c、hid_mouse.c、hid_media.c、hid_no_ep.c、custom_hid.c):实现 USB 侧的 HID 键盘/鼠标/媒体类设备,用于有线模式或双模(BLE + USB)产品。BLE HID 与 USB HID 共享"HID 报告"这一抽象(Report Map、报告 ID、Boot Protocol 语义),但传输路径完全独立:USB 走端点中断传输,BLE 走 GATT Notification/Write。双模鼠标(mouse_dual)正是同时挂接这两条路径的产品形态。 - RCSP HID(
apps/app/bsp/common/third_party_profile/jieli/JL_rcsp/rcsp_hid/rcsp_hid_inter.c/.h):Jieli 私有 RCSP(Remote Control Serial Protocol)协议对 HID 功能的封装,供hid_rc遥控器应用通过私有协议实现按键透传/自定义命令,属于厂商私有通道,不依赖标准 HOGP。
本页不展开这两部分的内部实现,详细内容请参见对应的 USB 设备模块与 RCSP 模块页面。
核心流程
下图描述 HID 蓝牙应用从开机到事件处理的完整时序:
sequenceDiagram
participant Boot as 复位/引导
participant Main as app_main()
participant Cfg as 板级配置宏
participant SM as 应用状态机
participant App as HID 应用模块
participant Stack as BLE 协议栈
participant Host as 手机/PC 主机
Boot->>Main: 上电进入 app_main()
Main->>Main: update_success_boot_check()<br/>OTA 升级收尾处理
Main->>Main: check_power_on_key() / app_power_vbat_check()
Main->>Cfg: 读取 CONFIG_APP_* 确定分支
Main->>SM: main_application_operation_state(NULL, APP_STA_START)
SM->>SM: list_for_each_app_main 查找 name 匹配
SM->>App: dev->ops->state_machine(app, state, &it)
App->>Stack: 注册 HID GATT 服务(0x1812)<br/>基于 standard_hid.h UUID
App-->>Host: 广播 / 等待连接
Host->>Stack: 连接建立
Stack->>App: BLE 连接事件 -> sys_event
App->>SM: event_handler 处理连接事件
App->>App: 按键/位移 -> 组装 HID Report
App->>Stack: Report 特征 Notification (0x2A4D)
Stack-->>Host: GATT 通知上报
关键时序要点:
- 启动前置检查:升级检查、开机键、低压检测都在进入应用状态机前完成,保证应用启动时电源与固件状态安全;
- 按名路由:状态机通过
list_for_each_app_main查找与it.name匹配的应用,未命中则打印 "no app run"; - 事件驱动上报:HID 应用不是轮询上报,而是由 BLE 连接事件、按键事件驱动;按键/位移数据组装成 HID Report 后经
HID_REPORT_UUID_16(0x2A4D)特征以 GATT Notification 上报,主机端无需轮询,符合 BLE 低功耗设计。
使用示例
示例 1:选择 HID 键盘应用分支
在板级配置(如 board_aw33n_demo_cfg.h)中使能 CONFIG_APP_KEYBOARD 后,main_app_get_name() 会选中 hid_key 应用:
#if(CONFIG_APP_KEYBOARD)
it->name = "hid_key";
it->action = ACTION_HID_MAIN;
#elif(CONFIG_APP_REMOTE_CONTROL)
it->name = "hid_rc";
it->action = ACTION_REMOTE_CONTROL;
#elif(CONFIG_APP_MOUSE_DUAL)
it->name = "mouse_dual";
it->action = ACTION_MOUSE_MAIN;
#endif
来源:app_main.c
这段代码展示了模块的扩展方式:新增产品时在 #elif 链末尾追加分支,为 it 指定新的 name 与 action,并在 app_modules.h 中注册同名应用模块。
示例 2:BLE HID 服务 UUID 引用
HID 应用或协议栈服务注册代码引用 standard_hid.h 中的宏,保证与蓝牙 SIG 标准一致:
#define HID_UUID_16 0x1812
#define HID_INFORMATION_UUID_16 0x2A4A
#define HID_REPORT_MAP_UUID_16 0x2A4B
#define HID_CONTROL_POINT_UUID_16 0x2A4C
#define HID_REPORT_UUID_16 0x2A4D
#define PROTOCOL_MODE_UUID_16 0x2A4E
#define HID_REPORT_REFERENCE_UUID_16 0x2908
示例 3:启动状态机驱动应用
在 app_main() 末尾以 APP_STA_START 启动所选应用,应用模块通过回调表完成初始化与运行:
main_application_operation_state(NULL, APP_STA_START);
来源:app_main.c
配置选项
应用分支宏(编译期)
| 宏 | 选择的应用 | action | 说明 |
|---|---|---|---|
CONFIG_APP_KEYBOARD | hid_key | ACTION_HID_MAIN | BLE 键盘 |
CONFIG_APP_KEYFOB | keyfob | ACTION_KEYFOB | 车钥匙/防丢器 |
CONFIG_APP_KEYPAGE | keypage | ACTION_KEYPAGE | 翻页笔/演示笔 |
CONFIG_APP_REMOTE_CONTROL | hid_rc | ACTION_REMOTE_CONTROL | BLE 遥控器(可配合 RCSP) |
CONFIG_APP_MOUSE_DUAL | mouse_dual | ACTION_MOUSE_MAIN | 双模(BLE+USB)鼠标 |
CONFIG_APP_MOUSE_LOW_LATENCY | mouse_low_latency | ACTION_MOUSE_MAIN | 低延迟单连接鼠标 |
CONFIG_APP_IDLE | idle | ACTION_IDLE_MAIN | 空闲/无功能应用 |
来源:app_main.c
系统级开关
| 宏 | 类型 | 默认行为 | 说明 |
|---|---|---|---|
TCFG_POWER_ON_NEED_KEY | 布尔 | 关闭 | 开机需长按电源键确认,松开进入软关机 |
TCFG_SYS_LVD_EN | 布尔 | 关闭 | 使能系统低电压检测(app_power_vbat_check) |
UPDATE_V2_EN | 布尔 | — | 使能 V2 升级流程与开机升级状态检查 |
来源:app_main.c
BT 中断优先级
| 中断 | 优先级 | 说明 |
|---|---|---|
IRQ_BT_TIMEBASE_IP | 6 | BT 时基 |
IRQ_BLE_EVENT_IP | 5 | BLE RX 事件 |
IRQ_BLE_RX_IP | 5 | BLE 接收 |
IRQ_BTSTACK_MSG_IP | 4 | 蓝牙协议栈消息 |
IRQ_BREDR_IP | 3 | 经典蓝牙(本工程未使用) |
IRQ_BT_RXMCH_IP | 3 | 未使用 |
IRQ_AES_IP | 3 | AES 硬件加速 |
来源:app_main.c
内存段(由板级/全局配置宏决定大小)
| 段 | 宏 | 用途 |
|---|---|---|
.sstack | SYS_STACK_SIZE_ALL | 系统栈 |
.ustack | USR_STACK_SIZE_ALL | 用户栈 |
.sec_sys_heap | SYS_HEAP_SIZE | 系统堆 |
.sec_bt_nk_ram | BT_NK_RAM_SIZE_ALL | 蓝牙 NK RAM(常驻) |
.sec_bt_nv_ram | BT_NV_RAM_SIZE_ALL | 蓝牙 NV RAM |
来源:app_main.c
API 参考
void app_main(void)
HID 应用的 C 入口函数,由系统引导调用。
- 流程:升级状态检查 → 打印内存分配 → 开机键检测(可选)→ 低压检测(可选)→
main_application_operation_state(NULL, APP_STA_START) - 返回:无(不返回,进入应用循环)
- 注意:在调用应用状态机前完成所有电源/固件安全检查
static void main_app_get_name(struct intent *it)
根据 CONFIG_APP_* 编译宏选择应用分支,填充 struct intent 的 name 与 action。
- 参数:
it— 目标 intent 对象,由调用方分配,函数内部调用init_intent(it)初始化 - 行为:
#if/#elif链保证单分支命中;无匹配时ASSERT(0, "no app!!!") - 返回:无(通过
it输出)
static struct application *main_application_operation_state(struct application *app, enum app_state state)
在应用链表中按名称查找目标应用并驱动其状态机。
- 参数:
app— 当前应用实例(首次为NULL)state— 目标状态(如APP_STA_START)
- 流程:
main_app_get_name(&it)→list_for_each_app_main(dev)→memcmp(dev->name, it.name, strlen(it.name))匹配 →dev->ops->state_machine(app, state, &it) - 返回:
NULL
void main_sys_event_msg_handle(int *msg)
系统事件消息分发函数,供消息队列回调使用。
- 参数:
msg— 消息数组,msg[1]为struct sys_event *,msg[2]为struct application_operation * - 行为:调用目标应用的
event_handler(NULL, event_ptr)后调用event_pool_free(event_ptr)归还事件池内存 - 返回:无
来源:app_main.c
故障模式、边界情况与并发
故障模式
| 故障场景 | 表现 | 源码中的防护 |
|---|---|---|
| 未配置任何应用 | ASSERT(0, "no app!!!") 断言失败 | main_app_get_name() 的 #else 分支强制拦截,防止空工程发布 |
| 应用名不匹配 | 日志打印 no app run,状态机不执行 | memcmp 匹配失败时仅记录日志,框架继续运行,便于排查模块注册遗漏 |
| 低电压上电 | 应用可能运行不稳 | TCFG_SYS_LVD_EN 使能时 app_power_vbat_check() 在上电早期拦截 |
| 误触发上电 | 设备意外开机耗电 | TCFG_POWER_ON_NEED_KEY 使能时需长按确认,松开即软关机 |
| 升级后状态异常 | 升级完成却关机/开机错误 | update_success_boot_check() 区分测试盒升级(关机)与 APP-OTA(开机) |
| 事件池耗尽 | 事件丢失或内存耗尽 | event_pool_free(event_ptr) 强制归还;若应用未正确释放,事件池会耗尽,属实现错误 |
边界情况
- 分支互斥:
CONFIG_APP_*使用#if/#elif链而非独立#if,同时定义多个宏时只有最先命中的生效;若要启用新形态,必须保证链上宏互斥。 - 名称匹配长度:
memcmp(dev->name, it.name, strlen(it.name))只比较it.name的长度,要求应用注册名是唯一前缀(例如mouse_dual与mouse_low_latency必须以完整名注册,避免前缀冲突)。 - 纯 BLE 工程:
IRQ_BREDR_IP/IRQ_BT_RXMCH_IP标注 "no use",若误开经典蓝牙功能,相关中断配置需重新评估。
并发与实时性
- 中断优先级设计:BLE RX(5)高于协议栈消息(4),保证射频数据及时进入协议栈;HID 上报路径(按键 → 组装 Report → GATT Notification)运行在应用任务上下文,受
IRQ_BTSTACK_MSG_IP以下优先级任务影响,鼠标低延迟形态依赖这条链的确定性。 - 事件池复用:系统事件经
event_pool_free归还内存池,事件在高频场景(快速按键、连接/断开抖动)下不会产生堆碎片;但同一事件指针在分发期间不能被其他任务释放,否则会造成 use-after-free,event_handler必须同步完成事件消费。 - 内存段隔离:系统栈/用户栈/系统堆/蓝牙 NK/NV RAM 分别映射到独立段,蓝牙 RAM 与应用堆互不侵占,保证协议栈在应用内存耗尽时仍可运行。
性能与运维注意事项
- RAM 规划:
BT_NK_RAM_SIZE_ALL与BT_NV_RAM_SIZE_ALL决定 BLE 协议栈容量,改动需同步检查 ld 链接脚本;app_main()启动日志(nk_malloc/nv_malloc)可用于核对实际布局。 - 低延迟上报:鼠标低延迟形态(
CONFIG_APP_MOUSE_LOW_LATENCY)对 GATT Notification 的调度时延敏感,建议优先保障IRQ_BLE_EVENT_IP与协议栈任务优先级不被应用任务阻塞。 - 日志定位:应用框架打印
run app>>> <name>与no app run,启动阶段可通过这两条日志快速确认分支选择是否正确。 - 升级收尾:修改升级策略时注意
update_success_boot_check()的分支语义,避免升级成功后错误关机导致用户体验问题。
扩展点
- 新增产品形态:在
main_app_get_name()的#elif链追加CONFIG_APP_XXX→ 新name/action;在apps/demo/hid/board/bd57/新增board_aw33n_xxx_cfg.h与board_aw33n_xxx.c;在app_modules.h注册同名应用模块。 - 新增 HID 报告类型:
standard_hid.h的 UUID 覆盖标准 HOGP 特征;如需自定义报告(如厂商命令),可基于HID_REPORT_UUID_16扩展报告 ID,或在 RCSP 通道(rcsp_hid_inter)实现私有透传。 - 更换应用框架行为:应用通过
struct application_operation的state_machine与event_handler两个回调接入框架;自定义应用只需实现这两个回调并在app_modules.h注册。 - 板级复用:
standard_hid.h被apps/demo/transfer/等工程复用,HID GATT UUID 定义是跨工程的共享契约,新工程可直接 include。
测试情况说明
本页基于源码静态分析,未在仓库中发现 HID 应用模块的独立单元测试工程;SDK 采用硬件在环验证(真实设备 + 主机端 BLE 抓包/APP 验证)为主:
- 连接验证:通过手机/PC 扫描 HID Service(0x1812),读取 Report Map(0x2A4B)确认报告格式;
- 上报验证:按键/位移后抓取 Report 特征(0x2A4D)的 Notification,核对报告 ID 与数据;
- 多形态验证:切换
CONFIG_APP_*宏编译不同固件,验证run app>>>日志与对应产品行为一致。