说起BLE HID(Human Interface Device)设备,就不得不提USB HID。我的理解HID 读写报告属于传输层,所以BLE和USB在关于HID协议的规定都是一样的,运输层的通信不关心底层是通过蓝牙传还是USB传输。能研究BLE HID的朋友一定明白BLE通信是通过读写GATT属性表中的特征值实现的,同样HID是基于BLE实现的,所以HID协议中的读写报告,是基于BLE属性表中特征值实现的。蓝牙联盟已经替大家想好了,推出了HID profile规范,这个规范规定了BLE HID 模型是啥样的,大家都执行这个标准就可以互通往来了。
HID类设备定义连接https://usb.org/sites/default/files/hid1_11.pdf
一个报告以说USAGE_PAGE和USAGE开头 COLLECTION Application 和END_COLLECTION相当于一个大括号里面的内容是对 USAGE进行解释。 一个集合分配是一个报告ID,一个报告描述符内可以有多个集合。也就是可以有多个报告ID。每个集合内最多有3种报告。3种报告对应着input、 output、 feature三种报告。比如下图例子。最先出现的是input 其次是 output 所以这个集合有2个报告,报告顺序是input ,output 。依此类推,整理出整个报告描述符中的3种报告的顺序和数量与GATT HID profile 的report 特征值的一一对应关系。
0x05, 0x01, // Usage Pg (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection: (Application)
// 0x85, 0x03, // Report Id (3 for keyboard)
//
0x05, 0x07, // Usage Pg (Key Codes)
0x19, 0xE0, // Usage Min (224)
0x29, 0xE7, // Usage Max (231)
0x15, 0x00, // Log Min (0)
0x25, 0x01, // Log Max (1)
//
// Modifier byte
0x75, 0x01, // Report Size (1) 1 bit * 8
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input: (Data, Variable, Absolute)
// Reserved byte
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input: (Constant)
//LED repor
0x95, 0x05, //Report Count (5)
0x75, 0x01, //Report Size (1)
0x05, 0x08, //Usage Pg (LEDs )
0x19, 0x01, //Usage Min
0x29, 0x05, //Usage Max
0x91, 0x02, //Output (Data, Variable, Absolute)
//3 bit reserved
0x95, 0x01, //Report Count (1)
0x75, 0x03, //Report Size (3)
0x91, 0x01, //Output (Constant)
// Key arrays (6 bytes)
// this is key array,support simultaneously pressing 6keys report,
// from report_buf[3]~report_buf[3]
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Log Min (0)
0x25, 0xE7, // Log Max (237)
0x05, 0x07, // Usage Pg (Key Codes) , here is the key page,look usb hid key define
0x19, 0x00, // Usage Min (0)
0x29, 0xE7, // Usage Max (237)
0x81, 0x00, // Input: (Data, Array)
0xC0, // End Collection
使用富芮坤FR8016H 蓝牙5,0芯片,做一个蓝牙Joystick,Joystick有3个轴分别是X、Y、Z3个轴,加上8个按钮。每个轴对应一个字节,一个按钮对应一个比特所以8个按钮对应1个字节。所以输入报告需要4个字节。
数据结构如下所示:
在报告描述符中,一个项目必须包含三个字段, 定义3轴:
//(Generic Desktop) 详细解释上摇杆上的3个轴属于桌面设备
0x09, 0x30, // USAGE (X) 定义一个X轴
0x09, 0x31, // USAGE (Y) 定义一个Y轴
0x09, 0x32, // USAGE (Z) 定义一个Z轴
每个轴的逻辑范围 最小-127 最大12
0x15, 0x81, // LOGICAL_MINIMUM (-127) 对3个轴进行规范 逻辑最小值为-127
0x25, 0x7f, // LOGICAL_MAXIMUM (127) 对3个轴进行规范 逻辑最大值为127
用3个字节表示3个轴
//报告大小为8bit 有符号 对应-127 到127
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3) 3个轴总共有3个字节
发送这3个轴数据到电脑:
3个轴总共有3个字节
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
3个轴最终描述
0x05, 0x01, // USAGE_PAGE (Generic Desktop) 详细解释上摇杆上的3个轴属于桌面设备
0x09, 0x30, // USAGE (X) 定义一个X轴
0x09, 0x31, // USAGE (Y) 定义一个Y轴
0x09, 0x32, // USAGE (Z) 定义一个Z轴
0x15, 0x81, // LOGICAL_MINIMUM (-127) 对3个轴进行规范 逻辑最小值为-127
0x25, 0x7f, // LOGICAL_MAXIMUM (127) 对3个轴进行规范 逻辑最大值为127
//报告大小为8bit 有符号 对应-127 到127
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3) 3个轴总共有3个字节
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
定义8个按钮
0x05, 0x09, // USAGE_PAGE (Button) 详细解释 摇杆上有按钮
0x19, 0x01, // USAGE_MINIMUM (Button 1) 定义最小按钮
0x29, 0x08, // USAGE_MAXIMUM (Button 8) 定义最大按钮
每个按钮的状态有2种 0 和1
// 对8个按钮进行规范 逻辑最小值为0
0x25, 0x01, // LOGICAL_MAXIMUM (1) 对8个按钮进行规范 逻辑最大值为1
0x95, 0x08, // REPORT_COUNT (8) 8个按钮总共有1个字节 最小按钮到最大按钮对应低到高bit位
用1个字节表示8个按钮
0x75, 0x01, // REPORT_SIZE (1) 8个按钮总共1个字节
0x81, 0x02, // INPUT
最为输入报告发送到电脑上
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
最终的按钮表示方式
0x05, 0x09, // USAGE_PAGE (Button) 详细解释 摇杆上有按钮
0x19, 0x01, // USAGE_MINIMUM (Button 1) 定义最小按钮
0x29, 0x08, // USAGE_MAXIMUM (Button 8) 定义最大按钮
0x15, 0x00, // LOGICAL_MINIMUM (0) 对8个按钮进行规范 逻辑最小值为0
0x25, 0x01, // LOGICAL_MAXIMUM (1) 对8个按钮进行规范 逻辑最大值为1
0x95, 0x08, // REPORT_COUNT (8) 8个按钮总共有1个字节 最小按钮到最大按钮对应低到高bit位
0x75, 0x01, // REPORT_SIZE (1) 8个按钮总共1个字节
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
3个轴和8个按钮合并一起
0x05, 0x01, // USAGE_PAGE (Generic Desktop) 详细解释上摇杆上的3个轴属于桌面设备
0x09, 0x30, // USAGE (X) 定义一个X轴
0x09, 0x31, // USAGE (Y) 定义一个Y轴
0x09, 0x32, // USAGE (Z) 定义一个Z轴
0x15, 0x81, // LOGICAL_MINIMUM (-127) 对3个轴进行规范 逻辑最小值为-127
0x25, 0x7f, // LOGICAL_MAXIMUM (127) 对3个轴进行规范 逻辑最大值为127
//报告大小为8bit 有符号 对应-127 到127
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3) 3个轴总共有3个字节
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
0x05, 0x09, // USAGE_PAGE (Button) 详细解释 摇杆上有按钮
0x19, 0x01, // USAGE_MINIMUM (Button 1) 定义最小按钮
0x29, 0x08, // USAGE_MAXIMUM (Button 8) 定义最大按钮
0x15, 0x00, // LOGICAL_MINIMUM (0) 对8个按钮进行规范 逻辑最小值为0
0x25, 0x01, // LOGICAL_MAXIMUM (1) 对8个按钮进行规范 逻辑最大值为1
0x95, 0x08, // REPORT_COUNT (8) 8个按钮总共有1个字节 最小按钮到最大按钮对应低到高bit位
0x75, 0x01, // REPORT_SIZE (1) 8个按钮总共1个字节
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
要注意的是即使按照上面的写法电脑还是解析不出来,还要给报告描述符加上个壳用了标记自己是桌面摇杆设备。(TI 可以不写报告ID也能识别到,富芮坤 的SDK 不写报告ID 电脑识别不到)
USAGE_PAGE (Generic Desktop)
USAGE (Joystick)
COLLECTION (Application)
``Report Id (1)
``COLLECTION (Physical)
//上面的报告描述写到这里
``END COLLECTION
END COLLECTION
按照上诉要求整理,最后的报告描述符如下所示:
/******************************* HID Report Map characteristic defination */
static const uint8_t hid_report_map[] =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop) 开始声明当前设备为 桌面设备
0x09, 0x04, // USAGE (Joystick) 指明作为一个摇杆设备
//集合相当于括号,必须要以COLLECTION开始 END_COLLECTION结束
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // Report Id (1) 报告ID为1 规范上不写也可以但是实测富芮坤的SDK 不加电脑识别不到
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x01, // USAGE_PAGE (Generic Desktop) 详细解释上摇杆上的3个轴属于桌面设备
0x09, 0x30, // USAGE (X) 定义一个X轴
0x09, 0x31, // USAGE (Y) 定义一个Y轴
0x09, 0x32, // USAGE (Z) 定义一个Z轴
0x15, 0x81, // LOGICAL_MINIMUM (-127) 对3个轴进行规范 逻辑最小值为-127
0x25, 0x7f, // LOGICAL_MAXIMUM (127) 对3个轴进行规范 逻辑最大值为127
//报告大小为8bit 有符号 对应-127 到127
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3) 3个轴总共有3个字节
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
0x05, 0x09, // USAGE_PAGE (Button) 详细解释 摇杆上有按钮
0x19, 0x01, // USAGE_MINIMUM (Button 1) 定义最小按钮
0x29, 0x08, // USAGE_MAXIMUM (Button 8) 定义最大按钮
0x15, 0x00, // LOGICAL_MINIMUM (0) 对8个按钮进行规范 逻辑最小值为0
0x25, 0x01, // LOGICAL_MAXIMUM (1) 对8个按钮进行规范 逻辑最大值为1
0x95, 0x08, // REPORT_COUNT (8) 8个按钮总共有1个字节 最小按钮到最大按钮对应低到高bit位
0x75, 0x01, // REPORT_SIZE (1) 8个按钮总共1个字节
0x81, 0x02, // INPUT (Data,Var,Abs) 报告为输入,变量,值,绝对值。
0xc0, // END_COLLECTION //关集合,跟上面Physical的对应
0xc0 // END_COLLECTION //关集合,跟上面Application的对应
};
富芮坤提供了 ble_hid_kbd_mice demo 在此基础上修改蓝牙的报告描述符,报告特征值数量,即可完整通信。
按照开头的分析方法,分析Joystick报告描述符可知,Joystick需要一个input 报告 这个报告向主机发送4个字节的数据。
所以先报告的数量改成 1
// Number of HID reports defined in the service
#define HID_NUM_REPORTS 1
因为只有一个报告 所以BLE HID profile中多余的报告屏蔽掉,保留一个报告特征值,用于传输HID input 报告。
// HID Feature Report No 0
[HID_FEATURE_DECL_IDX] = {
{ UUID_SIZE_2, UUID16_ARR(GATT_CHARACTER_UUID) },
GATT_PROP_READ,
0,
NULL,
},
[HID_FEATURE_IDX] = {
{ UUID_SIZE_2, UUID16_ARR(REPORT_UUID) },
GATT_PROP_READ | GATT_PROP_NOTI,
60,
NULL,
},
[HID_REPORT_REF_FEATURE_IDX] = {
{ UUID_SIZE_2, UUID16_ARR(GATT_REPORT_REF_UUID) },
GATT_PROP_READ,
sizeof(hid_report_ref_t),
NULL,
},
[HID_FEATURE_CCCD_IDX] = {
{ UUID_SIZE_2, UUID16_ARR(GATT_CLIENT_CHAR_CFG_UUID) },
GATT_PROP_READ | GATT_PROP_WRITE,
0,
NULL,
},
再修改hid_gatt_add_service中的hid 报告信息数组,因为只有一个输入报告,所以只写一个报告即可。
/*********************************************************************
* @fn hid_gatt_add_service
*
* @brief Simple Profile add GATT service function.
* 添加GATT service到ATT的数据库里面。
*
* @param None.
*
*
* @return None.
*/
void hid_gatt_add_service(void)
{
gatt_service_t hid_profie_svc;
hid_profie_svc.p_att_tb = hid_profile_att_table;
hid_profie_svc.att_nb = HID_ATT_NB;
hid_profie_svc.gatt_msg_handler = hid_gatt_msg_handler;
hid_rpt_info[0].report_id = 1; //refer to report map, this is Joystick input.
hid_rpt_info[0].report_type = HID_REPORT_TYPE_INPUT; //att_table, perm must be GATT_PROP_READ | GATT_PROP_NOTI
hid_svc_id = gatt_add_service(&hid_profie_svc);
}
最后是发送报告,这里测试发现如果发送报告的数据特别频繁容易导致发送出错,所以这里 程序修改成,当3个轴数据 按钮 和之前不一样则更新数据。
adc_enable(NULL, NULL, 0);
adc_get_result(ADC_TRANS_SOURCE_PAD, 0x01, &result);
myJoystick.axis_x = 255.00*result/1023;
adc_get_result(ADC_TRANS_SOURCE_PAD, 0x02, &result);
myJoystick.axis_y = 255.00*result/1023;
adc_get_result(ADC_TRANS_SOURCE_PAD, 0x04, &result);
myJoystick.axis_z = 255.00*result/1023;
key[0] = myJoystick.axis_x ; //
key[1] = myJoystick.axis_y ; //
key[2] = myJoystick.axis_z ; // 1
if(oldMyJoystick.axis_x != myJoystick.axis_x||oldMyJoystick.axis_y != myJoystick.axis_y||oldMyJoystick.axis_z != myJoystick.axis_z)
{
hid_gatt_report_notify(slave_link_conidx,HID_KEYBOARD_RPT_ID,key,HID_KEYBOARD_IN_RPT_LEN);
}
oldMyJoystick.axis_x = myJoystick.axis_x ;
oldMyJoystick.axis_y = myJoystick.axis_y ;
oldMyJoystick.axis_z = myJoystick.axis_z ;
按照上述修改将程序下载到芯片中,电脑连接蓝牙,如下图所示。
只有当摇杆出现在鼠标,键盘和笔这一栏时,说明设备连接成功可以发送数据。
电脑端可以用win10 自带的驱动对摇杆进行测试,打开控制面板下的设备和打印机,如果写的没有问题,就会在设备栏出现个手柄。
当出现下面界面时,就可以按手柄上的按钮和3个轴,界面就会显示对于的值。
如果觉得自带的软件麻烦可以用 joystick tester软件也可以测试。
手机上安装 Game Pad Test 手机上只能识别到2个轴不知道啥原因
设计 BLE HID 设备的前提一定要搞懂 BLE特征值和HID报告描述符之间的关系,以及报告描述符中有多少个报告每个报告有多少个字节,如果这些关系弄明白了 做BLE HID 设备将轻车熟路了。
有朋友反馈,找不到我,今打油诗一首
加流一壶真千金
微云澹日映寒流
信意麾毫无点误
KING_SONGING
唱奇腾怪可删修
歌咏康衢了此生
的然民仰如父母
国家涵养自建隆
王俭归来幕府非
有朋自远发来,不亦说乎!