USB 手柄改蓝牙
之前介绍过一个USB键盘转蓝牙键盘的装置【参考1】,这次使用的同样的设计来实现USB游戏手柄改装为蓝牙游戏手柄。具体硬件设计在【参考1】中:接下来我们完全聚焦在软件设计上:首先,检查手上USB手柄的描述符。抓到的数据如下:
Usage Page (Generic Desktop)05 01
Usage (Joystick)09 04
Collection(Application)A1 01
Collection(Logical)A1 02
ReportSize (8)75 08
ReportCount (2)95 02
LogicalMinimum (0)15 00
LogicalMaximum (255)26 FF 00
PhysicalMinimum (0)35 00
PhysicalMaximum (255)46 FF 00
Usage(X)09 30
Usage(Y)09 31
Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit)81 02
ReportCount (1)95 01
Input (Cnst,Ary,Abs)81 01
ReportCount (2)95 02
Usage(Z)09 32
Usage(Rz)09 35
Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit)81 02
ReportSize (4)75 04
ReportCount (1)95 01
LogicalMaximum (7)25 07
PhysicalMaximum (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
ReportSize (1)75 01
ReportCount (12)95 0C
LogicalMaximum (1)25 01
PhysicalMaximum (1)45 01
UsagePage (Button)05 09
UsageMinimum (Button 1)19 01
UsageMaximum (Button 12)29 0C
Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit)81 02
UsagePage (Vendor-Defined 1)06 00 FF
ReportSize (1)75 01
ReportCount (8)95 08
LogicalMaximum (1)25 01
PhysicalMaximum (1)45 01
Usage(Vendor-Defined 1)09 01
Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit)81 02
EndCollectionC0
Collection(Logical)A1 02
ReportSize (8)75 08
ReportCount (7)95 07
PhysicalMaximum (255)46 FF 00
LogicalMaximum (255)26 FF 00
Usage(Vendor-Defined 2)09 02
Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit)91 02
EndCollectionC0
End CollectionC0
使用逻辑分析仪可以看到Button6按下时,数据为 7F 7F 00 80 80 0F 02 00
按下Button3时,数据为 7F 7F 00 80 80 4F 00 00
按下Button9时,数据为 7F 7F 00 80 80 0F 10 00
正常的做法是 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;
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;
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, p, p, p, p, p, p, p);
if (bleGamepad.isConnected())
{
if (memcmp(p, Button, 8) != 0) {
ESP_LOGI("", "HID report: %02x %02x %02x %02x %02x %02x %02x %02x",
p, p, p, p, p, p, p, p);
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;
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;
}
}
参考:1. https://mc.dfrobot.com.cn/thread-313801-1-1.html2. 在线 HID Descriptor 分析工具 http://eleccelerator.com/usbdescreqparser/
修改后的库
完整的代码:
https://www.bilibili.com/video/BV18e411K7v6/
我有一个2.4G的无线键盘,接收器丢了,能不能反向改装为有线键盘? glwz007 发表于 2022-10-16 15:22
我有一个2.4G的无线键盘,接收器丢了,能不能反向改装为有线键盘?
这个应该可以,只是比较折腾,拆开之后重新焊接解析键盘矩阵
更简单的方法是直接买个同型号的接收器,配对一下就能用 厉害厉害 {:6_215:}{:6_215:}{:6_215:} 点赞!! 修改后的库 为什么不用代码直接获取设备描述符然后替换到HIDReport Map中呢 SamToT 发表于 2024-4-16 17:24
为什么不用代码直接获取设备描述符然后替换到HIDReport Map中呢
可以,应该没问题 很显然,我们小白是看不懂的,膜拜 请问能分享完整软件环境吗? tyyx 发表于 2024-7-19 10:55
请问能分享完整软件环境吗?
都是开源的啊,你缺哪个? 我在ESP-IDF 5.2.2下编译出错了。
所以,请问能分享下项目文件吗?
需要 Ble Gamepad 库,usb_host.h是哪个库的?还有哪几个库呢?
非常感谢! tyyx 发表于 2024-7-20 16:21
我在ESP-IDF 5.2.2下编译出错了。
所以,请问能分享下项目文件吗?
需要 Ble Gamepad 库,usb_host.h是哪个 ...
这个项目是 Arduino 的,不是 IDF 的 tyyx 发表于 2024-7-20 16:21
我在ESP-IDF 5.2.2下编译出错了。
所以,请问能分享下项目文件吗?
需要 Ble Gamepad 库,usb_host.h是哪个 ...
库的话,参考之前的文章https://mc.dfrobot.com.cn/thread-313801-1-1.html
页:
[1]