基于STM32CubeMX使用USB_HID(以DIY XBOX手柄为例)
华工RobotIC实验室
2024-01-31
DIY
单片机
手柄
1. USB_HID简介2. CubeMX配置3. 描述符简介与代码编写4. XBOX手柄按键解码 一、USB_UID简介HID全称是Human Interface Device,人机交互设备。我们生活中所有的有线外设,和使用2.4G收发器的无线外设,都是通过该协议与电脑通讯的。比如鼠标、键盘、手柄等。但需要指出的是,蓝牙耳机、蓝牙鼠标等基于蓝牙的无线外设使用的是蓝牙HID协议,与本文讨论的不同。外设通过USB_HID协议告诉电脑,我是什么、我有什么功能、我传来的信号是什么含义。在设备插入电脑时,其会向电脑发送一系列“描述符”(Description),描述自身的方方面面,此时“设备与打印机”界面便会弹出设备图标,表明该其已被电脑识别。 在识别完成后,设备开始与电脑正常通讯,通讯的数据被打包,并不断传给电脑。这个“包”被称作“报表”(Report)。这是一串在外人看来毫无意义的二进制数据,它的长度、它表达的含义,均由你在“报表描述符”(Report Description)中自定义。报表就好像密码,而报表描述符是密码本,电脑会对照报表描述符,将报表翻译成一系列指令。可以看出,该协议极为复杂,想要完全了解它很困难,协议中每个描述符都能写出比此文还长的文章。因此,本文旨在从DIY的角度,利用CubeMX与HAL库,跳过协议原文,用STM32做出你想要的外设。本文使用STM32F103C8T6演示,侧重于USB_HID协议的代码编写,不涉及手柄硬件实现与外壳建模。 二、CubeMX配置1.使能高速晶振 打开串口调试 2.打开USB,勾选Device(FS),FS:Full Speed 3.配置时钟树 4.打开USB_DEVICE,选择Human Interface Device Class(HID) 5.打开任意一个GPIO (PB12),方便检验代码 下图为引脚定义与接线方式 6.勾选“生成.c/.h文件” 7.命名工程、选择IDE与版本、生成Keil代码 三、描述符简介与代码编写在写代码前需要说明的是,未编译前的代码中不包含.h文件,因此需要在进行下一步之前编译文件。当.c文件前面出现小加号的时候就说明成功了。 (1) 设备描述符 Device Description该描述符负责向电脑提供如下信息:VID(供应商识别码)、PID(产品识别码)、设备支持的语言、制造商名称、产品类型、使用的USB版本号等。文件路径如下图,文件名usbd_desc.c 仅需要在文件中修改如下代码,即可被电脑识别为XBOX手柄:#define USBD_VID 1118 //供应商识别码#define USBD_LANGID_STRING 1033 //语言识别码(1033代表英文 5124代表中文))#define USBD_MANUFACTURER_STRING "@Microsoft" //制造商#define USBD_PID_FS 654 //产品识别码#define USBD_PRODUCT_STRING_FS "Controller" //产品类型#define USBD_CONFIGURATION_STRING_FS "Custom HID Config"#define USBD_INTERFACE_STRING_FS "HID Interface" 可以看到,仅仅修改了上述代码,电脑就已经把它识别成了XBOX手柄,但电脑还不认为这是一个游戏控制器,这个设备还不能向电脑发送任何有效指令。 Plus:如果想要DIY其他外设,下图是查看设备VID与PID的方法 (2) HID结构体接下来要修改的代码,均在usbd_hid.c与usbd_hid.h文件中,路径见下图 在修改其他配置描述符之前,我们先要对HID结构体进行一些修改,在这个结构体中,记录了stm32的HID功能如何初始化、如何启动、如何接收信息等等。以下是这个结构体默认时的样子:USBD_ClassTypeDef USBD_HID ={ USBD_HID_Init, USBD_HID_DeInit, USBD_HID_Setup, NULL, /*EP0_TxSent*/ NULL, /*EP0_RxReady*/ USBD_HID_DataIn, /*DataIn*/ NULL, /*DataOut*/ NULL, /*SOF */ NULL, NULL, USBD_HID_GetHSCfgDesc, USBD_HID_GetFSCfgDesc, USBD_HID_GetOtherSpeedCfgDesc, USBD_HID_GetDeviceQualifierDesc,};我们需要把下面三行代码全部修改成USBD_HID_GetFSCfgDesc, USBD_HID_GetHSCfgDesc, USBD_HID_GetFSCfgDesc, USBD_HID_GetOtherSpeedCfgDesc,即取消High Speed;Other Speed这两种传输速度,全部改成以Full Speed传输。USB1.1支持Full Speed,速度为12Mbps;USB2.0支持High Speed,速度为480Mbps。因为stm32f103只支持12Mbps,所以要把这个结构体中的传输速度全部改成Full Speed。 (3) 配置描述符 Configuration Description配置描述符负责向电脑描述这个设备有多少个接口,每个接口有什么端点。HAL库提供了三种速度下的配置描述符,即Full Speed,High Speed,Other Speed,他们的名称如下:__ALIGN_BEGIN static uint8_t USBD_HID_CfgFSDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END =__ALIGN_BEGIN static uint8_t USBD_HID_CfgHSDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END =__ALIGN_BEGIN static uint8_t USBD_HID_OtherSpeedCfgDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END =我们只需要修改Full Speed的配置描述符,其他保持默认即可(删掉也行)。代码来自国外大佬用逻辑分析仪抓出的原版XBOX手柄的配置描述符,具体每行代码有什么含义,可能只有微软公司自己知道,我们直接复制粘贴就好啦。__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,0x04,0x01,0x00,0xA0,0xFA,0x09, USB_DESC_TYPE_INTERFACE, 0x00, 0x00,0x02,0xFF,0x5D,0x01,0x00,0x11,0x21, 0x10,0x01,0x01,0x25,0x81,0x14,0x03,0x03, 0x03,0x04,0x13,0x02,0x08,0x03,0x03,0x07, USB_DESC_TYPE_ENDPOINT, 0x81,0x03,0x20,0x00, 0x0A,0x07, USB_DESC_TYPE_ENDPOINT, 0x02,0x03,0x20,0x00, 0x08,0x09, USB_DESC_TYPE_INTERFACE, 0x01,0x00,0x02,0xFF,0x5D,0x01,0x00,0x1B,0x21,0x00,0x01,0x01,0x01,0x83,0x40,0x01,0x04,0x20,0x16,0x85,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x07, USB_DESC_TYPE_ENDPOINT, 0x83,0x03,0x20,0x00, 0x02,0x07, USB_DESC_TYPE_ENDPOINT, 0x04,0x03,0x20,0x00,0x04,0x09, USB_DESC_TYPE_INTERFACE, 0x02,0x00,0x01,0xFF,0x5D,0x02,0x00,0x09, 0x21, 0x00,0x01,0x01,0x22,0x86,0x07,0x00,0x07, USB_DESC_TYPE_ENDPOINT, 0x86,0x03,0x20, 0x00,0x10,0x09, USB_DESC_TYPE_INTERFACE,0x03,0x00,0x00,0xFF,0xFD,0x13,0x04,0x06,0x41,0x00,0x01,0x01,0x03,};在做好上述修改后,我们还需要修改配置描述符的长度,他的长度记录在“USB_HID_CONFIG_DESC_SIZ”中。我们要进入usbd_hid.h文件中,把它的值从34U改为139U。 如果我们想DIY别的外设,可以去网上找对应外设的配置描述符。HAL库提供的所有描述符代码都是鼠标的,而键盘的描述符网上有很多资料,自己去找就好。 (4) 报表描述符 Report Description报表描述符的作用是向电脑提供“密码本”,让电脑能够翻译设备发来的报表(Report)。然而XBOX手柄的报表描述符极为特殊,它早已被微软写好,不能被我们自定义。无论我们在程序中如何修改报表描述符,都无法增加或减少手柄的功能,和它发送的报表的格式。代码替换如下:__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END ={0x00, 0xC0,}; 0x00代表不包含任何含义 0xC0代表此集合终止我们还需要修改报表描述符的长度,其记录在“HID_MOUSE_REPORT_DESC_SIZE”中,位于usbd_hid.h,从74U修改为2U,修改方法如下图 (5) 发送报表准备工作已经完成,接下来可以在主函数中发送报表了。主函数需要引用两个头文件 “usbd_hid.h” “usbd_desc.h”接着需要声明结构体,并创建一个数组来存储报表信息,数组长度为14,报表中信息的含义详见下一节 “XBOX手柄报表解码”。extern USBD_HandleTypeDef hUsbDeviceFS;uint8_t TXData[14]={0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};注意:TXData第二个数固定为0x14 在usbd_hid.h文件中更改数据包的最大长度,其记录在“HID_EPIN_SIZE”中,让其大于等于0x14U 发送报表所用的函数名为USBD_HID_SendReport(A, B, C)其中A为前面步骤声明的结构体,B为要发送的数据,C为发送数据的长度 下图举了一个例子,当PB12引脚为高电平时,TXData[3] = 0x04,意思是LOGO键被按下 可以看到,当按钮被按下时,PB12脚电平被拉高,电脑成功识别了它,并打开了Game Bar 接着把TXData[3] = 0x04 改为 TXData[3] = 0x10,意思是手柄的A键被按下。当我长按按钮时,代表着A键的小红点也亮了起来,说明识别成功! 综上,我们已经完成了所有USB_HID协议方面的代码,后面就可以根据你自己的电路去设计具体的控制逻辑了。下面我会详细阐述TXData这14个字节分别是什么含义,又代表着XBOX手柄上的哪些按键和摇杆。 四、XBOX手柄报表解码由于XBOX手柄的报表描述符已被微软固定,我们无法自定义,因此想要DIY一个XBOX手柄就需要知道报表中每个bit的含义。先定义一下手柄各个按键、扳机、摇杆的名称,以防误解 手柄传输给电脑的报表长度为14个Byte,下表为每个Byte的具体功能:(1) 按键组1(在后续的讨论中,1为按下按键,0为松开按键) Eg:若只按下A键与Y键,则代表按键组2的这一个Byte变为1001 0000,十六进制为0x90。其余Byte不变,为0000 0000。因此,此时手柄发送给电脑的数据(TXData)为:0x00, 0x14, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 (3) 扳机扳机值的范围为0000 0000 ~ 1111 1111,即十六进制的0x00~0xFF不按扳机时为0x00,完全按下为0xFF Eg: 若左扳机按下一半,右扳机完全按下,则代表左扳机的Byte变为0000 1111 (0x0F),代表右扳机的Byte变为1111 1111 (0xFF)此时手柄发送给电脑的数据为:0x00, 0x14, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 (4) 摇杆摇杆的数据比较特殊,其使用2个Byte来表示一个轴的位置信息,它的范围是 -32767 ~ 32767(摇杆初始位置为0)bit15为符号位,当摇杆的值是负数时为1,正数时为0bit0~bit14为数据位,它能表达的最大值为111 1111 1111 1111(十进制32767 十六进制0x7FFF)这16个bit被拆分成了两个Byte,bit0~bit7称作LSB,bit8~bit15称作MSB Eg:假设右摇杆X轴值为32767,右摇杆Y轴值为-20000 32767 = 0111 1111 1111 1111 = 0x7FFF = 0xFF(LSB); 0x7F(MSB)-20000 = 1100 1110 0010 0000 = 0xCE20= 0x20(LSB); 0xCE(MSB)此时手柄发送给电脑的数据为:0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F, 0x20, 0xCE