基于 NXP QN9080 如何实现 BLE HID 键盘 LED 灯控处理

一、HID 简介

人机接口设备(HID)是 USB 和 BLE 中常用的设备类型,是直接与人交互的设备,例如键盘、鼠标和游戏杆等。只要符合 HID 类别规范的设备都是 HID 设备。HID配置文件规范定义了蓝牙 HID 设备和蓝牙 HID 主机使用的协议、过程和特性,本篇我们主要讲解的是蓝牙 HID 设备,它是一种设备,提供与蓝牙 HID 主机之间的人工或其他数据输入和输出的服务。蓝牙 HID 设备的例子主要有键盘、鼠标、操纵杆、手柄、遥控器、以及电压表和温度传感器等。

接下来我们要讲的是蓝牙键盘的应用。本文会基于 HID 规范做一些简要介绍,并基于 QN9080 SDK 及 QN908x DK 开发板开发一个简单的键盘 Demo 演示。

二、HOGP 规范要求

文档定义了 BLE 设备如何通过 GATT(通用属性概要文件)支持 BLE 协议栈的 HID 服务。BLE HID 协议同样符合 USB HID 协议部分。HID 服务中强制要求服务需要

包括:HID Service、Battery Service、Device Information。


图一

三、Windows 和 MAC 关于键盘灯状态处理部分的区别:

① Windows

通过查看文档 《device class definition for human interface device (HID)》相关章节可知,在修饰符字节数据即主机下发的 Set Report 命令中,包含了 CAPS 灯等状态的数据,这意味着每个 LED 的状态都必须包含在输出报告中,我们可以通过即时的判断这些 bit 数据来获取当前的灯状态。一个字节包含了 8 个位,该字节数据 bit0对应为 NUM LOCK 灯,bit1 为 CAPS LOCK,bit2 为 SCOLL LOCK,bit3 到 bit7 的数据在键盘应用上比较少,通常不使用,我们只需要了解 bit0-2这三个灯状态的处理即可。


图二

按照蓝牙核心规范要求,BLE 键盘的数据发送间隔最小可设置为 7.5ms,我们可以使用特定发送和接收函数去上报和获取灯状态。

② MAC

通过查看苹果开发者网站的《辅助设计指南》可知,MAC 所对应的蓝牙键盘也应支持 HID 协议并遵守需求中列出的所有需求。

MAC 连接的键盘可对应支持以下灯状态:

  • CAPS 设备锁定状态。
  • 设备连接状态,如蓝牙状态。
  • 键盘的电池状态。

按照文档要求,MAC 不支持键盘中任何其他状态的 LED 灯,因为他们不被 MAC 支持。

查看连接参数要求章节可知,通常 MAC 支持的最小时间间隔为 15ms,这表明了 BLE 中数据的发送间隔最小为 15ms,如果一个键盘同时请求 15 毫秒的 Interval Min和 Interval Max,一些设备会将间隔调整到 30 毫秒以平衡功率和性能限制。如果是 BLE HID 键盘设备的话,连接间隔低于 11.25ms 有可能会被 MAC 电脑接受,否则连接间隔将会采用 MAC 主机所下发的连接间隔。关于连接参数的设定要求可详细查看《蓝牙核心规范 V5.1》。

 


图三

需要注意的是:如果在 BLE 键盘代码中设置了较小的连接间隔,这有可能会导致 MAC 电脑无法即时获取和下发当前的 CAPS 灯状态,因此,我们对 MAC 电脑进行一些配置来达到关闭 MAC 电脑 CAPS LOCK 键延迟功能。

苹果在 MAC 电脑产品中,为了更好的人性化设计,添加了防止用户误触的问题,为 CAPS LOCK 按键加入了延迟功能,对于不少常用 Windows 电脑的用户来说,体验感大打折扣,明明按下了 CAPS LOCK 按键,却没有切换输入模式。下面我们先进行 CAPS LOCK 按键延迟功能的调整。首先在 MAC 电脑上,选择系统偏好设置中的“辅助功能”,找到键盘设置中硬件设置的选项,慢速键可调整在按下按键和按键被激活之间的时间量,将“启用慢速键”选项打开,默认延迟时间为中等挡位,此时若使用 BLE 键盘进行 CAPS LOCK 数据的上报,需要将数据发送间隔设定为 60ms 以上才能被 MAC 电脑识别到,因此,我们需要将这个延迟时间调整到最短,来达到对 CAPS LOCK 按键的最快响应速度。


图四

四、代码实现流程

下面我们来详细了解具体的代码实现过程,关于键盘部分的开发可参考博文《基于 NXP QN9080 蓝牙键盘开发》,可以实现基本的键盘应用。由于篇幅原因,我们不再对环境搭建部分进行讲解。首先,我们可以通过恩智浦官网 http://mcuxpresso.nxp.com/ 下载最新的 SDK,SDK 的 IDE 支持 IAR、KEIL、MCUXpresso,可自主选择。

硬件使用 NXP QN9080 DK 板,板载带有三个按键,包括一个复位按键和两个自定义按键。以下我将介绍如何实现 CAPS 灯状态的获取。

1、8个字节通用键盘数据定义:

typedef struct keyboardHidReport_tag{

uint8_t controlkey;

uint8_t reservedkey;

uint8_t keyboard1;

uint8_t keyboard2;

uint8_t keyboard3;

uint8_t keyboard4;

uint8_t keyboard5;

uint8_t keyboard6;

}keyboardHidReport_t;

 

以上数据包括了 1 字节的控制键数据,1 字节的保留按键数据和 6 个字节的普通按键数据。

2、按键处理函数:

void BleApp_HandleKeys(key_event_t events);此函数为 SDK 中的按键处理函数,我们可以在该函数中实现板载 button1,button2 的自定义逻辑处理,

目前我们使用这两个按键置标志位去发送两个按键值。


3、GATT 配置部分:在 gatt_db.h 中,我们需要添加一个 output 属性用来获取 CAPS 灯状态。具体代码如下:

CHARACTERISTIC(char_output_report, 0x2A4D, ( gGattCharPropRead_c | gGattCharPropWriteWithoutRsp_c|gGattCharPropWrite_c) )
VALUE_VARLEN(value_output_report, 0x2A4D, (gPermissionFlagReadable_c|gPermissionFlagWritable_c),1, 1,0x00)
CCCD(cccd_output_report)
DESCRIPTOR(desc_output_report_ref, 0x2908, (gPermissionFlagReadable_c), 2, 0x01, 0x02) //inform output report



4、数据发送函数:该函数可发送 boot protocol 和 Report Protocol 的数据。

static void SendReport(keyboardHidReport_t *pReport)
{
hidProtocolMode_t protocolMode = 0xFFU;
/* Toggle Protocol Mode */
(void)Hid_GetProtocolMode((uint16_t)service_hid, &protocolMode);
if (protocolMode == gHid_BootProtocolMode_c)
{
(void)Hid_SendBootKeyboardInputReport(hidServiceConfig.serviceHandle, sizeof(keyboardHidReport_t), pReport);
}
else if (protocolMode == gHid_ReportProtocolMode_c)
{
(void)Hid_SendInputReport(hidServiceConfig.serviceHandle, sizeof(keyboardHidReport_t), pReport);

}

else
{
; /* For MISRA Compliance */

}
}



5、Low Power Timer 定时器回调处理函数:该回调函数主要进行一些数据的上报处理,或者自定义的扫描按键部分等。 

static void TimerHidMouseCallback(void * pParam)
{
//0X53-->NUM LOCK
//0x39-->CAPS lock
//0X47-->SCOLL LOCK
if(press_flag==1)
{
press_flag = 0;
keyboardHidReport_t keyboardReport = { 0, 0, 0x53, 0, 0, 0, 0, 0 };
SendReport(&keyboardReport);
}
else if(press_flag==2)
{
press_flag = 0;
keyboardHidReport_t keyboardReport = { 0, 0, 0x39, 0, 0, 0, 0, 0 };
SendReport(&keyboardReport);
}
else if(press_flag==0)
{
press_flag = 6;
keyboardHidReport_t keyboardReport = { 0, 0, 0, 0, 0, 0, 0, 0 };
SendReport(&keyboardReport);
}
/* Start measurements */
(void)TMR_StartLowPowerTimer(mHidDemoTimerId, gTmrLowPowerSingleShotMillisTimer_c,
mHidReportInterval_c, TimerHidMouseCallback, NULL);
}

以上 demo 代码可实现 CAPS LOCK 和 NUM LOCK 的数据上报部分。

6、命令接收处理回调函数: 

static void BleApp_GattServerCallback (deviceId_t deviceId, gattServerEvent_t* pServerEvent)
{
uint16_t handle;
uint8_t status;
switch (pServerEvent->eventType)
{
case gEvtMtuChanged_c:
break;
case gEvtAttributeWritten_c:
case gEvtAttributeWrittenWithoutResponse_c:
{
//添加命令接收处理函数部分,类似于 USB HID 中的 SET REPORT
handle = pServerEvent->eventData.attributeWrittenEvent.handle;
//CAPS 灯处理handle
if (handle == value_output_report)
{
BleApp_ReceivedDataHandler(deviceId,
pServerEvent->eventData.attributeWrittenEvent.aValue,
pServerEvent>eventData.attributeWrittenEvent.cValueLength);
//打印对应值为灯状态处理部分
PRINTF(" pServerEvent->eventData.attributeWrittenEvent.aValue[0] = %d\r\n",
pServerEvent->eventData.attributeWrittenEvent.aValue[0]);
}
status = (uint8_t) gAttErrCodeNoError_c;
// 可支持苹果设备的睡眠唤醒处理
if (handle == (uint16_t) value_hid_control_point) {
status = Hid_ControlPointHandler((uint16_t) service_hid,
pServerEvent->eventData.attributeWrittenEvent.aValue[0]);
}
(void) GattServer_SendAttributeWrittenStatus(deviceId, handle, status);
}
break;
case gEvtCharacteristicCccdWritten_c:
//使能 CCCD 部分
handle = pServerEvent->eventData.charCccdWrittenEvent.handle;
uint8_t value = pServerEvent->eventData.charCccdWrittenEvent.newCccd;
PRINTF("Enable notification, handle: %d, value: %d.\r\n", handle,value);
break;
default:
; /* For MISRA Compliance */
break;
}
}

gEvtAttributeWritten_c, gEvtAttributeWrittenWithoutResponse_c 这两个 case 可以进行键盘灯状态处理命令的接收和苹果设备睡眠唤醒的处理命令以及实现OTA 升级的部分等,其中,前者可接收 MAC 电脑下发的 CAPS 灯状态,后者可接收 Windows 的 CAPS 灯状态。

通过以上配置后,我们通过 Window 电脑蓝牙与设备进行连接。连接成功后,我们可以看到第三行打印数据 aValue[0] = 1 即 bit0 等于 1,说明此时电脑端的NUM LOCK 灯是处于常亮状态,这时候会给设备端进行同步,将处理命令也发给设备。第四行打印数据表明 bit0 和 bit1 均等于 1,说明这个时候电脑端CAPS LOCK 灯和 NUM LOCK 同时处于常亮的状态。其余位的灯处理作者不再赘述,大家可以自行尝试。


图五

然后我们通过 MAC 电脑与设备进行蓝牙连接,连接完成后,MAC 电脑会对设备各个属性进行 CCCD 操作,此时,我们可以发现 MAC 电脑连接上设备后,不会立即下发一个 MAC 端当前 CAPS 灯状态的处理命令,这是与 Windows 处理机制有所不同的地方。这时候我们使用设备端进行 CAPS LOCK 数据的上报,可以发现 attributeWrittenEvent.aValue[0] = 2,即 bit1 等于 1,说明 CAPS LOCK 当前灯状态为常量,输入模式已切换。当我们再次上报 CAPS LOCK 按键可以发现,最后一行打印数据为 0,说明此时 CAPS LOCK 灯状态为灭,以上,即为 BLE HID 键盘实现 CAPS LOCK 灯处理部分的说明。


图六

五、总结

通过以上的说明,相信大家应该已经了解如何使用 QN9080 的 BLE SDK 去实现键盘 CAPS 灯处理的开发应用,后续我会推出更多关于 BLE 和 Gaming应用的博文,请大家多多关注~

六、参考资料

【1】《HID over GATT Profile Specification.pdf》

【2】《Accessory-Design-Guidelines.pdf》

【3】《HID_BLE_GATT.pdf》

【4】《device class definition for human interface device.pdf》

技术文档

类型标题档案
硬件Datasheet

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论