全文目录
- 参考文章
- 创建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
的值必须是 0x03
bInterfaceSubClass
的值为 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描述符工具:
官网下载:http://usb.org/sites/default/files/documents/dt2_4.zip
百度网盘:http://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接口即可恢复正常。