这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【STM32WBA55CG开发板评测】8、蓝牙键盘

共2条 1/1 1 跳转至

【STM32WBA55CG开发板评测】8、蓝牙键盘

助工
2025-01-08 11:15:44   被打赏 50 分(兑奖)     打赏

先推荐2个SIG关于蓝牙和HID的文档:

Assigned Numbers》可以查UUID

https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Assigned_Numbers/out/en/Assigned_Numbers.pdf?v=1736225423311

《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设备服务

1.png



为了搭建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)

image.png

image.png

初始化代码

image.png

二、HID特征

SERVICE1下有6个特征

image.png

查了一下6个特征的解释:

inputReport(输入报告)


  • 定义:它是人机接口设备(HID)向与之连接的主机设备(如电脑、智能手机等)发送的数据报告,主要用于传达用户在设备上的操作信息。例如,在蓝牙键盘的情境下,当用户按下某个按键时,键盘会生成一个包含按键相关信息的输入报告,并通过蓝牙连接发送给与之配对的设备,以便设备能准确识别用户输入的字符或执行相应的操作指令。

  • 内容与格式:其内容格式由设备的 Report Map(报告映射)来规定,通常包含了代表操作行为的特定字节或字段。比如,对于键盘输入报告,可能会有字节表示哪些按键被按下、按键的按下顺序等;对于蓝牙鼠标,可能涉及鼠标移动的方向、距离以及按键点击情况等信息。不同类型的 HID 设备有着各自对应的输入报告格式,以准确反映设备的操作状态。

  • 作用:实现了将 HID 设备上的用户操作转化为可被主机设备识别的数据,是人机交互信息传递的关键环节,使得主机能够实时响应 HID 设备的操作,像在文本输入、游戏操控等各种应用场景中都起着基础性作用。

reportMap(报告映射)


  • 定义:是一种对蓝牙 HID 设备数据格式进行详细描述的规则设定。它明确了输入报告、输出报告以及特征报告等各类报告中数据的具体含义、排列顺序以及长度等信息。

  • 具体功能:可以把它想象成一份 “数据字典”,以蓝牙游戏手柄为例,reportMap 会规定输入报告里哪个字节代表左摇杆的水平方向移动、哪个字节对应右摇杆的垂直移动,以及各个按键状态在数据中的具体位置等。同样,对于输出报告(如果有从主机到设备的反向控制信息传输),也会定义发送的数据格式,比如主机向手柄发送震动强度调节指令时的数据格式。通过这种精确的描述,保证了设备之间数据交流的准确性和可理解性,无论设备是由哪个厂商生产,只要遵循相同的报告映射规则,就能实现良好的互操作性。

  • 应用场景:在开发蓝牙 HID 设备、编写设备驱动程序以及实现不同设备间兼容通信等方面都起着关键作用。设备制造商依据想要实现的功能设计 reportMap,操作系统和应用程序开发者则根据这个规则来解析和处理来自不同设备的数据。

hidInformation(HID 信息)


  • 定义:涵盖了与人机接口设备相关的各类基础信息,包括设备的类型(如键盘、鼠标、游戏手柄等)、支持的功能(如多媒体键、特定快捷键等)、设备的制造商信息、设备的版本号以及所遵循的 HID 标准版本等内容。

  • 作用与用途:对于主机设备来说,通过获取 HID 信息能够全面了解与之连接的 HID 设备的基本情况,从而更好地对其进行适配、管理和驱动安装(如果需要)。例如,操作系统在检测到一个新的蓝牙键盘连接后,会首先读取其 HID 信息,确认它是哪种语言布局的键盘、是否支持额外的功能键等,进而自动配置相应的驱动程序或提供合适的设置界面供用户进一步个性化配置该键盘。在设备兼容性和故障排查等方面,HID 信息也是重要的参考依据,比如判断设备是否满足特定软件应用的使用要求,或者分析设备出现异常时是否是因为功能不支持等原因造成的。

