ESP32-S3 是乐鑫科技推出的一款 WIFI/蓝牙 MCU,它可以看作是 ESP32-S2 的升级版本,相比S2 增加了蓝牙功能,因此我们有机会设计各种蓝牙和USB 相关的作品。这次带来的就是能够将 USB 鼠标转为蓝牙鼠标的装置。
电路比较简单,基本上相当于ESP32-S3最小系统引出 USB 接口即可:
板子上分别有一个USB公头和母头,公头用于取电,母头用于连接设备:
程序基本结构是:
- USB Host 负责获得和接收解析鼠标数据
- BLeMouse 负责将收到的数据重新发送出去
其中的 USB Host 基于esp32-usb-host-demos-main 库。BLE 鼠标基于ESP32-BLE-Mouse-Main库。因为这个库并不支持直接发送 RAW 数据,所以对其进行简单修改,增加了一个函数
void SendData(unsigned char *Data);
代码如下:
void BleMouse::SendData(unsigned char *Data)
{
if (this->isConnected())
{
uint8_t m[5];
m[0] = Data[0];
m[1] = Data[1];
m[2] = Data[2];
m[3] = Data[3];
m[4] = 0;
this->inputMouse->setValue(m, 5);
this->inputMouse->notify();
}
}
这个 BLE Mouse库发送的数据格式是 5字节,分别是鼠标按键,X,Y,垂直滚轮和水平滚轮。我是用的是微软鼠标 IntelliMouse Optical 1.1A,没有水平滚轮。
完整代码如下:
#include <elapsedMillis.h>
#include <BleMouse.h>
#include <usb/usb_host.h>
#include "show_desc.hpp"
#include "usbhhelp.hpp"
BleMouse bleMouse;
bool isMouse = false;
bool isMouseReady = false;
uint8_t MouseInterval;
bool isMousePolling = false;
elapsedMillis MouseTimer;
const size_t Mouse_IN_BUFFER_SIZE = 8;
usb_transfer_t *MouseIn = NULL;
// 对Mouse发送 Setup Pakcage的 usb_transfer
usb_transfer_t *MouseOut = NULL;
void Mouse_transfer_cb(usb_transfer_t *transfer)
{
if (Device_Handle == transfer->device_handle) {
isMousePolling = false;
if (transfer->status == 0) {
ESP_LOGI("", "HID report: %02x", transfer->actual_num_bytes);
if (transfer->actual_num_bytes == 0x04) {
uint8_t *const p = transfer->data_buffer;
ESP_LOGI("", "HID report: %02x %02x %02x %02x",
p[0], p[1], p[2], p[3]);
bleMouse.SendData(p);
}
else {
ESP_LOGI("", "Mouse boot hid transfer too short or long");
}
}
else {
ESP_LOGI("", "transfer->status %d", transfer->status);
}
}
}
void check_interface_desc_boot_Mouse(const void *p)
{
const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p;
if ((intf->bInterfaceClass == USB_CLASS_HID) &&
(intf->bInterfaceSubClass == 1) &&
(intf->bInterfaceProtocol == 2)) {
isMouse = true;
ESP_LOGI("", "Claiming a boot Mouse!");
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(Mouse_IN_BUFFER_SIZE, 0, &MouseIn);
if (err != ESP_OK) {
MouseIn = NULL;
ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err);
return;
}
MouseIn->device_handle = Device_Handle;
MouseIn->bEndpointAddress = endpoint->bEndpointAddress;
MouseIn->callback = Mouse_transfer_cb;
MouseIn->context = NULL;
isMouseReady = true;
MouseInterval = endpoint->bInterval;
ESP_LOGI("", "USB boot Mouse 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_Mouse(p);
break;
case USB_B_DESCRIPTOR_TYPE_ENDPOINT:
show_endpoint_desc(p);
if (isMouse && MouseIn == 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()
{
ESP_LOGI("", "Starting BLE work!");
bleMouse.begin();
usbh_setup(show_config_desc_full);
}
void loop()
{
usbh_task();
if (isMouseReady && !isMousePolling && (MouseTimer > MouseInterval)) {
MouseIn->num_bytes = 4;
esp_err_t err = usb_host_transfer_submit(MouseIn);
if (err != ESP_OK) {
ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err);
}
isMousePolling = true;
MouseTimer = 0;
}
}
测试视频:
https://www.bilibili.com/video/BV1bV4y1x777/
特别声明:本制作使用立创EDA设计电路图和PCB,其中BOM提及的元件编号属于立创商城。但是这并不表示本人认可、推荐、认同、建议用户使用立创商城所售商品。本人并不保证、承诺任何人根据本涉及购买使用任何立创商城商品复现本作品能够正常工作。
本作品的电路图和PCB: