之前介绍过一个USB键盘转蓝牙键盘的装置【参考1】,这次使用的同样的设计来实现USB游戏手柄改装为蓝牙游戏手柄。 具体硬件设计在【参考1】中: ![USB 手柄改蓝牙图1](https://mc.dfrobot.com.cn/data/attachment/forum/202209/26/152727fgvpj0v0c8zp0spc.jpg)
接下来我们完全聚焦在软件设计上: 首先,检查手上USB手柄的描述符。抓到的数据如下:
Usage Page (Generic Desktop) | 05 01 | Usage (Joystick) | 09 04 | Collection (Application) | A1 01 | Collection (Logical) | A1 02 | Report Size (8) | 75 08 | Report Count (2) | 95 02 | Logical Minimum (0) | 15 00 | Logical Maximum (255) | 26 FF 00 | Physical Minimum (0) | 35 00 | Physical Maximum (255) | 46 FF 00 | Usage (X) | 09 30 | Usage (Y) | 09 31 | Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) | 81 02 | Report Count (1) | 95 01 | Input (Cnst,Ary,Abs) | 81 01 | Report Count (2) | 95 02 | Usage (Z) | 09 32 | Usage (Rz) | 09 35 | Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) | 81 02 | Report Size (4) | 75 04 | Report Count (1) | 95 01 | Logical Maximum (7) | 25 07 | Physical Maximum (315) | 46 3B 01 | Unit (Eng Rot: Degree) | 65 14 | Usage (Hat Switch) | 09 39 | Input (Data,Var,Abs,NWrp,Lin,Pref,Null,Bit) | 81 42 | Unit (None) | 65 00 | Report Size (1) | 75 01 | Report Count (12) | 95 0C | Logical Maximum (1) | 25 01 | Physical Maximum (1) | 45 01 | Usage Page (Button) | 05 09 | Usage Minimum (Button 1) | 19 01 | Usage Maximum (Button 12) | 29 0C | Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) | 81 02 | Usage Page (Vendor-Defined 1) | 06 00 FF | Report Size (1) | 75 01 | Report Count (8) | 95 08 | Logical Maximum (1) | 25 01 | Physical Maximum (1) | 45 01 | Usage (Vendor-Defined 1) | 09 01 | Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) | 81 02 | End Collection | C0 | Collection (Logical) | A1 02 | Report Size (8) | 75 08 | Report Count (7) | 95 07 | Physical Maximum (255) | 46 FF 00 | Logical Maximum (255) | 26 FF 00 | Usage (Vendor-Defined 2) | 09 02 | Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) | 91 02 | End Collection | C0 | End Collection | C0 |
使用逻辑分析仪可以看到Button6按下时,数据为 7F 7F 00 80 80 0F 02 00 ![USB 手柄改蓝牙图2](https://mc.dfrobot.com.cn/data/attachment/forum/202209/26/155740hmsmmximcs7m619i.png)
按下Button3时,数据为 7F 7F 00 80 80 4F 00 00 ![USB 手柄改蓝牙图3](https://mc.dfrobot.com.cn/data/attachment/forum/202209/26/155751dwwv7j2awzywnej2.png)
按下Button9时,数据为 7F 7F 00 80 80 0F 10 00 ![USB 手柄改蓝牙图4](https://mc.dfrobot.com.cn/data/attachment/forum/202209/26/155756hmy77re7ervyvgf1.png)
正常的做法是 USB Host 解析,然后从 BLE GamePad上发送出去,但是很明显这种方法非常麻烦。因此,这里研究一种直接将取得的数据转发出去的方法。 游戏手柄是 HID设备,蓝牙是在 USB协议推出之后才逐渐成熟的,因此这部分遵从了USBHID 的协议。在设备发现时,会传输一个类似 HID 描述符的东西给主机,告诉主机后面的数据格式。在 BLE 协议中,这个地方称作HIDreport map。 这里我们使用ESP32-BLE-Gamepad-master库来模拟BLE Gamepad。前面的HIDreport map代码可以在BleGamepad.cpp看到:
- uint8_t *customHidReportDescriptor = new uint8_t[hidReportDescriptorSize];
- memcpy(customHidReportDescriptor, tempHidReportDescriptor, hidReportDescriptorSize);
- BleGamepadInstance->hid->reportMap((uint8_t*)customHidReportDescriptor, hidReportDescriptorSize);
- BleGamepadInstance->hid->startServices();
复制代码
接下来就开始修改代码: 1. 用前面的 USB 手柄的 HID描述符替换库中的HIDReport Map。 - 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
- 0x09, 0x04, // Usage (Joystick)
- 0xA1, 0x01, // Collection (Application)
- 0xA1, 0x02, // Collection (Logical)
- 0x75, 0x08, // Report Size (8)
- 0x95, 0x02, // Report Count (2)
- 0x15, 0x00, // Logical Minimum (0)
- 0x26, 0xFF, 0x00, // Logical Maximum (255)
- 0x35, 0x00, // Physical Minimum (0)
- 0x46, 0xFF, 0x00, // Physical Maximum (255)
- 0x09, 0x30, // Usage (X)
- 0x09, 0x31, // Usage (Y)
- 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
- 0x95, 0x01, // Report Count (1)
- 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
- 0x95, 0x02, // Report Count (2)
- 0x09, 0x32, // Usage (Z)
- 0x09, 0x35, // Usage (Rz)
- 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
- 0x75, 0x04, // Report Size (4)
- 0x95, 0x01, // Report Count (1)
- 0x25, 0x07, // Logical Maximum (7)
- 0x46, 0x3B, 0x01, // Physical Maximum (315)
- 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
- 0x09, 0x39, // Usage (Hat switch)
- 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
- 0x65, 0x00, // Unit (None)
- 0x75, 0x01, // Report Size (1)
- 0x95, 0x0C, // Report Count (12)
- 0x25, 0x01, // Logical Maximum (1)
- 0x45, 0x01, // Physical Maximum (1)
- 0x05, 0x09, // Usage Page (Button)
- 0x19, 0x01, // Usage Minimum (0x01)
- 0x29, 0x0C, // Usage Maximum (0x0C)
- 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
- 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
- 0x75, 0x01, // Report Size (1)
- 0x95, 0x08, // Report Count (8)
- 0x25, 0x01, // Logical Maximum (1)
- 0x45, 0x01, // Physical Maximum (1)
- 0x09, 0x01, // Usage (0x01)
- 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
- 0xC0, // End Collection
- 0xA1, 0x02, // Collection (Logical)
- 0x75, 0x08, // Report Size (8)
- 0x95, 0x07, // Report Count (7)
- 0x46, 0xFF, 0x00, // Physical Maximum (255)
- 0x26, 0xFF, 0x00, // Logical Maximum (255)
- 0x09, 0x02, // Usage (0x02)
- 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
- 0xC0, // End Collection
- 0xC0, // End Collection
复制代码
2. 直接用上述数据替换之后无法正常工作,现象是系统能够发现手柄,但是系统自带的 Game Controller 中的手柄无法正常出现按键。经过研究发现ESP32-BLE-Gamepad能够正常工作的代码中,REPORTMAP多一个REPORTID。因此修改上面的代码如下:
- 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
- 0x85, 0x03, // Report ID
- 0x09, 0x04, // Usage (Joystick)
- 0xA1, 0x01, // Collection (Application)
- 0xA1, 0x02, // Collection (Logical)
- 0x75, 0x08, // Report Size (8)
复制代码
在英文中,有 JoyStick 和Gamepad的区别,前者可以理解为飞行摇杆这种;后者可以理解为玩拳皇这种的手柄)。
3.1. 接下来我们还要在 BLE GAMEPAD 库中添加一个转发的函数,将收到的 buffer完整的从 BLE 发送出去,代码如下: - void BleGamepad::MySendReport(uint8_t *buffer)
- {
- if (this->isConnected())
- {
- this->inputGamepad->setValue(buffer, 8);
- this->inputGamepad->notify();
- }
- }
复制代码
4.1. 最终代码如下: - #include <BleGamepad.h>
-
- #include <elapsedMillis.h>
- #include <usb/usb_host.h>
- #include "show_desc.hpp"
- #include "usbhhelp.hpp"
-
- BleGamepad bleGamepad;
-
- bool isGamepad = false;
- bool isGamepadReady = false;
- uint8_t GamepadInterval;
- bool isGamepadPolling = false;
- elapsedMillis GamepadTimer;
-
- uint8_t Button[8];
-
- const size_t Gamepad_IN_BUFFER_SIZE = 8;
- usb_transfer_t *GamepadIn = NULL;
-
- void Gamepad_transfer_cb(usb_transfer_t *transfer)
- {
- if (Device_Handle == transfer->device_handle) {
- isGamepadPolling = false;
- if (transfer->status == 0) {
- if (transfer->actual_num_bytes == 8) {
- uint8_t *const p = transfer->data_buffer;
- //ESP_LOGI("", "HID report: %02x %02x %02x %02x %02x %02x %02x %02x",
- // p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
- if (bleGamepad.isConnected())
- {
- if (memcmp(p, Button, 8) != 0) {
- ESP_LOGI("", "HID report: %02x %02x %02x %02x %02x %02x %02x %02x",
- p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
- memcpy(Button,p,8);
- bleGamepad.MySendReport(Button);
- }
- }
- }
- else {
- ESP_LOGI("", "Gamepad boot hid transfer too short or long");
- }
- }
- else {
- ESP_LOGI("", "transfer->status %d", transfer->status);
- }
- }
- }
-
- void check_interface_desc_boot_Gamepad(const void *p)
- {
- const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p;
-
- if ((intf->bInterfaceClass == USB_CLASS_HID) &&
- (intf->bInterfaceSubClass == 0x00) &&
- (intf->bInterfaceProtocol == 0x00)) {
- isGamepad = true;
- ESP_LOGI("", "Claiming a boot Gamepad!");
- esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle,
- intf->bInterfaceNumber, intf->bAlternateSetting);
- if (err != ESP_OK) ESP_LOGI("", "usb_host_interface_claim failed: %x", err);
- }
- }
-
- void prepare_endpoint(const void *p)
- {
- const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p;
- esp_err_t err;
-
- // must be interrupt for HID
- if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_INT) {
- ESP_LOGI("", "Not interrupt endpoint: 0x%02x", endpoint->bmAttributes);
- return;
- }
- if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) {
- err = usb_host_transfer_alloc(Gamepad_IN_BUFFER_SIZE, 0, &GamepadIn);
- if (err != ESP_OK) {
- GamepadIn = NULL;
- ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err);
- return;
- }
- GamepadIn->device_handle = Device_Handle;
- GamepadIn->bEndpointAddress = endpoint->bEndpointAddress;
- GamepadIn->callback = Gamepad_transfer_cb;
- GamepadIn->context = NULL;
- isGamepadReady = true;
- GamepadInterval = endpoint->bInterval;
- ESP_LOGI("", "USB boot Gamepad ready");
- }
- else {
- ESP_LOGI("", "Ignoring interrupt Out endpoint");
- }
- }
-
- void show_config_desc_full(const usb_config_desc_t *config_desc)
- {
- // Full decode of config desc.
- const uint8_t *p = &config_desc->val[0];
- static uint8_t USB_Class = 0;
- uint8_t bLength;
- for (int i = 0; i < config_desc->wTotalLength; i += bLength, p += bLength) {
- bLength = *p;
- if ((i + bLength) <= config_desc->wTotalLength) {
- const uint8_t bDescriptorType = *(p + 1);
- switch (bDescriptorType) {
- case USB_B_DESCRIPTOR_TYPE_DEVICE:
- ESP_LOGI("", "USB Device Descriptor should not appear in config");
- break;
- case USB_B_DESCRIPTOR_TYPE_CONFIGURATION:
- show_config_desc(p);
- break;
- case USB_B_DESCRIPTOR_TYPE_STRING:
- ESP_LOGI("", "USB string desc TBD");
- break;
- case USB_B_DESCRIPTOR_TYPE_INTERFACE:
- USB_Class = show_interface_desc(p);
- check_interface_desc_boot_Gamepad(p);
- break;
- case USB_B_DESCRIPTOR_TYPE_ENDPOINT:
- show_endpoint_desc(p);
- if (isGamepad && GamepadIn == NULL) prepare_endpoint(p);
- break;
- case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER:
- // Should not be config config?
- ESP_LOGI("", "USB device qual desc TBD");
- break;
- case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION:
- // Should not be config config?
- ESP_LOGI("", "USB Other Speed TBD");
- break;
- case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER:
- // Should not be config config?
- ESP_LOGI("", "USB Interface Power TBD");
- break;
- case 0x21:
- if (USB_Class == USB_CLASS_HID) {
- show_hid_desc(p);
- }
- break;
- default:
- ESP_LOGI("", "Unknown USB Descriptor Type: 0x%x", bDescriptorType);
- break;
- }
- }
- else {
- ESP_LOGI("", "USB Descriptor invalid");
- return;
- }
- }
- }
-
- void setup()
- {
- usbh_setup(show_config_desc_full);
- bleGamepad.begin();
- }
-
- void loop()
- {
- usbh_task();
-
- if (isGamepadReady && !isGamepadPolling && (GamepadTimer > GamepadInterval)) {
- GamepadIn->num_bytes = 8;
- esp_err_t err = usb_host_transfer_submit(GamepadIn);
- if (err != ESP_OK) {
- ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err);
- }
- isGamepadPolling = true;
- GamepadTimer = 0;
- }
- }
复制代码
参考:
|