上一篇水文中,老周给大伙伴们扯了关于 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"
- }
- ],
- "<strong>toolsDependencies</strong>": [
- {
- "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 "."
- <strong>REQUIRES esp_tinyusb</strong>)
复制代码 可以点 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 <strong><em>CFG_TUD_HID CONFIG_TINYUSB_HID_COUNT</em></strong>
- #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 && <em><strong>CFG_TUD_HID</strong></em>)
- //--------------------------------------------------------------------+
- // 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)),
- <strong>TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE))</strong>
- };
- /**
- * @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_cbtud_hid_get_report_cbtud_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(<strong>HID_ITF_PROTOCOL_KEYBOARD</strong>)),
- TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(<strong>HID_ITF_PROTOCOL_MOUSE</strong>))};
复制代码 这里已经指定了鼠标的 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 (<strong><em>tud_mounted()</em></strong>)
- {
- // 正向移动
- 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 口,电池供电时不需要数据线。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |