嵌入式开发|使用stm32配置自定义的HID设备

STM32USB设备设计步骤: 申明:文章为原创性文章,转载请申明!!!
本文不对USB协议进行讲述,对于usb协议,我建议大家静下心好好去看下对应的资料,USB协议不是一个简单的协议,不是一两天就能弄透彻的!对usb协议零基础者,建议大家可以看《圈圈教你玩USB》这本书的前面一部分将USB协议的,去网上搜能搜到电子版的!
1. 使用CubeMX生成一个HID工程(注意不是Custom HID),默认生成的为模拟鼠标工程,我们需要在此基础上修改成我们自己的HID设备。 Cubemx配置步骤:

  1. System Core->RCC->High Speed Clock: Crystal/Ceramic Resonator
  2. System Core->SYS: Serial Wire
  3. Connectivity->USB_OTG_FS->Mode: Device_Only
  4. Middleware->USB_DEVICE->Class For FS IP: Human Interface Device Class (HID)
  5. 然后设置下System Core->NVIC中的USB OTG FS global interrupt 优先级
  6. 再配置时钟 确保USB时钟为48MHZ即可
  7. 之后可以生成代码了
    至此,模板已经建立完成!!!!
2. 打开工程,编译下载,保证你硬件没问题,插上电脑之后,在设备管理器中便可以看到多了一个USB输入设备! 3. 在main.c中添加以下应用代码:
#include "usbd_hid.h" extern USBD_HandleTypeDef hUsbDeviceFS;

修改main函数
int main(void) { struct mouseHID_t { uint8_t buttons; int8_t x; int8_t y; int8_t wheel; }; struct mouseHID_t mouseHID; mouseHID.buttons = 0; mouseHID.x = 10; mouseHID.y = 0; mouseHID.wheel = 0; HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USB_DEVICE_Init(); while (1) { USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&mouseHID, sizeof(struct mouseHID_t)); HAL_Delay(1000); }

编译下载,然后把板子插上电脑,不动你的鼠标,你会看到鼠标每隔1s会往右边移动一点点,这就是模拟鼠标功能!
至此,我们的模板已经验证完成,接下来就是在此基础上修改成我们所需要的自定义的设备了!
为什么要该呢?
因为此模板有局限性,这是一个模拟鼠标的模板,当然也就只能实现基础的鼠标的功能,但是我们往往做成USB设备,不会只弄个鼠标,甚至有的只是借助usb来实现通讯,那么使用鼠标这个肯定是不行的,而且此模板一次只能发送4个字节的数据,4个字节怎么可能满足我们的需求!
4. 熟悉工程的结构
与usb有关的文件有如下文件:

**stm32f1xx.it.c 存放usb中断服务函数
usb_device.c USB初始化
usbd_desc.c 重点存放 USB设备描述符
usbd_conf.c 发送接收、初始化、回调函数 等等底层函数 与HAL库结合
usbd_core.c 内核相关
usbd_ctlreq.c 控制请求
usbd_ioreq.c 输入输出请求
usbd_hid.c HID配置
**
其中对于我们使用最为重要的是
usbd_desc.c
usbd_hid.c
我们主要修改这两个函数内的描述符,其他usb协议逻辑啥的我们基本不需要动
5. 修改设备描述符
usbd_desc.c文件内存放了设备描述符的数组

__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { 0x12,/*bLength */ USB_DESC_TYPE_DEVICE,/*bDescriptorType*/ 0x00,/*bcdUSB */ 0x02, 0x00,/*bDeviceClass*/ 0x00,/*bDeviceSubClass*/ 0x00,/*bDeviceProtocol*/ USB_MAX_EP0_SIZE,/*bMaxPacketSize*/ LOBYTE(USBD_VID),/*idVendor*/ HIBYTE(USBD_VID),/*idVendor*/ LOBYTE(USBD_PID_FS),/*idProduct*/ HIBYTE(USBD_PID_FS),/*idProduct*/ 0x00,/*bcdDevice rel. 2.00*/ 0x02, USBD_IDX_MFC_STR,/*Index of manufacturerstring*/ USBD_IDX_PRODUCT_STR,/*Index of product string*/ USBD_IDX_SERIAL_STR,/*Index of serial number string*/ USBD_MAX_NUM_CONFIGURATION/*bNumConfigurations*/ };

