【Funpack3-1】基于XG24-EK2703A的BLE HID蓝牙键盘+鼠标复合设备功能开发
该项目使用了Silicon Labs的XG24-EK2703A开发板,实现了蓝牙键盘+鼠标复合设备的设计,它的主要功能为:使用板载两个按键,当BTN0按下,向上翻页;当BTN1按下,向下翻页;当两按键同时按下2s后,向主机依次发送字符“EETREE.CN”。。
标签
嵌入式系统
Funpack活动
Simplicity Studio
蓝牙鼠标
蓝牙键盘
XG24-EK2703A
BLE HID
Silicon Labs
葉SiR
更新2024-03-05
北京大学
194

项目介绍

本项目基于Silicon Labs XG24-EK2703A开发板,通过HID协议实现了一个蓝牙键盘+鼠标复合设备,可通过按键实现上下翻页、发送字符功能。使用板载两个按键,当BTN0按下,向上翻页;当BTN1按下,向下翻页;当两按键同时按下2s后,向主机依次发送字符“EETREE.CN”。

👉 Simplicity Studio 5


硬件介绍

XG24-EK2703A是一款基于EFR32MG24片上系统的开发套件,具备超低成本、低功耗和小巧的特点。该套件支持2.4GHz无线通信,兼容蓝牙LE、蓝牙mesh、Zigbee、Thread和Matter协议,为无线物联网产品的开发和原型制作提供了极大的便利。包含:

  1. 一个USB接口
  2. 一个板载SEGGER J-Link 调试器,支持SWD
  3. 两个LED和两个按钮
  4. 虚拟COM端口
  5. 数据包跟踪接口(PTI)
  6. 一个支持外部硬件连接的mikroBus插座和一个Qwiic连接器
  7. 32 位 ARM Cortex-M33,78 MHz最高工作频率
  8. 1536 kB 闪存和 256 kB RAM


项目设计

开发环境及工程参考

本项目使用Silicon Labs官方的IDE Simplicity Studio 5开发,使用Gecko SDK v4.4.0,GNU ARM Toolchain 12.2。工程目录上,按照Bluetooth - SoC Empty 空白示例的代码组织形式即可。主要的业务代码写在 app.c 及 app.h 内,外设、驱动及蓝牙部分通过 .slcp 文件配置。


👉 本工程参考SiliconLabs蓝牙应用示例:bluetooth_hid_keyboard


总体流程图

所使用的系统外设:两个按键、两个LED及蓝牙栈。

  • 在按键中断回调中,根据不同按键按下,置位或清除各按键按下的事件
  • 使用FreeRTOS操作系统,创建按键响应任务,用以实现两个按键按下的响应服务:循环读取按键按下事件,当按键单独按下时,则用一枚举变量 km_status 记录:
    • 当BTN0按下,置 KM_SCROLL_UP
    • 当BTN1按下,置 KM_SCROLL_DOWN
    • 当同时按下,且无定时器在运行,则开启2s定时器,该定时器绑定一回调函数,在该回调内:置 km_status 为 KM_SEND_STRING,同时反转两LED状态(便于观察现象)
    • 最后,都向蓝牙栈发送外部事件信号
  • 在蓝牙事件回调中,当接收到外部事件信号后,根据 km_status 值进行相应操作。从而实现上/下翻页、发送字符的功能。



硬件基本配置

在基于 “Bluetooth - Soc Empty” 空白示例的基础上,打开 .slcp 文件,在 SOFTWARE COMPONENTS 选项卡下安装如下组件:

  • [Platform] → [Driver] → [Button] → [Simple Button],例化 btn0 与 btn1,对应开发板上两个按键,均设置为中断模式
  • [Platform] → [Driver] → [LED] → [Simple LED],例化 led0 与 led1,对应开发板上两个 LED
  • [Services] → [IO Stream] → [IO Stream: USART],保持默认配置即可
  • [Application] → [Utility] → [Timer for FreeRTOS]
  • [Application] → [Utility] → [Log]


并且,参考SiliconLabs蓝牙应用示例:bluetooth_hid_keyboard,使用该示例提供的 GATT 配置,导入到自己的工程中:

  1. 打开项目中 .slcp 文件
  2. 在 CONFIGURATION TOOLS 选项卡下找到 Bluetooth GATT Configurator
  3. 导入 config/btconf/gatt_configuration.btconf 文件
  4. 保存 GATT 配置


后续还会进行一定程度的修改。


