本节我们了解和实现Nucleo-H503RB开发板的USB HID模拟键盘的功能。
模拟USB HID Keyboard指的是在嵌入式系统中模拟实现一个符合USB HID标准的键盘设备。这样的模拟键盘可以通过USB接口与计算机或其他主机设备进行通信,发送键盘按键事件,从而实现对计算机的控制和操作。
通常需要以下步骤:
硬件连接:首先,需要将嵌入式设备与计算机或其他主机通过USB接口连接起来。这通常涉及到嵌入式设备的OTG接口和PC的USB接口的连接,具体连接方式可能因设备而异。
驱动与协议实现:在嵌入式设备上,需要实现USB HID的驱动和通信协议。这包括在嵌入式系统中编写代码,以模拟键盘设备的行为,并处理与主机之间的通信。
键盘事件模拟:模拟键盘需要能够生成并发送键盘按键事件。这通常涉及到编写代码来模拟按下和释放键盘按键的过程,并通过USB接口将这些事件发送给主机。
一,USB枚举过程
USB的驱动过程,就是一个枚举过程。
USB枚举过程是指当USB设备插入到主机后,主机如何识别并配置该设备的过程。这个过程是确保设备能够正常工作并与主机进行通信的关键步骤。以下是USB枚举过程的主要步骤:
设备检测与复位:当USB设备插入到主机的USB端口时,主机首先会检测到设备的存在。然后,主机会对设备进行复位操作,将其置于一个已知的初始状态。
获取设备描述符:复位完成后,主机会向设备发送一个获取设备描述符的请求。设备描述符包含了设备的基本信息,如厂商ID、产品ID、设备版本等。设备在收到请求后,会返回其设备描述符给主机。
设置设备地址:在获取到设备描述符后,主机会给设备分配一个唯一的地址。这个地址将用于主机与设备之间的后续通信。
再次获取设备描述符:使用新分配的地址,主机会再次发送获取设备描述符的请求,以确保设备已经正确响应了地址分配。
获取配置描述符:接下来,主机会请求设备的配置描述符。配置描述符包含了设备的配置信息,如支持的接口数量、端点数量等。
获取接口和端点描述符:主机继续请求设备的接口描述符和端点描述符。这些描述符提供了关于设备如何与主机通信的详细信息,如数据传输类型、最大数据包大小等。
获取字符串描述符:为了显示给用户更友好的信息,主机还可以请求设备的字符串描述符,这些描述符包含了设备的名称、制造商信息等。
选择设备配置:最后,主机会根据获取到的配置信息,选择适当的设备配置。选择配置后,设备就处于可以使用的状态了。
在整个枚举过程中,主机和设备之间通过一系列的请求和响应进行通信。这些通信遵循USB协议的规定,确保了设备能够被主机正确识别并配置。枚举过程的成功完成是USB设备能够正常工作的前提。
USB描述符主要包括标准描述符、HID描述符、HUB描述符等。
基础描述符包括设备描述符、配置描述符、字符串描述符、接口描述符、端点描述符、设备限定描述符、其他速率配置描述符等。
1,基础描述符介绍
这七种基础描述符具有类似的格式,比如说它们的第一个字段都是 bLength,第二个字段都是 bDescriptorType。七种描述符在使用时以 bDescriptorType 字段来区分。描述符类型与对应的 bDescriptorType 字段值对应关系为:
1)设备描述符: 设备描述符描述的是设备的整体信息,与设备本身一一对应,一个设备只能有一个设备描述符。在主机对 USB 设备枚举的过程中,首先要做的就是获取设备描述符,以对设备有一个整体的了解。设备描述符由 14 个字段组成,总长度 18 字节。
2)配置描述符:一个 USB 设备可以有多种配置,不同的配置使设备工作在不同的状态下,每个配置必须有一个配置描述符。其格式包括 8 个字段,共 9 字节。
3)字符串描述符: 在 USB 协议中字符串描述符是可选的。字符串描述符用于保存一些供应商名称、产品序列号等文本信息。它的长度是不固定的,随字符串的数量和信息的长度变化而变化。
4)接口描述符:接口是端点的集合,负责完成 USB 的特定功能,例如数据的输入输出。接口描述符用于描述一个接口,包含了接口的特性,如端点个数,所属设备类和子类等。它有 9 个字段,共 9 字节。
5)端点描述符:端点描述符用于指出 USB 端点的特性,包括其所支持的传输类型、传输方向等信息。USB 中规定,端点 0 没有端点描述符,其余端点必须包含端点描述符。端点描述符由 6 个字段组成,共 7 个字节。
2,HID描述符
USB 设备中有一大类就是 HID 设备,即 Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘等,主要用于人与计算机进行交互。它是 USB 协议最早支持的一种设备类。HID 设备可以作为低速、全速、高速设备用。由于 HID 设备要求用户输入能得到及时响应,故其传输方式通常采用中断方式。在 USB 协议中,HID 设备的定义放置在接口描述符中,USB 的设备描述符和配置描述符中不包含 HID 设备的信息。因此,对于某些特定的 HID 设备,可以定义多个接口,只有其中一个接口为 HID 设备类即可。
当定义一个设备为 HID 设备时,其设备描述符应为:
bDeviceClass=0
bDeviceSubClass=0
bDeviceProtocol=0
其接口描述符应该:
bInterfaceClass=0x03
另外(接口描述符): 对无引导的 HID 设备,子类代码 bInterfaceSubClass 应置 0,此时 bInterfaceProtocol 无效,置零即可。即为:
bInterfaceClass=0x03
bInterfaceSubClass=0
bInterfaceProtocol=0
对支持引导的 USB 设备,子类代码 bInterfaceSubClass 应置 1,此时 bInterfaceProtocol 可以为 1 或 2,1 表 示键盘接口,2 表示鼠标接口。其参考设置如下:
bInterfaceClass=0x03
bInterfaceSubClass=1
bInterfaceProtocol=1 或 2
HID 设备支持 USB 标准描述符中的五个:设备描述符、配置描述符、接口描述符、端点描述符、字符串描 述符。除此之外,HID 设备还有三种特殊的描述符:HID 描述符、报告描述符、物理描述符。一个 USB 设备只 能支持一个 HID 描述符,但可以支持多个报告描述符,而物理描述符则可以有也可以没有。
1)HID描述符:
2)报告描述符:HID 设备的报告描述符是一种数据报表,主要用于定义 HID 设备和 USB 主机之间的数据交换格式,HID 设备报告描述符的类型值为 0x22。 报告描述符使用自定义的数据结构,用于传输特定的数据包。例如对于键盘,需要在数据包中指明按键的值,报告描述符把这些数据打包发给主机,主机对发来的数据进行处理。它有四个组成部分:
各字段含义:
bSize:占用两个位,指示数据部分,即[data]字段的长度,00bà表没有数据字节,01bà表只有一个数据 字节,10b 表示有两个数据字节,11b 表有 4 个数据字节。
bType:数据项类型,用于指明数据项的类型。00bà主数据类型,01bà全局数据类型,10bà局部数据类 型,11bà保留。
bTag:数据项标签,用于指明数据项的功能。报告描述符需要包含的数据项标签有:输入输出数据项标签、 用法数据项标签、用法页数据项标签、逻辑最小和最大值数据项标签、报告大小数据项标签以及报告计数数据 项标签。 [data]:数据字节,随着前面 bSize 定义的大小而变化。
关于USB更详细的使用查阅:
二,USB硬件接口
三,STM32CubeMX配置USB
1,使能USB功能及配置
设备USB类型为HID,选择鼠标和键盘。其他可以使用默认参数。也可以去修改设备名称、PID、VID等。
USB的初始化我们在任务中去初始化。所以在工程管理中作如下配置。
然后点击生产代码。
生成的代码中多出与USB相关的文件了。
发现STM32H5使用了USBx的协议栈。USBx Device 协议栈内部实现了一套复杂的基于事件和消息驱动机制的数据流传输和控制传输逻辑,用户 Application 只需要使用其提供的 APIs 即可实现 USB 通信。
2,集成 ThreadX实时系统
在cubeMX上集成了Azure RTOS ThreadX实时系统,我们直接配置使用即可。
关于 ThreadX的源码可以在github上下载。
https://github.com/eclipse-threadx/threadx
https://github.com/eclipse-threadx
官网地址:
https://azure.microsoft.com/zh-cn/services/rtos/#overview
ST有对 Threadx有一些介绍:
https://www.st.com/zh/partner-products-and-services/azure-rtos-threadx.html
STM32H503RB内存也不小了,不知道为什么这个芯片cubemx没有集成FreeRTOS。ThreadX好像是被微软收购后就是免费的实时系统,但是认证要求非常高。ThreadX仅需2KB和1KB RAM的小指令区域,可有效节省占用空间。所有对内存比较小,又想使用rtos的,这个也是一种选择。
我们使用CubeMX添加threadx:
在3处把TICK修改为1000,方便计算。
把SYS下的Timbase Source修改为其他TIM7定时器。
3,编写代码实现一个Threadx的一个任务调度
我们把前面章节实现按键修改led闪烁频率的功能在THreadx的任务重运行。
在main函数中已经实现了threadx内核初始化,初始化调用了启动内核tx_kernel_enter();
启动内核时,系统会创建一个默认任务tx_application_define();在tx_application_define()中实现用户任务的入口函数App_ThreadX_Init();在App_ThreadX_Init()中创建一个任务tx_app_thread_entry。
在任务tx_app_thread_entry这种实现我们的应用。
边缘、下载、运行。
4,编写HID键盘相关代码
在THreadx调用初始化MX_ThreadX_Init();启动内核系统时,系统会创建一个默认任务 tx_application_define();在tx_application_define()中除了建立用户任务的入口函数App_ThreadX_Init(); 还对USBx设备进行了初始化MX_USBX_Device_Init();在MX_USBX_Device_Init创建了一个任务 app_ux_device_thread_entry。在MX_USBX_Device_Init中实现这个枚举过程的所有参数及回调函数。
在任务app_ux_device_thread_entry函数中实现USB硬件初始化。
MX_USB_PCD_Init()和hpcd_USB_DRD_FS是在usb.c中定义。所以我们需要添加头文件和外部引用。
我再MX_USBX_Device_Init()中创建一个任务,实现模拟键盘功能;
在ux_device_keyboard.c中USBD_HID_Keyboard_Activate函数添加hid_keyboard获取键盘HID接口。
usbx_hidkeyboard_thread_entry任务函数中实现模拟键盘按键发送。通过用户按键来实现键盘的输入参数。
发生数据钱我们先了解下HID 键盘描述符。
USB HID键盘描述符,可以在网上搜索udb键盘描述符。
https://www.usbzh.com/article/detail-326.html
想更详细的知道USB。可以直接进入USB官网查询。
搜索HID,可以搜索到相关信息。
我们使用cubeMX生产的代码已经添加键盘报告描述符
键盘编码参照官网文献
键盘发送数据,一次发送8个字节。
buffer[0]- bit0: Left CTRL
bit1: Left SHIFT
bit2: Left ALT
bit3: Left Gul
bit4: Right CTRL
bit5: Right SHlFT
bit6: Right ALT
bit7: Right Gul
buffer[1]-Padding = Always 0x00
buffer[2]- Key1
buffer[3]- Key2
buffer[4]- Key3
buffer[5]- key4
buffer[6]- Key5
buffer[7]- Key 6
我这里使用按键实现ABCDE的输入。
编译、下载、执行。
在网上搜索一个键盘在线测试工具。
https://www.zfrontier.com/lab/keyboardTester
现在开始按键就可以看到效果了。