返回列表 发新帖我要提问本帖赏金: 30.00元(功能说明)

[STM32F1] 基于STM32F103实现Xbox 360 Controller for Windows 之Xinput

[复制链接]
3669|9
 楼主| lilijin1995 发表于 2022-12-26 16:53 | 显示全部楼层 |阅读模式
本帖最后由 lilijin1995 于 2022-12-27 20:05 编辑

背景
上个月由于家里有事,实现了并枚举成功的Xinput,就差数据处理没有完成,请假在家,客户急要,只好请同事改改,玩usb hid已经差不多两年了,至今也算没有实现Xbox 360 Controller for Windows 如下图,一来不知道怎么实现,二来没有画时间去琢磨;年底没啥事做,咱们现在就来实现这样手柄。
1496263a969427febf.png


下面简单说一下实现步骤,并附上相关的参考资料:首先是实现步骤:
第一步,设计硬件,硬件还是我们之前设计的摇杆板,摇杆板只有一个摇杆和8个按键,但这并不妨碍我们学习Xinput协议。
第二步,设计软件,这里的软件设计最重要的在于USB hid相关的描述符修改。
第三步,下载验证,完成软件设计之后,我们将通过视频给大家展示我们所实现的效果。

接着是参考资料:
https://github.com/nesvera/STM32-X360-xinput
这个是GitHub上面的大佬开源的,大家想要学习,自己下载学习即可。

硬件设计:
首先我们设计一个基于STM32F103C8T6的评估板,原理图如下:
6780463a99cfb98338.png
由原理图我们可以知道,我们设计了8颗轻触按键,另有摇杆一个,同时有WS2812B一颗用于装饰。
最终我们设计的成品如下图:
76249f2de75b5f17aeb51cc993765f8b836634e6.jpg


软件设计我们的软件是基于STM32 HAL库的,HAL库中的USB HID是比较简单的,我们要向实现一个STM32只要通过修改几个描述符,增加几个厂商处理请求即可。
1.修改设备描述符:
  1. __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
  2. {
  3.   0x12,                       /*bLength */
  4.   USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
  5.   0x00,                       /*bcdUSB */
  6.   0x02,
  7.   0x00,                       /*bDeviceClass*/
  8.   0x00,                       /*bDeviceSubClass*/
  9.   0x00,                       /*bDeviceProtocol*/
  10.   USB_MAX_EP0_SIZE,           /*bMaxPacketSize*/
  11.   LOBYTE(USBD_VID),           /*idVendor*/
  12.   HIBYTE(USBD_VID),           /*idVendor*/
  13.   LOBYTE(USBD_PID_FS),        /*idProduct*/
  14.   HIBYTE(USBD_PID_FS),        /*idProduct*/
  15.   0x00,                       /*bcdDevice rel. 2.00*/
  16.   0x02,
  17.   USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
  18.   USBD_IDX_PRODUCT_STR,       /*Index of product string*/
  19.   USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
  20.   USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/
  21. };
需要修改的设备描述符中的USBD_VID和USBD_PID_FS,这两个分别是厂商ID和产品ID,厂商ID是USB IF组织分配的,以下是微软的VID以及Xinput的PID

  1. #define USBD_VID     0x045E
  2. #define USBD_PID_FS     0x028E
