先推荐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。