一、USB简介
USB(Universal Serial BUS)通用串行总线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。USB 是在 1994 年底由英特尔、康柏、IBM、Microsoft 等多家公司联合提出的。
USB 发展到现在已经有 USB1.0/1.1/2.0/3.0 等多个版本。目前用的最多的就是 USB1.1 和 USB2.0,USB3.0 目前已经开始普及。STM32F103 自带的 USB 符合 USB2.0 规范,不过 STM32F103 的 USB 都只能用来做设备,而不能用作主机。
标准 USB 共四根线组成,除 VCC/GND 外,另外为 D+,D-; 这两根数据线采用的是差分电压的方式进行数据传输的。在 USB 主机上,D-和 D+都是接了 15K 的电阻到低的,所以在没有设备接入的时候,D+、D-均是低电平。而在 USB 设备中,如果是高速设备,则会在 D+上接一个 1.5K 的电阻到 VCC,而如果是低速设备,则会在 D-上接一个 1.5K 的电阻到 VCC。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。
STM32F103 的 MCU 自带 USB 从控制器,符合 USB 规范的通信连接;PC 主机和微控制器之间的数据传输是通过共享一专用的数据缓冲区来完成的,该数据缓冲区能被 USB 外设直接访问。这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用 512 字节缓冲区(专用的 512 字节,和 CAN 共用),最多可用于 16 个单向或 8 个双向端点。USB 模块同 PC 主机通信,根据 USB 规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括 CRC 的生成和校验。
1.1 USB HID简介
USB HID类是USB设备的一个标准设备类,包括的设备非常多。HID类设备定义它属于人机交互操作的设备,用于控制计算机操作的一些方面,如USB鼠标、USB键盘、USB游戏操纵杆等。但HID设备类不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。
USB HID设备的一个好处就是操作系统自带了HID类的驱动程序,而用户无需去开发驱动程序,只要使用API系统调用即可完成通信。
官方资料:http://www.usb.org/developers/hidpage 其中包含最主要的两个说明:
- 《Device Class Definition for human interface device (HID)》【描述了 HID 的基本组成和格式】
- 《Universal Serial Bus HID Usage Tables》【对上面文档的补充,将各种不同的 HID 设备的基本组成列举出来】
二、新建工程
1. 打开 STM32CubeMX 软件,点击“新建工程” 
2. 选择 MCU 和封装 
3. 配置时钟 RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)  选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz 修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置 
4. 配置调试模式 非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器 SYS 设置,选择 Debug 为 Serial Wire 
三、USB
3.1 参数配置
在 Connectivity 中选择 USB 设置,并勾选 Device(FS) 激活 USB 设备。 
在 Parameter Settings 进行具体参数配置。 
- Speed:
Full Speed 12MBit/s(固定为全速) - Low Power: 默认
Disabled(在任何不需要使用usb模块的时候,通过写控制寄存器总可以使usb模块置于低功耗模式(low power mode ,suspend模式)。在这种模式下,不产生任何静态电流消耗,同时usb时钟也会减慢或停止。通过对usb线上数据传输的检测,可以在低功耗模式下唤醒usb模块。也可以将一特定的中断输入源直接连接到唤醒引脚上,以使系统能立即恢复正常的时钟系统,并支持直接启动或停止时钟系统。)
3.2 引脚配置
USB 的 DP 引脚必须上拉 1.5K 欧的电阻,电脑才能检测到 USB,否则检测不到。  查看野火指南者开发板原理图可知,需要将 PD6 配置为低电平使能 USB。  在右边图中找到 PD6 引脚,选择 GPIO_Output。  在GPIO output level 中选择 Low 输出低电平。 
3.3 配置时钟
选择 Clock Configuration,USB 时钟配置为 48MHz,且来源最好是外部晶振分频得到。 
3.4 USB Device
USB有主机(Host)和设备(Device)之分。一般电脑的USB接口为主机接口,而键盘、鼠标、U盘等则为设备。
部分型号的STM32芯片有1~2个USB接口。像STM32F103系列的有一个USB Device接口,STM32F407系列的有2个USB接口,既可以作为HOST,又可以作为Device,还可以作为OTG接口。
在 Middleware 中选择 USB_DEVICE 设置,在 Class For FS IP 设备类别选择 Human Interface Device Class(HID) 人机接口设备。  参数配置保持默认。 
- HID_FS_BINTERVAL(主机读取设备数据时间间隔):
0xA(STM32将数据发送到一个缓存区,而不是直接发送到上位机,而上位机每隔一端时间会来访问缓冲区读取数据。读取时间间隔过快会导致多次数据发送,过慢会导致数据丢失) - USBD_MAX_NUM_INTERFACES (Maximum number of supported interfaces)(最大支持HID设备的接口数):
1(应为现在只有键盘,所以1就行,如果是需要同时键盘,鼠标,手柄啥的,根据数量选择即可)
设备描述符保持默认。 
四、添加按键
4.1 GPIO配置
在 System Core 中选择 GPIO 设置。 
在右边图中找到按键对应引脚,选择 GPIO_Input。 
五、生成代码
输入项目名和项目路径  选择应用的 IDE 开发环境 MDK-ARM V5  每个外设生成独立的 ’.c/.h’ 文件 不勾:所有初始化代码都生成在 main.c 勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。  点击 GENERATE CODE 生成代码 
六、修改usbd_hid.c
打开工程文件夹Middlewares/USB_Device_Library下usbd_hid.c文件 
6.1 修改接口描述符(可跳过)
HID设备的描述符除了**5个USB的标准描述符(设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符)**外,还包括三个HID设备类特定的描述符:HID描述符、报告描述符(Report)、实体描述符(Physical)。
他们之间的层次关系如图: 
打开usbd_hid.c文件,找到USBD_HID_CfgFSDesc配置全速描述符数组定义处。
- 配置描述符
bNumInterfaces表示这个设备有多少个接口。 MaxPower 100 mA表示这个设备需要从总线上获取100mA电流。
__ALIGN_BEGIN static uint8_t USBD_HID_CfgFSDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
0x09,
USB_DESC_TYPE_CONFIGURATION,
USB_HID_CONFIG_DESC_SIZ,
0x00,
0x01,
0x01,
0x00,
0xE0,
0x32,
- 接口描述符
bInterfaceClass的值必须是 0x03 bInterfaceSubClass的值为 0 或 1, 为1表示HID设备是一个启动设备(BootDevice, 一般对PC机有意义,意思是BIOS启动时能识别您使用的HID设备,切只有标准鼠标或者键盘才能称为BootDevice),为0表示HID设备是操作系统启动厚才能识别使用的设备。
0x09,
USB_DESC_TYPE_INTERFACE,
0x00,
0x00,
0x01,
0x03,
0x01,
0x02,
0,
这里我们将bInterfaceProtocol的值改为1:(0 — NONE,1 — Keyboard(键盘),2 — Mouse (鼠标),3~255 Reserved) 
- HID描述符
HID描述符关联于接口描述符,因而如果一个设备只有一个接口描述符,则无论它有几个端点描述符,HID设备只有一个HID描述符。HID设备描述符主要描述HID规范的版本号, HID通信所使用的额外描述符,报告描述符的长度等。
0x09,
HID_DESCRIPTOR_TYPE,
0x11,
0x01,
0x00,
0x01,
0x22,
HID_MOUSE_REPORT_DESC_SIZE,
0x00,
下表为HID描述符的结构。
| 偏移量 | 域 | 大小(Byte) | 值 | 描述 |
|---|
| 0 | bLength | 1 | 数字 | 此描述符的长度,以字节为单位 | | 1 | bDescriptorType | 1 | 常量 | 描述符种类(此处 0x21为HID类) | | 2 | bcdHID | 2 | 数字 | HID规范版本号(BCD码),采用4个16进制的BCD格式编码 | | 4 | bCountryCode | 1 | 数字 | 硬件目的国家的识别码 | | 5 | bNumDescriptors | 1 | 数字 | 支持的附属描述符数目 | | 6 | bDescriptorType | 1 | 常量 | 0x21-HID描述符,0x22-报告描述符,0x23-实体描述符 | | 7 | wDescriptorLength | 2 | 数字 | 报告描述符的总长度 | | 9 | bDescriptorType | 1 | 常量 | 用于识别描述符类型的常量,使用有一个以上描述符的设备 | | 10 | wDescriptorLength | 2 | 数字 | 描述符总长度,使用在有一个以上描述符的设备 |
- 端点描述符
bEndpointAddress表示端点地址,表示当前这个接口所需要的端点资源,输入(相对于主机而言)端点最高位为1,输出(相对于主机而言)端点最高位为0。HID设备一般都是使用中断端点进行数据传输。 wMaxPacketSize表示该端点上数据传输的数量。 bInterval表示主机查询设备数据的时间间隔,如果设置的太长,则键盘输入延迟很高。
0x07,
USB_DESC_TYPE_ENDPOINT,
HID_EPIN_ADDR,
0x03,
HID_EPIN_SIZE,
0x00,
HID_FS_BINTERVAL,
};
6.2 修改报告描述符
下载 HID Descriptor Tool (DT) HID描述符工具: 官网下载:https://usb.org/sites/default/files/documents/dt2_4.zip 百度网盘:https://pan.baidu.com/s/1ayjdQtc7e9NWwYJqdp0pXA?pwd=4ghb 提取码:4ghb
打开 File——》Open...——》keybrd.hid  我们可以看到HID键盘的描述符情况: 
打开usbd_hid.c文件,找到HID_MOUSE_ReportDesc数组定义处(默认生产HID设备为Mouse,这里数组名不影响,只要里面的描述符是键盘的就行)。
0x05, 0x01,
0x09, 0x06,
0xa1, 0x01,
0x05, 0x07,
0x19, 0xe0,
0x29, 0xe7,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x08,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x03,
0x95, 0x05,
0x75, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x05,
0x91, 0x02,
0x95, 0x01,
0x75, 0x03,
0x91, 0x03,
0x95, 0x06,
0x75, 0x08,
0x15, 0x00,
0x25, 0x65,
0x05, 0x07,
0x19, 0x00,
0x29, 0x65,
0x81, 0x00,
0xc0,

