3.4.4. APP - Bluetooth Dual-Mode Dongle

3.4.4.1. 概述

  • 蓝牙dongle符合USB和BLE传输标准,具有即插即用,方便实用的特点。它可用于蓝牙设备之间的数据传输,让电脑能够和周边的蓝牙设备进行无线连接和数据的通讯,自动发现和管理远程蓝牙设备、资源和服务,实现蓝牙设备之间的绑定和自动连接。

  • 蓝牙dongle支持BLE和2.4G两种连接模式;支持连接指定蓝牙名或mac地址;应用示例是连接杰理的鼠标。

image-20241120160749212

3.4.4.2. 工程配置

  • APP选择,配置app_config.h

apps/demo/transfer/include/app config.h

//apps example 选择,只能选1个,要配置对应的board_config.h
#define CONFIG_APP_LE_TRANS               0 /*!< LE's slave */
#define CONFIG_APP_MULTI                  0 /*!< AW33N暂仅支持主机, 待迭代. BLE多机应用,支持2.4g code,支持4主,3主1从,2主2从... */
#define CONFIG_APP_NONCONN_24G            0 /*!< 2.4G 非连接收发 */
#define CONFIG_APP_DONGLE                 1 /*!< usb + 蓝牙(ble 主机),PC hid设备, 使用需要配置板级CONFIG_BOARD_AW33N_DONGLE */
#define CONFIG_APP_AT_CHAR_COM            0 /*!< AT com 字符串格式命令 */
#define CONFIG_APP_CUSTOM_DB_UPDATE       0 /*!< 自定义双备份升级(串口传输方式), 可用于第三方升级协议接入 */
#define CONFIG_APP_FINDMY                 0 /*!< FINDMY, 使用需要配置板级CONFIG_BOARD_AW33N_FMY */
  • 板级配置board_config.h

apps/demo/transfer/board/bd57/board_config.h

// #define CONFIG_BOARD_AW33N_DEMO
#define CONFIG_BOARD_AW33N_DONGLE
// #define CONFIG_BOARD_AW33N_FMY
// #define CONFIG_BOARD_AW33N_FASTBOOT
// #define CONFIG_BOARD_AW33N_FHN
// #define CONFIG_BOARD_AW33N_FMY_FHN
  • 配置BLE或者2.4G模式,若选择2.4G配对码必须跟对方的配对码一致,改成0则是普通的ble通用dongle

apps/demo/transfer/examples/dongle/app_dongle.h

//2.4G模式: 0---ble, 非0---2.4G配对码,2.4G配对码主从需要相同
// #define CFG_RF_24G_CODE_ID       (0) //32bits
#define CFG_RF_24G_CODE_ID       (0xAF9A9357) //32bits,可用void access_addr_generate(u8 *aa);生成
  • 配置高回报率模式,用于2.4g模式鼠标1k回报率,如果需要做1k回报率无线鼠标方案需要打开,目前只支持1ms连接

apps/demo/transfer/include/app config.h

#elif CONFIG_APP_DONGLE  /* CONFIG_APP_DONGLE */
#define CONFIG_BLE_CONNECT_SLOT             1 /*!< BLE高回报率设置, 支持私有协议,所有周期单位为us,适配1k回报率鼠标需要开此宏 */

#if CONFIG_BLE_CONNECT_SLOT
#define CONFIG_CCE_PER_CHECK_ENABLE         1   /*!< AFH使能*/
#define HIGH_REPORT_RATE_1MS                1   /*!< 1ms,support dual conn */
#define HIGH_REPORT_RATE_2SLOT              0   /*!< 2slot,1.25ms,support dual conn */
#define HIGH_REPORT_RATE_4SLOT              0   /*!< 4slot,2.5ms,support dual conn */
#define HIGH_REPORT_RATE_500US              0   /*!< 500us,not support dual conn */
#endif /* CONFIG_BLE_CONNECT_SLOT */
  • 配置搜索匹配连接设备

apps/demo/transfer/examples/dongle/ble_dg_central.c

//------!!!!注意:如果是OTA升级的话,这里名字修改还需要去ota_dg_central.c修改dg_ana_remoter_name1/2
static const uint8_t dg_central_test_remoter_name1[] = "AW33N(BLE)";//3模鼠标
/* static const uint8_t dg_central_test_remoter_name2[] = "AC897N_MX(BLE)";//鼠标 */
static const uint8_t dg_central_test_remoter_name2[] = "LOW_LATCY(BLE)";//低延时从机
static const client_match_cfg_t dg_central_match_device_table[] = {
    {
        .create_conn_mode = BIT(CLI_CREAT_BY_NAME),
        .compare_data_len = sizeof(dg_central_test_remoter_name1) - 1, /*!< 去结束符 */
        .compare_data = dg_central_test_remoter_name1,
        .filter_pdu_bitmap = 0,
    },
    {
        .create_conn_mode = BIT(CLI_CREAT_BY_NAME),
        .compare_data_len = sizeof(dg_central_test_remoter_name2) - 1, /*!< 去结束符 */
        .compare_data = dg_central_test_remoter_name2,
        .filter_pdu_bitmap = 0,
    }
};
  • 默认上电3秒可配对新设备,超时之后只回连已配对设备。