2.修改配置/接口/HID/端点/厂商描述符:
  1. /* USB CUSTOM_HID device FS Configuration Descriptor */
  2. __ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgFSDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =
  3. {
  4.     /************** Configuration Descriptor 1 Bus Powered, 500 mA ****************/
  5.     0x09, /* bLength: Configuration Descriptor size */
  6.     USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
  7.     USB_CUSTOM_HID_CONFIG_DESC_SIZ,
  8.     /* wTotalLength: Bytes returned */
  9.     0x00,
  10.     0x04,         /*bNumInterfaces: 1 interface*/
  11.     0x01,         /*bConfigurationValue: Configuration value*/
  12.     0x00,         /*iConfiguration: Index of string descriptor describing
  13.   the configuration*/
  14.     0xA0,         /*bmAttributes: Bus Powered, Remote Wakeup*/
  15.     0xFA,         /*MaxPower 500 mA: this current is used for detecting Vbus*/

  16.     /**************Interface Descriptor 0/0 Vendor-Specific, 2 Endpoints****************/
  17.     /* 09 */
  18.     0x09,         /*bLength: Interface Descriptor size*/
  19.     USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  20.     0x00,         /*bInterfaceNumber: Number of Interface*/
  21.     0x00,         /*bAlternateSetting: Alternate setting*/
  22.     0x02,         /*bNumEndpoints*/
  23.     0xFF,         /*Vendor-Specific*/
  24.     0x5D,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  25.     0x01,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  26.     0x00,            /*iInterface: Index of string descriptor*/
  27.     /**************Unrecognized Class-Specific Descriptor***********************/
  28.     /* 18 */
  29.     0x11,         /*bLength: CUSTOM_HID Descriptor size*/
  30.     0x21, /*bDescriptorType: CUSTOM_HID*/
  31.     0x10,0x01,0x01,0x25,0x81,0x14,0x03,0x03,
  32.     0x03,0x04,0x13,0x02,0x08,0x03,0x03,
  33.     /**************Endpoint Descriptor 81 1 In, Interrupt, 4 ms******************/
  34.     /* 27 */
  35.     0x07,          /*bLength: Endpoint Descriptor size*/
  36.     USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  37.     CUSTOM_HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  38.     0x03,          /*bmAttributes: Interrupt endpoint*/
  39.     CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */
  40.     0x00,
  41.     CUSTOM_HID_FS_BINTERVAL,          /*bInterval: Polling Interval */
  42.     /**************Endpoint Descriptor 02 2 Out, Interrupt, 8 ms******************/
  43.     /* 34 */
  44.     0x07,          /*bLength: Endpoint Descriptor size*/
  45.     USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  46.     0x02,     /*bEndpointAddress: Endpoint Address (IN)*/
  47.     0x03,          /*bmAttributes: Interrupt endpoint*/
  48.     0x20, /*wMaxPacketSize: 2 Byte max */
  49.     0x00,
  50.     0x08,          /*bInterval: Polling Interval */

  51.     /**************Interface Descriptor 1/0 Vendor-Specific, 2 Endpoints****************/
  52.     /* 09 */
  53.     0x09,         /*bLength: Interface Descriptor size*/
  54.     USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  55.     0x01,         /*bInterfaceNumber: Number of Interface*/
  56.     0x00,         /*bAlternateSetting: Alternate setting*/
  57.     0x02,         /*bNumEndpoints*/
  58.     0xFF,         /*Vendor-Specific*/
  59.     0x5D,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  60.     0x01,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  61.     0x00,            /*iInterface: Index of string descriptor*/
  62.     /**************Unrecognized Class-Specific Descriptor***********************/
  63.     /* 18 */
  64.     0x1B,         /*bLength: CUSTOM_HID Descriptor size*/
  65.     0x21, /*bDescriptorType: CUSTOM_HID*/
  66.     0x00,0x01,0x01,0x01,0x83,0x40,0x01,0x04,
  67.     0x20,0x16,0x85,0x00,0x00,0x00,0x00,0x00,
  68.     0x00,0x16,0x05,0x00,0x00,0x00,0x00,0x00,
  69.     0x00,
  70.     /**************Endpoint Descriptor 81 1 In, Interrupt, 4 ms******************/
  71.     /* 27 */
  72.     0x07,          /*bLength: Endpoint Descriptor size*/
  73.     USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  74.     0x83,     /*bEndpointAddress: Endpoint Address (IN)*/
  75.     0x03,          /*bmAttributes: Interrupt endpoint*/
  76.     0x20, /*wMaxPacketSize: 2 Byte max */
  77.     0x00,
  78.     0x02,          /*bInterval: Polling Interval */
  79.     /**************Endpoint Descriptor 02 2 Out, Interrupt, 8 ms******************/
  80.     /* 34 */
  81.     0x07,          /*bLength: Endpoint Descriptor size*/
  82.     USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  83.     0x04,     /*bEndpointAddress: Endpoint Address (IN)*/
  84.     0x03,          /*bmAttributes: Interrupt endpoint*/
  85.     0x20, /*wMaxPacketSize: 2 Byte max */
  86.     0x00,
  87.     0x04,          /*bInterval: Polling Interval */

  88.     /**************Interface Descriptor 2/0 Vendor-Specific, 2 Endpoints****************/
  89.     /* 09 */
  90.     0x09,         /*bLength: Interface Descriptor size*/
  91.     USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  92.     0x02,         /*bInterfaceNumber: Number of Interface*/
  93.     0x00,         /*bAlternateSetting: Alternate setting*/
  94.     0x01,         /*bNumEndpoints*/
  95.     0xFF,         /*Vendor-Specific*/
  96.     0x5D,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  97.     0x02,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  98.     0x00,            /*iInterface: Index of string descriptor*/
  99.     /**************Unrecognized Class-Specific Descriptor***********************/
  100.     /* 18 */
  101.     0x09,         /*bLength: CUSTOM_HID Descriptor size*/
  102.     0x21, /*bDescriptorType: CUSTOM_HID*/
  103.     0x00,0x01,0x01,0x22,0x86,0x07,0x00,
  104.     /**************Endpoint Descriptor 81 1 In, Interrupt, 4 ms******************/
  105.     /* 27 */
  106.     0x07,          /*bLength: Endpoint Descriptor size*/
  107.     USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  108.     0x86,     /*bEndpointAddress: Endpoint Address (IN)*/
  109.     0x03,          /*bmAttributes: Interrupt endpoint*/
  110.     0x20, /*wMaxPacketSize: 2 Byte max */
  111.     0x00,
  112.     0x10,          /*bInterval: Polling Interval */

  113.     /**************nterface Descriptor 3/0 Vendor-Specific, 0 Endpoints****************/
  114.     /* 09 */
  115.     0x09,         /*bLength: Interface Descriptor size*/
  116.     USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  117.     0x03,         /*bInterfaceNumber: Number of Interface*/
  118.     0x00,         /*bAlternateSetting: Alternate setting*/
  119.     0x00,         /*bNumEndpoints*/
  120.     0xFF,         /*Vendor-Specific*/
  121.     0xFD,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  122.     0x13,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  123.     0x04,            /*iInterface: Index of string descriptor*/
  124.     /**************Unrecognized Class-Specific Descriptor***********************/
  125.     /* 18 */
  126.     0x06,         /*bLength: CUSTOM_HID Descriptor size*/
  127.     0x41, /*bDescriptorType: CUSTOM_HID*/
  128.     0x00,0x01,0x01,0x03
  129. };