应用初始化

在 app.h 内,定义四种按键按下的枚举类型,分别表示:未按下、发送字符(两按键同时按下)、上翻页(BTN0按下)及下翻页(BTN1按下):


typedef enum
{
KM_IDLE = 0U,
KM_SEND_STRING = 1U,
KM_SCROLL_UP = 2U,
KM_SCROLL_DOWN = 3U,
} km_status_t;
```
在初始化阶段,先创建按键按下事件组、按键响应任务。

```c
#define KM_BTN_TASK_NAME "keymouse_btn"
#define KM_BTN_TASK_STACK_SIZE 1024
#define KM_BTN_TASK_STATIC 0

TaskHandle_t km_btn_task_handle = NULL;
static EventGroupHandle_t xbtn_events = NULL;
static km_status_t km_status = KM_IDLE;

SL_WEAK void app_init(void)
{
xbtn_events = xEventGroupCreate();
if (xbtn_events == NULL) {
app_log_error("BTN events create failed\r\n");
}
xTaskCreate(km_btn_task,
KM_BTN_TASK_NAME,
configMINIMAL_STACK_SIZE,
NULL,
tskIDLE_PRIORITY,
&km_btn_task_handle);
}


按键中断回调

按键中断回调定义在 `void sl_button_on_change(const sl_button_t *handle)` 内,可参考示例修改。在此,根据触发中断的 `handle` 判断是哪个按键按下或释放,相应地置位或清除事件位 `xbtn_events` 。


#include "sl_simple_button_instances.h"
#define BTN0_PRESSED (1 << 0)
#define BTN1_PRESSED (1 << 1)
#define BTN_NONE_PRESSED 0
#define BTN_BOTH_PRESSED (BTN0_PRESSED | BTN1_PRESSED)
void sl_button_on_change(const sl_button_t *handle)
{
BaseType_t xHigherPriorityTaskWoken;
if (&sl_button_btn0 == handle) {
if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
xEventGroupSetBitsFromISR(xbtn_events, BTN0_PRESSED, &xHigherPriorityTaskWoken);
}
else {
xEventGroupClearBitsFromISR(xbtn_events, BTN0_PRESSED);
}
}
if (&sl_button_btn1 == handle) {
if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
xEventGroupSetBitsFromISR(xbtn_events, BTN1_PRESSED, &xHigherPriorityTaskWoken);
}
else {
xEventGroupClearBitsFromISR(xbtn_events, BTN1_PRESSED);
}
}
}


定时器回调

该回调函数被捆绑在2s不自动重载定时器上,由于定时器是在两按键同时按下并持续2s后才结束,因此在回调内,需清除两个按键按下事件,最后发送给蓝牙栈外部事件信号。


static void btn_press_timer_cb(app_timer_t *timer, void *data)
{
(void)data;
(void)timer;
BaseType_t xResult;
xResult = xEventGroupClearBitsFromISR(xbtn_events, BTN_BOTH_PRESSED);
if (xResult == pdFAIL) {
app_log_error("Clear BTN_BOTH_PRESSED event failed\r\n");
}
km_status = KM_SEND_STRING;
sl_led_toggle(&sl_led_led0);
sl_led_toggle(&sl_led_led1);
sl_bt_external_signal(1);
}


按键响应任务

主体为一循环。在循环内,通过 `xEventGroupGetBits` 读取按键事件,并做出不同响应。该事件在应用初始化时创建。用一bool型变量 `is_running` 记录定时器是否在运行,从而避免在两按键一直按下时反复重启定时器。由于可能出现先两按键按下,再释放一个或两个按键的情况,因此在其他情况下,都关闭定时器。


static void km_btn_task(void *p_arg)
{
app_timer_t btn_press_timer;
bool is_running = false;
EventBits_t btn_events;

while (1) {
btn_events = xEventGroupGetBits(xbtn_events);
switch (btn_events) {
case (BTN_BOTH_PRESSED):
if (!is_running) {
app_timer_start(&btn_press_timer, 2000, btn_press_timer_cb, NULL, false);
is_running = true;
}
case (BTN0_PRESSED):
app_timer_stop(&btn_press_timer);
km_status = KM_SCROLL_UP; // scroll up
sl_bt_external_signal(1);
break;
case (BTN1_PRESSED):
app_timer_stop(&btn_press_timer);
km_status = KM_SCROLL_DOWN; // scroll down
sl_bt_external_signal(1);
break;
default:
app_timer_stop(&btn_press_timer);
is_running = false;
break;
}
vTaskDelay(pdMS_TO_TICKS(50));
}
vTaskDelete(NULL);
}


