1. USB_HID简介
2. CubeMX配置
3. 描述符简介与代码编写
4. XBOX手柄按键解码
HID全称是Human Interface Device,人机交互设备。我们生活中所有的有线外设,和使用2.4G收发器的无线外设,都是通过该协议与电脑通讯的。比如鼠标、键盘、手柄等。但需要指出的是,蓝牙耳机、蓝牙鼠标等基于蓝牙的无线外设使用的是蓝牙HID协议,与本文讨论的不同。
外设通过USB_HID协议告诉电脑,我是什么、我有什么功能、我传来的信号是什么含义。在设备插入电脑时,其会向电脑发送一系列“描述符”(Description),描述自身的方方面面,此时“设备与打印机”界面便会弹出设备图标,表明该其已被电脑识别。
在识别完成后,设备开始与电脑正常通讯,通讯的数据被打包,并不断传给电脑。这个“包”被称作“报表”(Report)。这是一串在外人看来毫无意义的二进制数据,它的长度、它表达的含义,均由你在“报表描述符”(Report Description)中自定义。报表就好像密码,而报表描述符是密码本,电脑会对照报表描述符,将报表翻译成一系列指令。
可以看出,该协议极为复杂,想要完全了解它很困难,协议中每个描述符都能写出比此文还长的文章。因此,本文旨在从DIY的角度,利用CubeMX与HAL库,跳过协议原文,用STM32做出你想要的外设。
本文使用STM32F103C8T6演示,侧重于USB_HID协议的代码编写,不涉及手柄硬件实现与外壳建模。
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手柄的报表描述符已被微软固定,我们无法自定义,因此想要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,正数时为0
bit0~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
云FAE请选择您要咨询的方向,专业工程师为您服务!咨询客服