Dongle 适配器应用
Dongle 适配器应用(app_dongle)是 AW31N SDK 中基于 transfer demo 的一个独立应用示例:它把 AW318N/AW31N 芯片配置成 USB BLE 适配器(Dongle),一端作为 BLE Central 接收键鼠外设的 HID 数据,另一端通过 USB HID 设备接口把数据上报给 PC,实现"无线键鼠收发器"的完整链路。
Purpose and Scope
本文档讲解 Dongle 适配器应用的全部实现机制:
- 应用的注册方式与生命周期(
REGISTER_APPLICATION+ 状态机); - 启动流程:蓝牙协议栈初始化、USB 设备初始化、主消息循环;
- BLE Central(
ble_dg_central/ HID Host)到 USB HID 设备的上行数据通路; - 按键事件、蓝牙事件、PC 命令事件、OTG 升级事件的分发;
- 低功耗(soft poweroff 与 LP target)与双设备通道支持;
- 板级配置(
board_aw318n_dongle_cfg.h)与可配置项。
本页不覆盖以下主题(属于同目录其他页面):
- BLE 协议栈通用初始化细节:见 "Bluetooth Stack" / "BTStack 任务" 相关页面;
- Dongle 作为 Central 的连接管理、配对与扫描逻辑:见 "BLE Dongle Central" 页面;
- Dongle OTA 升级协议:见 "Dongle OTA" 页面;
- USB 协议栈本身(枚举、描述符框架):见 "USB Device" 页面。
Overview
Dongle(适配器)是一类常见的外设形态:一个 USB 小棒插入 PC,即可把无线(BLE / 2.4G)键鼠、遥控器等设备桥接到 PC。相比把 BLE 协议栈直接做进 PC 主机,Dongle 方案让任何带 USB 口的电脑都无需额外驱动即可使用无线外设——因为 Dongle 对 PC 呈现为标准 HID 键鼠设备。
在本 SDK 中,该能力由 apps/demo/transfer/examples/dongle/app_dongle.c 实现,其核心设计是双向角色分离:
- 对无线外设:Dongle 是 BLE Central / HID Host(
GATT_ROLE_CLIENT),负责扫描、连接、配对并接收外设的 HID 报告; - 对 PC:Dongle 是 USB HID 设备(键盘/鼠标/多媒体控制),通过
usb_hid_mouse_send_data()把收到的 HID 报告原样转发。
应用还同时支持:
- 2.4G 私有协议模式:通过
CFG_RF_24G_CODE_ID配置配对码,非 0 时启用 2.4G 跳频连接(主从需相同配对码); - 双设备通道:当
CONFIG_BT_GATT_CLIENT_NUM == 2时,可同时连接两个 BLE 外设并各自映射到 USB 通道; - OTA 升级:在
RCSP_BTMATE_EN使能时,通过自定义 HID 通道接收 PC 下发固件并转发到 OTA 流程(ota_dg_central); - 低功耗:软关机时先主动断开蓝牙链路,再进入
SOFT_MODE或SOFT_BY_POWER_MODE电源模式。
Architecture
下图展示 Dongle 适配器应用的整体架构与数据流方向:
flowchart TD
subgraph sg_Wireless["无线外设侧"]
BLE_DEV["BLE 键鼠外设<br/>(HID Peripheral)"]
RF_DEV["2.4G 外设"]
end
subgraph sg_Dongle["AW31N Dongle 固件 (app_dongle)"]
APP["app_dongle<br/>REGISTER_APPLICATION"]
BT_STACK["btstack_init / ble_dg_central<br/>(BLE Central, GATT_ROLE_CLIENT)"]
RF_24G["rf_set_24g_hackable_coded<br/>(2.4G 私有协议)"]
HID_HOST["dongle_ble_hid_input_handler<br/>HID 数据入口"]
USB_DEV["usb_hid_mouse_send_data<br/>(USB HID 设备)"]
EVT["dongle_event_handler<br/>按键/蓝牙/PC/OTG 事件"]
OTA["dongle_ota_init / custom_hid_rx<br/>(RCSP_BTMATE_EN)"]
LP["REGISTER_LP_TARGET<br/>dongle_state_idle_query"]
end
subgraph sg_PC["PC 主机侧"]
PC["标准 HID 键鼠设备<br/>无需专用驱动"]
end
BLE_DEV -->|"BLE HID 报告"| BT_STACK
RF_DEV -->|"2.4G 数据"| RF_24G
RF_24G --> BT_STACK
BT_STACK --> HID_HOST
HID_HOST --> USB_DEV
USB_DEV -->|"USB 枚举为 HID"| PC
EVT --> APP
APP --> BT_STACK
APP --> USB_DEV
OTA --> USB_DEV
LP --> APP
架构要点:
app_dongle是一个通过REGISTER_APPLICATION注册的标准 SDK 应用,由应用框架按ACTION_DONGLE_MAIN拉起,其运行主体是dongle_app_start()中的消息循环(get_msg+app_comm_process_handler)。- 蓝牙侧使用
btstack_init()初始化协议栈,并以ble_dg_central(Dongle Central 模块)作为 GATT Client 角色管理外设连接。 - USB 侧在
dongle_usb_start()中注册 HID 报告描述符后启动设备,数据上行路径为:BLE 外设 →dongle_ble_hid_input_handler()→usb_hid_mouse_send_data()→ PC。 - 所有系统事件(按键、蓝牙状态、PC 命令、OTG)统一汇入
dongle_event_handler,保证事件串行处理、无并发竞争。
应用注册与生命周期
Dongle 应用遵循 SDK 的 application 框架。文件末尾通过 REGISTER_APPLICATION 宏注册:
static const struct application_operation app_dongle_ops = {
.state_machine = dongle_state_machine,
.event_handler = dongle_event_handler,
};
/*
* 注册AT Module模式
*/
REGISTER_APPLICATION(app_dongle) = {
.name = "dongle",
.action = ACTION_DONGLE_MAIN,
.ops = &app_dongle_ops,
.state = APP_STA_DESTROY,
};
Source: app_dongle.c
REGISTER_APPLICATION 将 app_dongle 登记到应用表中,应用框架在收到 ACTION_DONGLE_MAIN intent 时创建该应用实例,并驱动其状态机。状态机实现如下:
static int dongle_state_machine(struct application *app, enum app_state state, struct intent *it)
{
switch (state) {
case APP_STA_CREATE:
break;
case APP_STA_START:
if (!it) {
break;
}
switch (it->action) {
case ACTION_DONGLE_MAIN:
dongle_app_start();
break;
}
break;
case APP_STA_PAUSE:
case APP_STA_RESUME:
case APP_STA_STOP:
break;
case APP_STA_DESTROY:
log_info("APP_STA_DESTROY\n");
break;
}
return 0;
}
Source: app_dongle.c
设计意图:Dongle 是常驻型应用,dongle_app_start() 内部是一个永不返回的 while(1) 消息循环,因此 APP_STA_PAUSE/RESUME/STOP 均为空实现;APP_STA_START 是唯一真正干活的状态。
启动流程
dongle_app_start() 是应用入口,执行顺序为:设置时钟 → 初始化蓝牙 → 初始化 USB → 进入消息循环:
static void dongle_app_start()
{
log_info("=======================================\n");
log_info("---------usb + dongle start demo-------\n");
log_info("=======================================\n");
log_info("app_file: %s", __FILE__);
#if CONFIG_BLE_CONNECT_SLOT
clk_set("sys", 160000000);//低延时模式默认配置160M
clk_set("lsb", 80000000);//HSB: 160M - LSB: 80M
#else
clk_set("sys", TCFG_CLOCK_SYS_HZ);
clk_set("lsb", TCFG_CLOCK_LSB_HZ);
#endif
clock_bt_init();
dongle_bt_start();
dongle_usb_start();
int msg[4] = {0};
while (1) {
get_msg(sizeof(msg) / sizeof(int), msg);
app_comm_process_handler(msg);
}
}
Source: app_dongle.c
关键点:
- 时钟策略:
CONFIG_BLE_CONNECT_SLOT(BLE 连接时隙)开启时强制 160M 系统时钟 + 80M LSB,用于低延时模式;否则使用板级TCFG_CLOCK_SYS_HZ/TCFG_CLOCK_LSB_HZ。这直接影响 HID 上报的端到端时延。 - 主循环:
get_msg阻塞等待消息(消息为 4 个 int,即 16 字节),app_comm_process_handler统一处理,保持单线程串行模型,蓝牙/USB 回调都通过消息或事件机制回到此上下文。
蓝牙初始化
static void dongle_bt_start()
{
uint32_t sys_clk = clk_get("sys");
bt_pll_para(TCFG_CLOCK_OSC_HZ, sys_clk, 0, 0);
btstack_ble_start_before_init(NULL, 0);
#if CFG_RF_24G_CODE_ID
rf_set_24g_hackable_coded(CFG_RF_24G_CODE_ID);
#endif
btstack_init();
}
Source: app_dongle.c
设计意图:先根据当前系统时钟配置蓝牙 PLL 参数(保证 RF 频率精度),再启动 BLE 协议栈;若 CFG_RF_24G_CODE_ID 非 0,则同时使能 2.4G 私有协议并写入配对码——配对码是主从设备建立 2.4G 链路的凭据,必须一致(见 app_dongle.h 中 CFG_RF_24G_CODE_ID 的注释)。
USB 设备初始化与 HID 描述符
static void dongle_usb_start()
{
#if (TCFG_PC_ENABLE)
//配置选择上报PC的描述符
//first device
log_info("register channel 1");
#if CONFIG_HIDKEY_REPORT_TEST & BIT(0)
usb_hid_mouse_set_report_map(sHIDReportDesc_hidkey, sizeof(sHIDReportDesc_hidkey));
#else
usb_hid_mouse_set_report_map(sHIDReportDesc_mouse, sizeof(sHIDReportDesc_mouse));
#endif
#if (CONFIG_BT_GATT_CLIENT_NUM == 2)
//second device
log_info("register channel 2");
#if CONFIG_HIDKEY_REPORT_TEST & BIT(1)
//usb_hid_set_second_repport_map(sHIDReportDesc_hidkey, sizeof(sHIDReportDesc_hidkey));
#else
//usb_hid_set_second_repport_map(sHIDReportDesc_stand_keyboard2, sizeof(sHIDReportDesc_stand_keyboard2));
#endif
#endif
#if TCFG_OTG_USB_DEV_EN
const struct otg_dev_data otg_data = {
.usb_dev_en = TCFG_OTG_USB_DEV_EN,
.slave_online_cnt = TCFG_OTG_SLAVE_ONLINE_CNT,
.slave_offline_cnt = TCFG_OTG_SLAVE_OFFLINE_CNT,
.host_online_cnt = TCFG_OTG_HOST_ONLINE_CNT,
.host_offline_cnt = TCFG_OTG_HOST_OFFLINE_CNT,
.detect_mode = TCFG_OTG_MODE,
.detect_time_interval = TCFG_OTG_DET_INTERVAL,
.usb_otg_sof_check_init = usb_otg_sof_check_init,
};
usb_otg_init(NULL, (void *)&otg_data);
#else
usb_start();
#endif
usb_slave_set_status_hander(dg_central_usb_status_handler);
#if RCSP_BTMATE_EN
dongle_ota_init();
//usb TODO
custom_hid_set_rx_hook(NULL, dongle_custom_hid_rx_handler);//重注册接收回调到dongle端
/* download_buf = malloc(1024); */
/* dongle_return_online_list(); */
sys_timeout_add(NULL, dongle_return_online_list, 4000);
#endif
#endif
}
Source: app_dongle.c
设计意图与要点:
- 报告描述符可切换:
CONFIG_HIDKEY_REPORT_TEST的 bit0/bit1 分别控制通道 1/2 上报 PC 的 HID 描述符类型(多媒体控制sHIDReportDesc_hidkeyvs 鼠标sHIDReportDesc_mouse),便于产测时验证 USB 通道。 - 双通道预留:
CONFIG_BT_GATT_CLIENT_NUM == 2时注册第二路描述符(代码中第二路 map 接口被注释,作为扩展预留)。 - OTG 模式:板级使能 OTG 时使用
usb_otg_init并传入检测参数(在线/离线计数、检测模式与间隔),否则直接usb_start()。 - USB 状态回调:
usb_slave_set_status_hander(dg_central_usb_status_handler)把 USB 枚举/断开状态通知给 Dongle Central 模块(ble_dg_central),用于联动蓝牙连接策略。 - OTA 钩子:
RCSP_BTMATE_EN使能时注册自定义 HID 接收钩子dongle_custom_hid_rx_handler接收 PC 下发的升级命令,并在上电 4 秒后通过dongle_return_online_list上报在线设备列表(配合RCSP_BTMATEPC 工具)。
HID 报告描述符定义在 app_dongle.h,例如多媒体控制(Consumer Control)描述符(35 字节),包含音量 +/-、播放/暂停、静音、上一曲/下一曲等 16 个 1-bit 用量:
static const u8 sHIDReportDesc_hidkey[] = {
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, HIDKEY_REPORT_ID, // Report ID (1)
0x09, 0xE9, // Usage (Volume Increment)
0x09, 0xEA, // Usage (Volume Decrement)
0x09, 0xCD, // Usage (Play/Pause)
0x09, 0xE2, // Usage (Mute)
0x09, 0xB6, // Usage (Scan Previous Track)
0x09, 0xB5, // Usage (Scan Next Track)
0x09, 0xB3, // Usage (Fast Forward)
0x09, 0xB4, // Usage (Rewind)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x10, // Report Count (16)
0x81, 0x02, // Input (Data,Var,Abs,...)
0xC0, // End Collection
// 35 bytes
};
Source: app_dongle.h
启动时序总览
sequenceDiagram
participant FW as 应用框架
participant APP as app_dongle
participant BT as btstack / ble_dg_central
participant USB as USB Device
participant PC as PC 主机
FW->>APP: ACTION_DONGLE_MAIN intent
activate APP
APP->>APP: clk_set(sys/lsb)
APP->>APP: clock_bt_init()
APP->>BT: dongle_bt_start()<br/>(bt_pll_para / btstack_init)
APP->>USB: dongle_usb_start()<br/>(注册 HID map + usb_start)
USB-->>PC: USB 枚举为 HID 键鼠
USB-->>APP: dg_central_usb_status_handler
APP->>APP: get_msg 循环 (app_comm_process_handler)
Note over APP: 常驻消息循环,处理按键/BT/PC/OTG 事件
deactivate APP
此时序展示了从应用拉起、双栈(BLE + USB)初始化到常驻事件循环的完整生命周期。
数据通路:BLE HID → USB HID
Dongle 的核心转发逻辑非常简单——原样搬移 HID 报告。BLE 外设(键鼠)上报的 HID 数据由协议栈回调到 dongle_ble_hid_input_handler(),该函数直接把它送入 USB 端点:
//ble 接收设备数据
int dongle_ble_hid_input_handler(uint8_t *packet, uint16_t size)
{
/* log_info("ble_hid_data_input:size=%d", size); */
/* log_info_hexdump(packet, size); */
/* rf_unpack_data(packet, size); */
#if (TCFG_PC_ENABLE)
// 1ms高回报率时不开putchar,否则会影响收数
/* putchar('&'); */
return usb_hid_mouse_send_data(packet, size);
#else
log_info("chl1 disable!!!\n");
return 0;
#endif
}
//ble 接收第二个设备数据
int dongle_second_ble_hid_input_handler(uint8_t *packet, uint16_t size)
{
/* log_info("ble_hid_data_input:size=%d", size); */
/* put_buf(packet, size); */
/* putchar('#'); */
#if (TCFG_PC_ENABLE) && (CONFIG_BT_GATT_CLIENT_NUM == 2)
//return hid_send_second_data(packet, size);
return 0;
#else
log_info("chl2 disable!!!\n");
return 0;
#endif
}
Source: app_dongle.c
设计意图:
- 零拷贝转发:BLE 收到的
packet直接作为 USB 报告发送,不解析、不重组,既降低时延也避免协议耦合——USB 端的报告格式由注册的 HID 描述符决定,与 BLE 端报告格式保持一致即可(两者由 HID 协议天然对齐)。 - 性能注释:源码注释明确指出"1ms 高回报率时不开 putchar,否则会影响收数"——在高回报率(1000Hz 轮询)场景,串口打印会成为瓶颈,生产代码应关闭调试输出。
- 双通道:第二个 BLE 设备的数据走
dongle_second_ble_hid_input_handler,仅在TCFG_PC_ENABLE && CONFIG_BT_GATT_CLIENT_NUM == 2时有效;当前hid_send_second_data被注释,第二路上报为预留扩展点。
数据通路如下图所示:
flowchart LR
DEV1["BLE 外设 1<br/>(HID Peripheral)"] -->|"GATT 通知/写"| CEN1["ble_dg_central<br/>GATT_ROLE_CLIENT"]
DEV2["BLE 外设 2"] -->|"GATT 通知/写"| CEN2["第二通道<br/>(CONFIG_BT_GATT_CLIENT_NUM==2)"]
CEN1 -->|"packet/size"| IN1["dongle_ble_hid_input_handler"]
CEN2 -->|"packet/size"| IN2["dongle_second_ble_hid_input_handler"]
IN1 -->|"usb_hid_mouse_send_data"| EP["USB HID IN 端点"]
IN2 -->|"预留 hid_send_second_data"| EP
EP -->|"USB 轮询"| PC["PC 主机"]
事件处理与分发
所有系统事件通过 dongle_event_handler 串行进入应用,避免回调上下文中的并发问题:
static int dongle_event_handler(struct application *app, struct sys_event *event)
{
switch (event->type) {
case SYS_KEY_EVENT:
dongle_key_event_handler(event);
return 0;
case SYS_BT_EVENT:
if ((uint32_t)event->arg == SYS_BT_EVENT_TYPE_CON_STATUS) {
dongle_bt_connction_status_event_handler(&event->u.bt);
} else if ((uint32_t)event->arg == SYS_BT_EVENT_TYPE_HCI_STATUS) {
dongle_bt_hci_event_handler(&event->u.bt);
#if RCSP_BTMATE_EN
} else if ((uint32_t)event->arg == DEVICE_EVENT_FROM_PC) {
dongle_pc_event_handler(&event->u.bt);//dongle pc命令回调处理
} else if ((uint32_t)event->arg == DEVICE_EVENT_FROM_OTG) {
dongle_otg_event_handler(&event->u.bt);//dongle ota升级数据透传
#endif
}
return 0;
case SYS_DEVICE_EVENT:
if ((uint32_t)event->arg == DEVICE_EVENT_FROM_POWER) {
/* return app_power_event_handler(&event->u.dev, dongle_set_soft_poweroff); */
}
#if TCFG_CHARGE_ENABLE
else if ((uint32_t)event->arg == DEVICE_EVENT_FROM_CHARGE) {
app_charge_event_handler(&event->u.dev);
}
#endif
return 0;
default:
return FALSE;
}
return FALSE;
}
Source: app_dongle.c
蓝牙状态事件统一转发给公共模块 bt_comm_ble_*_event_handler:
static int dongle_bt_hci_event_handler(struct bt_event *bt)
{
//对应原来的蓝牙连接上断开处理函数 ,bt->value=reason
log_info("----%s reason %x %x", __FUNCTION__, bt->event, bt->value);
bt_comm_ble_hci_event_handler(bt);
return 0;
}
static int dongle_bt_connction_status_event_handler(struct bt_event *bt)
{
log_info("----%s %d", __FUNCTION__, bt->event);
bt_comm_ble_status_event_handler(bt);
return 0;
}
Source: app_dongle.c
按键事件
static void dongle_key_event_handler(struct sys_event *event)
{
uint16_t event_type;
uint32_t key_value;
if (event->arg == (void *)DEVICE_EVENT_FROM_KEY) {
event_type = event->u.key.event;
key_value = event->u.key.value;
log_info("app_key_event: %d,%d\n", event_type, key_value);
if (event_type == KEY_EVENT_TRIPLE_CLICK
&& (key_value == TCFG_ADKEY_VALUE3 || key_value == TCFG_ADKEY_VALUE0)) {
/* dongle_power_event_to_user(POWER_EVENT_POWER_SOFTOFF); */
/* dongle_set_soft_poweroff(); */
return;
}
if (event_type == KEY_EVENT_DOUBLE_CLICK && key_value == TCFG_ADKEY_VALUE0) {
dg_central_clear_pair();
return;
}
#if (USER_SUPPORT_PROFILE_HID ==1)
if (event_type == KEY_EVENT_LONG && key_value == TCFG_ADKEY_VALUE0) {
log_info("hid disconnec test");
user_hid_disconnect();
}
#endif
#if (TCFG_PC_ENABLE) && CONFIG_HIDKEY_REPORT_TEST
if (event_type == KEY_EVENT_CLICK && key_value == TCFG_ADKEY_VALUE0) {
uint8_t packet_buf[3] = {HIDKEY_REPORT_ID, CONSUMER_PLAY_PAUSE & 0x0ff, CONSUMER_PLAY_PAUSE >> 8}; //pp key
log_info("key_00");
#if CONFIG_HIDKEY_REPORT_TEST & BIT(0)
log_info(">>>>>>>usb test play/pause");
usb_hid_mouse_send_data(packet_buf, sizeof(packet_buf));
os_time_dly(1);
packet_buf[1] = 0;
usb_hid_mouse_send_data(packet_buf, sizeof(packet_buf));
#endif
}
if (event_type == KEY_EVENT_CLICK && key_value == TCFG_ADKEY_VALUE1) {
uint8_t packet_buf[3] = {HIDKEY_REPORT_ID, CONSUMER_MUTE & 0x0ff, CONSUMER_MUTE >> 8}; //pp key
log_info("key_01");
#if (CONFIG_HIDKEY_REPORT_TEST & BIT(0))
log_info(">>>>>>>>>>usb test mute");
usb_hid_mouse_send_data(packet_buf, sizeof(packet_buf));
os_time_dly(1);
packet_buf[1] = 0;
usb_hid_mouse_send_data(packet_buf, sizeof(packet_buf));
#endif
}
#endif
}
}
Source: app_dongle.c
按键语义汇总:
| 按键事件 | 键值 | 动作 | 说明 |
|---|---|---|---|
| 单击 | TCFG_ADKEY_VALUE0 | 发送 Consumer Play/Pause 报告(两次,第二次清零) | 仅 CONFIG_HIDKEY_REPORT_TEST 使能时用于 USB 通道测试 |
| 单击 | TCFG_ADKEY_VALUE1 | 发送 Consumer Mute 报告 | 同上,产测用 |
| 双击 | TCFG_ADKEY_VALUE0 | dg_central_clear_pair() | 清除 Central 配对信息,便于重新配对 |
| 长按 | TCFG_ADKEY_VALUE0 | user_hid_disconnect() | 仅 USER_SUPPORT_PROFILE_HID==1 时(HID Profile 模式) |
| 三击 | TCFG_ADKEY_VALUE0/3 | (被注释)软关机 | 低功耗策略当前由电源模块统一管理 |
其中"发送后清零"(os_time_dly(1) 后再发 packet_buf[1]=0)是 HID 键值上报的标准做法:按键是边沿触发的,必须上报按下+释放两个状态,PC 端才认为是"一次按键"而非"按住"。
低功耗与软关机
Dongle 是 USB 供电设备,低功耗策略主要体现在蓝牙链路管理和系统 LP 门控:
static void dongle_set_soft_poweroff(void)
{
log_info("set_soft_poweroff\n");
#if (TCFG_LOWPOWER_PATTERN == SOFT_MODE)
app_dongle_is_active = 1;
#endif
//必须先主动断开蓝牙链路,否则要等链路超时断开
#if (USER_SUPPORT_PROFILE_HID==1)
user_hid_exit();
#endif
btstack_ble_exit(0);
if (ble_comm_dev_is_connected(GATT_ROLE_SERVER) || ble_comm_dev_is_connected(GATT_ROLE_CLIENT)) {
#if (TCFG_LOWPOWER_PATTERN == SOFT_MODE)
//soft 方式非必须等链路断开
sys_timeout_add(NULL, app_power_set_soft_poweroff, WAIT_DISCONN_TIME_MS);
#elif (TCFG_LOWPOWER_PATTERN == SOFT_BY_POWER_MODE)
//must wait disconn
app_power_soft.wait_disconn = 1;
#endif
} else {
app_power_set_soft_poweroff(NULL);
}
}
Source: app_dongle.c
关键设计:软关机前必须先主动断开蓝牙链路(源码注释:"必须先主动断开蓝牙链路,否则要等链路超时断开")。根据电源模式不同,有两种等待策略:
SOFT_MODE:无需等待链路断开,用sys_timeout_add延迟WAIT_DISCONN_TIME_MS后执行软关机;SOFT_BY_POWER_MODE:必须等待链路断开,置app_power_soft.wait_disconn = 1由电源模块在断开事件后继续。
系统休眠门控通过 LP target 注册:
//-----------------------
//system check go sleep is ok
static uint8_t dongle_state_idle_query(void)
{
return !app_dongle_is_active;
}
REGISTER_LP_TARGET(dongle_state_lp_target) = {
.name = "dongle_state_deal",
.is_idle = dongle_state_idle_query,
};
Source: app_dongle.c
REGISTER_LP_TARGET 把 dongle_state_idle_query 注册为系统休眠判定条件之一:只有 Dongle 不活跃(app_dongle_is_active == 0)时才允许系统进入低功耗,保证适配器在链路活跃期间不会意外休眠。
配置选项
Dongle 应用的配置分散在应用头文件与板级配置文件中,以下为关键开关汇总:
应用层配置(app_dongle.h / app_dongle.c)
| 配置宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CONFIG_APP_DONGLE | bool | — | 编译开关,非 0 时编译整个 Dongle 应用(#if (CONFIG_APP_DONGLE) 包裹) |
CONFIG_HIDKEY_REPORT_TEST | int | 0 | USB 上报测试通道:bit0 通道1、bit1 通道2;非 0 时用 Consumer 多媒体描述符代替鼠标描述符 |
CFG_RF_24G_CODE_ID | u32 | 0 | 2.4G 配对码;0 表示纯 BLE 模式,非 0 使能 2.4G 私有协议(主从需相同) |
HIDKEY_REPORT_ID | u8 | 0x1 | Consumer 报告 ID |
KEYBOARD_REPORT_ID | u8 | 0x1 | 键盘报告 ID |
COUSTOM_CONTROL_REPORT_ID | u8 | 0x2 | 自定义控制报告 ID |
MOUSE_POINT_REPORT_ID | u8 | 0x3 | 鼠标点报告 ID |
MOUSE_REPORT_ID | u8 | 0x01 | 鼠标报告 ID(5 键 + 滚轮 + X/Y) |
CONSUMER_* | u16 | 0x0001~0x0080 | 多媒体键值:音量+/−、播放暂停、静音、上一曲/下一曲、快进快退 |
USER_SUPPORT_PROFILE_HID / USER_SUPPORT_PROFILE_SPP | bool | — | 蓝牙 Profile 选择;两者不能同时打开,否则编译报错(#error " not support double profile!!!!!!") |
CONFIG_BT_GATT_CLIENT_NUM | int | — | GATT Client 数量,==2 时使能第二 USB 通道 |
CONFIG_BLE_CONNECT_SLOT | bool | — | 低延时连接时隙,开启时系统时钟强制 160M/80M |
TCFG_PC_ENABLE | bool | — | 是否上报 PC(决定 USB HID 转发是否生效) |
TCFG_OTG_USB_DEV_EN | bool | — | 使能 OTG 设备模式(替代直接 usb_start()) |
RCSP_BTMATE_EN | bool | — | 使能 RCSP BTMate PC 工具联调(OTA 钩子、在线列表上报) |
TCFG_LOWPOWER_PATTERN | enum | — | 低功耗模式:SOFT_MODE(延时软关机)/ SOFT_BY_POWER_MODE(等链路断开) |
板级配置(board_aw318n_dongle_cfg.h)
| 配置宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_UART0_ENABLE | bool | 1 | 串口打印使能 |
TCFG_UART0_TX_PORT | IO | IO_PORTA_03 | 串口打印 TX 脚 |
TCFG_UART0_BAUDRATE | u32 | 1000000 | 打印波特率(1M) |
TCFG_COMMON_UART_ENABLE | bool | 0 | 通用数据转串口模块(透传) |
KEY_AD_EN | bool | 1 | AD 按键使能 |
AD_KEY_IO | IO | IO_PORTA_08 | AD 按键采样 IO |
EXTERN_R_UP | u32 | 100 | 外挂上拉电阻(KΩ);0 使用内部 10K 上拉 |
ADC10_33 | u32 | 0x3ff | 10-bit ADC 满量程 |
TCFG_ADKEY_VALUE0~9 | u8 | 0~9 | AD 按键分压判定的键值表(阈值按电阻分压中点计算) |
TCFG_ADC_VBAT_CH_EN / TCFG_ADC_VTEMP_CH_EN | bool | 1 | 电池电压/温度 ADC 通道 |
KEY_IO_EN / KEY_MATRIX_EN / TCFG_IR_ENABLE | bool | 0 | IO/矩阵/红外按键(Dongle 板默认关闭) |
TCFG_POWER_ON_NEED_KEY | bool | 0 | 是否需要长按开机 |
AD 按键阈值的计算逻辑体现了"电阻分压 + 中点判定"的经典做法:ADKEY1_x = (ADC10_x + ADC10_x+1)/2,其中各档 ADC 值由 ADC10_33 * R/(R+R_UP) 推算,保证相邻按键的判定区间不重叠。
API 参考
以下为 Dongle 应用对外暴露的关键接口(均为 app_dongle.c 中定义,供 BLE Central / USB 模块调用):
int dongle_ble_hid_input_handler(uint8_t *packet, uint16_t size)
BLE 外设 HID 数据入口(由 ble_dg_central 回调)。
- 参数:
packet— BLE 收到的 HID 报告缓冲区;size— 报告长度。 - 返回:
usb_hid_mouse_send_data的返回值(成功/失败)。 - 行为:
TCFG_PC_ENABLE时直接转发到 USB HID 端点;否则仅打印 "chl1 disable!!!" 并返回 0。
int dongle_second_ble_hid_input_handler(uint8_t *packet, uint16_t size)
第二个 BLE 外设的数据入口。
- 条件:
TCFG_PC_ENABLE && CONFIG_BT_GATT_CLIENT_NUM == 2时生效;当前内部转发调用被注释,返回 0(预留)。 - 行为:未使能时打印 "chl2 disable!!!" 并返回 0。
static void dongle_set_soft_poweroff(void)
软关机入口:主动退出 HID Profile(如使能)、btstack_ble_exit(0),根据链路连接状态与低功耗模式决定立即关机或等待断开。
static void dongle_bt_start(void)
蓝牙初始化:bt_pll_para 配置 PLL → btstack_ble_start_before_init → 可选 rf_set_24g_hackable_coded → btstack_init。
static void dongle_usb_start(void)
USB 初始化:注册 HID 报告描述符 → OTG 或普通 USB 启动 → 注册 USB 状态回调 → 可选 OTA 钩子与在线列表定时上报。
int dongle_bt_hci_event_handler(struct bt_event *bt) / int dongle_bt_connction_status_event_handler(struct bt_event *bt)
HCI 状态 / 连接状态事件转发到 bt_comm_ble_*_event_handler,供公共蓝牙状态机处理。
失败模式、边界情况与并发
编译期约束
USER_SUPPORT_PROFILE_HID与USER_SUPPORT_PROFILE_SPP同时打开会触发#error " not support double profile!!!!!!"——HID 与 SPP 不能共存于同一固件,因为蓝牙协议栈同一时刻只维护一种 Profile 会话。
链路断开与软关机竞态
- 软关机时若蓝牙仍处于连接态,直接进入休眠会导致链路超时(数秒级)才被系统发现。
SOFT_MODE用WAIT_DISCONN_TIME_MS延时兜底,SOFT_BY_POWER_MODE用wait_disconn标志等待真正断开——后者更严谨但关机延迟取决于断链时间。 dongle_set_soft_poweroff当前在按键三击处被注释,说明产品化时软关机入口由电源/用户事件层统一触发,避免按键误触。
高回报率收数瓶颈
- 源码注释明确:1ms 高回报率(1000Hz)下禁止在收数路径上
putchar,否则影响收数。日志(log_info)本身也应裁剪,生产版本建议关闭LOG_DEBUG_ENABLE。
USB 未枚举 / 未使能
TCFG_PC_ENABLE为 0 时,HID 数据入口直接丢弃(打印 "chl1 disable!!!"),防止无 USB 端点时空转。- USB 枚举状态通过
dg_central_usb_status_handler回调通知 Central 模块,使蓝牙连接策略与 USB 在线状态联动(如 PC 休眠时停止扫描)。
事件模型与并发
- 所有事件(
SYS_KEY_EVENT/SYS_BT_EVENT/SYS_DEVICE_EVENT)在dongle_event_handler中单线程串行处理,无锁设计;蓝牙回调、USB 回调均不直接执行业务逻辑,而是通过事件/消息投递到主循环,避免多上下文数据竞争。 - 唯一的定时器回调(
sys_timeout_add的dongle_return_online_list)运行在定时器上下文,仅做在线列表上报,不触碰共享状态。
性能与运维考虑
- 时钟与延时:
CONFIG_BLE_CONNECT_SLOT强制 160M sys / 80M LSB,是 HID 低时延链路的必要配置;量产产品需评估功耗与延时的折中。 - 串口:UART0 打印默认 1M 波特率(
TCFG_UART0_BAUDRATE=1000000),TX 为IO_PORTA_03,接收脚NO_CONFIG_PORT(纯输出日志)。 - OTA 在线列表:上电 4 秒后主动向 PC 上报在线设备列表(
sys_timeout_add(..., 4000)),配合 BTMate 工具使用;若无需该功能可关闭RCSP_BTMATE_EN减少代码量。 - 产测:
CONFIG_HIDKEY_REPORT_TEST允许一键切换 USB 上报为多媒体键并触发 Play/Pause、Mute 测试报文,用于产线 USB 通道功能验证。
扩展点
- 第二设备通道:
CONFIG_BT_GATT_CLIENT_NUM == 2+ 取消注释usb_hid_set_second_repport_map/hid_send_second_data,即可启用双 BLE 外设同时上报。 - HID 描述符自定义:在
app_dongle.h中新增sHIDReportDesc_*描述符数组(如消费类、键盘复合、鼠标),通过usb_hid_mouse_set_report_map切换,可适配任意 HID 外设形态。 - 2.4G 私有协议:设置
CFG_RF_24G_CODE_ID非 0 即切换到 2.4G 模式,配对码主从一致即可建立链路(配对码可用access_addr_generate生成)。 - PC 命令扩展:
RCSP_BTMATE_EN下新增DEVICE_EVENT_FROM_PC/DEVICE_EVENT_FROM_OTG分支即可扩展自定义 PC 命令与透传通道。 - Profile 选择:
USER_SUPPORT_PROFILE_HID使能后 Dongle 还可作为 HID Device 主动连接(user_hid_disconnect/user_hid_exit),为"Dongle 主动连接单一外设"的场景预留。
相关链接
- app_dongle.c — Dongle 应用主实现
- app_dongle.h — HID 描述符与配置宏定义
- board_aw318n_dongle.c — Dongle 板级初始化
- board_aw318n_dongle_cfg.h — Dongle 板级引脚/外设配置
- board_aw318n_dongle_global_build_cfg.h — Dongle 全局构建开关
- 相关模块(不在本页展开):
ble_dg_central.h(BLE Central 连接管理)、ota_dg_central.h(Dongle OTA)、usb_suspend_resume.h(USB 挂起/恢复)
测试与验证
Dongle 示例目录(apps/demo/transfer/examples/dongle/)未包含独立的单元测试文件,其正确性验证主要依赖以下内建机制:
- 编译期约束:
#error " not support double profile!!!!!!"在 HID 与 SPP Profile 同时使能时直接中断编译,从源头杜绝不合法配置组合(见 app_dongle.c)。 - USB 通道产测模式:
CONFIG_HIDKEY_REPORT_TEST将 USB 上报切换为 Consumer 描述符,并通过单击 ADKEY0/ADKEY1 主动注入 Play/Pause、Mute 测试报文(按下+释放两次发送),无需 BLE 外设即可在 PC 端验证 USB HID 链路(见 app_dongle.c)。 - 实机联调:
RCSP_BTMATE_EN使能后可通过 BTMate PC 工具下发命令、接收在线设备列表与 OTA 数据,是整机功能验证的主要途径。 - SDK 补丁同步:仓库
patch_release/AW31N_开机&低功耗&VM兼容性修复说明_20250102/下存在同版本app_dongle.c/board_aw318n_dongle.c的副本,说明 Dongle 应用随 SDK 补丁(开机、低功耗、VM 兼容性修复)同步演进——升级 SDK 时需比对补丁目录与主分支的差异。
总结
Dongle 适配器应用以"BLE Central + USB HID Device"的双栈架构,在 AW31N 上实现了无线键鼠收发器这一典型场景:启动时并行初始化蓝牙与 USB,运行期通过单线程事件模型把 BLE 外设的 HID 报告零拷贝转发到 PC,同时支持 2.4G 私有协议、双设备通道、OTA 升级与低功耗管理。其设计上的关键取舍——转发不解析(低时延)、事件串行化(无锁安全)、描述符可切换(产测友好)、软关机先断链(休眠可靠)——使其成为一个结构清晰、易于裁剪和扩展的参考应用。开发者可按"配置选项"表中的开关逐项裁剪,或按"扩展点"接入自定义 HID 外设形态与 PC 命令。