全文目录
- 创建CubeMX工程
- 一、新建工程
- 二、USB
- 三、生成代码
- 四、了解usbd_hid.c
- 更改代码实现鼠标改键盘
- 键码值(keyCode)
- 补充
参考文章
STM32CubeMX学习笔记(45)——USB接口使用(HID鼠标)STM32-USB学习系列(六):USB-HID键盘的实现以及键盘报文描述符的简介HID设备(USB键鼠/扫码枪)转串口(UART)键盘键值及字符处理示例——CH9350本文对在以上三篇文章中引用的内容进行整合,并通过实践验证了各个步骤的可行性,增加了代码修改方面的相关细节
创建CubeMX工程
一、新建工程
- 打开 STM32CubeMX 软件,点击“新建工程”
- 选择 MCU 和封装
- 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置 - 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
二、USB
- 参数配置
在 Connectivity 中选择 USB 设置,并勾选 Device (FS) 激活 USB 设备。
“FS” stands for “Full Speed”. - 在 Parameter Settings 进行具体参数配置。
- Speed: Full Speed 12MBit/s(固定为全速)
Low Power: 默认 Disabled(在任何不需要使用usb模块的时候,通过写控制寄存器总可以使usb模块置于低功耗模式(low power mode ,suspend模式)。在这种模式下,不产生任何静态电流消耗,同时usb时钟也会减慢或停止。通过对usb线上数据传输的检测,可以在低功耗模式下唤醒usb模块。也可以将一特定的中断输入源直接连接到唤醒引脚上,以使系统能立即恢复正常的时钟系统,并支持直接启动或停止时钟系统。) - 引脚配置
USB 的 DP 引脚必须上拉 1.5K 欧的电阻,电脑才能检测到 USB,否则检测不到。 - 野火:需要将 PD6 配置为低电平使能 USB
- 在右边图中找到 PD6 引脚,选择 GPIO_Output。
- 在GPIO output level 中选择 Low 输出低电平。
- 正点原子:只需将排针4–6和3–5分别用跳线帽相连
- 由于本次需要实现USB HID keyboard,故需要使用开关KEY0~2。可参照以上的GPIO配置步骤将相应的引脚配置为GPIO_Input,Pull_up。
- 配置时钟
选择 Clock Configuration,USB 时钟配置为 48MHz,且来源最好是外部晶振分频得到。 - 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就行,如果是需要同时键盘,鼠标,手柄之类的,根据数量选择即可)
设备描述符保持默认。
三、生成代码
- 输入项目名和项目路径
- 选择应用的 IDE 开发环境 MDK-ARM V5
- 每个外设生成独立的 ’.c/.h’ 文件
不选:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。 - 点击 GENERATE CODE 生成代码
四、了解usbd_hid.c
打开工程文件夹Middlewares/USB_Device_Library
下usbd_hid.c
文件
- USB HID描述符
HID设备的描述符除了5个USB的标准描述符(设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符)外,还包括3个HID设备类特定的描述符:HID描述符、报告描述符(Report)、实体描述符(Physical)。
他们之间的层次关系如图:
打开usbd_hid.c
文件,找到USBD_HID_CfgFSDesc
配置全速(Full Speed)描述符数组定义处(由于配置USB设备时将其置为了全速,所以不用管USBD_HID_CfgHSDesc
(High Speed))
• 配置描述符bNumInterfaces
表示这个设备有多少个接口。MaxPower 100 mA
表示这个设备需要从总线上获取100mA电流。
/* USB HID device FS Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_CfgFSDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
0x09, /* bLength: Configuration Descriptor size */
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
USB_HID_CONFIG_DESC_SIZ,
/* wTotalLength: Bytes returned */
0x00,
0x01, /*bNumInterfaces: 1 interface*/
0x01, /*bConfigurationValue: Configuration value*/
0x00, /*iConfiguration: Index of string descriptor describing
the configuration*/
0xE0, /*bmAttributes: bus powered and Support Remote Wake-up */
0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/
• 接口描述符bInterfaceClass
的值必须是 0x03bInterfaceSubClass
的值为 0 或 1, 为1表示HID设备是一个启动设备(BootDevice, 一般对PC机有意义,意思是BIOS启动时能识别所使用的HID设备,且只有标准鼠标或者键盘才能称为BootDevice),为0表示HID设备是操作系统启动厚才能识别使用的设备。bInterfaceProtocol
的值为 2 表示鼠标接口:(0 — NONE,1 — Keyboard(键盘),2 — Mouse (鼠标),3~255 Reserved)
/************** Descriptor of Joystick Mouse interface ****************/
/* 09 */
0x09, /*bLength: Interface Descriptor size*/
USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
0x00, /*bInterfaceNumber: Number of Interface*/
0x00, /*bAlternateSetting: Alternate setting*/
0x01, /*bNumEndpoints*/
0x03, /*bInterfaceClass: HID*/
0x01, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
0x02, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
0, /*iInterface: Index of string descriptor*/
• HID描述符
HID描述符关联于接口描述符,因而如果一个设备只有一个接口描述符,则无论它有几个端点描述符,HID设备只有一个HID描述符。HID设备描述符主要描述HID规范的版本号, HID通信所使用的额外描述符,报告描述符的长度等。
/******************** Descriptor of Joystick Mouse HID ********************/
/* 18 */
0x09, /*bLength: HID Descriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x11, /*bcdHID: HID Class Spec release number*/
0x01,
0x00, /*bCountryCode: Hardware target country*/
0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/
0x22, /*bDescriptorType*/
HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
0x00,
• 端点描述符bEndpointAddress
表示端点地址,表示当前这个接口所需要的端点资源,输入(相对于主机而言)端点最高位为1,输出(相对于主机而言)端点最高位为0。HID设备一般都是使用中断端点进行数据传输。wMaxPacketSize
表示该端点上数据传输的数量。bInterval
表示主机查询设备数据的时间间隔,如果设置的太长,则键盘输入延迟很高。
- 报告描述符
下载 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...
→mouse.hid
(键盘应该使用keybrd.hid
,但本文为在程序中保留鼠标相关内容,故在mouse.hid
中直接进行修改) - 我们可以看到HID鼠标的描述符情况:
- 打开
usbd_hid.c
文件,找到HID_MOUSE_ReportDesc
数组定义处(默认生产HID设备为Mouse,所以需要增加键盘数组)。
更改代码实现鼠标改键盘
- 修改HID的接口描述符与报文描述符
注意:端点描述符不做修改!即 HID_EPIN_SIZE 还是0x04 (对应4字节大小,后面在发送键盘报文的时候,需要将键盘的报文大小修改为 4个字节的数组或者结构体)
在usbd_hid.h
中引入宏定义USE_KEYBOARD_HID
和HID_KEYBOARD_REPORT_DESC_SIZE
#define USE_KEYBOARD_HID 1 //WY: 1=enable; 0=disable
#define HID_EPIN_ADDR 0x81U
#define HID_EPIN_SIZE 0x04U
#define USB_HID_CONFIG_DESC_SIZ 34U
#define USB_HID_DESC_SIZ 9U
#define HID_MOUSE_REPORT_DESC_SIZE 74U
#define HID_KEYBOARD_REPORT_DESC_SIZE 63U //WY: for keyboard
在USBD_HID_CfgFSDesc
中修改
/************** Descriptor of Joystick Mouse interface ****************/
/* 09 */
0x09, /*bLength: Interface Descriptor size*/
USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
0x00, /*bInterfaceNumber: Number of Interface*/
0x00, /*bAlternateSetting: Alternate setting*/
0x01, /*bNumEndpoints*/
0x03, /*bInterfaceClass: HID*/
0x01, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
#if(USE_KEYBOARD_HID)
0x01, //WY: nInterfaceProtocol: keyboard
#else
0x02, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
#endif
0, /*iInterface: Index of string descriptor*/
/******************** Descriptor of Joystick Mouse HID ********************/
/* 18 */
0x09, /*bLength: HID Descriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x11, /*bcdHID: HID Class Spec release number*/
0x01,
0x00, /*bCountryCode: Hardware target country*/
0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/
0x22, /*bDescriptorType*/
#if(USE_KEYBOARD_HID)
HID_KEYBOARD_REPORT_DESC_SIZE,
#else
HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
#endif
0x00,
- 修改
USBD_HID_Setup
函数
在HID Setup的过程中会获取对应键盘报文描述符
case USB_REQ_GET_DESCRIPTOR:
if (req->wValue >> 8 == HID_REPORT_DESC)
{
#if(USE_KEYBOARD_HID)
len = MIN(HID_KEYBOARD_REPORT_DESC_SIZE, req->wLength);
pbuf = HID_KEYBOARD_ReportDesc;
#else
len = MIN(HID_MOUSE_REPORT_DESC_SIZE, req->wLength);
pbuf = HID_MOUSE_ReportDesc;
#endif
}
- 添加以及修改键盘报文描述符
注意:修改部分为最后面按键输入描述部分,将后面6个按键缩减为2个按键(即:从6个字节缩减到2个字节),配合上前面2个字节的输入报告,正好是4个字节,刚好符合原来HID端点描述符一次最多发送的大小
将原有HID_MOUSE_ReportDesc
的内容注释掉,并在其后增加以下定义
__ALIGN_BEGIN static uint8_t HID_KEYBOARD_ReportDesc[HID_KEYBOARD_REPORT_DESC_SIZE] __ALIGN_END =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard) /* 用途为键盘 */
0xa1, 0x01, // COLLECTION (Application) /* 表示应用结合,必须以END_COLLECTION来结束 */
0x05, 0x07, // USAGE_PAGE (Keyboard) /* 用途页为按键 */
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) /* 用途最小值 左Ctrl */
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) /* 用途最大值 右GUI */
0x15, 0x00, // LOGICAL_MINIMUM (0) /* 逻辑最小值 0 */
0x25, 0x01, // LOGICAL_MAXIMUM (1) /* 逻辑最大值 1 */
0x75, 0x01, // REPORT_SIZE (1) /* 报告位大小(这个字段的宽度为1bit) */
0x95, 0x08, // REPORT_COUNT (8) /* 输入报告第一个字节(报告位大小 8bit) */
0x81, 0x02, // INPUT (Data,Var,Abs) /* 报告为输入用 , 从左ctrl到右GUI 8bit刚好构成1个字节*/
0x95, 0x01, // REPORT_COUNT (1) /* 报告位数量 1个 */
0x75, 0x08, // REPORT_SIZE (8) /* 输入报告的第二给字节(报告位大小 8bit) */
0x81, 0x03, // INPUT (Cnst,Var,Abs) /* 输入用的保留位,设备必须返回0 */
0x95, 0x05, // REPORT_COUNT (5) /* 报告位数量 5个 */
0x75, 0x01, // REPORT_SIZE (1) /* 报告位大小,1bit */
0x05, 0x08, // USAGE_PAGE (LEDs) /* 用途为LED */
0x19, 0x01, // USAGE_MINIMUM (Num Lock) /* 用途最小值 NUM Lock LED灯 */
0x29, 0x05, // USAGE_MAXIMUM (Kana) /* 用途最大值 Kana 灯 */
0x91, 0x02, // OUTPUT (Data,Var,Abs) /* 输出用途,用于控制LED等 */
0x95, 0x01, // REPORT_COUNT (1) /* 报告位数量 1个 */
0x75, 0x03, // REPORT_SIZE (3) /* 报告位大小 3bit */
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)/* 用于字节补齐,跟前面5个bit进行补齐 */
0x95, 0x02, // REPORT_COUNT (6) /* 报告位数量 6个*/
0x75, 0x08, // REPORT_SIZE (8) /* 报告位大小 8bit */
0x15, 0x00, // LOGICAL_MINIMUM (0) /* 逻辑最小值0 */
0x25, 0xFF, // LOGICAL_MAXIMUM (255) /* 逻辑最大值255 */
0x05, 0x07, // USAGE_PAGE (Keyboard) /* 用途页为按键 */
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) /* 使用值最小为0 */
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) /* 使用值最大为65 */
0x81, 0x00, // INPUT (Data,Ary,Abs) /* 输入用,变量,数组,绝对值 */
0xc0 /* END_COLLECTION */
};
键码值(keyCode)
- 以键盘为例(扫码枪一般模拟键盘输入)报文示例如下:
报文前三个字节的固定内容可自动生成,故代码中只需通过后8个字节传递正确的键码值,例如在main.c
中定义:
uint8_t txbuffer[8]= {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
uint8_t sendbuffer_Enter[8]={0x00,0x00,0x28,0x00,0x00,0x00,0x00,0x00};
uint8_t sendbuffer_Aa[8]={0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00};
uint8_t sendbuffer_Space[8]={0x00,0x00,0x2C,0x00,0x00,0x00,0x00,0x00};
发送报文使用USBD_HID_SendReport
函数,为使代码友好易读,故在usb_device.c
中对此函数进行封装,以便在main
函数中直接调用:
uint8_t MX_USB_DEVICE_Send(uint8_t *sendbuffer)
{
return USBD_HID_SendReport(&hUsbDeviceFS,sendbuffer,8); //Full speed
}
- 全键盘码值表如下提供参考:
//Keybord keyvalue define
#define KEY_NULL 0x00 // NULL
#define KEY_A 0x04 // A
#define KEY_B 0x05 // B
#define KEY_C 0x06 // C
#define KEY_D 0x07 // D
#define KEY_E 0x08 // E
#define KEY_F 0x09 // F
#define KEY_G 0x0A // G
#define KEY_H 0x0B // H
#define KEY_I 0x0C // I
#define KEY_J 0x0D // J
#define KEY_K 0x0E // K
#define KEY_L 0x0F // L
#define KEY_M 0x10 // M
#define KEY_N 0x11 // N
#define KEY_O 0x12 // O
#define KEY_P 0x13 // P
#define KEY_Q 0x14 // Q
#define KEY_R 0x15 // R
#define KEY_S 0x16 // S
#define KEY_T 0x17 // T
#define KEY_U 0x18 // U
#define KEY_V 0x19 // V
#define KEY_W 0x1A // W
#define KEY_X 0x1B // X
#define KEY_Y 0x1C // Y
#define KEY_Z 0x1D // Z
#define KEY_1 0x1E // 1 and !
#define KEY_2 0x1F // 2 and @
#define KEY_3 0x20 // 3 and #
#define KEY_4 0x21 // 4 and $
#define KEY_5 0x22 // 5 and %
#define KEY_6 0x23 // 6 and ^
#define KEY_7 0x24 // 7 and &
#define KEY_8 0x25 // 8 and *
#define KEY_9 0x26 // 9 and (
#define KEY_0 0x27 // 10 and )
#define KEY_ENTER 0x28 // ENTER
#define KEY_ESC 0x29 // ESC
#define KEY_BACKSPACE 0x2A // BACKSPACE
#define KEY_TAB 0x2B // TAB
#define KEY_SPACE 0x2C // SPACE
#define KEY_SUB 0x2D // - and _
#define KEY_EQUAL 0x2E // = and +
#define KEY_LEFT_BRACKET 0x2F // [ and {
#define KEY_RIGHT_BRACKET 0x30 // ] and }
#define KEY_VERTICAL_LINE 0x31 // "\" and |
#define KEY_WAVE 0x32 // ` and ~
#define KEY_SEMICOLON 0x33 // ; and :
#define KEY_QUOTE 0x34 // ' and "
#define KEY_THROW 0x35 // ~ and `
#define KEY_COMMA 0x36 // , and <
#define KEY_DOT 0x37 // . and >
#define KEY_QUESTION 0x38 // / and ?
#define KEY_CAPS_LOCK 0x39 // CAPS
#define KEY_F1 0x3A
#define KEY_F2 0x3B
#define KEY_F3 0x3C
#define KEY_F4 0x3D
#define KEY_F5 0x3E
#define KEY_F6 0x3F
#define KEY_F7 0x40
#define KEY_F8 0x41
#define KEY_F9 0x42
#define KEY_F10 0x43
#define KEY_F11 0x44
#define KEY_F12 0x45
#define KEY_PRT_SCR 0x46
#define KEY_SCOLL_LOCK 0x47
#define KEY_PAUSE 0x48
#define KEY_INS 0x49
#define KEY_HOME 0x4A
#define KEY_PAGEUP 0x4B
#define KEY_DEL 0x4C
#define KEY_END 0x4D
#define KEY_PAGEDOWN 0x4E
#define KEY_RIGHT_ARROW 0x4F
#define KEY_LEFT_ARROW 0x50
#define KEY_DOWN_ARROW 0x51
#define KEY_UP_ARROW 0x52
//Num Pad
#define KEY_PAD_NUMLOCK 0x53
#define KEY_PAD_DIV 0x54
#define KEY_PAD_MUL 0x55
#define KEY_PAD_SUB 0x56
#define KEY_PAD_ADD 0x57
#define KEY_PAD_ENTER 0x58
#define KEY_PAD_1 0x59
#define KEY_PAD_2 0x5A
#define KEY_PAD_3 0x5B
#define KEY_PAD_4 0x5C
#define KEY_PAD_5 0x5D
#define KEY_PAD_6 0x5E
#define KEY_PAD_7 0x5F
#define KEY_PAD_8 0x60
#define KEY_PAD_9 0x61
#define KEY_PAD_0 0x62
#define KEY_PAD_DOT 0x63
#define KEY_PRESSED 0x00
#define KEY_RELEASED 0x01
// Control
#define KEY_LCTRL 0xE0 // left ctrl // #define KEY_LCTRL 0x01
#define KEY_LALT 0xE2 // left Alt // #define KEY_LALT 0x04
#define KEY_LSHFIT 0xE1 // left Shift // #define KEY_LSHFIT 0x02
#define KEY_LWIN 0xE3 // left windows // #define KEY_LWIN 0x08
#define KEY_RWIN 0xE7 // right windows // #define KEY_RWIN 0x80
#define KEY_RSHIFT 0xE5 // right Shift // #define KEY_RSHIFT 0x20
#define KEY_RALT 0xE6 // right Alt // #define KEY_RALT 0x40
#define KEY_RCTRL 0xE4 // right Ctrl // #define KEY_RCTRL 0x10
#define KEY_APP 0x65 // Application // #define KEY_APP 0x65
#define KEY_K14 0x89 // international key
#define KEY_KR_L 0x91
#define KEY_K107 0x85
#define KEY_K45 0x64
#define KEY_K42 0x32
#define KEY_K131 0x8b
#define KEY_K132 0x8a
#define KEY_K133 0x88
#define KEY_K56 0x87
#define KEY_KR_R 0x90
补充
电脑设备管理器显示“未知USB设备(设备描述符请求失败)”时:
个人经验:
最好在CubeMX程序第一次生成之后(无改动)确认是否能识别设备,若此时识别成功,则之后再遇“未知USB设备(设备描述符请求失败)”问题时,重新插拔USB接口即可恢复正常。