蓝牙事件回调

参考SiliconLabs蓝牙应用示例:bluetooth_hid_keyboard,修改蓝牙事件回调中当 MSG_ID 为 `sl_bt_evt_system_external_signal_id` 时的部分代码:根据 `km_status` 状态分别实现上/下翻页、发送字符,且这几个功能分别用函数封装。最后,置 `km_status = KM_IDLE` 。


...
case sl_bt_evt_system_external_signal_id:
if (notification_enabled == 1 && km_status != KM_IDLE) {
if (km_status == KM_SEND_STRING) {
send_eetree_string();
}
else if (km_status == KM_SCROLL_UP) {
scroll_with_distance(0x01);
}
else { // KM_SCROLL_DOWN
scroll_with_distance(0xFF);
}
app_log_info("Key report %d was sent\r\n", km_status);
km_status = KM_IDLE;
}
break;
...


BLE HID

HID(Human Interface Device)人体学接口设备,是生活中常见的输入设备,比如键盘、鼠标等。早期的HID是设备大部分都是通过USB接口来实现,蓝牙技术出现后,通过蓝牙作为传输层,实现了无线HID设备。通过低功耗蓝牙实现的HID功能一般简称为HOGP(HID over Gatt Profile)。BLE HID 规范以 USB HID 规范为基础,因此具体含义仍需参照USB HID文档。

👉 参考:【BLE】HID设备的实现(蓝牙自拍杆、蓝牙键盘、蓝牙鼠标、HID复合设备)


Report Map及报文

键盘设备

👉 参考:DIY蓝牙键盘(1) - 理解键盘报文


Report Map用十六进制数据,描述HID设备的基本信息,例如,按键数量,数据的最大最小值,功能等。为了实现鼠标+键盘复合设备,参考SiliconLabs蓝牙应用示例:bluetooth_hid_keyboard所给出的一个键盘设备的报告映射,并加入Report ID条目:



这样描述的键盘设备具有通用键盘的基本功能,将按键与释放键信息通过输入报告发送到主机。同时,使用常见的键盘报文结构,其中包含保留字节、修饰符字节与6个键码字节(可以描述最多6个按键同时按下)。键盘报文格式如下所列:


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

Byte 7

含义

Modifier byte

Reserved byte

Key code 1

Key code 2

Key code 3

Key code 4

Key code 5

Key code 6

其中,第一个字节从LSB开始依次表示:


Bit 0

Bit 1

Bit 2

Bit 3

Bit 4

Bit 5

Bit 6

Bit 7

含义

L Ctrl

L Shift

L Alt

L GUI

R Ctrl

R Shift

R Alt

R GUI

第二个字节保留(默认为0)。后面6个字节的每个字节都可以表示一个按键的状态,可以同时有多个按键按下。在手册《HID Usage Tables For Universal Serial Bus (USB)》中,规定了键码与按键的对应关系,例如:



例如,下述两个报文分别表示a与A(同时按下左Shift + a):


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

Byte 7

含义

含义

0x00

0x00

0x04

0x00

0x00

0x00

0x00

0x00

a


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

Byte 7

含义

0x04

0x00

0x04

0x00

0x00

0x00

0x00

0x00

A

此外,在发送按下按键的信息后,还需发送释放按键的报文,否则键盘将一直按住。


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

Byte 7

含义

0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x00

释放


鼠标设备

鼠标设备的报告映射如下所列:



对于鼠标,上报的数据我们定义了4个字节。其中Byte 0 的bit 0~2分别表示鼠标左键、右键与中键,后4位由设备定义(默认为0)。Byte 1 表示鼠标指针X轴移动,Byte 2 表示鼠标指针Y轴移动(有符号数,具体数值与移动距离的关系可实际测试),Byte 3 表示滚轮移动。鼠标的报文格式如下所列:


Byte 0

Byte 1

Byte 2

Byte 3

含义

bit 0~2 左、右、中键

指针X方向移动

指针Y方向移动

滚轮移动


复合设备

对于两个或以上的HID复合设备来说,需要额外用Report ID描述,在此键盘Report ID为1,鼠标为2。同时,设备的报文前需额外一个字节表示Report ID。将上述两个设备的报告映射写在一起,即可描述键盘+鼠标的复合设备。如图所示,将这个长字节配置至 Bluetooth GATT Configurator 内。



发送字符串

通过键盘设备发送字符串“EETREE.CN”,各字符码表为:0x08(e)、0x17(t)、0x15(r)、0x37(.)、0x06(c)、0x11(n)。要实现大写,还需要修饰键按下左Shift。例如,发送“E”,并结合键盘的Report ID(0x01):


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

Byte 7

Byte 8

含义

0x01

0x04

0x00

0x08

0x00

0x00

0x00

0x00

0x00

E


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

Byte 7

Byte 8

含义

0x01

0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x00

释放

发送一个字符后,再发送一次全0报文,表示按键释放。两各按键前后最好间隔几十毫秒。由于所发送字符串“EETREE.CN”有连续的字符,因此不方便在一次报文中发送(如此两个“EE”将仅表达一次“E”按下),且“.”无需修饰符,因此索性每个字符都单次发送。


#define REPORT_ID_INDEX         0
#define KB_REPORT_ID 0x01
#define MODIFIER_INDEX 1
#define DATA_INDEX 3
#define LSHIFT_KEY_OFF 0x00
#define LSHIFT_KEY_ON 0x02
static uint8_t kb_report_data[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

void send_keyboard(uint8_t caps_key, uint8_t c)
{
sl_status_t sc;
memset(kb_report_data, 0, sizeof(kb_report_data));
kb_report_data[REPORT_ID_INDEX] = KB_REPORT_ID;
kb_report_data[MODIFIER_INDEX] = caps_key;
kb_report_data[DATA_INDEX] = c;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
sizeof(kb_report_data),
kb_report_data);
app_assert_status(sc);
memset(kb_report_data, 0, sizeof(kb_report_data));
kb_report_data[REPORT_ID_INDEX] = KB_REPORT_ID;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
sizeof(kb_report_data),
kb_report_data);
app_assert_status(sc);
sl_sleeptimer_delay_millisecond(20);
}

void send_eetree_string()
{
send_keyboard(LSHIFT_KEY_ON, 0x08); // E
send_keyboard(LSHIFT_KEY_ON, 0x08); // E
send_keyboard(LSHIFT_KEY_ON, 0x17); // T
send_keyboard(LSHIFT_KEY_ON, 0x15); // R
send_keyboard(LSHIFT_KEY_ON, 0x08); // E
send_keyboard(LSHIFT_KEY_ON, 0x08); // E
send_keyboard(LSHIFT_KEY_OFF,0x37); // .
send_keyboard(LSHIFT_KEY_ON, 0x06); // C
send_keyboard(LSHIFT_KEY_ON, 0x11); // N
}


上/下滚动

通过鼠标设备实现上下滚动,并结合鼠标的Report ID(0x02):


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

含义

0x02

0x00

0x00

0x00

0x01

上滚


Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

含义

0x02

0x00

0x00

0x00

0xFF

下滚

#define MOUSE_REPORT_ID         0x02
#define WHEEL_INDEX 4
static uint8_t mouse_report_data[] = { 0, 0, 0, 0, 0 };

void scroll_with_distance(uint8_t distance)
{
sl_status_t sc;
memset(mouse_report_data, 0, sizeof(mouse_report_data));
mouse_report_data[REPORT_ID_INDEX] = MOUSE_REPORT_ID;
mouse_report_data[WHEEL_INDEX] = distance;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
sizeof(mouse_report_data),
mouse_report_data);
app_assert_status(sc);
}


功能展示

开发板连接PC并配对蓝牙后,可以看到XG24 KeyMouse设备已连接,且电量为100%。功能演示参见视频。




👉 详细展示参见:B站:基于XG24-EK2703A的BLE HID蓝牙键盘+鼠标复合设备功能开发

项目总结

本次项目通过BLE HID协议,实现了键盘+鼠标复合设备,使用两个按键实现上/下翻页、发送字符串的功能。Silicon Labs的IDE总体感觉还不错,直接在IDE内把GSDK、编译工具链都给安装好。对于配置开发板的外设、IO口、驱动、蓝牙GATT配置等有图形化界面,上手较为容易。

附件下载
xg24_ble_hid_keymouse_ssv5.zip
工程源文件
团队介绍
北京大学 软件与微电子学院 集成电路工程专业就读
团队成员
葉SiR
二次元の开发者;👉 GitHub: https://github.com/KafCoppelia
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号