ESP32-S3实现远程虚拟的USB有线鼠标键盘
想要用ESP32-S3实现远程虚拟USB有线鼠标键盘,让ESP32-S3同时具备两个关键能力:一是模拟成电脑可识别的USB有线HID设备(鼠标+键盘),二是通过远程通信(WiFi/BLE)接收控制指令,最终让电脑像接了真实USB键鼠一样,响应远程的操作。这个方案无需额外USB转串口模块,直接利用ESP32-S3的USB-OTG功能实现"有线键鼠模拟",远程端通过WiFi(推荐,传输距离远)或BLE控制。
一、核心实现原理
ESP32-S3的USB-OTG接口支持USB Device模式,可配置为HID(人机接口设备)类,向电脑发送标准的鼠标/键盘HID报告,电脑会自动识别为"USB有线键鼠";同时ESP32-S3通过WiFi建立TCP/UDP连接,接收远程端(比如手机/另一台电脑)的控制指令,解析后转换成HID报告发送给电脑,完成远程操作。
简单来说:
远程控制端 → WiFi(TCP) → ESP32-S3(解析指令) → USB HID模拟 → 电脑(识别为有线键鼠)
二、硬件准备(极简,新手友好)
| 组件 | 规格/要求 | 作用说明 |
|---|---|---|
| ESP32-S3开发板 | 带USB-OTG口(如ESP32-S3-DevKitC-1) | 核心,同时实现WiFi通信和USB HID模拟 |
| USB Type-C数据线 | 数据款(非仅充电) | 连接ESP32-S3的USB-OTG口到电脑 |
| 电脑(Windows/Linux) | 任意版本 | 识别ESP32-S3为USB键鼠并响应操作 |
| 远程控制端 | 手机/另一台电脑(带网络调试助手) | 发送键鼠控制指令(如"移动鼠标""按键盘A") |
三、软件环境准备
- Arduino IDE配置 (新手首选,无需复杂编译):
- 安装Arduino IDE,在「文件→首选项」中添加ESP32开发板地址:
https://dl.espressif.com/dl/package_esp32_index.json; - 在「工具→开发板→ESP32 Arduino」中选择
ESP32-S3 Dev Module; - 关键配置:
USB Mode选USB-OTG (HID),CPU Frequency选80MHz(降频降功耗),Flash Size选4MB。
- 安装Arduino IDE,在「文件→首选项」中添加ESP32开发板地址:
四、完整实现代码(WiFi+USB HID键鼠,开箱即用)
以下代码实现核心功能:
- ESP32-S3连接指定WiFi,作为TCP服务器等待远程指令;
- 模拟USB有线鼠标(移动、左键点击)+ 键盘(单键、组合键);
- 自定义简单指令协议,远程端发送指令即可控制。
cpp
#include <Arduino.h>
#include <WiFi.h>
#include <USBHID.h>
#include <HIDReports.h>
#include <HIDTypes.h>
// ===================== 配置项(修改为你的信息)=====================
#define WIFI_SSID "你的WiFi名称"
#define WIFI_PWD "你的WiFi密码"
#define TCP_PORT 8888 // TCP通信端口
#define BAUD_RATE 115200 // 串口波特率(调试用)
// ===================== USB HID 配置(鼠标+键盘)=====================
USBHID hid;
// 鼠标HID报告描述符(标准格式:X/Y移动、滚轮、按键)
uint8_t mouseReportDesc[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xA1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xA1, 0x00, // COLLECTION (Physical)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x03, // REPORT_COUNT (3)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x01, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x38, // USAGE (Wheel)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7F, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xC0, // END_COLLECTION
0xC0 // END_COLLECTION
};
// 键盘HID报告描述符(标准104键+修饰键)
uint8_t keyboardReportDesc[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xA1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)
0x19, 0xE0, // USAGE_MINIMUM (Keyboard Left Control)
0x29, 0xE7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x08, // REPORT_COUNT (8)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x65, // LOGICAL_MAXIMUM (101)
0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)
0x19, 0x00, // USAGE_MINIMUM (Reserved)
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Array)
0xC0 // END_COLLECTION
};
// 定义HID报告结构
HIDReport mouseReport = {mouseReportDesc, sizeof(mouseReportDesc)};
HIDReport keyboardReport = {keyboardReportDesc, sizeof(keyboardReportDesc)};
// ===================== TCP通信相关 =====================
WiFiServer tcpServer(TCP_PORT);
WiFiClient tcpClient;
String recvData = "";
// ===================== 键鼠控制函数(核心)=====================
// 鼠标控制:x/y为移动偏移(-127~127),btn为按键(1=左键,2=右键,4=中键),wheel为滚轮(-127~127)
void mouseControl(int8_t x, int8_t y, uint8_t btn, int8_t wheel = 0) {
uint8_t report[4] = {btn, x, y, wheel}; // 鼠标报告:按键、X、Y、滚轮
hid.sendReport(&mouseReport, report, sizeof(report));
}
// 键盘控制:modifier为修饰键(如0x01=左Ctrl,0x02=左Shift),key为按键(如0x04=A,0x05=B)
void keyboardControl(uint8_t modifier, uint8_t key) {
uint8_t report[8] = {modifier, 0, key, 0, 0, 0, 0, 0}; // 键盘报告格式
hid.sendReport(&keyboardReport, report, sizeof(report));
delay(50); // 按键保持时间
// 释放按键(避免长按)
memset(report, 0, sizeof(report));
hid.sendReport(&keyboardReport, report, sizeof(report));
}
// ===================== 指令解析(自定义简单协议)=====================
// 指令格式示例:
// 鼠标移动:M|x|y (如M|10|5 → 鼠标右移10,下移5)
// 鼠标左键点击:MB|1 (MB|0=释放,MB|1=按下)
// 键盘按键:K|mod|key (如K|0|4 → 按A键;K|1|4 → 按Ctrl+A)
void parseCmd(String cmd) {
cmd.trim(); // 去除空格
if (cmd.startsWith("M|")) { // 鼠标移动
cmd = cmd.substring(2);
int x = cmd.substring(0, cmd.indexOf("|")).toInt();
int y = cmd.substring(cmd.indexOf("|")+1).toInt();
mouseControl((int8_t)x, (int8_t)y, 0);
Serial.printf("鼠标移动:X=%d, Y=%d\n", x, y);
} else if (cmd.startsWith("MB|")) { // 鼠标按键
int btn = cmd.substring(3).toInt();
mouseControl(0, 0, btn);
Serial.printf("鼠标按键:%d\n", btn);
} else if (cmd.startsWith("K|")) { // 键盘按键
cmd = cmd.substring(2);
int mod = cmd.substring(0, cmd.indexOf("|")).toInt();
int key = cmd.substring(cmd.indexOf("|")+1).toInt();
keyboardControl((uint8_t)mod, (uint8_t)key);
Serial.printf("键盘按键:修饰键=%d, 按键=%d\n", mod, key);
}
}
// ===================== WiFi连接 =====================
void connectWiFi() {
WiFi.begin(WIFI_SSID, WIFI_PWD);
Serial.print("连接WiFi...");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi连接成功!");
Serial.print("ESP32-S3 IP地址:");
Serial.println(WiFi.localIP());
tcpServer.begin(); // 启动TCP服务器
Serial.printf("TCP服务器已启动,端口:%d\n", TCP_PORT);
}
// ===================== 初始化 =====================
void setup() {
Serial.begin(BAUD_RATE);
delay(1000);
// 初始化USB HID
hid.addReport(&mouseReport);
hid.addReport(&keyboardReport);
hid.begin();
while (!hid.ready()) { // 等待HID初始化完成
delay(100);
Serial.print(".");
}
Serial.println("\nUSB HID初始化完成,电脑应识别为USB键鼠!");
// 连接WiFi
connectWiFi();
}
// ===================== 主循环 =====================
void loop() {
// 检测TCP客户端连接
if (!tcpClient.connected()) {
tcpClient = tcpServer.available();
if (tcpClient) {
Serial.println("远程客户端已连接!");
}
delay(100);
return;
}
// 读取远程指令
while (tcpClient.available() > 0) {
char c = tcpClient.read();
if (c == '\n') { // 按换行符分割指令
parseCmd(recvData);
recvData = "";
} else {
recvData += c;
}
}
delay(10);
}
五、关键代码解析(新手必看)
-
USB HID报告描述符:
- 是电脑识别"USB键鼠"的核心,上述代码用的是标准HID报告格式,无需修改,电脑会自动驱动;
- 鼠标报告包含"3个按键(左/右/中)、X/Y移动、滚轮",键盘报告包含"8个修饰键(Ctrl/Shift/Alt等)+ 6个普通按键"。
-
指令协议:
- 自定义极简协议,远程端发送指令时按格式(如
M|10|5),ESP32-S3解析后调用mouseControl/keyboardControl发送HID报告; - 键盘按键码参考标准HID值(如0x04=A、0x05=B、0x1E=0、0x44=Enter),修饰键码:0x01=左Ctrl、0x02=左Shift、0x04=左Alt、0x08=左Win。
- 自定义极简协议,远程端发送指令时按格式(如
-
TCP通信:
- ESP32-S3作为TCP服务器,远程端(手机/电脑)用"网络调试助手"连接其IP+端口(8888),发送指令即可控制。
六、测试步骤(5分钟验证)
- 烧录代码 :修改
WIFI_SSID和WIFI_PWD后,烧录到ESP32-S3,用USB Type-C线连接ESP32-S3的USB-OTG口到电脑; - 识别设备:电脑会自动识别"USB Human Interface Device"(无需装驱动),在「设备管理器」可看到"鼠标""键盘";
- 远程控制 :
- 打开串口监视器,查看ESP32-S3的IP地址(如192.168.1.100);
- 远程端(如电脑)打开"网络调试助手",选择「TCP客户端」,输入IP+8888,连接成功后发送指令:
- 发送
M|10|5→ 鼠标向右移动10像素、向下移动5像素; - 发送
MB|1→ 鼠标左键按下,发送MB|0→ 释放; - 发送
K|0|4→ 按下并释放A键,发送K|1|4→ 按下并释放Ctrl+A。
- 发送
七、避坑指南(新手常踩)
- USB线选数据款:仅充电的USB线会导致电脑无法识别HID设备,务必用带数据传输的Type-C线;
- HID初始化等待 :代码中
while (!hid.ready())必须保留,否则HID未就绪会导致指令无响应; - 指令格式严格 :远程指令需按
M|x|y/MB|btn/K|mod|key格式,且以换行符结尾(网络调试助手勾选"发送新行"); - WiFi断连处理:代码可扩展"重连WiFi"逻辑,避免远程端断连后无法恢复。
总结
实现ESP32-S3远程虚拟USB有线键鼠的核心关键点:
- USB HID配置:用标准报告描述符让电脑识别为有线键鼠,无需额外驱动;
- 远程通信:WiFi TCP是最通用的远程方式,传输距离远、稳定性高;
- 指令解析:自定义简单协议,降低远程端开发成本,适配手机/电脑等控制端。
这套方案无需复杂硬件,代码开箱即用,既实现了"USB有线键鼠模拟"的稳定性,又具备"远程控制"的灵活性,适合远程办公、智能家居控制等场景。