3.4.5. APP - Bluetooth Dual-Mode Dongle

3.4.5.1. 概述

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

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

image-20241120160749212

3.4.5.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 //蓝牙LE多连 支持2.4g code
#define CONFIG_APP_NONCONN_24G            0 //2.4G 非连接收发
#define CONFIG_APP_DONGLE                 1 //usb + 蓝牙(ble 主机),PC hid设备, 使用需要配置板级board_aw31n_dongle.h
#define CONFIG_APP_AT_CHAR_COM            0 //AT com 字符串格式命令
#define CONFIG_APP_IDLE                   0 // 空闲任务
  • 板级配置board_config.h

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

#ifndef BOARD_CONFIG_H
#define BOARD_CONFIG_H

// #define CONFIG_BOARD_AW31N_DEMO
#define CONFIG_BOARD_AW318N_DONGLE

#include "board_aw31n_demo_cfg.h"
#include "board_aw318n_dongle_cfg.h"
  • 配置BLE或者2.4G模式,若选择2.4G配对码必须跟对方的配对码一致,如果需要做1k回报率无线鼠标方案则需要打开,改成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回报率无线鼠标方案需要打开

apps/demo/transfer/include/app_config.h

#define CONFIG_BLE_CONNECT_SLOT            1 //BLE高回报率设置, 支持私有协议
  • 配置搜索匹配连接设备

static const uint8_t dg_central_test_remoter_name1[] = "Lenovo Go Multi-Device Mouse";//键盘
/* static const uint8_t dg_central_test_remoter_name2[] = "AC897N_MX(BLE)";//鼠标 */
static const uint8_t dg_central_test_remoter_name2[] = "AW31N_MOUSE(BLE)";//鼠标
  • 默认上电3.5秒根据信号强度rssi配对近距离的设备,若配对失败,停止搜索。回连已有的配对设备。

//dongle 上电开配对管理,若配对失败,没有配对设备,停止搜索
#define POWER_ON_RECONNECT_START   (1)   // 上电先回连
#define POWER_ON_SWITCH_TIME       (3500)//unit ms,切换搜索回连周期
#define MATCH_DEVICE_RSSI_LEVEL    (-50)  //RSSI 阈值

3.4.5.3. 主要代码说明

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

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

//==========鼠标
static const u8 sHIDReportDesc_mouse[] = {
    0x05, 0x01,                    // Usage Page (Generic Desktop Ctrls) - 定义了后续字段的用途范畴,这里是通用桌面控制。
    0x09, 0x02,                    // Usage (Mouse) - 指定当前设备为鼠标。
    0xA1, 0x01,                    // Collection (Application) - 开始应用层集合,标志着一个应用集合的开始。
    0x85, 0x01,                    //   Report ID (1) - 报告ID为1。
    0x09, 0x01,                    //   Usage (Pointer) - 指明是一个指针设备(在这里,是个鼠标)。
    0xA1, 0x00,                    //   Collection (Physical) - 开始物理集合,表示这是个物理设备。
    // Buttons (5 buttons)
    0x95, 0x05,                    //     Report Count (5) - 报告包含5项数据。
    0x75, MOUSE_REPORT_ID,                    //     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)
    0x75, 0x0C,                    //     Report Size (12) - 每项数据的大小为12位。
    0x95, 0x02,                    //     Report Count (2) - 两项数据(X轴和Y轴)。
    0x05, 0x01,                    //     Usage Page (Generic Desktop Ctrls) - 同上,指定用途页面为通用桌面控制。
    0x09, 0x30,                    //     Usage (X) - X轴。
    0x09, 0x31,                    //     Usage (Y) - Y轴。
    0x16, 0x01, 0xF8,              //     Logical Minimum (-2047) - X和Y轴数据的逻辑最小值为-2047。
    0x26, 0xFF, 0x07,              //     Logical Maximum (2047) - X和Y轴数据的逻辑最大值为2047。
    0x81, 0x06,                    //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - 输入类型,代表了轴的移动。
    0xC0,                          //   End Collection - 结束当前集合。
    0xC0,                           // End Collection - 结束最外层的集合。

    // Volume and media control report
    0x05, 0x0C, // Usage Page (Consumer Devices)
    0x09, 0x01, // Usage (Consumer Control)
    0xA1, 0x01, // Collection (Application)
    0x85, 0x02, // Report ID (2)
    0x09, 0xE9, // Usage (Volume Up)
    0x09, 0xEA, // Usage (Volume Down)
    0x09, 0xCD, // Usage (Play/Pause)
    0x09, 0xE2, // Usage (Mute)
    0x09, 0xB6, // Usage (Scan Next Track)
    0x09, 0xB5, // Usage (Scan Previous Track)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x01, // Logical Maximum (1)
    0x75, 0x01, // Report Size (1)
    0x95, 0x06, // Report Count (6)
    0x81, 0x02, // Input (Data,Var,Abs)
    // Padding for alignment
    0x95, 0x01, // Report Count (1)
    0x75, 0x02, // Report Size (2)
    0x81, 0x01, // Input (Const,Array,Abs)
    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)
{
    /* PC进入睡眠模式,usb suspend,断开所有连接,只保留scan */
    if (status == USB_SLAVE_SUSPEND && dg_central_wait_usb_wakeup) {
        dg_central_disconnect_all();
    }
}
  • 高回报率回调接口,用CONFIG_BLE_CONNECT_SLOT宏打开

