杰理 SDK 文档中心
首页
首页
  • 概述与快速入门

    • 芯片平台与 SDK 概述
    • 环境搭建与编译工具链
    • 快速开始:选型、编译与烧录
    • 烧录与量产工具
  • 构建系统与板级工程

    • 顶层 Makefile 与编译目标
    • 板级工程与配置
    • 后处理与配置工具
  • HID 人机交互应用

    • HID 应用架构总览
    • 键盘、翻页器与遥控应用
    • 鼠标应用:单模、双模与低延迟
    • 空闲应用与初始化流程
  • BLE 透传与数传应用

    • 透传应用总览
    • 多连接与无连接传输
    • AT 命令模组应用
    • Dongle 适配器应用
  • BSP 公共模块

    • 蓝牙公共处理
    • 按键、LED 与红外
    • 传感器与编码器
    • 存储、VM 与文件系统
    • 电源管理与低功耗
    • 消息调度与通信外设
  • 协议栈与预编译库

    • 蓝牙协议栈库
    • 设备驱动与文件系统库
    • 音频、升级与其他库
  • 开发资料与补丁发布

    • 文档资料中心
    • 版本补丁与兼容性修复

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_hidkey vs 鼠标 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_BTMATE PC 工具)。

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_VALUE0dg_central_clear_pair()清除 Central 配对信息,便于重新配对
长按TCFG_ADKEY_VALUE0user_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_DONGLEbool—编译开关,非 0 时编译整个 Dongle 应用(#if (CONFIG_APP_DONGLE) 包裹)
CONFIG_HIDKEY_REPORT_TESTint0USB 上报测试通道:bit0 通道1、bit1 通道2;非 0 时用 Consumer 多媒体描述符代替鼠标描述符
CFG_RF_24G_CODE_IDu3202.4G 配对码;0 表示纯 BLE 模式,非 0 使能 2.4G 私有协议(主从需相同)
HIDKEY_REPORT_IDu80x1Consumer 报告 ID
KEYBOARD_REPORT_IDu80x1键盘报告 ID
COUSTOM_CONTROL_REPORT_IDu80x2自定义控制报告 ID
MOUSE_POINT_REPORT_IDu80x3鼠标点报告 ID
MOUSE_REPORT_IDu80x01鼠标报告 ID(5 键 + 滚轮 + X/Y)
CONSUMER_*u160x0001~0x0080多媒体键值:音量+/−、播放暂停、静音、上一曲/下一曲、快进快退
USER_SUPPORT_PROFILE_HID / USER_SUPPORT_PROFILE_SPPbool—蓝牙 Profile 选择;两者不能同时打开,否则编译报错(#error " not support double profile!!!!!!")
CONFIG_BT_GATT_CLIENT_NUMint—GATT Client 数量,==2 时使能第二 USB 通道
CONFIG_BLE_CONNECT_SLOTbool—低延时连接时隙,开启时系统时钟强制 160M/80M
TCFG_PC_ENABLEbool—是否上报 PC(决定 USB HID 转发是否生效)
TCFG_OTG_USB_DEV_ENbool—使能 OTG 设备模式(替代直接 usb_start())
RCSP_BTMATE_ENbool—使能 RCSP BTMate PC 工具联调(OTA 钩子、在线列表上报)
TCFG_LOWPOWER_PATTERNenum—低功耗模式:SOFT_MODE(延时软关机)/ SOFT_BY_POWER_MODE(等链路断开)

来源:app_dongle.h、app_dongle.c

板级配置(board_aw318n_dongle_cfg.h)

配置宏类型默认值说明
TCFG_UART0_ENABLEbool1串口打印使能
TCFG_UART0_TX_PORTIOIO_PORTA_03串口打印 TX 脚
TCFG_UART0_BAUDRATEu321000000打印波特率(1M)
TCFG_COMMON_UART_ENABLEbool0通用数据转串口模块(透传)
KEY_AD_ENbool1AD 按键使能
AD_KEY_IOIOIO_PORTA_08AD 按键采样 IO
EXTERN_R_UPu32100外挂上拉电阻(KΩ);0 使用内部 10K 上拉
ADC10_33u320x3ff10-bit ADC 满量程
TCFG_ADKEY_VALUE0~9u80~9AD 按键分压判定的键值表(阈值按电阻分压中点计算)
TCFG_ADC_VBAT_CH_EN / TCFG_ADC_VTEMP_CH_ENbool1电池电压/温度 ADC 通道
KEY_IO_EN / KEY_MATRIX_EN / TCFG_IR_ENABLEbool0IO/矩阵/红外按键(Dongle 板默认关闭)
TCFG_POWER_ON_NEED_KEYbool0是否需要长按开机

来源:board_aw318n_dongle_cfg.h

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 通道功能验证。

扩展点

  1. 第二设备通道:CONFIG_BT_GATT_CLIENT_NUM == 2 + 取消注释 usb_hid_set_second_repport_map / hid_send_second_data,即可启用双 BLE 外设同时上报。
  2. HID 描述符自定义:在 app_dongle.h 中新增 sHIDReportDesc_* 描述符数组(如消费类、键盘复合、鼠标),通过 usb_hid_mouse_set_report_map 切换,可适配任意 HID 外设形态。
  3. 2.4G 私有协议:设置 CFG_RF_24G_CODE_ID 非 0 即切换到 2.4G 模式,配对码主从一致即可建立链路(配对码可用 access_addr_generate 生成)。
  4. PC 命令扩展:RCSP_BTMATE_EN 下新增 DEVICE_EVENT_FROM_PC / DEVICE_EVENT_FROM_OTG 分支即可扩展自定义 PC 命令与透传通道。
  5. 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/)未包含独立的单元测试文件,其正确性验证主要依赖以下内建机制:

  1. 编译期约束:#error " not support double profile!!!!!!" 在 HID 与 SPP Profile 同时使能时直接中断编译,从源头杜绝不合法配置组合(见 app_dongle.c)。
  2. USB 通道产测模式:CONFIG_HIDKEY_REPORT_TEST 将 USB 上报切换为 Consumer 描述符,并通过单击 ADKEY0/ADKEY1 主动注入 Play/Pause、Mute 测试报文(按下+释放两次发送),无需 BLE 外设即可在 PC 端验证 USB HID 链路(见 app_dongle.c)。
  3. 实机联调:RCSP_BTMATE_EN 使能后可通过 BTMate PC 工具下发命令、接收在线设备列表与 OTA 数据,是整机功能验证的主要途径。
  4. 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 命令。

Prev
AT 命令模组应用