- 嵌入式
- 语言运行时
- 编程语言
- 解释器
- 编译器
- 物联网
- 系统编程
【免费下载链接】micropython
MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems
本篇技术指南以 MicroPython 官方文档 docs/library/pyb.USB_HID.rst 为主体,系统讲解pyb.USB_HID类的使用方法:如何通过pyb.usb_mode()使能 HID 接口、如何用send()与recv()收发 HID 报告,并深入 STM32 移植层源码(ports/stm32/usb.c、ports/stm32/usbd_hid_interface.c)剖析其底层实现。读完本文,你将能够独立编写"板卡伪装成鼠标/键盘"的实战程序,理解 HID 报告描述符、报文格式与超时机制,并能基于仓库内的预置常量(pyb.hid_mouse、pyb.hid_keyboard)或自定义报告描述符完成任意 HID 设备的模拟。
一、pyb.USB_HID 是什么
USB_HID类是 MicroPython pyboard(STM32 移植版)提供的 USB Human Interface Device(人机接口设备,HID)类。它允许创建一个表示板卡 USB HID 接口的对象,用于将开发板模拟为鼠标、键盘等标准外设,从而向连接的主机(如 PC)发送输入事件或接收主机下发的报告。
import pyb hid = pyb.USB_HID() # 创建 USB_HID 对象该类的核心特征可以概括为两点:
- 它是"接口"而非"实体":按 ports/stm32/usb.c 中的设计哲学注释,USB 本身不是一个实体,真正可访问的是各个接口(VCP、MSC、HID)。
pyb.USB_HID()只是取回该接口对应的对象。 - 使用前必须先用
pyb.usb_mode()打开 HID 接口:在 pyb.USB_HID.rst 中明确说明"Before you can use this class, you need to usepyb.usb_mode()to set the USB mode to include the HID interface"。在 usb.c 的make_new中甚至留有一行TODO raise exception if USB is not configured for HID注释,意味着当前版本在未启用 HID 时创建对象并不会主动报错,但收发操作将无法正常工作。
从源码看,pyb_usb_hid_obj是一个单例对象,其结构仅包含对象基类与指向usb_device_t的指针:
typedef struct _pyb_usb_hid_obj_t { mp_obj_base_t base; usb_device_t *usb_dev; } pyb_usb_hid_obj_t;构造器pyb_usb_hid_make_new不接受任何参数(mp_arg_check_num(n_args, n_kw, 0, 0, false)),直接返回该单例。
二、前置条件:用 pyb.usb_mode() 使能 HID 接口
在使用USB_HID之前,必须先通过pyb.usb_mode()将板卡 USB 模式配置为包含 HID 的复合模式。该函数的完整签名与说明见 docs/library/pyb.rst:
pyb.usb_mode(modestr, port=-1, vid=0xf055, pid=-1, msc=(), hid=pyb.hid_mouse, high_speed=False)2.1 modestr 可选模式
| modestr | 含义 |
|---|---|
None | 禁用 USB |
'VCP' | 仅 VCP(虚拟串口)接口 |
'MSC' | 仅 MSC(大容量存储设备)接口 |
'VCP+MSC' | VCP 与 MSC |
'VCP+HID' | VCP 与 HID(人机接口设备) |
'VCP+MSC+HID' | VCP、MSC 与 HID(仅 PYBD 系列板卡可用) |
为向后兼容,'CDC'等价于'VCP'(同理'CDC+MSC'、'CDC+HID')。该兼容表在 usb.c 的pyb_usb_mode_table中有完整实现,其中还包含MICROPY_HW_USB_CDC_NUM >= 2/3时支持的多 VCP 组合模式(如'2xVCP'、'3xVCP+MSC+HID'等,取决于具体板卡配置)。
2.2 其它参数
- port:整数(0, 1, ...),选择板卡的多 USB 端口中的哪一个;
-1表示使用默认或自动选择的端口。 - vid / pid:自定义 VID(厂商 ID)与 PID(产品 ID);
pid=-1时会根据modestr自动选择内置的 PID(源码中对应MICROPY_HW_USB_PID_*系列宏,如MICROPY_HW_USB_PID_CDC_HID)。 - msc:仅 MSC 模式有效,指定要暴露的 SCSI LUN 列表,如
msc=(pyb.Flash(), pyb.SDCard())。 - hid:仅 HID 模式有效,指定 HID 细节,为一个五元组
(subclass, protocol, max packet length, polling interval, report descriptor)。默认值为适合 USB 鼠标的参数;仓库还预置了适合键盘的pyb.hid_keyboard常量。 - high_speed:设为
True时,若硬件支持则启用 USB HS(高速)模式。
2.3 预置 HID 常量:pyb.hid_mouse 与 pyb.hid_keyboard
在 usb.c 中,这两个常量被定义为由 5 个元素组成的 ROM 元组:
// 鼠标:(subclass, protocol, max_packet, polling_interval, report_desc) { MP_ROM_INT(1), // subclass: boot MP_ROM_INT(2), // protocol: mouse MP_ROM_INT(USBD_HID_MOUSE_MAX_PACKET), MP_ROM_INT(8), // polling interval: 8ms ... } // 键盘:(subclass, protocol, max_packet, polling_interval, report_desc) { MP_ROM_INT(1), // subclass: boot MP_ROM_INT(1), // protocol: keyboard MP_ROM_INT(USBD_HID_KEYBOARD_MAX_PACKET), MP_ROM_INT(8), // polling interval: 8ms ... }两者均采用Boot subclass(子类 1)与8ms 轮询间隔,区别仅在于协议号(鼠标 2、键盘 1)与各自的报告描述符、最大包长。hid_mouse与hid_keyboard在 modpyb.c 中被注册为pyb模块的属性。
2.4 实战:在 boot.py 中按需切换 USB 模式
仓库自带示例 examples/SDdatalogger/boot.py 演示了在boot.py中根据按键状态动态选择 USB 模式的典型写法:
if switch_value: pyb.usb_mode("VCP+MSC") pyb.main("cardreader.py") # 按下开关:读卡器模式 else: pyb.usb_mode("VCP+HID") pyb.main("datalogger.py") # 未按开关:HID 模式(配合 USB_HID 收发)三、构造器
USB_HID()创建一个新的 USB_HID 对象,无任何参数。如前所述,源码中它返回的是全局单例,与创建的次数无关;因此也可以安全地在多个地方重复调用。
import pyb hid = pyb.USB_HID() # 获得 HID 接口对象四、发送数据:USB_HID.send(data)
USB_HID.send(data)通过 USB HID 接口发送一个 HID 报告(report)。data可以是:
- 整数构成的 tuple / list;
- 或者
bytearray(字节缓冲)。
4.1 源码中的参数处理与限制
在 usb.c 的pyb_usb_hid_send中,参数按以下规则处理:
- 首先尝试将参数当作可读缓冲(
mp_get_buffer)处理——即bytearray等直接可用; - 若失败,则按 tuple/list 解析(
mp_obj_get_array),逐元素取整数值填入临时缓冲; - 限制:tuple/list 形式的报告长度超过 8 字节时会抛出
ValueError: tuple/list too large for HID report; use bytearray instead——因为临时缓冲只有byte temp_buf[8]。要发送更长的报告(如键盘的 8 字节报告以外的自定义报告),应改用bytearray; - 底层调用
USBD_HID_SendReport完成发送,成功返回发送字节数,失败返回0。
发送方法返回发送的字节数(失败时为 0),不会抛出异常。
4.2 鼠标报告格式(pyb.hid_mouse)
pyb.hid_mouse的完整报告描述符定义在 usbd_cdc_msc_hid.c。综合描述符可以确定鼠标报告为4 字节:
| 字节 | 含义 |
|---|---|
| 0 | 按键位:bit0 左键、bit1 右键、bit2 中键(高 5 位为填充) |
| 1 | X 轴相对位移(-127 ~ 127) |
| 2 | Y 轴相对位移(-127 ~ 127) |
| 3 | 滚轮相对位移(-127 ~ 127) |
对应描述符关键片段:Report Count(3), Report Size(1)的 3 个按钮位、Report Size(5)的填充位,以及Logical Minimum(-127) / Logical Maximum(127)、Report Size(8), Report Count(3)的 X/Y/Wheel 三个相对位移字节。
import pyb hid = pyb.USB_HID() # 移动鼠标:向右 50、向下 30,无按键 hid.send((0, 50, 30, 0)) # 单击左键:按下再松开 hid.send((1, 0, 0, 0)) hid.send((0, 0, 0, 0)) # 滚动滚轮向上 hid.send((0, 0, 0, 1))4.3 键盘报告格式(pyb.hid_keyboard)
键盘报告描述符见 usbd_cdc_msc_hid.c,报告为标准的8 字节Boot Keyboard 格式:
| 字节 | 含义 |
|---|---|
| 0 | 修饰键位:bit0 Ctrl、bit1 Shift、bit2 Alt、bit3 GUI(Win/Command)…… |
| 1 | 保留字节(恒为 0) |
| 2 ~ 7 | 6 个同时按下的按键键码(0 ~ 101,见描述符Logical Maximum(101)) |
import pyb, time hid = pyb.USB_HID() keyboard = pyb.hid_keyboard # 先用键盘常量配置 USB 模式(见下) # 按下并松开字母 'a'(键码 0x04) hid.send((0, 0, 0x04, 0, 0, 0, 0, 0)) time.sleep_ms(50) hid.send((0, 0, 0, 0, 0, 0, 0, 0)) # 按下 Ctrl+C(修饰键 bit0 + 键码 0x06) hid.send((0x01, 0, 0x06, 0, 0, 0, 0, 0)) time.sleep_ms(50) hid.send((0, 0, 0, 0, 0, 0, 0, 0))注意:若要用键盘协议,需在
usb_mode中传入hid=pyb.hid_keyboard,否则板卡上报的是鼠标协议,主机端会按鼠标报告解析这些字节。
五、接收数据:USB_HID.recv(data, *, timeout=5000)
USB_HID.recv(data, *, timeout=5000)在总线上接收数据,data的两种形式对应两种行为:
- data 为整数:表示要接收的字节数,函数会分配一个新的字节缓冲返回,内含实际接收到的字节;
- data 为可变缓冲(如
bytearray):接收到的字节被填入该缓冲,函数返回实际读入的字节数。
timeout是等待接收的超时时间,单位为毫秒,默认 5000ms。超时行为在源码中有明确定义:usbd_hid_rx在超时后返回-MP_ETIMEDOUT,而pyb_usb_hid_recv捕获到负返回值时不抛出异常,而是将结果置为0(usb.c)——即超时时整数形式返回空字节串、缓冲形式返回 0。
5.1 底层接收机制
接收路径的核心实现在 usbd_hid_interface.c:
usbd_hid_init:初始化report_in_len = USBD_HID_REPORT_INVALID(即(size_t)-1,表示"无待读报告"),并返回内部缓冲report_in_buf的地址,供底层 USB 驱动存放首个传入报告;usbd_hid_receive:当中断端点收到主机下发的 IN 报告时,仅记录长度(report_in_len = len),不立即安排下一次接收,直到用户读取当前报告;usbd_hid_rx:在超时时间(timeout_ms)内轮询等待report_in_len变为有效值;一旦有数据,拷贝min(len, report_in_len)字节到用户缓冲,随后调用USBD_HID_ReceivePacket调度下一个报告的接收,并返回实际拷贝字节数。
这一设计意味着recv()一次调用对应读取一个完整 HID 报告(报告长度由最大包长HID_DATA_FS_MAX_PACKET_SIZE决定,见 usbd_hid_interface.h)。
5.2 接收示例
接收方向通常用于"板卡接收主机下发"的场景,例如键盘的 LED 输出报告、自定义 HID 设备的命令下发:
import pyb hid = pyb.USB_HID() # 方式一:按字节数接收,返回新缓冲 data = hid.recv(8, timeout=1000) # 方式二:接收进预分配缓冲,返回字节数 buf = bytearray(8) n = hid.recv(buf, timeout=1000) print(n, buf)5.3 与 select/poll 配合
USB_HID对象实现了流式接口的ioctl(usb.c),支持MP_STREAM_POLL_RD(可读:有报告待接收)与MP_STREAM_POLL_WR(可写:底层允许发送报告)。因此可以配合select.select()在多个流对象间轮询,避免阻塞:
import pyb, select hid = pyb.USB_HID() vcp = pyb.USB_VCP() while True: r, w, x = select.select([hid, vcp], [], []) if hid in r: data = hid.recv(8, timeout=0) # 处理 HID 输入六、完整实战:将板卡变成组合输入设备
综合以上内容,一个把 STM32 板卡同时伪装成鼠标与键盘(通过VCP+HID保持串口调试能力)的完整程序如下:
import pyb, time # 1. 在 boot.py 中先行配置,或在此调用(推荐放在 boot.py) pyb.usb_mode('VCP+HID', hid=pyb.hid_mouse) hid = pyb.USB_HID() # 模拟鼠标移动轨迹 for i in range(10): hid.send((0, 5, 0, 0)) # 每次向右移动 5 time.sleep_ms(20) # 发送一次左键点击 hid.send((1, 0, 0, 0)) time.sleep_ms(20) hid.send((0, 0, 0, 0))若需在鼠标与键盘之间切换,需要在切换后重新上电或重新执行pyb.usb_mode,因为源码注释明确 USB 设备"只在设备的通电生命周期内初始化一次"(only init USB once in the device's power-lifetime,见 usb.c),运行中反复切换模式不会生效。
七、自定义 HID 设备(进阶)
usb_mode的hid参数接受任意(subclass, protocol, max packet length, polling interval, report descriptor)五元组,因此理论上可以模拟任意符合 HID 规范的设备,例如游戏手柄、多媒体控制键等。报告描述符需按 USB HID 规范手工构造为字节序列:
import pyb # 自定义报告描述符(此处仅为示意,需按目标设备规范编写) custom_desc = bytes([ 0x05, 0x01, # Usage Page (Generic Desktop) 0x09, 0x05, # Usage (Game Pad) 0xA1, 0x01, # Collection (Application) ... 0xC0, # End Collection ]) pyb.usb_mode('VCP+HID', hid=(1, 0, 8, 8, custom_desc)) # subclass/协议/最大包长/轮询间隔/描述符 hid = pyb.USB_HID() hid.send(bytearray([...])) # 按自定义描述符定义的长度发送报告从 usb.c 可看到hid关键字参数默认值为pyb.hid_mouse,即不传时按鼠标协议工作。
八、注意事项与限制小结
- 必须先行使能 HID 模式:在调用
pyb.USB_HID()收发之前,确保boot.py中已调用pyb.usb_mode('VCP+HID', ...)(或'VCP+MSC+HID',后者仅 PYBD 板卡支持)。 - USB 配置只在通电生命周期内生效一次:
usb_mode必须在boot.py阶段调用,程序运行中途切换不会重新枚举设备。 - tuple/list 发送上限 8 字节:更长的报告请使用
bytearray,否则抛ValueError。 - 超时返回 0 / 空数据而非异常:
recv超时不抛异常,编程时需自行判断返回值。 - 旧的
pyb.hid()函数已弃用:docs/library/pyb.rst 中说明pyb.hid((buttons, x, y, z))已弃用,应改用pyb.USB_HID.send();对应 C 侧实现pyb_hid_send_report也标注为 deprecated(usb.c)。 - 平台相关性:
pyb.USB_HID属 STM32 移植层专属接口(modpyb.c 注册于pyb模块),其它移植(如 ESP32、rp2)没有该模块,跨平台项目需使用各自移植的 HID API(如machine.USBDevice)。
九、进一步阅读
- 类文档原文:docs/library/pyb.USB_HID.rst
pyb.usb_mode()/pyb.hid_mouse/pyb.hid_keyboard完整说明:docs/library/pyb.rst- Python 层绑定实现(send/recv/ioctl/构造器):ports/stm32/usb.c
- 底层报告接收引擎:ports/stm32/usbd_hid_interface.c 与 usbd_hid_interface.h
- 鼠标/键盘报告描述符与 USB 描述符:usbd_cdc_msc_hid.c
- 真实使用示例:examples/SDdatalogger/boot.py
- 嵌入式
- 语言运行时
- 编程语言
- 解释器
- 编译器
- 物联网
- 系统编程
【免费下载链接】micropython
MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems
相关推荐
Flipper Zero终极指南:USB模拟技术与键盘鼠标劫持实战
Flipper Zero终极指南:USB模拟技术与键盘鼠标劫持实战 Flipper Zero是一款功能强大的便携式多功能工具,专为安全研究人员、硬件爱好者和渗透
示例工程AutoHotkey键盘鼠标模拟:keyboard_mouse模块API详解
AutoHotkey键盘鼠标模拟:keyboard_mouse模块API详解 你是否还在为重复的键盘鼠标操作感到厌烦?想通过脚本自动化日常办公任务却不知从何入手
RPAGUI 自动化桌面应用NanoKVM虚拟设备终极指南:如何实现USB键盘鼠标的完美模拟
NanoKVM虚拟设备终极指南:如何实现USB键盘鼠标的完美模拟 NanoKVM是一款基于RISC V架构的开源IP KVM解决方案,它提供了强大的 虚拟设备功
物联网嵌入式音视频后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考