6.3 修改报告描述符大小
打开usbd_hid.h文件,修改HID_MOUSE_REPORT_DESC_SIZE的值为63。 
七、修改main.c
添加头文件和USB设备句柄。
#include "usbd_hid.h"
extern USBD_HandleTypeDef hUsbDeviceFS;
添加一个数组变量,用于传输键盘参数的,Byte0是传控制键,Byte1是保留键,不用改;Byte3~byte7都可以存放传输的按键值。
uint8_t buffer[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
添加按键检测及传输键值到电脑。
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USB_DEVICE_Init();
while (1)
{
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0)
{
buffer[0] = 0x02;
buffer[2] = 0x04;
USBD_HID_SendReport(&hUsbDeviceFS, buffer, 8);
HAL_Delay(15);
buffer[0] = 0x00;
buffer[2] = 0x00;
USBD_HID_SendReport(&hUsbDeviceFS, buffer, 8);
HAL_Delay(15);
while(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0)
HAL_Delay(15);
}
}
}
 对应键值可和此文件中的HID Usage ID对应,是16进制(如字符‘a’对应键值为0x04) USB_HIDtoKBScanCodeTranslationTable.pdf 
八、查看效果
编译工程,下载到板子上,插上USB线连接到电脑上,识别出为键盘设备

**注意:**如果设备带有感叹号,则参考下面十、注意事项 
按下按键时在电脑上输入一个大写的‘A’。 
九、工程代码
链接:https://pan.baidu.com/s/1E7wwefhBNgCRKX1tgG0kxA?pwd=agm4 提取码:agm4
十、注意事项
用户代码要加在 USER CODE BEGIN N 和 USER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。 
如果USB端口出现感叹号设备无法启动的问题,可适当将堆改大,如0x400  
? 由 Leung 写于 2022 年 10 月 26 日
? 参考:STM32F042F6P6-USB设备开发最小系统之键盘示例 USB 协议分析之 HID 设备 关于STM32的USB设备库DIY机械键盘 STM32-USB学习系列(六):USB-HID键盘的实现以及键盘报文描述符的简介 DIY一个usb数字键盘(一) 基于stm32cubemx HAL库 usb hid协议 DIY一个usb数字键盘(二)逻辑部分 基于stm32cubemx HAL库 usb hid协议 STM32 基础系列教程 24 - USB_HID_key
|