3.4.5. APP - Bluetooth Dual-Mode Dongle
3.4.5.1. 概述
蓝牙dongle符合USB和BLE传输标准,具有即插即用,方便实用的特点。它可用于蓝牙设备之间的数据传输,让电脑能够和周边的蓝牙设备进行无线连接和数据的通讯,自动发现和管理远程蓝牙设备、资源和服务,实现蓝牙设备之间的绑定和自动连接。
蓝牙dongle支持BLE和2.4G两种连接模式;支持连接指定蓝牙名或mac地址;应用示例是连接杰理的鼠标。
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) |
行为正常 |