设计支持
搜索 搜索
基于STM32CubeMX使用USB_HID(以DIY XBOX手柄为例)

1. USB_HID简介

2. CubeMX配置

3. 描述符简介与代码编写

4. XBOX手柄按键解码

 

一、USB_UID简介

HID全称是Human Interface Device,人机交互设备。我们生活中所有的有线外设,和使用2.4G收发器的无线外设,都是通过该协议与电脑通讯的。比如鼠标、键盘、手柄等。但需要指出的是,蓝牙耳机、蓝牙鼠标等基于蓝牙的无线外设使用的是蓝牙HID协议,与本文讨论的不同。

外设通过USB_HID协议告诉电脑,我是什么、我有什么功能、我传来的信号是什么含义。在设备插入电脑时,其会向电脑发送一系列“描述符”(Description),描述自身的方方面面,此时“设备与打印机”界面便会弹出设备图标,表明该其已被电脑识别。

 

在识别完成后,设备开始与电脑正常通讯,通讯的数据被打包,并不断传给电脑。这个“包”被称作“报表”(Report)。这是一串在外人看来毫无意义的二进制数据,它的长度、它表达的含义,均由你在“报表描述符”(Report Description)中自定义。报表就好像密码,而报表描述符是密码本,电脑会对照报表描述符,将报表翻译成一系列指令。

可以看出,该协议极为复杂,想要完全了解它很困难,协议中每个描述符都能写出比此文还长的文章。因此,本文旨在从DIY的角度,利用CubeMXHAL库,跳过协议原文,用STM32做出你想要的外设。

本文使用STM32F103C8T6演示,侧重于USB_HID协议的代码编写,不涉及手柄硬件实现与外壳建模。

 

二、CubeMX配置

1.使能高速晶振 打开串口调试

 

 

2.打开USB,勾选Device(FS)FSFull 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其他外设,下图是查看设备VIDPID的方法

 

 

(2) HID结构体

接下来要修改的代码,均在usbd_hid.cusbd_hid.h文件中,路径见下图

 

在修改其他配置描述符之前,我们先要对HID结构体进行一些修改,在这个结构体中,记录了stm32HID功能如何初始化、如何启动、如何接收信息等等。以下是这个结构体默认时的样子:

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 SpeedOther Speed这两种传输速度,全部改成以Full Speed传输。USB1.1支持Full Speed,速度为12MbpsUSB2.0支持High Speed,速度为480Mbps。因为stm32f103只支持12Mbps,所以要把这个结构体中的传输速度全部改成Full Speed

 

(3) 配置描述符 Configuration Description

配置描述符负责向电脑描述这个设备有多少个接口,每个接口有什么端点。HAL库提供了三种速度下的配置描述符,即Full SpeedHigh SpeedOther 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协议方面的代码,后面就可以根据你自己的电路去设计具体的控制逻辑了。下面我会详细阐述TXData14个字节分别是什么含义,又代表着XBOX手柄上的哪些按键和摇杆。

 

四、XBOX手柄报表解码

由于XBOX手柄的报表描述符已被微软固定,我们无法自定义,因此想要DIY一个XBOX手柄就需要知道报表中每个bit的含义。

先定义一下手柄各个按键、扳机、摇杆的名称,以防误解

 

手柄传输给电脑的报表长度为14Byte,下表为每个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) 摇杆

摇杆的数据比较特殊,其使用2Byte来表示一个轴的位置信息,它的范围是 -32767 ~ 32767(摇杆初始位置为0


bit15为符号位,当摇杆的值是负数时为1,正数时为0

bit0~bit14为数据位,它能表达的最大值为111 1111 1111 1111(十进制32767 十六进制0x7FFF)

16bit被拆分成了两个Bytebit0~bit7称作LSBbit8~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

标签: DIY 单片机 手柄
0
点赞
0
收藏
评论
0/1000
全部评论 (0)条
热点标签
更多
相关文章推荐

云FAE请选择您要咨询的方向,专业工程师为您服务!咨询客服