这个配置描述符即可比较长,不过也比较统一,大家可以直接复制参考代码中的也可以,不过要注意以下接口索引号,端点地址之类的,比如我们定义的
  1. #define CUSTOM_HID_EPIN_ADDR                 0x81U
  2. #define CUSTOM_HID_EPIN_SIZE   20U
这个端点1地址以及端点大小正是我们自己定义的,大小一定不能小于20,因为微软XBOX360手柄定义的报表描述符正是20个字节。
大家可能比较好奇,XBOX360手柄为什么没有报表没舒服,我们在代码中也没有实现,直接给它初始化为0,这是为何?
其实我也不清楚,是不是系统自己只要认对应的VID和PID以及配置描述符正确,应该就可以正确枚举了?其实我们Usb主机好像也确实没有问我们设备要报表描述符。

3. 添加厂商请求的处理。在USBD_CUSTOM_HID_Setup函数值,除了类请求USB_REQ_TYPE_CLASS、标准请求USB_REQ_TYPE_STANDARD,还需要增加一个厂商请求USB_REQ_TYPE_VENDOR,如下:
  1. static uint8_t  USBD_CUSTOM_HID_Setup(USBD_HandleTypeDef *pdev,
  2.                                       USBD_SetupReqTypedef *req)
  3. {
  4.   USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;
  5.   uint16_t len = 0U;
  6.   uint8_t  *pbuf = NULL;
  7.   uint16_t status_info = 0U;
  8.   uint8_t ret = USBD_OK;

  9.   switch (req->bmRequest & USB_REQ_TYPE_MASK)
  10.   {
  11.     case USB_REQ_TYPE_VENDOR:
  12.         switch (req->bRequest)
  13.         {
  14.         case 0x01:
  15.         {
  16.             /* code */
  17.             if(req->wLength == 0x14)
  18.             {
  19.                 len  = MIN(0x14, req->wLength);
  20.                 pbuf = vendor_request_content;
  21.             }
  22.             else if(req->wLength == 0x08)
  23.             {
  24.                 len  = MIN(0x8, req->wLength);
  25.                 pbuf = vendor_re2;
  26.             }
  27.             else if(req->wLength == 0x04)
  28.             {
  29.                 len  = MIN(0x04, req->wLength);
  30.                 pbuf = vendor_re2;
  31.             }
  32.             break;
  33.         }

  34.         default:
  35.             break;
  36.         }
  37.         USBD_CtlSendData(pdev, pbuf, len);
  38.         break;
  39.     default:
  40.       USBD_CtlError(pdev, req);
  41.       ret = USBD_FAIL;
  42.       break;
  43.   }
  44.   return ret;
  45. }