上面数组中使用到了的宏定义
#defineUSB_DESC_TYPE_DEVICE0x01U #define USB_MAX_EP0_SIZE64U #define USBD_VID1155 #define USBD_LANGID_STRING1033 #define USBD_MANUFACTURER_STRING"STMicroelectronics" #define USBD_PID_FS22315 #define USBD_PRODUCT_STRING_FS"STM32 Human interface" #define USBD_CONFIGURATION_STRING_FS"HID Config" #define USBD_INTERFACE_STRING_FS"HID Interface" #defineUSBD_IDX_MFC_STR0x01U #defineUSBD_IDX_PRODUCT_STR0x02U #defineUSBD_IDX_SERIAL_STR0x03U

【嵌入式开发|使用stm32配置自定义的HID设备】我们一般修改usb设备的厂家ID,也就是对应的USBD_VID为0xff,0xff应该没有哪个厂家申请吧
#define USBD_VID0xFF

厂家ID是需要像USB协会申请的,要交保护会,有的id被别的公司申请了,如果你不小心用了别人的,电脑识别之后会自动加载别人厂家对应的驱动程序,有可能导致你的实验失败
6.修改usb_hid.c,打造我们自己的特定设备 usb_hid.c这个函数有点坑的,在这个函数中有几个配置数组实际是没有使用到的,但是你不要去删除,他被其他函数调用,别管就好了,我们需要配置只有两个数组
/* USB HID device FS Configuration Descriptor */ __ALIGN_BEGIN static uint8_t USBD_HID_CfgFSDesc[USB_HID_CONFIG_DESC_SIZ]__ALIGN_END = { ... }//USB 鼠标端口描述符 __ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]__ALIGN_END = { ... }