apps/demo/transfer/examples/dongle/ble_dg_central.h

//dongle 上电开配对管理,设置上电允许按名字配对新设备的窗口,之后切换为只回连已配对设备
#define POWER_ON_RECONNECT_START   (0)   /*!< 上电先回连 */
#define POWER_ON_SWITCH_TIME       (3000)/*!< unit ms,上电允许按名字配对新设备的窗口时间, 之后切换为只回连已配对设备 */
#define MATCH_DEVICE_RSSI_LEVEL    (-50)  /*!< RSSI 阈值 */

3.4.4.3. 主要代码说明

  • 蓝牙dongle实现文件 app_dongle.c和ble_dg_central.c,负责模块初始化、处理协议栈事件和命令数据控制发送等。

  • HID描述符, 描述为一个鼠标,report id1是鼠标的移动和按键控制报表,适配HID鼠标应用

//==========鼠标
static const u8 sHIDReportDesc_mouse[] = {
    0x05, 0x01,                    // Usage Page (Generic Desktop Ctrls) - 定义了后续字段的用途范畴,这里是通用桌面控制。
    0x09, 0x02,                    // Usage (Mouse) - 指定当前设备为鼠标。
    0xA1, 0x01,                    // Collection (Application) - 开始应用层集合,标志着一个应用集合的开始。
    0x85, MOUSE_REPORT_ID,         //   Report ID (1) - 报告ID为1。
    0x09, 0x01,                    //   Usage (Pointer) - 指明是一个指针设备(在这里,是个鼠标)。
    0xA1, 0x00,                    //   Collection (Physical) - 开始物理集合,表示这是个物理设备。
    // Buttons (5 buttons)
    0x95, 0x05,                    //     Report Count (5) - 报告包含5项数据。
    0x75, 0x01,                    //     Report Size (1) - 每项数据大小为1位。
    0x05, 0x09,                    //     Usage Page (Button) - 用途页面为按钮。
    0x19, 0x01,                    //     Usage Minimum (0x01) - 按钮的最小值为1(第一个按钮)。
    0x29, 0x05,                    //     Usage Maximum (0x05) - 按钮的最大值为5(第五个按钮)。
    0x15, 0x00,                    //     Logical Minimum (0) - 数据的逻辑最小值为0。
    0x25, 0x01,                    //     Logical Maximum (1) - 数据的逻辑最大值为1。
    0x81, 0x02,                    //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - 输入类型,代表了按钮的状态。
    // Padding for the buttons - 3 bits
    0x95, 0x01,                    //     Report Count (1) - 一项填充数据。
    0x75, 0x03,                    //     Report Size (3) - 填充大小为3位,用于对齐字节。
    0x81, 0x01,                    //     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - 输入类型为常量,用于占位不使用。
    // Wheel (8 bits)
    0x75, 0x08,                    //     Report Size (8) - 滚轮数据的大小为8位。
    0x95, 0x01,                    //     Report Count (1) - 一项数据。
    0x05, 0x01,                    //     Usage Page (Generic Desktop Ctrls) - 同上。
    0x09, 0x38,                    //     Usage (Wheel) - 滚轮。
    0x15, 0x81,                    //     Logical Minimum (-127) - 滚轮数据的逻辑最小值为-127。
    0x25, 0x7F,                    //     Logical Maximum (127) - 滚轮数据的逻辑最大值为127。
    0x81, 0x06,                    //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - 输入类型,代表了滚轮的滚动。
    // X and Y Axis (12 bits each)
#if (TCFG_OPTICAL_SENSOR_DATA_BITS_SELECT == OPTICAL_SENSOR_DATA_16BITS)
    0x75, 0x10,                    //     Report Size (16) - 每项数据的大小为16位。
#else /* (TCFG_OPTICAL_SENSOR_DATA_BITS_SELECT == OPTICAL_SENSOR_DATA_16BITS) */
    0x75, 0x0C,                    //     Report Size (12) - 每项数据的大小为12位。
#endif /* TCFG_OPTICAL_SENSOR_DATA_BITS_SELECT == OPTICAL_SENSOR_DATA_16BITS */
    0x95, 0x02,                    //     Report Count (16) - 两项数据(X轴和Y轴)。
    0x05, 0x01,                    //     Usage Page (Generic Desktop Ctrls) - 同上,指定用途页面为通用桌面控制。
    0x09, 0x30,                    //     Usage (X) - X轴。
    0x09, 0x31,                    //     Usage (Y) - Y轴。
#if (TCFG_OPTICAL_SENSOR_DATA_BITS_SELECT == OPTICAL_SENSOR_DATA_16BITS)
    0x16, 0x00, 0x80,              //     Logical Minimum (-32768) - X和Y轴数据的逻辑最小值为-32768。
    0x26, 0xFF, 0x7F,              //     Logical Maximum (32767) - X和Y轴数据的逻辑最大值为32767。
#else /* (TCFG_OPTICAL_SENSOR_DATA_BITS_SELECT == OPTICAL_SENSOR_DATA_16BITS) */
    0x16, 0x01, 0xF8,              //     Logical Minimum (-2048) - X和Y轴数据的逻辑最小值为-2048。
    0x26, 0xFF, 0x07,              //     Logical Maximum (2047) - X和Y轴数据的逻辑最大值为2047。
#endif /* TCFG_OPTICAL_SENSOR_DATA_BITS_SELECT == OPTICAL_SENSOR_DATA_16BITS */
    0x81, 0x06,                    //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - 输入类型,代表了轴的移动。
    0xC0,                          /*!< End Collection - 结束当前集合 */
    0xC0,                           /*!< End Collection - 结束最外层的集合 */
};
  • 使用指定的uuid与从机通信, 需要与从机配合, 省掉了搜索uuid的时间