#if 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
 */
/*************************************************************************************************/
bool ll_conn_rx_acl_hook_get(const uint8_t *const data, size_t len)
{
    uint8_t hid_send_packet[MOUSE_USB_PACKET_LEN];
    //demo code
    uint16_t conn_handle = little_endian_read_16(data, 0) & 0x0fff;
    uint16_t l2cap_chl_id = little_endian_read_16(data, 6);
    //check pdu_start,conn_handle,channel_id
    if ((data[1] & 0x20) && (conn_handle >= 0x50 && conn_handle < (0x50 + CONFIG_BT_GATT_CLIENT_NUM)) && l2cap_chl_id == 4) {
        uint16_t att_handle = little_endian_read_16(data, 9);
        if (att_handle != 0x27) {
            /* putchar('e'); */
            return false;
        }
        // mouse report id
        hid_send_packet[0] = MOUSE_REPORT_ID;
        // mouse report start data
        const uint8_t *priv = &data[11];

        uint8_t usb_status_ret = usb_slave_status_get();
        if (usb_status_ret == USB_SLAVE_RESUME) {
            wdt_clear();
            if (dg_central_wait_usb_wakeup) {
                memcpy(&hid_send_packet[1], priv, MOUSE_USB_PACKET_LEN - 1);
                /* log_info_hexdump(hid_send_packet, MOUSE_USB_PACKET_LEN); */
            } else {
                log_info("clear length: %d", MOUSE_USB_PACKET_LEN - 1);
                memset(&hid_send_packet[1], 0, MOUSE_USB_PACKET_LEN - 1); //发空包
            }
        } 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;
        } else {
            ;
        }

        int ret = dongle_ble_hid_input_handler(hid_send_packet, MOUSE_USB_PACKET_LEN);

        if (ret && dg_central_wait_usb_wakeup == 0) {
            dg_central_wait_usb_wakeup = 1;
            log_info("send 0packet success!\n");
        }

        return true;
    }
    //putchar('E');
    return false;
}

/*************************************************************************************************/
/*!
 *  \brief      1. 用于做数据发送(适用于500us,1ms双连接流程),不开sm优先级高于ll_conn_rx_acl_hook_get,
                   return false则走ll_conn_rx_acl_hook_get流程
                2. 底层有事件触发后回调直接来上层拿数据发送,函数名不可修改,无需调用;
                3. 不可做耗时操作(需要在us级);
 *
 *  \param
 *
 *  \return
 *
 *  \note
 */
/*************************************************************************************************/
bool bb_le_rx_data_pdu_hook_get(void *priv, const u8 *data, u8 len)
{
    if (config_le_sm_support_enable) {
        return false;
    }

    uint16_t conn_handle = ll_vendor_get_link_handle(priv);
    if (!conn_handle) {
        return false;
    }

    uint8_t hid_send_packet[MOUSE_USB_PACKET_LEN];
    uint16_t l2cap_chl_id = little_endian_read_16(data, 2);
    //check conn_handle,channel_id
    if ((conn_handle >= 0x50 && conn_handle < (0x50 + CONFIG_BT_GATT_CLIENT_NUM)) && l2cap_chl_id == 4) {
        uint16_t att_handle = little_endian_read_16(data, 5);
        if (att_handle != 0x27) {
            return false;
        }

        // mouse report id
        hid_send_packet[0] = MOUSE_REPORT_ID;
        // mouse report start data
        const uint8_t *data_priv = &data[7];

        uint8_t usb_status_ret = usb_slave_status_get();

        if (usb_status_ret == USB_SLAVE_RESUME && dg_central_wait_usb_wakeup) {
            wdt_clear();
            memcpy(&hid_send_packet[1], data_priv, MOUSE_USB_PACKET_LEN - 1);
            dongle_ble_hid_input_handler(hid_send_packet, MOUSE_USB_PACKET_LEN);
            return true;
        }
    }
    return false;
}

#endif


  • 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.5.4. case默认应用资源(默认使用AW31N DEMO开发板)

Note

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

  • I/O口占用情况

I/O

默认占用情况

PA3

默认打印UART0引脚

PA8

adkey检测引脚

  • 硬件定时器占用情况

定时器

默认占用情况

TIMER0

暂无使用

TIMER1

暂无使用

TIMER2

暂无使用

TIMER3

暂无使用

  • 串口占用情况

串口

默认占用情况

UART0

SDK打印

UART1

暂无使用

UART2

暂无使用

  • USB占用情况

USB

默认占用情况

USB0

默认使用

3.4.5.5. case默认应用功能现象

操作

现象结果

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

行为正常

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

行为正常

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

行为正常