上一篇水文中,老周给大伙伴们扯了关于 idf 中添加自定义 Arduino 组件的方案。这次咱们做一下 USB 鼠标玩玩。
很遗憾的是,老周无能,在 Arduino-esp32 组件依赖 TinyUSB 组件时无法进行编译,不管怎么配置都会找不到 tusb.h 文件;就算把 tinyUSB 内置到 arduino-esp32 的源码中也报错;调整代码中的 extern C 语句,又会导致找不到 C++ 类......反正,就是搞不下来。不过,在 idf 中使用 esp_tinyusb 组件是可以正常编译的。据说官方的 arduino-lib-builder 项目 clone 下来是可以正常编译(当然,官方只是说在 Ubuntu 和 树莓派 上测试通过,并没说在 Windows 下可以编译。有人说在 WSL 中可以编译,不过老周未测试,不敢下结论)。
思考其原因,大概有三:1、C 和 C++ 代码混合编译经常会这样;2、可能需要定义特殊的宏;3、官方的 builder 项目中是要对代码"打补丁"后再编译的,可能要改什么。
其实,自己编译一般是有计划修改源代码或订制自己的库。如果没这个需求,咱们直接用官方编译好的库,可以少一些折腾。
根据老周实战的结果,给大伙伴推荐两种 esp32 模拟 USB 鼠标的方案(为了让大伙学得没有压力,USB 键盘暂时不弄)。这两种方案老周都是验证过的,能运行,并且电脑能识别出鼠标。接下来,开工!
方案A:使用 Arduino 库(这种是最简单的)
因为用到 Arduino IDE,老周简单说一下安装事项。咱们作为一名合格的、有技术含量的、迷倒千万妹子的码农,绝对不能在安装开发工具这个关卡给夹脑袋,否则,说句好唱不好听的,真的太低能了。Arduino 2 是重新开发过的,有那么点 VS Code 的味了。老周建议下载 .zip 版本,这个是最好的,解压出来,想放哪就放哪,不依赖系统目录。
打开Arduino IDE,执行菜音【文件】-【首选项】。在设置窗口中滚动到下方,有个"其他开发板管理地址",点击输入框右边的按钮。

在弹出的对话框中填入以下URL,并点"确定"。
https://github.com/espressif/arduino-esp32/releases/download/3.2.0/package_esp32_index.json

设置这个URL后才能获取到乐鑫官方最新的库。
接下来最折腾的是安装 esp32 库,因为不可描述的原因,有时会连不上 github,导致很多压缩包下载不了。
在IDE的开发板管理器窗格中,搜索"esp32",就能找到乐鑫官方维护的库,现在最新是 3.2.0。
不过,相信各位都知道有文件加速这种网站,你网上搜搜就有了。我们可以从 JSON 文件中获取到各个压缩包的下载链接的,方法如下:
1、找到你的用户目录下的 AppData/local,里面有个 Arduino15 目录;
2、进去 Arduino15 目录,你会看到几个 JSON 文件;
3、如果你只使用发布版本,不使用预览版,那直接找到 package_esp32_index.json 文件;
4、打开上面提到的 JSON 文件(用 VS Code 最好),从 platforms 下的 toolsDependencies 节点可以知道依赖的工具。
{
"name": "esp32",
"architecture": "esp32",
"version": "3.2.0",
"category": "ESP32",
"url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esp32-3.2.0.zip",
"archiveFileName": "esp32-3.2.0.zip",
"checksum": "SHA-256:d38b16fef6e519fc0d19bc5af0b39cdbed7dfc2ce69214c1971ded0e61ecd911",
"size": "25447136",
"help": {
"online": ""
},
"boards": [
{
"name": "ESP32 Dev Board"
},
{
"name": "ESP32-C3 Dev Board"
},
{
"name": "ESP32-C6 Dev Board"
},
{
"name": "ESP32-H2 Dev Board"
},
{
"name": "ESP32-P4 Dev Board"
},
{
"name": "ESP32-S2 Dev Board"
},
{
"name": "ESP32-S3 Dev Board"
},
{
"name": "Arduino Nano ESP32"
}
],
"toolsDependencies": [
{
"packager": "esp32",
"name": "esp32-arduino-libs",
"version": "idf-release_v5.4-2f7dcd86-v1"
},
{
"packager": "esp32",
"name": "esp-x32",
"version": "2411"
},
{
"packager": "esp32",
"name": "xtensa-esp-elf-gdb",
"version": "14.2_20240403"
},
{
"packager": "esp32",
"name": "esp-rv32",
"version": "2411"
},
{
"packager": "esp32",
"name": "riscv32-esp-elf-gdb",
"version": "14.2_20240403"
},
{
"packager": "esp32",
"name": "openocd-esp32",
"version": "v0.12.0-esp32-20241016"
},
{
"packager": "esp32",
"name": "esptool_py",
"version": "4.9.dev3"
},
{
"packager": "esp32",
"name": "mkspiffs",
"version": "0.2.3"
},
{
"packager": "esp32",
"name": "mklittlefs",
"version": "3.0.0-gnu12-dc7f933"
},
{
"packager": "arduino",
"name": "dfu-util",
"version": "0.11.0-arduino5"
}
]
},
然后在 Arduino IDE 中看看哪个文件下载挂了。