//---------------------------------------------------------------------------
//指定搜索uuid
static const target_uuid_t  dg_central_search_uuid_table[] = {
    {
        .services_uuid16 = 0x1800,
        .characteristic_uuid16 = 0x2a00,
        .opt_type = ATT_PROPERTY_READ,
    },

    {
        .services_uuid16 = 0x180a,
        .characteristic_uuid16 = 0x2a50,
        .opt_type = ATT_PROPERTY_READ,
    },

    /* { */
    /* .services_uuid16 = 0x1812, */
    /* .characteristic_uuid16 = 0x2a4b, */
    /* .opt_type = ATT_PROPERTY_READ, */
    /* }, */

    {
        .services_uuid16 = 0x1812,
        .characteristic_uuid16 = 0x2a4b,
        .opt_type = ATT_PROPERTY_READ,
        .read_long_enable = 1,
    },

    {
        .services_uuid16 = 0x1812,
        .characteristic_uuid16 = 0x2a4d,
        .opt_type = ATT_PROPERTY_NOTIFY,
        .read_report_reference = 1,
    },

#if RCSP_BTMATE_EN //确保远端设备是否支持ota
    {
        .services_uuid16 = 0xae00,
        .characteristic_uuid16 = 0xae01,
        .opt_type = ATT_PROPERTY_WRITE_WITHOUT_RESPONSE,
    },

    {
        .services_uuid16 = 0xae00,
        .characteristic_uuid16 = 0xae02,
        .opt_type = ATT_PROPERTY_NOTIFY,
    },
#endif

    /* { */
    /* .services_uuid16 = 0x1812, */
    /* .characteristic_uuid16 = 0x2a33, */
    /* .opt_type = ATT_PROPERTY_NOTIFY, */
    /* }, */

    /* { */
    /* .services_uuid16 = 0x1801, */
    /* .characteristic_uuid16 = 0x2a05, */
    /* .opt_type = ATT_PROPERTY_INDICATE, */
    /* }, */

};
  • dongle睡眠行为,电脑休眠dongle进入suspend断开所有连接,仅保留scan

/*************************************************************************************************/
/*!
 *  \brief      USB状态变更回调
 *  \param      [in] NUll
 *
 *  \return
 *
 *  \note
 */
/*************************************************************************************************/
void dg_central_usb_status_handler(const usb_dev usb_id, usb_slave_status status)
{
    log_info("%s[status:%d]", __func__, status);
    /* PC进入睡眠模式,usb suspend,断开所有连接,只保留scan */
    if (status == USB_SLAVE_SUSPEND && dg_central_wait_usb_wakeup) {
        dg_central_disconnect_all();
    }
}
  • 高回报率回调接口,用CONFIG_BLE_CONNECT_SLOT宏打开


/*************************************************************************************************/
/*!
 *  \brief      1. 基带接口接收数据接口
                2. 底层有事件触发后回调直接来上层拿数据发送,函数名不可修改,无需调用;
                3. 不可做耗时操作(需要在us级);
                4. 数据接收不会再走dg_central_event_packet_handler-GATT_COMM_EVENT_GATT_DATA_REPORT
 *
 *  \param      [in] NUll
 *
 *  \return
 *
 *  \note       1. 中断回调及所加函数中不能添加打印, 会影响cpu执行效率.
                   ps: 如加调试打印, 结束后请注释.
 */