实现上面这些,基本在电脑端可以枚举成功一个Xbox 360 Controller for Windows 手柄设备。但是我们并不能满足一次。
4. 解析摇杆电位器和按键数据。
  1. void Xinput_Handle(void)
  2. {
  3.         int16_t X=0,Y=0;
  4.     //X-Y
  5.     for(u8 i=0; i<AD_DATA_SIZE;)
  6.     {
  7.         AdXSum += AD_DATA[i];
  8.         i++;
  9.         AdYSum += AD_DATA[i];
  10.         i++;
  11.     }
  12.     Xtemp=AdXSum/10;
  13.     AdXSum=0;
  14.     Ytemp=AdYSum/10;
  15.     AdYSum=0;
  16.     if(Xtemp>Xmax)
  17.         Xtemp=Xmax;
  18.     if(Xtemp<Xmin)
  19.         Xtemp=Xmin;

  20.     if(Ytemp>Ymax)
  21.         Ytemp=Ymax;
  22.     if(Ytemp<Ymin)
  23.         Ytemp=Ymin;
  24.     X=(int16_t)map( Xtemp, Xmin, Xmax, INT16_MAX, INT16_MIN );
  25.     Y=(int16_t)map( Ytemp, Ymin, Ymax, INT16_MIN, INT16_MAX );
  26.         
  27.         
  28.         //Button

  29.     if((UPKEY)==0)//Y
  30.     {
  31.         TXData[BUTTON_PACKET_2] |= Y_MASK_ON;
  32.     }else{
  33.                 TXData[BUTTON_PACKET_2] &= Y_MASK_OFF;
  34.         }
  35.     if((DNKEY)==0)//A
  36.     {
  37.         TXData[BUTTON_PACKET_2] |= A_MASK_ON;
  38.     }else{
  39.                 TXData[BUTTON_PACKET_2] &= A_MASK_OFF;
  40.                
  41.         }
  42.    
  43.     if(LFKEY==0)
  44.     {
  45.         TXData[BUTTON_PACKET_2] |= X_MASK_ON;
  46.     } else {
  47.         TXData[BUTTON_PACKET_2] &= X_MASK_OFF;
  48.     }
  49.     if(RGKEY==0)
  50.     {
  51.         TXData[BUTTON_PACKET_2] |= B_MASK_ON;
  52.     } else {
  53.         TXData[BUTTON_PACKET_2] &= B_MASK_OFF;
  54.     }
  55.         
  56.     if(BKKEY==0)//BUTTON_BACK
  57.     {
  58.         TXData[BUTTON_PACKET_1] |= BACK_MASK_ON;
  59.     } else {
  60.         TXData[BUTTON_PACKET_1] &= BACK_MASK_OFF;
  61.     }
  62.     if(MDKEY==0)//BUTTON_LB
  63.     {
  64.         TXData[BUTTON_PACKET_2] |= LB_MASK_ON;
  65.     } else {
  66.         TXData[BUTTON_PACKET_2] &= LB_MASK_OFF;
  67.     }
  68.     if(STKEY==0)//BUTTON_START
  69.     {
  70.         TXData[BUTTON_PACKET_1] |= START_MASK_ON;
  71.     } else {
  72.         TXData[BUTTON_PACKET_1] &= START_MASK_OFF;
  73.     }        
  74.     if(TBKEY==0)//BUTTON_RB
  75.     {
  76.         TXData[BUTTON_PACKET_2] |= RB_MASK_ON;
  77.     } else {
  78.         TXData[BUTTON_PACKET_2] &= RB_MASK_OFF;
  79.     }        
  80.     if(SW1!=0)
  81.     {
  82.         TXData[BUTTON_PACKET_2] |= **_MASK_ON;
  83.     } else {
  84.         TXData[BUTTON_PACKET_2] &= **_MASK_OFF;
  85.     }
  86.         TXData[LEFT_STICK_X_PACKET_LSB] = LOBYTE(Y);                // (CONFERIR)
  87.         TXData[LEFT_STICK_X_PACKET_MSB] = HIBYTE(Y);
  88.         //Left Stick Y Axis
  89.         TXData[LEFT_STICK_Y_PACKET_LSB] = LOBYTE(X);
  90.         TXData[LEFT_STICK_Y_PACKET_MSB] = HIBYTE(X);
  91.         //Clear DPAD
  92.         TXData[BUTTON_PACKET_1] &= DPAD_MASK_OFF;
  93.         USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,(u8*)&TXData, 20);

  94. }