错误信息中已经告诉咱们下载链接了,直接复制到加速工具下载。下载后扔到 Arduino15/stagging/packages 目录下,然后重新打开 Arduino IDE ,再安装一次。直到所有包都正确下载。如果错误信息中没看到URL,可以根据工具名称和版本,在上面提到的 package_esp32_index.json 文件中查找下载地址。
"name": "esp-rv32",
"version": "2411",
"systems": [
{
"host": "x86_64-pc-linux-gnu",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-x86_64-linux-gnu.tar.gz",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-x86_64-linux-gnu.tar.gz",
"checksum": "SHA-256:a16942465d33c7f0334c16e83bc6feb62e06eeb79cf19099293480bb8d48c0cd",
"size": "593721156"
},
{
"host": "aarch64-linux-gnu",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-aarch64-linux-gnu.tar.gz",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-aarch64-linux-gnu.tar.gz",
"checksum": "SHA-256:22486233d0e0fd58a54ae453b701f195f1432fc6f2e17085b9d6c8d5d9acefb7",
"size": "587879927"
},
{
"host": "arm-linux-gnueabihf",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-arm-linux-gnueabi.tar.gz",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-arm-linux-gnueabi.tar.gz",
"checksum": "SHA-256:27a72d5d96cdb56dae2a1da5dfde1717c18a8c1f9a1454c8e34a8bd34abe662d",
"size": "586531522"
},
{
"host": "i686-pc-linux-gnu",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-i586-linux-gnu.tar.gz",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-i586-linux-gnu.tar.gz",
"checksum": "SHA-256:b7bd6e4cd53a4c55831d48e96a3d500bfffb091bec84a30bc8c3ad687e3eb3a2",
"size": "597070471"
},
{
"host": "x86_64-apple-darwin",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-x86_64-apple-darwin_signed.tar.gz",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-x86_64-apple-darwin_signed.tar.gz",
"checksum": "SHA-256:5f8b571e1aedbe9f856f3bdeca6600cd5510ccff1ca102c4f001421eda560585",
"size": "602343061"
},
{
"host": "arm64-apple-darwin",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-aarch64-apple-darwin_signed.tar.gz",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-aarch64-apple-darwin_signed.tar.gz",
"checksum": "SHA-256:a7276042a7eb2d33c2dff7167539e445c32c07d43a2c6827e86d035642503e0b",
"size": "578521565"
},
{
"host": "i686-mingw32",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-i686-w64-mingw32.zip",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-i686-w64-mingw32.zip",
"checksum": "SHA-256:54193a97bd75205678ead8d11f00b351cfa3c2a6e5ab5d966341358b9f9422d7",
"size": "672055172"
},
{
"host": "x86_64-mingw32",
"url": "https://github.com/espressif/crosstool-NG/releases/download/esp-14.2.0_20241119/riscv32-esp-elf-14.2.0_20241119-x86_64-w64-mingw32.zip",
"archiveFileName": "riscv32-esp-elf-14.2.0_20241119-x86_64-w64-mingw32.zip",
"checksum": "SHA-256:24c8407fa467448d394e0639436a5ede31caf1838e35e8435e19df58ebed438c",
"size": "677812937"
}
]
},
根据不同的系统平台选好目标,其中,url 字段就是下载地址了。
接下来可以干活了。用封装好的 arduino 库模拟 USB 鼠标是很简单的,只用一个 USBHIDMouse 类就搞定。
1、实例化;
2、调用 begin 方法初始化;
3、移动鼠标时调用 move 方法。该方法的声明如下:
void move(int16_t x, int16_t y, int8_t wheel = 0, int8_t pan = 0);
x、y 就是水平和垂直方向上移动的量,相对坐标,比如,x = 5,就是鼠标向右移动5个单位(像素)。后面两个参数默认给了0,调用时如不需要可以不传值。wheel 是滚轮的滚动量,pan 表示水平滚动的量(要用到水平滚动时)。
咱们写一段代码,让鼠标在屏幕上画正方形,即向右 -> 向下 -> 向左 -> 向上回到原来的位置。
#include "USB.h"
#include "USBHIDMouse.h"
USBHIDMouse mouse; // 实例化
const int8_t move_d = 3; // 单次鼠标移动量
const int total_count = 200; // 一个方向移动总次数
int count; // 记录发了多少次坐标
int step; // 后面用于做比较,0表示向左,1表示向下......
void setup() {
count = 0;
step = 0;
USB.begin(); // 注意,不要忘了这一行
mouse.begin(); // 初始化
}
void loop() {
switch (step)
{
case 0: // 向右移动
if(count < total_count)
{
mouse.move(move_d, 0);
count++;
}
else
{
// 换下一个移动方向
step = 1;
count = 0;
}
break;
case 1: // 向下移动
if(count < total_count)
{
mouse.move(0, move_d);
count ++;
}
else
{
// 下一个方向
step = 2;
count = 0;
}
break;
case 2: // 向左移动
if(count < total_count)
{
mouse.move(-move_d, 0);
count++;
}
else
{
step = 3;
count=0;
}
break;
case 3: // 向上移动
if(count < total_count)
{
mouse.move(0, -move_d);
count++;
}
else
{
step = 0;
count = 0;
}
break;
default:
break;
}
delay(10); // 延时(毫秒级)
}
相信大伙伴们能看懂代码的。首先,包含 USB.h 和 USBHIDMouse.h;然后直接可以创建 USBHIDMouse 实例。在初始化时,一定要先初始化 USB,再初始化鼠标,即 USB.begin 方法一定要先调用。在 loop 函数中,用 move 方法移动鼠标就是了,简单吧。
写好程序后,需要配置一下 USB Mode 参数。在 Arduino IDE 窗口中,点击【工具】菜单。在子菜单中执行【USB Mode: XXX】->【USB OTG(TinyUSB)】。