只需要修改上述两个数组的内容就可以了,主要就是这两个数组,首先给大家看下我改成了什么样子吧
/* 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 描述符类型 配置描述符为0x02*/ 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 字符串索引值 为0表示没有字符串*/ 0x80,/*bmAttributes: bus ext-powered and not Support Remote Wake-up D7保留必须为1 D6为0不是自供电 D5为0不支持远程唤醒 D4-D0保留设置为0*/ 0x32,/*MaxPower 100 mA: this current is used for detecting Vbus 设备需要获取的电流值 单位2mA*//****************接口描述符****************/ /************** Descriptor of Custom device interface ****************/ 0x09,/* bLength 描述符长度*/ USB_DESC_TYPE_INTERFACE,/* bDescriptorType 描述符类型 接口描述符为0x04 */ 0x00,/* bInterfaceNumber 接口的编号 从0开始 多个接口时接口编号应不一样 */ 0x00,/* bAlternateSetting 接口备用编号 很少使用 一般为0 */ 0x02,/* bNumEndpoints 使用的端点数(不包含端点0) */ 0x03,/* bInterfaceClass: HID 接口使用的类 HID类为0x03*/ 0x00,/* bInterfaceSubClass : 1=BOOT, 0=no boot 接口使用的子类 */ 0x00,/* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse 接口使用的协议 */ 0x00,/* iInterface 接口描述字符串索引值 0表示没有字符串*//****************HID描述符****************/ /************** Descriptor of HID ************************************/ /* 18 */ 0x09,/*bLength: HID Descriptor size 描述符长度*/ HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID 描述符类型 HID描述符为0x21*/ 0x11,/*bcdHID: HID Class Spec release number HID使用的协议版本 1.11 */ 0x01, 0x00,/*bCountryCode: Hardware target country 设备适应的国家*/ 0x01,/*bNumDescriptors: Number of HID class descriptors to follow 下级 HID描述符 的数量(可以是报告描述符也可以是物理描述符,但是至少有一个报告描述符,我们这个只有一个就是下面的鼠标报告描述符数组)*/ 0x22,/*bDescriptorType 下级描述符类型 0x22代表报告描述符*/ HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor 下级描述符的长度 注意大小为2个字节,所以后面有个0x00*/ 0x00,#if 0 //此为工程模板的鼠标配置 因为我们自己写了一个所以这个我们可以不用管了,放在这参考,可供和上面的比对 /************** 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*/ 0x00,/*bInterfaceSubClass : 1=BOOT, 0=no boot*/ 0x00,/*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ 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*/ HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/ 0x00, #endif/****************端点描述符****************/ /******************** Descriptor of Mouse endpoint ********************/ /* 27 输入端点描述符*/ 0x07,/*bLength: Endpoint Descriptor size 描述符的长度*/ USB_DESC_TYPE_ENDPOINT, /*bDescriptorType: 描述符的类型 端点描述符为0x05*/ HID_EPIN_ADDR,/*bEndpointAddress: Endpoint Address (IN) 端点地址 D7为端点传输方向,1输入0输出 D3-D0为端点号 D6-D4保留*/ 0x03,/*bmAttributes: Interrupt endpoint 端点属性 此处为中断传输*/ HID_EPIN_SIZE, /*wMaxPacketSize:端点支持的最大包长大小 长度2个字节*/ 0x00, HID_FS_BINTERVAL,/*bInterval: Polling Interval 对于中断传输表示端点的查询时间,其他传输请参考协议*//* 34 输出端点描述符*/ 0x07,/*bLength: Endpoint Descriptor size描述符的长度 */ USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:描述符的类型 端点描述符为0x05*/ HID_EPOUT_ADDR,/*bEndpointAddress: Endpoint Address (OUT)端点地址 D7为端点传输方向,1输入0输出 D3-D0为端点号 D6-D4保留 */ 0x03,/*bmAttributes: Interrupt endpoint端点属性 此处为中断传输*/ HID_EPIN_SIZE, /*wMaxPacketSize:端点支持的最大包长大小 长度2个字节*/ 0x00, HID_FS_BINTERVAL,/*bInterval: Polling Interval对于中断传输表示端点的查询时间,其他传输请参考协议 */ }; //USB 鼠标报告描述符 __ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]__ALIGN_END = { /* note: use "HID Descriptor Tool" */#if 0 //自定义输入设备 只具有一个输入端口 /* Raw Data -----------------------------Item Tag(value)------------------------------------ */ 0x05, 0x0C,/* Usage Page (Consumer Devices)*/ 0x09, 0x01,/* Usage (Consumer Control)*/ 0xA1, 0x01,/* Collection (Application)*/ 0x09, 0x01,/*Usage (Consumer Control)*/ 0x15, 0x00,/*Logical Minimum (0)*/ 0x25, 0xff,/*Logical Maximum (-1)*/ 0x75, 0x08,/*Report Size (8)*/ 0x95, 40,/*Report Count (40)*/ 0x81, 0x02,/*Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit)*/ 0xC0/* End Collection*/ #endif //输入输出设备 具有一个输入端口一个输出端口 /* note: use "HID Descriptor Tool" */ /* Raw Data -----------------------------Item Tag(value)------------------------------------ */ 0x05, 0x0C,/* Usage Page (Consumer Devices)*/ 0x09, 0x01,/* Usage (Consumer Control)*/ 0xA1, 0x01,/* Collection (Application)*/ 0x09, 0x01,/*Usage (Consumer Control)*/ 0x15, 0x00,/*Logical Minimum (0)*/ 0x25, 0xff,/*Logical Maximum (-1)*/ 0x75, 0x08,/*Report Size (8)*/ 0x95, 40,/*Report Count (40)*/ 0x81, 0x02,/*Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) */ 0x09, 0x01,/*Usage (Consumer Control)*/ 0x15, 0x00,/*Logical Minimum (0)*/ 0x25, 0xff,/*Logical Maximum (-1)*/ 0x75, 0x08,/*Report Size (8)*/ 0x95, 40,/*Report Count (40)*/ 0x91, 0x02,/* Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) */ 0xC0/* End Collection*///0x05,0x01, // 0x09,0x02, // 0xA1,0x01, // 0x09,0x01,// 0xA1,0x00, // 0x05,0x09, // 0x19,0x01, // 0x29,0x03,// 0x15,0x00, // 0x25,0x01, // 0x95,0x03, // 0x75,0x01,// 0x81,0x02, // 0x95,0x01, // 0x75,0x05, // 0x81,0x01,// 0x05,0x01, // 0x09,0x30, // 0x09,0x31, // 0x09,0x38,// 0x15,0x81, // 0x25,0x7F, // 0x75,0x08, // 0x95,0x03,// 0x81,0x06, // 0xC0,0x09, // 0x3c,0x05, // 0xff,0x09,// 0x01,0x15, // 0x00,0x25, // 0x01,0x75, // 0x01,0x95,// 0x02,0xb1, // 0x22,0x75, // 0x06,0x95, // 0x01,0xb1,// 0x01,0xc0 };

