项目描述
本项目基于 xG24-EK2703A 开发套件实现一个蓝牙鼠标/键盘的复合设备,开发套件上有两只按钮,分别短按两只按钮后模拟鼠标滚轮向上翻页和向下翻页一行的功能,同时长按两只按钮两秒后模拟键盘依次发送字符 EETREE.CN。
本项目开发过程中使用 Simplicity Studio 开发环境,并基于官方的蓝牙 HID 键盘示例程序进行修改。
硬件资源
xG24-EK2703A 开发套件
EFR32xG24 Explorer 套件是一个基于 EFR32MG24 片上系统的小封装开发和评估平台。EFR32xG24 Explorer 套件专注于快速原型化和概念创建 2.4 GHz 无线协议的 IoT 应用程序,包括蓝牙 LE、蓝牙网状网络、Zigbee、Thread 和 Matter。
EFR32MG24B210F1536IM48
- 高性能 2.4 GHz 无线电
- 78 MHz 32-位 ARM® Cortex®-M33
- 1536 kB 闪存和 256 kB RAM
板级外设
- 两个 LED 和两个按钮
- 重置按钮
- 板载 SEGGER J-Link 调试器
- 虚拟 COM 端口
设计思路
- 蓝牙 GATT 配置
配置 HID(Human Interface Device)服务描述键盘和鼠标的输入功能,并确保在蓝牙连接上正确识别为键盘和鼠标复合设备。 - 配置 Button/LED 等硬件资源,用于实现逻辑控制
- 键盘功能实现
检测到两个按钮同时被按压时,开启一个两秒钟的单次计时器,如果计时期间保持按压状态,那么两秒计时结束后,在回调函数中记录键盘事件,并发送 evt_system_external_signal 事件给蓝牙协议栈,在蓝牙事件处理函数中,发送 EETREE.CN 对应的蓝牙 HID 报文。如果不能保持按压状态,则立即停止计时器。 - 鼠标功能实现
检测到任意一个按钮被按压时,分别记录鼠标滚轮向上翻页和向下翻页事件,同时发送 evt_system_external_signal 事件给蓝牙协议栈,在蓝牙事件处理函数中,分别发送鼠标滚轮向上翻页和向下翻页一行的 HID 报文。
软件流程
开发环境
Simplicity Studio Version 5
Silicon Labs Gecko SDK
安装 gecko-sdk 有两种方式:
- 使用 Simplicity Studio 自带的 Installation Manager 安装
安装过程中需要调用 git 克隆 gecko-sdk 仓库,我尝试了两三次,大概在安装进度 80% 时中断了,最后放弃使用这种方法。
- 从 github 下载 SDK 发布版本,然后在 Simplicity Studio -> Preferences -> Simplicity Studio -> SDKs 窗口中手动添加 SDK 路径
这里我下载的是 v4.4.1 版本https://github.com/SiliconLabs/gecko_sdk/releases/download/v4.4.1/gecko-sdk.zip
Silicon Labs bluetooth applications
- 下载地址
git clone https://github.com/SiliconLabs/bluetooth_applications.git
- 添加仓库
在 Simplicity Studio -> Preferences -> Simplicity Studio -> External Repos 窗口中添加仓库路径
工程开发
导入 Bluetooth - HID Keyboard 工程
- 连接板卡,在 Debug Adapters 窗口可以看到板卡信息
- 点击板卡,IDE 右侧会出现详细信息,点击 EXAMPLE PROJECTS & DEMOS 标签页,然后搜索 HID,然后创建 Bluetooth - HID Keyboard 工程
- 选择工程保存路径,这里我选择拷贝源文件到工程路径
配置蓝牙 GATT Profile
在工程目录 config/btconf 中,双击 gatt_configuration.btconf 打开 Bluetooth GATT Configurator 界面,可以配置不同的 Service。
Generic Access Service
在 Device Name Characteristic 页面,修改 device_name 为 "EETREE HID",如下图:
Human Interface Device Service
Report Map 简述
Report Map 是一种用于描述蓝牙设备的数据格式的标准。它定义了蓝牙HID(Human Interface Device)Profile中的数据报告的结构,包括键盘、鼠标、游戏手柄等输入设备通过蓝牙传输数据时使用的报告格式。通过使用ReportMap,蓝牙设备制造商能够遵循统一的数据格式标准,从而提高设备的兼容性,使得这些设备可以与各种不同的蓝牙主机设备进行通信。本项目需要实现键盘加鼠标的功能,所以需要键盘和鼠标对应的 Report Map。
键盘 Report Map
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0xE0, // Usage Minimum (0xE0)
0x29, 0xE7, // Usage Maximum (0xE7)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0x00, // Usage Minimum (0x00)
0x29, 0x65, // Usage Maximum (0x65)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
鼠标 Report Map
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x85, 0x02, // Report ID (2)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x03, // Usage Maximum (0x03)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x38, // Usage (Wheel)
0x15, 0x81, // Logical Minimum (-127)
0x75, 0x08, // Report Size (8)
0x95, 0x03, // Report Count (3)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
GATT configurator 更新 Report Map
将键盘和鼠标的 Report Map 以16进制字符串的形式组合在一起,然后在 Report Map Characteristic 页面,修改 report_map,如下图:
修改完毕后保存 gatt_configuration.btconf 文件,IDE 会自动在 autogen 目录生成 gatt_db.c,里面包含 bluetooth gatt configuration。
软件组件配置
按键配置
在导入Bluetooth-Hid-Keyboard 工程时,Simplicity Studio 自动添加了 btn0 的实例,可以按照下图手动添加 btn1 实例。添加完成后,IDE 根据板卡型号自动生成了 btn1 实例相关的代码。
LED 配置
双击打开 bluetooth_hid_keyboard.slcp,然后在 SOFTWARE COMPONENTS 页面,依次点击 Platform -> Driver -> LED -> Simple LED,然后点击 Install,在弹出窗口中分别添加 led0 和 led1 实例。同样,IDE 根据板卡型号自动生成了 led0 和 led1 实例相关的代码。
TIMER 配置
同样在 SOFTWARE COMPONENTS 页面,依次点击 Application -> Utility -> Timer,然后点击 Install,在弹出窗口中分别添加 led0 和 led1 实例。同样,IDE 会自动添加 。
代码实现
按键状态 handler
void sl_button_on_change(const sl_button_t *handle)
{
if (&sl_button_btn0 == handle) {
if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
app_log("Button-0 pushed - callback\r\n");
btn_status |= 1;
sl_led_turn_on(&sl_led_led0);
} else {
app_log("Button-0 released - callback \r\n");
btn_status &= ~1;
sl_led_turn_off(&sl_led_led0);
}
}
if (&sl_button_btn1 == handle) {
if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
app_log("Button-1 pushed - callback\r\n");
btn_status |= 2;
sl_led_turn_on(&sl_led_led1);
} else {
app_log("Button-1 released - callback \r\n");
btn_status &= ~2;
sl_led_turn_off(&sl_led_led1);
}
}
}
按键状态逻辑处理
SL_WEAK void app_process_action(void)
{
/////////////////////////////////////////////////////////////////////////////
// Put your additional application code here! //
// This is called infinitely. //
// Do not call blocking functions from here! //
/////////////////////////////////////////////////////////////////////////////
if (btn_status == 1) {
app_timer_stop(&btn_timer);
sl_bt_external_signal(1);
} else if (btn_status == 2) {
app_timer_stop(&btn_timer);
sl_bt_external_signal(1);
} else if (btn_status == 3) {
if (btn_timer_status == 0) {
app_timer_start(&btn_timer, 2000, btn_timer_cb, NULL, 0);
btn_timer_status = 1;
}
} else {
app_timer_stop(&btn_timer);
btn_timer_status = 0;
}
}
蓝牙协议栈事件处理
- 键盘报文 ID 为 1,占 1 字节,数据长度为 8 字节,总报文长度为 9 字节
- 鼠标报文 ID 为 2,占 1 字节,数据长度为 4 字节,总报文长度为 4 字节
通过蓝牙发送报文时,一定不能搞错这个长度,本人在调试期间因为搞错长度浪费了很多时间。
case sl_bt_evt_system_external_signal_id:
if (notification_enabled == 1) {
memset(input_report_data, 0, sizeof(input_report_data));
switch (btn_status) {
case 1: // scroll up
input_report_data[0] = 0x02;
input_report_data[4] = 0x01;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
5, input_report_data);
app_assert_status(sc);
app_log("Mouse report was sent\r\n");
break;
case 2: // scroll down
input_report_data[0] = 0x02;
input_report_data[4] = 0xff;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
5, input_report_data);
app_assert_status(sc);
app_log("Mouse report was sent\r\n");
break;
case 3:
for (uint8_t i = 0; i < sizeof(eetree_key_array); i++) {
input_report_data[0] = 0x01;
input_report_data[1] = (eetree_key_array[i] != 0x37) ? CAPSLOCK_KEY_ON : CAPSLOCK_KEY_OFF;
input_report_data[3] = eetree_key_array[i];
sc = sl_bt_gatt_server_notify_all(gattdb_report,
9, input_report_data);
app_assert_status(sc);
input_report_data[0] = 0x01;
input_report_data[1] = CAPSLOCK_KEY_OFF;
input_report_data[3] = 0x00;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
9, input_report_data);
app_assert_status(sc);
}
app_log("Key report was sent\r\n");
break;
default:
break;
}
功能展示
蓝牙连接设备
- 搜索设备
- 连接成功
键盘鼠标演示
见视频
参考资料链接
- bluetooth_applications/bluetooth_hid_keyboard at master · SiliconLabs/bluetooth_applications · GitHub
- DIY蓝牙键盘(1) - 理解键盘报文 - 知乎 (zhihu.com)
- USB标准请求及描述符在线分析工具 - USB中文网 (usbzh.com)
- HID报告描述符Report Descriptor解析分析 - USB中文网 (usbzh.com)
- 读懂 HID 报告描述符 (实现全键无冲的键盘 HID 报告描述符)_使用hid descriptor tool生成自定义hid报告描述符-CSDN博客
- HID Keyboard & Mouse descriptor. - Climber丶 - 博客园 (cnblogs.com)
- USB开源项目:Easy USB 51 Programer Plus-USB HID复合设备实例—键盘+鼠标USB开发网百合电子工作室USB专题站 (baiheee.com)
- 【BLE】HID设备的实现(蓝牙自拍杆、蓝牙键盘、蓝牙鼠标、HID复合设备)_蓝牙键盘教程代码-CSDN博客
- Universal Serial Bus (USB)
- 【经验】蓝牙SoC EFR32BG22增加Button功能并发送Notify数据包的方法 (sekorm.com)
- SimplicityStudio_v5.6.4.0+efr32mg21 学习笔记(3) 按键操作_efr32中my-code文件夹-CSDN博客
- BLE HID 协议-----蓝牙鼠标 代码流程分析-CSDN博客
- 基于XG24-EK2703A的BLE HID蓝牙键盘+鼠标复合设备功能开发(BLE+HID+FreeRTOS+Gecko SDK)-CSDN博客