/*************************************************************************************************/
#define MOUSE_1MS_CLEAR_WDT_CNT_MAX            2000   /*!< 鼠标1ms模式清看门狗计数上限 */
static uint32_t mouse_reset_cnt;
bool bb_le_rx_data_pdu_hook_get(void *priv, const u8 *data, u8 len)
{
    int ret = 0;
    uint16_t conn_handle = ll_vendor_get_link_handle(priv);
    if (!conn_handle) {
        log_info("conn_handle error , conn_handle = %d", conn_handle);
        return true;
    }
    mouse_reset_cnt++;
    if (mouse_reset_cnt > MOUSE_1MS_CLEAR_WDT_CNT_MAX) {
        wdt_clear();
        mouse_reset_cnt = 0;
        // log_info("len = %d",len);
        // put_buf((u8 *)data, len);
        // log_info("conn handle = %x",conn_handle);
    }

    uint8_t usb_status_ret = usb_slave_status_get();
    // log_info("conn usb_status_ret = %x",usb_status_ret);
    // log_info("dg_central_wait_usb_wakeup handle = %x",dg_central_wait_usb_wakeup);
    if (usb_status_ret == USB_SLAVE_RESUME) {
        ret = len;
        if (dg_central_wait_usb_wakeup) {
            wdt_clear();

            /*根据conn handle对应的appearance区分设备类型, 走不同的USB发数接口:
             * 键盘数据走键盘接口, 鼠标数据走鼠标接口*/
            uint16_t appearance = dg_central_get_conn_appearance(conn_handle);
            // log_info("appearance = %d",appearance);
            if (BLE_APPEARANCE_HID_KEYBOARD == appearance) {
                /*键盘设备: 走键盘USB发数接口*/
                if (len < KEYBOARD_USB_PACKET_LEN - 1) {
                    /*短包不拷贝, 避免越界读*/
                    log_info("recevie data len error");
                    return true;
                }
                uint8_t hid_keyboard_send_packet[KEYBOARD_USB_PACKET_LEN];
                hid_keyboard_send_packet[0] = HIDKEY_REPORT_ID;
                memcpy(&hid_keyboard_send_packet[1], data, KEYBOARD_USB_PACKET_LEN - 1);
                ret = dongle_second_ble_hid_input_handler(hid_keyboard_send_packet, KEYBOARD_USB_PACKET_LEN);
            } else {
                /*鼠标/其它设备: 走鼠标USB发数接口*/
                if (len < MOUSE_USB_PACKET_LEN - 1) {
                    /*短包不拷贝, 避免越界读*/
                    log_info("recevie data len error");
                    return true;
                }
                uint8_t hid_mouse_send_packet[MOUSE_USB_PACKET_LEN];
                hid_mouse_send_packet[0] = MOUSE_REPORT_ID;
                memcpy(&hid_mouse_send_packet[1], data, MOUSE_USB_PACKET_LEN - 1);
                ret = dongle_ble_hid_input_handler(hid_mouse_send_packet, MOUSE_USB_PACKET_LEN);
            }
            /* put_buf(hid_send_packet,MOUSE_USB_PACKET_LEN); */
        }
    } else if (usb_status_ret == USB_SLAVE_SUSPEND) {
        dg_central_wait_usb_wakeup = 0;
        // log_info("send remote_wakeup\n");
        usb_remote_wakeup(0);
        return true;
    }

    if (ret && dg_central_wait_usb_wakeup == 0) {
        dg_central_wait_usb_wakeup = 1;
    }
    return true;
}
  • ble->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
}

3.4.4.4. case默认应用资源(默认使用AW33N DEMO开发板)

Note

demo默认占用外设资源如下,具体可以查看apps/demo/transfer/board/bd57/board_aw33n_dongle_cfg.h

  • I/O口占用情况

I/O

默认占用情况

PA4

默认打印UART0引脚

PA1

adkey检测引脚

  • 硬件定时器占用情况

定时器

默认占用情况

TIMER0

暂无使用

TIMER1

暂无使用

TIMER2

暂无使用

TIMER3

暂无使用

  • 串口占用情况

串口

默认占用情况

UART0

SDK打印

UART1

暂无使用

UART2

暂无使用

  • USB占用情况

USB

默认占用情况

USB0

默认使用

3.4.4.5. case默认应用功能现象

操作

现象结果

支持进入睡眠模式,usb suspend,蓝牙断开连接,只保留scan

行为正常

能够成功绑定连接鼠标,实现电脑控制

行为正常

usb 唤醒pc 兼容性(win7/win8/win10/win11)

行为正常