如果不修改 USB Mode,烧录之后电脑可能识别不到,或者要重置几次才能识别。这个就是关闭默认的串口输出,所以你不能通过 USB 口来查看日志了。
编译,上传到 esp32 开发板上,有的板子是手动进入烧录模式的,可能要手动重启一下板子。如果没问题,你会看到鼠标动了。

方案B:idf 搭配 esp_tinyusb 组件
每次看到有人鼓吹图形化开发什么的,心里就想嘲笑一番。为啥呢?其实那个是给小朋友玩的,不是咱们成年人用的。能不能快速成形跟用不用图形工具无关,也与用不用低代码无关,而是跟有没有被严重封装好的组件。比如,上面咱们用的 USBHIDMouse 类,人家就是高度封装好的,基本上一行代码初始化就可以读写了,这样写代码甚至比你用鼠标拖控件还快(除非你 C++ 语法学得极烂)。
现在很多工具,真的,营销成分多一些。就算给小朋友玩,好玩是好玩了,但的确培养不了什么编程习惯。以前给小朋友学用的是 BASIC 语言,最起码还是真枪实弹地写代码。代码不见得要写复杂,几行,几段都行。主要是养成好的思维和习惯,才有身临其境的氛围。老周上初中的时候,也是用 QBASIC 入门的,还是 DOS 窗口的,写一些数学算法的东西,还有从奥赛书上抄的算法。也没觉得有多难,还更有乐趣。
扯远了,下面介绍第二种方案。虽然严重封装好的组件好用,但也有很显著的缺点的。如果在初始化时候需要配置详细的参数,比如,电脑识别到鼠标后,显示我自定义的厂商名称,产品ID等。一种方法是把 Arduino 的库的代码自己修改再用;另一种更好的方法是用 idf 实现,控制起来更灵活,尽管要多写点代码。实际开发中经常会这样的,不是你想偷懒就能偷的。
先介绍一下库,这里实际上会用到两个库。到 Esp Component Registry 上搜索"tinyusb",会搜到两个结果。

