MicroPython pyboard 化身 USB 鼠标:从 boot.py 配置到加速度计控制实战
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
本教程基于仓库中的 docs/pyboard/tutorial/usb_mouse.rst 展开,讲解如何把 pyboard 从默认的 U 盘模式切换为 USB 鼠标(HID 设备)模式:先通过boot.py中的pyb.usb_mode('VCP+HID')完成模式切换,再借助pyb.USB_HID().send()手动发送鼠标事件,最后用板载加速度计实现"倾斜即移动"的体感鼠标。读完本文,你将掌握 pyboard USB 设备接口的配置原理、HID 报告的数据格式,以及安全模式(safe mode)这一在 HID 模式下救急编辑文件的关键技巧,并能举一反三地自定义 HID 键盘等设备。
pyboard 的 USB 设备本质:接口(interface)而非单一实体
pyboard 本身就是一个 USB 设备,默认配置为"虚拟串口 + 大容量存储(U 盘)"。在 ports/stm32/usb.c 的源码注释中,作者明确阐述了其设计哲学:
MicroPython bindings for USB:
pyb.usb_mode(...)configures the USB on the board. The USB itself is not an entity, rather the interfaces are, and can be accessed by creating objects, such aspyb.USB_VCP()andpyb.USB_HID().
也就是说,USB 本身不是一个对象,而是多个**接口(interface)**的组合,每个接口可以通过独立的 Python 对象访问:
pyb.USB_VCP()—— 虚拟串口接口,用于读写数据、承载 REPL;pyb.USB_HID()—— 人机交互设备接口,用于发送/接收 HID 报告(本例中即鼠标事件);- MSC 接口 —— 大容量存储,即你看到的 U 盘。
这套接口组合在 ports/stm32/modpyb.c 中注册到pyb模块:pyb.usb_mode、pyb.USB_HID、pyb.USB_VCP、pyb.hid_mouse、pyb.hid_keyboard(其中后两者仅在MICROPY_HW_USB_HID使能时编译,旧版遗留的全局发送函数已标记为 deprecated,推荐使用USB_HID.send)。
第一步:编辑 boot.py,开启 VCP+HID 模式
boot.py是 pyboard 上电后最先执行的脚本,负责配置 USB 和文件系统(应用代码放在main.py)。全新的boot.py大致如下:
# boot.py -- run on boot to configure USB and filesystem # Put app code in main.py import pyb #pyb.main('main.py') # main script to run after this one #pyb.usb_mode('VCP+MSC') # act as a serial and a storage device #pyb.usb_mode('VCP+HID') # act as a serial device and a mouse启用鼠标模式只需取消最后一行注释:
pyb.usb_mode('VCP+HID') # act as a serial device and a mouse如果你已经改过boot.py,最小的可用写法只有两行:
import pyb pyb.usb_mode('VCP+HID')这行代码告诉 pyboard 在上电时把自己配置为同时具备 VCP(Virtual COM Port,虚拟串口)和 HID(Human Interface Device,本例为鼠标)两种 USB 接口的设备。
注意:此时板子同时还是 VCP 虚拟串口,因此你依然可以用串口程序连接 REPL 交互(这也是后续"手动发送鼠标事件"的前提)。
保存文件后,弹出/卸载 pyboard 的 U 盘,再按 RST 复位键。此时电脑应能识别出一个新的鼠标设备——恭喜,你的 pyboard 已经"变成"鼠标了。
pyb.usb_mode 支持的全部模式
在 ports/stm32/usb.c 中定义了完整的模式表(pyb_usb_mode_table),pyb.usb_mode接受的第一个参数即表中字符串:
| 模式字符串 | 含义 | 默认 PID 宏 |
|---|---|---|
'VCP' | 仅虚拟串口 | MICROPY_HW_USB_PID_CDC |
'MSC' | 仅大容量存储 | MICROPY_HW_USB_PID_MSC |
'VCP+MSC' | 串口 + U 盘(默认) | MICROPY_HW_USB_PID_CDC_MSC |
'VCP+HID' | 串口 + HID(鼠标/键盘) | MICROPY_HW_USB_PID_CDC_HID |
'VCP+MSC+HID' | 串口 + U 盘 + HID 三合一 | MICROPY_HW_USB_PID_CDC_MSC_HID |
旧名称(如'CDC'、'CDC+MSC'、'CDC+HID')出于向后兼容仍然支持(对应源码中的deprecated_str字段)。此外,pyb.usb_mode()不带参数调用时返回当前模式字符串;传入None则禁用 USB。部分板卡还支持双串口('2xVCP'等),取决于MICROPY_HW_USB_CDC_NUM的编译配置。
第二步:手动发送鼠标事件,理解 HID 报告格式
用串口程序连接 REPL,依次输入以下命令:
>>> hid = pyb.USB_HID() >>> hid.send((0, 100, 0, 0)) # (button status, x-direction, y-direction, scroll)鼠标应当向右移动 100 像素。send()接收的元组包含 4 个字节,即一份标准的 4 字节鼠标 HID 报告:
- button status(按键状态):bit0=左键、bit1=右键、bit2=中键,按下对应的位就置 1;
- x-direction(x 方向位移):正数向右、负数向左(有符号 8 位整数);
- y-direction(y 方向位移):正数向下、负数向上(注意坐标系与屏幕显示习惯相反);
- scroll(滚轮):滚动量,正负对应滚轮方向。
100表示在 x 方向移动了 100 像素。报告是"相对位移"语义:每次send发送的是一个增量,多次发送即累积移动。
让鼠标左右摆动
>>> import math >>> def osc(n, d): ... for i in range(n): ... hid.send((0, int(20 * math.sin(i / 10)), 0, 0)) ... pyb.delay(d) ... >>> osc(100, 50)osc的第一个参数n是发送的鼠标事件次数,第二个参数d是两次事件之间的延时(毫秒)。正弦函数把 x 位移变成平滑的正负交替,鼠标便来回摆动。可以尝试修改幅度(如 20)、频率(如i/10)和延时(如 50)来感受参数对运动轨迹的影响。
练习题:利用math.cos和math.sin组合 x、y 分量,让鼠标画出一个圆。
USB_HID.send 的底层实现与边界
pyb.USB_HID()在 ports/stm32/usb.c 中实现为pyb_usb_hid_send。其核心逻辑有两点值得注意:
- 两种入参形式:既可以直接传
bytes/bytearray,也可以传元组或整数列表——元组/列表会被逐个转换成uint8_t字节(这正是本教程hid.send((0, 100, 0, 0))可用的原因); - 长度上限:当入参是元组/列表时,内部使用 8 字节临时缓冲(
byte temp_buf[8]),超过 8 字节会抛出ValueError: tuple/list too large for HID report; use bytearray instead——自定义更大的报告(如键盘 8 字节报告、多媒体按键报告)时,应改用bytearray传入; - 返回值:
send成功后返回发送的字节数;若底层USBD_HID_SendReport失败则返回0,可用于检测链路状态。
与send配套的还有USB_HID.recv(data, timeout=5000)方法(见 ports/stm32/usb.c),用于接收主机发来的 HID 报告——在双向 HID 通信场景(如模拟游戏手柄)中会用到。
第三步:用加速度计做一个"体感鼠标"
让鼠标跟随板子的倾斜角度移动,核心代码如下(可临时在 REPL 中逐行输入验证):
import pyb switch = pyb.Switch() accel = pyb.Accel() hid = pyb.USB_HID() while not switch(): hid.send((0, accel.x(), accel.y(), 0)) pyb.delay(20)但问题来了:板子当前是 HID 模式,没有 U 盘,你无法挂载文件系统去编辑main.py,也无法改回boot.py……此时就需要进入**安全模式(safe mode)**来"救场"。
进入安全模式的完整步骤
安全模式的完整说明见 reset tutorial,这里直接给出操作步骤:
- 按住USR 按键;
- 保持按住 USR,按下并松开RST 按键;
- 此时 LED 会循环显示:绿 → 橙 → 绿+橙 → 回到绿……;
- 持续按住 USR,直到只有橙色 LED 亮起,然后松开 USR;
- 橙色 LED 会快速闪烁 4 次后熄灭;
- 现在你已处于安全模式。
安全模式的源码级原理
安全模式为什么能让你重新访问 U 盘?从 ports/stm32/boardctrl.c 可以看到,boot.py与main.py的执行都受复位模式(reset_mode)控制:
// boardctrl_run_boot_py bool run_boot_py = state->reset_mode != BOARDCTRL_RESET_MODE_SAFE_MODE; // boardctrl_run_main_py bool run_main_py = state->reset_mode != BOARDCTRL_RESET_MODE_SAFE_MODE && pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL;安全模式(BOARDCTRL_RESET_MODE_SAFE_MODE,见 ports/stm32/boardctrl.h)下两者都不执行,于是板子以默认 USB 配置启动——U 盘回来了,你可以正常编辑boot.py和main.py。注意此时boot.py保持不变,因为我们编辑完main.py后还要回到 HID 模式。
复位模式的选择逻辑在 ports/stm32/boardctrl.c 的update_reset_mode中:上电时检测 USR 开关是否按下,若按下则 LED 以不同组合循环指示可选的复位模式,通过 LED 组合编码选择。教程中的"绿 → 橙 → 绿+橙"循环正是这一 LED 状态机在只有开关的 pyboard 上的呈现;模式选定后 LED 会闪烁对应次数加以确认。
提示:安全模式下
boot.py不执行,USB 恢复正常默认配置,因此 U 盘可见、串口可用,方便你修改文件或调试。
保存并运行
把上述代码保存为main.py(boot.py保持pyb.usb_mode('VCP+HID')不动),弹出 U 盘并复位。板子现在就是一只"倾斜控制"的鼠标:
- 倾斜角度映射为 x/y 位移,板子角度变化会带动鼠标移动;
- 按USR 按键即可停止鼠标运动(
while not switch()退出循环); - 你会发现y 轴方向是反的——这是加速度计坐标轴方向决定的,修复方法很简单:在
hid.send()的 y 分量前加负号:
hid.send((0, accel.x(), -accel.y(), 0))试试看能不能让鼠标纹丝不动地停在原地(那说明你握板的手法极其稳定)。
第四步:恢复正常模式
如果放任不管,pyboard 每次插入都会以鼠标模式运行。恢复正常模式的流程:
- 先进入安全模式(步骤同上);
- 编辑
boot.py,把VCP+HID那一行注释掉:
#pyb.usb_mode('VCP+HID') # act as a serial device and a mouse- 保存文件,弹出 U 盘,复位板子。
此时boot.py不再切换 USB 模式,pyboard 恢复为默认的"串口 + U 盘"形态。
进阶:鼠标协议之外——键盘模式与自定义报告描述符
pyb.usb_mode在 ports/stm32/usb.c 中暴露了若干可选关键字参数,其中hid参数决定了 HID 设备的协议描述:
- 默认值即
pyb.hid_mouse(subclass=1即 boot 子类、protocol=2即鼠标协议、轮询间隔 8ms、内置鼠标报告描述符,见 ports/stm32/usb.c); - 传
pyb.hid_keyboard即可把 pyboard 变成 USB 键盘(protocol=1、8ms 轮询,见 ports/stm32/usb.c); - 也可以传自定义 5 元组
(subclass, protocol, max_packet_len, polling_interval, report_desc),其中report_desc是一份完整的 HID 报告描述符(bytes),源码会将其复制保存以防被 GC 回收(ports/stm32/usb.c)。
示例:指定 VID/PID 的鼠标模式:
pyb.usb_mode('VCP+HID', vid=0xf055, pid=0x9800) # 自定义厂商/产品 ID pyb.usb_mode('VCP+HID', hid=pyb.hid_keyboard) # 变成键盘除此之外,pyb.usb_mode还支持port(选择 USB 端口,多 USB 的板卡可用)、msc(指定大容量存储逻辑单元,如pyb.Flash()或 SD 卡)等关键字参数。所有这些配置最终汇聚到 ports/stm32/usb.c 的pyb_usb_dev_init,由 STM32 USB 设备栈(usbd_cdc_msc_hid,见 ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h)统一配置 VID/PID、选择接口组合并初始化各接口。
总结
本文完整走通了 pyboard 变身 USB 鼠标的四个阶段:boot.py 配置模式 → REPL 手动验证 → 加速度计体感控制(含安全模式自救)→ 恢复正常模式。关键要点回顾:
pyb.usb_mode('VCP+HID')是切换鼠标模式的唯一入口,VCP 接口保留使 REPL 仍然可用;USB_HID.send((btn, x, y, scroll))发送 4 字节相对位移报告,元组会被自动转为字节,超 8 字节需用bytearray;- 加速度计方案的核心是
while not switch(): hid.send((0, accel.x(), -accel.y(), 0))循环,注意 y 轴取反; - HID 模式下无法挂载 U 盘,通过"按住 USR + 复位"进入安全模式即可重新编辑文件;
- 修改
hid参数即可把同一套机制复用到 USB 键盘或自定义 HID 设备。
如果想继续深入,可以阅读 pyb.USB_HID 参考文档(文中未列,实际接口见 modpyb.c)以及 pyb.usb_mode 相关文档,并结合 ports/stm32/usbd_hid_interface.c 了解报告收发的中断级实现。
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考