特别注意有两个申明需要修改!
/* USB HID device FS Configuration Descriptor */ __ALIGN_BEGIN static uint8_t USBD_HID_CfgFSDesc[USB_HID_CONFIG_DESC_SIZ]__ALIGN_END = { ... }

数组中配置 的时候使用的
#define HID_EPIN_ADDR0x81U #define HID_EPOUT_ADDR0x01U #define HID_EPIN_SIZE64//0x05U/* 最大的包数据大小 */#define USB_HID_CONFIG_DESC_SIZ41U//34U/*usb配置描述符的大小*/ #define USB_HID_DESC_SIZ9U #define HID_MOUSE_REPORT_DESC_SIZE31U//19U//74U

USB_HID_CONFIG_DESC_SIZ用来描述usb配置描述符的大小
HID_EPIN_SIZE用来设置最大的包数据大小,特别特别注意这个,这个是用来设置端点支持的最大包长度,当我们想要设置我们一次传输的数据量有多少时,首先需要设置HID_EPIN_SIZE的大小大于*最大的一包数据的长度,然后在HID描述符中配置端点的数据大小。*
修改完配置描述符之后,还需要制作我们特定的HID报告描述符
//USB 鼠标端口描述符 __ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]__ALIGN_END = { ... }

函数名可以不改,修改函数内部值即可,但是内部的数据怎么来呢?
这个就需要借助其他工具了,一般都是使用HID Descriptor Tool来自动生成,
软件怎么使用,大家网上查查吧,或者参考B站上的这个视频
HID怎么使用B站上的链接
,怎么配可以参考下面的照片
嵌入式开发|使用stm32配置自定义的HID设备
文章图片

下面是我的配置
嵌入式开发|使用stm32配置自定义的HID设备
文章图片

特别注意那个report_count的值,一定要小于我们前面说的那个HID_EPIN_SIZE
至此,我们自定义的HID设备就打造完成了,然后大家可以在main.c调用发送函数,然后把设备接在电脑上,电脑上安装一个USBlyzer监视下数据,如下所示
嵌入式开发|使用stm32配置自定义的HID设备
文章图片

我这只发了四个数据,当然发多个数据肯定是没问题的了
设备描述符 嵌入式开发|使用stm32配置自定义的HID设备
文章图片

嵌入式开发|使用stm32配置自定义的HID设备
文章图片

嵌入式开发|使用stm32配置自定义的HID设备
文章图片

配置描述符 嵌入式开发|使用stm32配置自定义的HID设备
文章图片

嵌入式开发|使用stm32配置自定义的HID设备
文章图片

接口描述符 嵌入式开发|使用stm32配置自定义的HID设备
文章图片

HID描述符(不是HID报告描述符) 嵌入式开发|使用stm32配置自定义的HID设备
文章图片

端点描述符 嵌入式开发|使用stm32配置自定义的HID设备
文章图片

HID报告描述符 使用HID Descriptor Tool来自动生成
参考
嵌入式开发|使用stm32配置自定义的HID设备
文章图片

下面是我的配置
嵌入式开发|使用stm32配置自定义的HID设备
文章图片

    推荐阅读