第三个已经"过时",不必管它。tinyusb 就是乐鑫移植的 tinnyUSB 库,而 esp_tinyusb 是做进一步封装,让你用起来更带劲。所以,esp_tinyusb 依赖 tinyusb。
在 idf 中直接使用 tinyusb 库,你有以下方案可选:
1、执行 idf.py add-dependency 命令,让 idf 工具自动替你下载;
2、手动下载库,放到项目目录下的 components 子目录中,无需在 CMake 中设置 EXTRA_COMPONENT_DIRS 变量。idf 工具会自动查找 components 目录下的组件;
3、手动下载,放到项目以外的目录下,需要通过 EXTRA_COMPONENT_DIRS 变量设置组件所在目录。
下面老周将采用第2种方法。手动下载 esp_tinyusb 和 tinyusb 两个库, 然后在项目的根目录下新建一个 components
目录,并把两个库解压到此目录下。
其结构如下:

这次老周用另一台电脑写代码,配置比较高,编译起来快。这台机器装的是 Mint Linux,操作和 Windows 下一样。用乐鑫官方的 VS Code 扩展工具新建一个空项目(和前面介绍自定义 Arduino 组件一样)。
新建项目后,第一时间做好配置。
1、选好开发板型号。


2、打开 main 目录下的 CMakeLists.txt 文件,在注册 main 组件时依赖 esp_tinyusb 库。
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES esp_tinyusb)
可以点 VS Code 底部状态栏上的"打开 IDF 终端"按钮,打开命令窗口,执行 idf.py reconfigure 命令,如果没报错,就说明没有语法错误了。
3、点击 VS Code 底部状态栏上的"SDK 编辑器"按钮,打开配置页面。
4、找到 Tiny USB Stack 节点下的"Human Interface Device Class(HID)" 条目,把"TinyUSB HID interface count"设置为 1。这个值默认是0,不改的话相关代码不会编译。

