先推荐2个SIG关于蓝牙和HID的文档:
《Assigned Numbers》可以查UUID
《HID OVER GATT PROFILE SPECIFICATION》
https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=245141
将STM32WBA55CG开发板配置为蓝牙键盘有官方的例子,下载地址:
https://github.com/stm32-hotspot/STM32WBA-BLE-HID-Keyboard
本文对蓝牙键盘的程序做个粗浅的解读吧。蓝牙和HID协议都比较复杂的,本人学识有限(就是好多内容都不理解),难免存在理解错误或偏差。
一、HID设备服务

为了搭建HID设备,需要通过蓝牙至少实现3个服务:HID Service、Battery Service、Device Information Service。
在STM32WBA55CG中对应3个服务,重点看一下SERVICE1:
SERVICE1------HID Service
UUID allocation type: SIG (蓝牙技术联盟,表示后面的UUID是SIG认证的标准UUID)
UUID:Human Interface Device(HIDS)-----0x1812(0x1812是标准UUID)


初始化代码

二、HID特征
SERVICE1下有6个特征

查了一下6个特征的解释:
inputReport(输入报告)
reportMap(报告映射)
hidInformation(HID 信息)
hidControlPoint(HID 控制点)
outputReport(输出报告)
featurereport(特征报告)
其中,UUID为Report(0x2a4d)的特征,需要 Characteristic Descriptors对特征进行进一步说明,如说明用于输入还是输出:


三、特征Report Map(0x2a4b)
可以简单的认为:通过Report Map将蓝牙配置为HID 键盘


report_keyboard是键盘报告描述:
/* USER CODE BEGIN PV */
static uint8_t report_keyboard[] =
{
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xe0, // Usage Minimum (224)
0x29, 0xe7, // Usage Maximum (231)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Constant) reserved byte(1)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (Page# for LEDs)
0x19, 0x01, // Usage Minimum (1)
0x29, 0x05, // Usage Maximum (5)
0x91, 0x02, // Output (Data, Variable, Absolute), Led report
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output (Data, Variable, Absolute), Led report padding
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Key codes)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x65, // Usage Maximum (101)
0x81, 0x00, // Input (Data, Array) Key array(6 bytes)
0x09, 0x05, // Usage (Vendor Defined)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bit)
0x95, 0x02, // Report Count (2)
0xB1, 0x02, // Feature (Data, Variable, Absolute)
0xC0 // End Collection (Application)
};在HIDS服务初始化函数中:
void HIDS_APP_Init(void)
{
UNUSED(HIDS_APP_Context);
HIDS_Init();
/* USER CODE BEGIN Service1_APP_Init */
HIDS_Data_t msg_conf;
tBleStatus result = BLE_STATUS_INVALID_PARAMS;
/* Register Input Report task */
UTIL_SEQ_RegTask( 1<< CFG_TASK_HID_UPDATE_REQ_ID, UTIL_SEQ_RFU, HIDS_APP_UpdateReport );
/* On connection the Protocol Mode is initialised to Report Protocol Mode */
HIDS_APP_Context.ProtocolMode = REPORT_PROTOCOL_MODE;
/* Set the Keyboard Report Map */
memset((void*)a_HIDS_UpdateCharData, 0, sizeof(a_HIDS_UpdateCharData));
memcpy((void*)a_HIDS_UpdateCharData, (void *)&report_keyboard, sizeof(report_keyboard));
msg_conf.p_Payload = a_HIDS_UpdateCharData;
msg_conf.Length = sizeof(report_keyboard);
result = HIDS_UpdateValue(HIDS_REM, &msg_conf);
if( result != BLE_STATUS_SUCCESS )
{
LOG_INFO_APP("Sending of Report Map Failed error 0x%X\n", result);
}将report_keyboard更新给HIDS_REM特征(Report Map)实现蓝牙键盘配置。
四、特征INPUTREP(0x2a4d)
可以理解为蓝牙键盘通过INPUTREP向计算机发送按键信息
static void HIDS_APP_B1Pressed(void)
{
static uint32_t k = 0;
tBleStatus ret;
uint8_t keys[] =
{
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M,
KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z,
KEY_ENTER
};
HIDS_CharOpcode_t report_type = HIDS_INPUTREP;
keyboard_report_t keyboard_report = {0};
HIDS_Data_t msg_conf;
uint8_t nmbTimes;
if((HIDS_APP_Context.ShiftEnable))
{
keyboard_report.modifier = KEY_MOD_LSHIFT;
}
keyboard_report.KEY1 = (uint8_t) keys[k];
LOG_INFO_APP("keyboard_report.KEY1 0x%x\n", keyboard_report.KEY1);
LOG_INFO_APP("keyboard_report.modifier 0x%x\n", keyboard_report.modifier);
memset((void*)a_HIDS_UpdateCharData, 0, sizeof(a_HIDS_UpdateCharData));
memcpy((void*)a_HIDS_UpdateCharData, (void *)&keyboard_report, sizeof(keyboard_report));
msg_conf.p_Payload = a_HIDS_UpdateCharData;
msg_conf.Length = sizeof(keyboard_report);
ret = HIDS_UpdateValue(report_type, &msg_conf);
if(ret != BLE_STATUS_SUCCESS)
{
LOG_INFO_APP("HIDS_UpdateValue fails\n");
}
else
{
LOG_INFO_APP(" Success: Keyboard notify\n\r");
k = (k + 1) % (sizeof(keys) / sizeof(keys[0]));
}
keyboard_report.modifier = 0x00;
keyboard_report.KEY1 = 0;
memset((void*)a_HIDS_UpdateCharData, 0, sizeof(a_HIDS_UpdateCharData));
memcpy((void*)a_HIDS_UpdateCharData, (void *)&keyboard_report, sizeof(keyboard_report));
msg_conf.p_Payload = a_HIDS_UpdateCharData;
msg_conf.Length = sizeof(keyboard_report);
nmbTimes = 0;
do
{
ret = HIDS_UpdateValue(report_type, &msg_conf);
nmbTimes++;
} while ((ret != BLE_STATUS_SUCCESS) && (nmbTimes < 200));
LOG_INFO_APP(" Success: Keyboard notify\n\r");
}上面,按下了B1键,拼接一个keyboard_report,通过INPUTREP通知到计算机
其中keyboard_report数据格式:
typedef struct
{
uint8_t modifier;
int8_t OEM;
int8_t KEY1;
int8_t KEY2;
int8_t KEY3;
int8_t KEY4;
int8_t KEY5;
int8_t KEY6;
} keyboard_report_t;这是一个标准的keyboard_report。
五、按键的处理

1、通过sequencer和TIMER注册,这是STM的蓝牙处理机制,sequencer定义一个任务,TIMER用于延时触发任务。
2、通过按键中断,调用TIMER注册的延时执行函数,
3、TIMER注册的Button_TriggerActions触发任务注册的函数HIDS_APP_UpdateButtonState
4、HIDS_APP_UpdateButtonState发送keyboard_report给计算机。
六、运行效果
1、搜寻蓝牙

输入111111,连接成功。
输入焦点放在调试串口工具输入框,按下B1,每按一次触发一次按键,依次输出01234567.....,日志中可以看发送的KEY内容:如0x24对应7。
