蓝牙hid协议:
蓝牙ble通过软件实现 HID,需要实现两个方面,广播和服务及特性
广播,HID广播数据要求如下
int wm_ble_hid_api_demo_adv(bool enable)
{
int rc;
if(enable) {
struct ble_hs_adv_fields fields;
const char *name;
/**
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info).
* o Device name.
* o user specific field (winner micro).
*/
memset(&fields, 0, sizeof fields);
/* Advertise two flags:
* o Discoverability in forthcoming advertisement (general)
* o BLE-only (BR/EDR unsupported).
*/
fields.flags = BLE_HS_ADV_F_DISC_LTD |
BLE_HS_ADV_F_BREDR_UNSUP; //ble设备,有限发现模式
name = "mykeybord8"; //设备名称 广播时显示的设备名称
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
fields.uuids16 = (ble_uuid16_t[]) {
BLE_UUID16_INIT(WM_GATT_HID_UUID)
}; //hid服务UUID1218
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;
fields.appearance = 0x03c1; //键盘外观
fields.appearance_is_present = 1;
rc = ble_gap_adv_set_fields(&fields);
if(rc != 0) {
TLS_BT_APPL_TRACE_ERROR("error setting advertisement data; rc=%d\r\n", rc);
return rc;
}
/* As own address type we use hard-coded value, because we generate
NRPA and by definition it's random */
rc = tls_nimble_gap_adv(WM_BLE_ADV_IND, 0);
if(rc != 0) {
TLS_BT_APPL_TRACE_ERROR("tls_nimble_gap_adv; rc=%d\r\n", rc);
return rc;
}
} else {
rc = tls_nimble_gap_adv(WM_BLE_ADV_STOP, 0);
}
return rc;
}
以上代码实现了,HID键盘的广播数据。
服务特性 如下
uint16_t g_ble_hid_attr_2a4a_handle;
uint16_t g_ble_hid_attr_2a4b_handle;
uint16_t g_ble_hid_attr_2a4c_handle;
uint16_t g_ble_hid_attr_2a4d_handle;
uint16_t g_ble_hid_attr_2a4d2_handle;
uint16_t g_ble_hid_attr_2a4e_handle;
uint16_t g_ble_demo_conn_handle ;
//以上为句柄定义,
#define WM_GATT_HID_UUID 0x1812 //hid服务 uuid
#define WM_GATT_HID_UUID1 0x2A4A //HID Information 的特性UUID
#define WM_GATT_HID_UUID2 0x2A4B //Report Map的特性UUID
#define WM_GATT_HID_UUID3 0x2A4C //Control Point的特性UUID
#define WM_GATT_HID_UUID4 0x2A4D //HID设备与HID主机之间交互数据(Report)的特性UUID
#define WM_GATT_HID_UUID5 0x2A4D //HID设备与HID主机之间交互数据(Report)的特性UUID
#define WM_GATT_HID_UUID6 0x2A4F //协议模式的特性UUID
static const struct ble_gatt_svc_def gatt_hid_svr_svcs[] = {
{
/* Service: uart */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(WM_GATT_HID_UUID),
.characteristics = (struct ble_gatt_chr_def[])
{ {
.uuid = BLE_UUID16_DECLARE(WM_GATT_HID_UUID1),
.val_handle = &g_ble_hid_attr_2a4a_handle,
.access_cb = gatt_svr_chr_hid_access_func,
.flags = BLE_GATT_CHR_F_READ,
}, {
.uuid = BLE_UUID16_DECLARE(WM_GATT_HID_UUID2),
.val_handle = &g_ble_hid_attr_2a4b_handle,
.access_cb = gatt_svr_chr_hid_access_func,
.flags = BLE_GATT_CHR_F_READ,
},{
.uuid = BLE_UUID16_DECLARE(WM_GATT_HID_UUID3),
.val_handle = &g_ble_hid_attr_2a4c_handle,
.access_cb = gatt_svr_chr_hid_access_func,
.flags = BLE_GATT_CHR_F_WRITE,
},
{
.uuid = BLE_UUID16_DECLARE(WM_GATT_HID_UUID4),
.val_handle = &g_ble_hid_attr_2a4d_handle,
.access_cb = gatt_svr_chr_hid_access_func,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
.descriptors = (struct ble_gatt_dsc_def[])
{
{
.uuid = BLE_UUID16_DECLARE(0x2908),
.att_flags = 1,
.access_cb = gatt_svr_chr_hid_access_func,
},
{
0,
}
},
},
{
.uuid = BLE_UUID16_DECLARE(WM_GATT_HID_UUID5),
.val_handle = &g_ble_hid_attr_2a4d2_handle,
.access_cb = gatt_svr_chr_hid_access_func,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE,
.descriptors = (struct ble_gatt_dsc_def[])
{
{
.uuid = BLE_UUID16_DECLARE(0x2908),
.att_flags = 2,
.access_cb = gatt_svr_chr_hid_access_func,
},
{
0,
}
},
},
{
.uuid = BLE_UUID16_DECLARE(WM_GATT_HID_UUID6),
.val_handle = &g_ble_hid_attr_2a4e_handle,
.access_cb = gatt_svr_chr_hid_access_func,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE,
}, {
0, /* No more characteristics in this service */
}
},
},
{
0, /* No more services */
},
};
回调函数
static int gatt_svr_chr_hid_access_func(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
int rc;
struct os_mbuf *om = ctxt->om;
switch(ctxt->op) {
case BLE_GATT_ACCESS_OP_WRITE_CHR://收到数据
printf("BLE_GATT_ACCESS_OP_WRITE_CHR attr_handle = %d\r\n",attr_handle);
while(om){
tls_print_bytes("op write", om->om_data, om->om_len);
om = SLIST_NEXT(om, om_next);
}
return 0;
case BLE_GATT_ACCESS_OP_READ_CHR:
printf("readattr_handle = %d\r\n",attr_handle);
if(attr_handle == g_ble_hid_attr_2a4a_handle)
{
printf("2a4a\r\n");
gatt_svc_test_read_value[0] = 0x01;
gatt_svc_test_read_value[1] = 0x01;
gatt_svc_test_read_value[2] = 0x00;
gatt_svc_test_read_value[3] = 0x02;
rc = os_mbuf_append(ctxt->om, &gatt_svc_test_read_value,4);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
/*HID的信息,其值为4个字节:
前两个字节是HID版本,一般填入0x01,0x01,表示版本号为1.1
第三个字节是Country Code,一般填入0x00
第四个字节是HID Flags,一般填入0x02,表示Normally Connectable。*/
}
else if(attr_handle == g_ble_hid_attr_2a4b_handle)
{
printf("2a4b\r\n");
rc = os_mbuf_append(ctxt->om, &bord_rep,sizeof (bord_rep));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
//HID设备与HID主机数据交互的方式
}
else if(attr_handle == g_ble_hid_attr_2a4d_handle)
{
printf("2a4d\r\n");
gatt_svc_test_read_value[0] = 0x01;
gatt_svc_test_read_value[1] = 0x01;
rc = os_mbuf_append(ctxt->om, &gatt_svc_test_read_value,2);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
else if(attr_handle == g_ble_hid_attr_2a4d2_handle)
{
printf("2a4d2\r\n");
gatt_svc_test_read_value[0] = 0x01;
gatt_svc_test_read_value[1] = 0x02;
rc = os_mbuf_append(ctxt->om, &gatt_svc_test_read_value,2);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
else if(attr_handle == g_ble_hid_attr_2a4e_handle)
{
printf("2a4e\r\n");
gatt_svc_test_read_value[0] = 0x01;
rc = os_mbuf_append(ctxt->om, &gatt_svc_test_read_value,1);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
//0x00表示Boot模式,0x01表示Report模式
}
return 0;
case BLE_GATT_ACCESS_OP_READ_DSC://表示读写特征描述符 01 02
printf("type is = BLE_GATT_ACCESS_OP_READ_DSC\r\n");
printf("readattr_handle = %d\r\n",attr_handle);
if(attr_handle == 11)
{
printf("2a4d dsc\r\n");
gatt_svc_test_read_value[0] = 0x01;
gatt_svc_test_read_value[1] = 0x01;
rc = os_mbuf_append(ctxt->om, &gatt_svc_test_read_value,2);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}else if (attr_handle == 14)
{
printf("2a4d2 dsc\r\n");
gatt_svc_test_read_value[0] = 0x01;
gatt_svc_test_read_value[1] = 0x02;
rc = os_mbuf_append(ctxt->om, &gatt_svc_test_read_value,2);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
} //同名UUID用2908特征描述符区分,一个发送一个接收
return 0;
default:
printf("type is = %d\r\n",ctxt->op);
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
}
启动HID
int start_hid()
{
tls_appl_trace_level = TLS_BT_LOG_VERBOSE;
tls_bt_init(0xff);
CHECK_SYSTEM_READY();
tls_ble_hid_demo_api_init(NULL,NULL);
return 0;
}
tls_bt_init(0xff); 08版本sdk不用改变。
tls_ble_hid_demo_api_init 函数按照08版本sdk来写。配置服务,添加服务,启动广播。
int tls_ble_hid_demo_api_init(tls_ble_uart_output_ptr uart_output_ptr, tls_ble_uart_sent_ptr uart_in_and_sent_ptr)
{
int rc = BLE_HS_EAPP;
CHECK_SYSTEM_READY();
TLS_BT_APPL_TRACE_DEBUG("%s, state=%d\r\n", __FUNCTION__, g_ble_hid_state);
if(g_ble_hid_state == BLE_SERVER_MODE_IDLE) {
//step 0: reset other services. Note
rc = ble_gatts_reset();
if(rc != 0) {
TLS_BT_APPL_TRACE_ERROR("tls_ble_server_demo_api_init failed rc=%d\r\n", rc);
return rc;
}
//step 1: config/adding the services
rc = wm_ble_hid_demo_gatt_svr_init();
if(rc == 0) {
ble_gap_event_listener_register(&ble_server_event_listener,
ble_gap_evt_cb, NULL);
TLS_BT_APPL_TRACE_DEBUG("tls_ble_server_demo_api_init register success\r\n");
/*step 2: start the service*/
rc = ble_gatts_start();
assert(rc == 0);
/*step 3: start advertisement*/
rc = wm_ble_hid_api_demo_adv(true);
if(rc == 0) {
g_ble_hid_state = BLE_SERVER_MODE_ADVERTISING;
}
} else {
TLS_BT_APPL_TRACE_ERROR("tls_ble_server_demo_api_init failed(rc=%d)\r\n", rc);
}
} else {
TLS_BT_APPL_TRACE_WARNING("tls_ble_server_demo_api_init registered\r\n");
rc = BLE_HS_EALREADY;
}
return rc;
}
启动广播后,在安卓手机端就应该能看到 ,自定义名称的蓝牙设备了,可以配对成功。
发送键码 1 测试
int send_key(void)
{
uint8_t key_a[] = {0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00};
tls_ble_hid_demo_api_send_msg_notify(key_a,8);
uint8_t key_aa[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
tls_ble_hid_demo_api_send_msg_notify(key_aa,8);
return 0;
}
report map 定义
static uint8_t bord_rep[] = {0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // REPORT_ID (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
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)
0x81, 0x02, // Input (Data, Variable, Absolute); Modifier byte
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Constant); Reserved byte
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (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 (Constant); 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)
0xc0
};
其他流程处理参照 sdk wm_ble_server_api_demo.c
挖坑,1:配对后,安卓取消配对,或者w801复位,都不能再次连接,只能取消配对和复位同时进行,才能再次连接。未能实现配对后自动连接
2:win11 连接失败,提示驱动错误,应该是hid协议处理上有点问题,