如果你好奇为什么的话,可以打开 esp_tinyusb 组件下的 include/tusb_config.h 文件,然后看到这两个地方。
#ifndef CONFIG_TINYUSB_HID_COUNT
# define CONFIG_TINYUSB_HID_COUNT 0
#endif
// 此处省略 711 个字
// Enabled device class driver
#define CFG_TUD_CDC CONFIG_TINYUSB_CDC_COUNT
#define CFG_TUD_MSC CONFIG_TINYUSB_MSC_ENABLED
#define CFG_TUD_HID CONFIG_TINYUSB_HID_COUNT
#define CFG_TUD_MIDI CONFIG_TINYUSB_MIDI_COUNT
#define CFG_TUD_VENDOR CONFIG_TINYUSB_VENDOR_COUNT
#define CFG_TUD_ECM_RNDIS CONFIG_TINYUSB_NET_MODE_ECM_RNDIS
#define CFG_TUD_NCM CONFIG_TINYUSB_NET_MODE_NCM
#define CFG_TUD_DFU CONFIG_TINYUSB_DFU_MODE_DFU
#define CFG_TUD_DFU_RUNTIME CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME
#define CFG_TUD_BTH CONFIG_TINYUSB_BTH_ENABLED
记住 CFG_TUD_HID 宏的值是来自 TINYUSB_HID_COUNT。
然后在 tinyusb 库中找到 src/class/hid/hid_device.c 文件。可以看到,如果 CFG_TUD_HID 宏的值不是大于 0 的话,那么代码就不会编译。
#include "tusb_option.h"
#if (CFG_TUD_ENABLED && **CFG_TUD_HID**)
//--------------------------------------------------------------------+
// INCLUDE
//--------------------------------------------------------------------+
#include "device/usbd.h"
#include "device/usbd_pvt.h"
#include "hid_device.h"
............
#endif
现在你懂了吧,为什么要把那个配置项改为1。
现在打开 main.c 文件,开始写代码。
USB 描述符是很复杂的东西,有兴趣的话可以去看看 USB 协议定义说明,没兴趣的话,直接从示例代码抄过来就行。这里没啥技巧可言,都是标准化的东东。
#include <stdio.h>
#include "tinyusb.h"
#include "class/hid/hid_device.h"
/************* TinyUSB 描述符 ****************/
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN)
/**
* @brief HID report descriptor(上报描述符)
*
* In this example we implement Keyboard + Mouse HID device,
* so we must define both report descriptors
*/
const uint8_t hid_report_descriptor[] = {
// 如果你要模拟键盘,请取消下面的注释
// TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)),
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE))
};
/**
* @brief String descriptor(字符描述符)
*/
const char* hid_string_descriptor[5] = {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"GuangDong-Fish", // 1: 生产商
"Big-Mouse", // 2: 产品
"8848", // 3: 序列号
"8848 HID Interface", // 4: HID 接口名称
};
/**
* @brief Configuration descriptor
*
* This is a simple configuration descriptor that defines 1 configuration and 1 HID interface
*/
static const uint8_t hid_configuration_descriptor[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 16, 10),
};
/********* TinyUSB HID 回调函数 ***************/
// Invoked when received GET HID REPORT DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
// We use only one interface and one HID report descriptor, so we can ignore parameter 'instance'
return hid_report_descriptor;
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
}
字符描述符那里,可以根据实际情况改一下产商、产品、序列号等信息,其他代码不用改。注意,这几个回调函数一定要留着,就算你用不上,也要留个空函数在那里:
tud_hid_descriptor_report_cb
tud_hid_get_report_cb
tud_hid_set_report_cb
好了,接下来 app_main 函数中的代码就要咱们自己写了。
先用 tinyusb_config_t 结构体进行配置。
const tinyusb_config_t tucfg =
{
.device_descriptor = NULL, // 不需要
.external_phy = false,
// 下面配置字符描述符
.string_descriptor = hid_string_descriptor,
.string_descriptor_count = 5, // 数组中元素个数
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = hid_configuration_descriptor, // HID configuration descriptor for full-speed and high-speed are the same
.hs_configuration_descriptor = hid_configuration_descriptor,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = hid_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
然后调用 tinyusb_driver_install 函数,完成初始化。
esp_err_t result = ESP_OK;
result = tinyusb_driver_install(&tucfg);
// 检查一下是否初始化成功
if (result != ESP_OK)
{
// ESP_LOGE("tusb", "tusb 初始化失败,主任务退出");
// return;
esp_restart(); // 重启
}
初始化已经完成,现在可以向主机发送鼠标操作了。发送鼠标信号用的是以下函数:
bool tud_hid_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal)
各参数含义如下:
**report_id:**报数据的ID,这个ID由前面的 retport 描述符指定,请回看上面的代码,即 hid_report_descriptor 变量。
const uint8_t hid_report_descriptor[] = {
// 如果你要模拟键盘,请取消下面的注释
// TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)),
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE))};
这里已经指定了鼠标的 report ID 是 HID_ITF_PROTOCOL_MOUSE,键盘的 report ID 是 HID_ITF_PROTOCOL_KEYBOARD。因此,在调用 tud_hid_mouse_report 函数时,report_id 参数的值就是 HID_ITF_PROTOCOL_MOUSE。
**buttons:**鼠标是否按下特定的键,参数值来自以下枚举类型:
typedef enum
{
MOUSE_BUTTON_LEFT = TU_BIT(0), ///< Left button
MOUSE_BUTTON_RIGHT = TU_BIT(1), ///< Right button
MOUSE_BUTTON_MIDDLE = TU_BIT(2), ///< Middle button
MOUSE_BUTTON_BACKWARD = TU_BIT(3), ///< Backward button,
MOUSE_BUTTON_FORWARD = TU_BIT(4), ///< Forward button,
}hid_mouse_button_bm_t;
**x、y:**鼠标移动的坐标量(相对),正值表示向右/向下移动,负值表示鼠标向左/上移动。
**vertical:**垂直滚动量,一般就是鼠标滚轮的滚动量。
**horizontal:**水平滚动的量(有时候会用到)。
为了让示例简单好懂,咱们在一个循环中先让鼠标向右下角移动,随后向左上角移动相同的次数。
int8_t move_dis = 5; // 每次移动的量
const uint16_t steps = 300; // 移动多少步
const int step_delay = 20; // 每一次移动后的延时
uint16_t n;
while (true)
{
if (*tud_mounted()*)
{
// 正向移动
for (n = 0; n < steps; n++)
{
tud_hid_mouse_report(
HID_ITF_PROTOCOL_MOUSE, // 报告ID
0, // 无任何键按下
move_dis, // X坐标上的移动量
move_dis, // Y坐标上的移动量
0, // 无垂直滚动
0 // 无水平滚动
);
vTaskDelay(pdMS_TO_TICKS(step_delay));
}
vTaskDelay(pdMS_TO_TICKS(800));
// 反向移动
for (n = 0; n < steps; n++)
{
tud_hid_mouse_report(
HID_ITF_PROTOCOL_MOUSE,
0,
-move_dis,
-move_dis,
0,
0);
vTaskDelay(pdMS_TO_TICKS(step_delay));
}
}
// 等待一段时间
vTaskDelay(pdMS_TO_TICKS(2500));
}
有一点很重要:每轮循环在移动鼠标前,一定要访问一下 tud_mounted 函数,确保它返回 true 才能发送 report 数据,否则会导致电脑识别不到鼠标。
使用 Linux 的话,在烧录时会有个 50 米大天坑。不填这个坑你是无法用 UART 或 USB JTag 来烧录的。就算你把当前用户添加到 dialout 分组也解决不了。系统因缺少 openOCD 的 udev rule 文件,openOCD 将无法连接。
解决:先找到你随 esp idf 一同安装的 openOCD 目录(在你指定的 IDF_TOOLS_PATH 下面),找到 tools/openocd-esp32/v0.12.0-esp32-<版本号>/openocd-esp32/share/openocd/contrib 目录,里面有个 60-openocd.rules 文件。把它复制到 /etc/udev/rules.d 目录下。
sudo cp <60-openocd.rules文件路径> /etc/udev/rules.d/
重启系统后,就能烧录了。
使用 USB 模拟键鼠后,你的 ESP32 板子就不能再使用 USB 口来查看日志了,而且这个玩法似乎用处不大,毕竟你不太可能真拿它来当鼠标用。不过,如果你的开发板带陀螺仪的话,那倒可以做成姿态鼠标,通过在空中旋转来移动鼠标。对,就是所谓的"空中飞鼠"。可能,也许,或者用蓝牙来模拟键鼠会好一些,不占用 USB 口,电池供电时不需要数据线。