【ESP32】两种模拟 USB 鼠标的方法

上一篇水文中,老周给大伙伴们扯了关于 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,执行菜音【文件】-【首选项】。在设置窗口中滚动到下方,有个“其他开发板管理地址”,点击输入框右边的按钮。

【ESP32】两种模拟 USB 鼠标的方法

在弹出的对话框中填入以下URL,并点“确定”。

https://github.com/espressif/arduino-esp32/releases/download/3.2.0/package_esp32_index.json

【ESP32】两种模拟 USB 鼠标的方法

设置这个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 中看看哪个文件下载挂了。

【ESP32】两种模拟 USB 鼠标的方法

错误信息中已经告诉咱们下载链接了,直接复制到加速工具下载。下载后扔到 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)】。

【ESP32】两种模拟 USB 鼠标的方法

如果不修改 USB Mode,烧录之后电脑可能识别不到,或者要重置几次才能识别。这个就是关闭默认的串口输出,所以你不能通过 USB 口来查看日志了。

编译,上传到 esp32 开发板上,有的板子是手动进入烧录模式的,可能要手动重启一下板子。如果没问题,你会看到鼠标动了。

【ESP32】两种模拟 USB 鼠标的方法

 

方案B:idf 搭配 esp_tinyusb 组件

每次看到有人鼓吹图形化开发什么的,心里就想嘲笑一番。为啥呢?其实那个是给小朋友玩的,不是咱们成年人用的。能不能快速成形跟用不用图形工具无关,也与用不用低代码无关,而是跟有没有被严重封装好的组件。比如,上面咱们用的 USBHIDMouse 类,人家就是高度封装好的,基本上一行代码初始化就可以读写了,这样写代码甚至比你用鼠标拖控件还快(除非你 C++ 语法学得极烂)。

现在很多工具,真的,营销成分多一些。就算给小朋友玩,好玩是好玩了,但的确培养不了什么编程习惯。以前给小朋友学用的是 BASIC 语言,最起码还是真枪实弹地写代码。代码不见得要写复杂,几行,几段都行。主要是养成好的思维和习惯,才有身临其境的氛围。老周上初中的时候,也是用 QBASIC 入门的,还是 DOS 窗口的,写一些数学算法的东西,还有从奥赛书上抄的算法。也没觉得有多难,还更有乐趣。

扯远了,下面介绍第二种方案。虽然严重封装好的组件好用,但也有很显著的缺点的。如果在初始化时候需要配置详细的参数,比如,电脑识别到鼠标后,显示我自定义的厂商名称,产品ID等。一种方法是把 Arduino 的库的代码自己修改再用;另一种更好的方法是用 idf 实现,控制起来更灵活,尽管要多写点代码。实际开发中经常会这样的,不是你想偷懒就能偷的。

先介绍一下库,这里实际上会用到两个库。到 Esp Component Registry 上搜索“tinyusb”,会搜到两个结果。

【ESP32】两种模拟 USB 鼠标的方法

第三个已经“过时”,不必管它。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 

目录,并把两个库解压到此目录下。

其结构如下:

【ESP32】两种模拟 USB 鼠标的方法

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

新建项目后,第一时间做好配置。

1、选好开发板型号。

【ESP32】两种模拟 USB 鼠标的方法

【ESP32】两种模拟 USB 鼠标的方法

 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 编辑器”按钮【ESP32】两种模拟 USB 鼠标的方法,打开配置页面。

4、找到 Tiny USB Stack 节点下的“Human Interface Device Class(HID)” 条目,把“TinyUSB HID interface count”设置为 1。这个值默认是0,不改的话相关代码不会编译。

【ESP32】两种模拟 USB 鼠标的方法

如果你好奇为什么的话,可以打开 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 口,电池供电时不需要数据线。

 

发表评论

您必须 [ 登录 ] 才能发表留言!

相关文章