hidControlPoint(HID 控制点)


  • 定义:它是主机设备与蓝牙 HID 设备之间进行交互控制的一个关键 “接口” 或机制,主机可以通过 HID 控制点向 HID 设备发送各种控制指令,同时也能从这里获取 HID 设备的状态信息等。

  • 功能举例:在配置蓝牙设备时,通过 HID 控制点可以对设备的工作模式进行调整。例如,对于蓝牙鼠标,可以设置其指针的移动速度、按键的功能分配(比如将某个按键定义为特定的快捷操作);对于蓝牙键盘,能设定按键的重复延迟、重复速率等参数。此外,还能用于获取设备当前的状态,像查询蓝牙游戏手柄的电量、各个按键是否正常工作等情况,并且在设备支持固件升级等功能扩展时,HID 控制点也是传输升级文件等相关数据的通道,实现设备功能的更新和完善。

  • 重要性:极大地增强了主机对 HID 设备的管理能力和控制灵活性,方便用户根据自身需求对设备进行个性化的设置和维护,确保设备在不同使用场景下都能发挥出最佳性能,同时也是保障设备间良好交互和协同工作的重要组成部分。

outputReport(输出报告)


  • 定义:与输入报告相对应,是从主机设备向蓝牙 HID 设备发送的数据报告,主要用于主机对 HID 设备进行反向控制,传达主机的一些指令或配置信息给设备。

  • 应用场景举例:以蓝牙音箱作为 HID 设备(具有一定的控制功能,比如调节音量等可被主机控制的操作)为例,当用户在手机(主机设备)上调节音量滑块时,手机会生成一个包含音量调节指令的输出报告,并通过蓝牙发送给蓝牙音箱,音箱接收到该报告后按照规定的数据格式解析出指令,进而执行相应的音量调节操作。再比如,对于一些具有震动反馈功能的蓝牙游戏手柄,主机可以通过输出报告向手柄发送不同强度的震动指令,让手柄根据游戏场景提供相应的震动体验。

  • 作用:实现了主机对 HID 设备的反向控制,拓展了人机交互的维度,使设备能够根据主机的指令做出相应的响应,丰富了设备的使用功能和交互效果。

featurereport(特征报告)


  • 定义:特征报告主要用于传达 HID 设备的某些特定特征或状态信息,它可以是设备主动向主机发送的,也可以是主机向设备请求后得到的。这些特征通常涉及到设备的一些属性、功能状态等方面,不同于输入报告侧重于用户操作的即时反馈,也不同于输出报告主要体现主机对设备的控制指令。

  • 示例与用途:例如,蓝牙 HID 设备的电池电量信息往往会通过特征报告发送给主机,主机接收到后可以在相应的界面上显示给用户,方便用户了解设备的电量情况。又比如,某些具有可调节灵敏度的蓝牙鼠标,其当前的灵敏度设置状态可以通过特征报告告知主机,若用户在主机端(如电脑的鼠标设置界面)查看该鼠标的详细参数时,就能获取到这一特征信息。在设备的故障诊断、功能优化等方面,特征报告也能提供重要的数据支持,通过分析特征报告中的内容,有助于排查设备是否存在异常状态以及是否需要对某些功能进行调整等。

其中,UUID为Report(0x2a4d)的特征,需要 Characteristic Descriptors对特征进行进一步说明,如说明用于输入还是输出:

image.png


image.png


三、特征Report Map(0x2a4b)

可以简单的认为:通过Report Map将蓝牙配置为HID 键盘

image.png

image.png

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。


五、按键的处理

image.png

1、通过sequencer和TIMER注册,这是STM的蓝牙处理机制,sequencer定义一个任务,TIMER用于延时触发任务。

2、通过按键中断,调用TIMER注册的延时执行函数,

3、TIMER注册的Button_TriggerActions触发任务注册的函数HIDS_APP_UpdateButtonState

4、HIDS_APP_UpdateButtonState发送keyboard_report给计算机。

六、运行效果

1、搜寻蓝牙

image.png

输入111111,连接成功。

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

image.png






关键词: STM32WBA55CG     蓝牙     键盘    

高工
2025-01-10 10:18:49     打赏
2楼

666666


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]