这里我们直接参考了GitHub上大佬的。其中map函数的作用把12位AD值0~4095转成16位的0~65535。然后扫描一下按键,按要求赋值即可。我们这里实现了左摇杆,以及A、B、X、Y、LB、RB、START、BACK等按键。

下载验证

请大家查看我们的视频:

















打赏榜单

21ic小管家 打赏了 30.00 元 2023-01-09
理由:签约作者奖励

呐咯密密 发表于 2022-12-27 22:32 | 显示全部楼层
实现效果可以啊
Bowclad 发表于 2023-1-1 17:15 | 显示全部楼层
厉害啊,搞的不错
zerorobert 发表于 2023-1-5 12:22 | 显示全部楼层
这个移植一个shell就行。
              
benjaminka 发表于 2023-1-6 15:27 | 显示全部楼层
xinput是什么功能?              
uytyu 发表于 2023-1-9 15:43 | 显示全部楼层
这个有什么作用?              
星辰大海不退缩 发表于 2023-1-9 16:08 来自手机 | 显示全部楼层
很有参考意义,细节也有提醒
hilahope 发表于 2023-1-11 22:07 | 显示全部楼层
如果usbhid不打开,数据通信就会错误的。
pmp 发表于 2023-1-16 15:53 | 显示全部楼层
这个学习一下了。              
nomomy 发表于 2023-1-19 17:09 | 显示全部楼层
little shell 了解一下。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

56

主题

165

帖子

8

粉丝
快速回复 在线客服 返回列表 返回顶部