ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

树莓派Pico USB接口深度解析:从设备模拟到主机模式

树莓派Pico USB接口深度解析:从设备模拟到主机模式 树莓派 Pico 的 USB 接口很多人用了一年半载还只把它当充电口和烧录口用。说实话有点可惜这颗 RP2040 芯片内置的 USB 控制器能力远比你想的强它既能当设备让 Pico 在电脑上变成一个串口、一把键盘、一个 U 盘也能切换成主机模式反过来读取 U 盘数据、接收 USB 键盘输入。这篇博文就把树莓派 Pico 的 USB 从硬件原理、外设架构讲到 MicroPython 软件控制结合我实际调试中踩过的坑尽量让你看完就能上手。适合刚入门、想搞懂 USB 到底怎么工作的朋友也适合已经在用 Pico、准备做键盘模拟、串口通信、USB 数据分析这类项目的同学。1. 树莓派 Pico 的 USB 硬件到底长什么样1.1 RP2040 的 USB 控制器一颗“五脏俱全”的芯RP2040 内部实际集成了一套完整的 USB 子系统不是简单把引脚拉出来就完事。从功能划分上它由物理层收发器PHY、串行接口引擎SIE、端点 FIFO、寄存器控制逻辑和 DMA 这几大部分组成。PHY 负责把 D / D- 两根差分线上的模拟信号转成数字电平SIE 负责解析 USB 协议中的位填充、同步字段、包标识符PID、CRC 校验这些底层逻辑。你在软件里写一个端点描述符、往 FIFO 丢一包数据剩下的时序都由硬件自动完成。这颗控制器支持 USB 1.1 Device 和 Host 两种角色速率是 Full Speed也就是 12Mbps。要注意它不支持 USB 2.0 的 High Speed 480Mbps也不支持 USB 3.x。很多第一次看到数据手册的人会问为什么 2021 年的芯片还在用 USB 1.1原因很简单12Mbps 对键盘、鼠标、串口、U 盘这类外设来说已经非常充裕。一个 CDC 虚拟串口跑满也就几 MbpsHID 键盘鼠标更是几百字节级别的吞吐量。真正需要高速率的场景是 UVC 摄像头、高速存储、音视频采集那本来就不是 RP2040 这种单片机该干的活。做低成本控制类产品USB 1.1 Full Speed 是性价比极高的选择协议简单、PCB 布线要求低、软件栈也轻量。芯片内部还带了 DMA 功能可以直接把内存里的数据搬运到 USB FIFO不占用 CPU。这个设计对做批量传输特别有用比如你要在 USB Host 模式下连续读 U 盘里的数据块用 DMA 可以边读边处理吞吐效率提升非常明显。1.2 引脚与外围电路D、D-、上拉电阻和时钟RP2040 的 USB 信号是复用在 GPIO0 和 GPIO1 上的官方原理图里通常标注为 USB_DM 和 USB_DP。其中 GPIO0 对应 D-GPIO1 对应 D。你在做自定义板子或者飞线调试的时候这两个脚不能接反一接反轻则枚举失败重则造成电气冲突。官方 Pico 板上的 USB 电路保护做得比较典型从 USB 座的 DM、DP 到芯片之间各串联了一个 22Ω 电阻这是为了匹配阻抗、减小信号反射同时在 D 和 D- 上各接了一个 TVS 二极管用于静电防护。自己做板子的时候这两个元件千万别省。我曾经为了省事直接飞线连接结果在干燥环境下一碰 USB 座芯片的 USB 控制器就挂了后来一查是静电打进芯片引脚非常肉痛。这里要特别注意上拉电阻的问题。USB 设备端需要在 D 线上接一个 1.5kΩ 上拉电阻主机端在 D / D- 上各接 15kΩ 下拉电阻。Host 就是通过检测 D 是否被拉高来判断有没有设备插入以及这个设备是全速12Mbps还是低速1.5Mbps。RP2040 芯片内部其实已经集成了 D 上拉电阻的控制逻辑软件里可以动态开关所以你在官方 Pico 原理图上看不到外置的 1.5kΩ 电阻。不过软件里上拉控制的时机很讲究必须在时钟稳定后才能开启否则设备插入瞬间主机可能收到不完整的复位信号。时钟方面USB Full Speed 设备要求 48MHz 的参考时钟。RP2040 片上有 PLL可以从 12MHz 外部晶振倍频得到这个 48MHz。很多人忽略一个点如果你在自定义板卡上没给 Pico 提供正确的晶振或者 PLL 配置错了USB 枚举会非常不稳定典型的症状是设备描述符请求失败。这种情况下程序本身能跑、灯能亮但电脑就是识别不出来。1.3 为什么 Pico 只能做到 USB 1.1 Full Speed按 USB 协议定义USB 2.0 包含了三种速率低速 1.5Mbps、全速 12Mbps、高速 480Mbps。树莓派 Pico 支持的 Full Speed 属于 USB 2.0 规范里的最低一档严谨点说它实现的是 USB 1.1 规范定义的全速模式但电气上与 USB 2.0 的全速模式兼容。今天绝大多数电脑 USB 口都能向下兼容全速设备。高速模式 480Mbps 对 PHY 的电流驱动能力、信号完整性和时钟精度要求很高需要额外的高速端接和更严格的 PCB 阻抗控制这会增加芯片成本和面积。RP2040 定位是低成本 MCU全套高速 PHY 显然不划算。对实际使用来说你不需要担心“全速会不会太慢”这个问题MicroPython 的 REPL、USB 键盘、虚拟串口全都是在 12Mbps 上跑的体感上毫无压力。如果你非要通过 USB 传视频流那 Pico 确实不合适但那种项目从一开始就不该选单片机。2. USB 协议基础读懂枚举、描述符和三种常见设备类2.1 端点、接口与配置USB 设备的三层结构USB 设备的逻辑结构可以理解成一栋楼。配置Configuration是最外层相当于整栋楼的水电总体方案接口Interface是楼层里的功能单元比如一个 USB 复合设备可以同时有“键盘接口”和“鼠标接口”端点Endpoint是具体的门数据从这些门进进出出。每个端点有一个编号和一个方向IN 方向表示从设备到主机OUT 方向表示从主机到设备方向的定义是以主机视角为准的这一点初学者最容易搞反。端点 0 是特殊端点所有 USB 设备必须有它专门承载控制传输枚举阶段的所有交互都在端点 0 上完成。除了端点 0设备还可以有若干其他端点根据不同传输类型分为四种控制传输Control、中断传输Interrupt、批量传输Bulk和等时传输Isochronous。键盘鼠标这类对实时性要求高的设备用中断传输注意协议里“中断”不是真的打断而是设备定期向主机查询一次有没有新数据虚拟串口和 U 盘用批量传输数据量大但允许排队等时传输用于音频视频不保证一定送达但讲究实时。RP2040 的 USB 外设端点资源有限做单片机设备绰绰有余但如果你想一口气搞出个多接口复合设备就要盘算一下端点资源够不够用。我自己做过一个同时带 CDC、MSC、HID 的复合设备调试时被端点分配折腾了一晚上所以一定要提前规划。2.2 枚举过程USB 设备如何“自我介绍”枚举是 USB 世界里最核心也最容易被忽视的一环。把 USB 设备插到电脑上接下来几毫秒内发生的事情大致如下设备获得 VBUS 供电内部上电复位但此时还不能响应任何请求。设备软件开启 D 上拉电阻主机检测到 D 被拉高知道有全速设备接入。主机对总线发出复位信号把 D / D- 都拉低一段时间。复位结束后设备默认地址是 0主机向地址 0 发送“获取设备描述符”请求。为了节省时间主机第一次只读取设备描述符的前 8 字节从里面拿到端点 0 的最大包长度。主机再次复位总线然后发送“设置地址”请求给设备分配一个唯一地址。主机用新地址重新获取完整设备描述符再获取配置描述符。主机根据描述符信息加载对应驱动最后发送“设置配置”请求让设备进入配置完成状态正式工作。你可以把枚举想象成一次面试设备先亮出简历设备描述符面试官觉得不错给定工号设置地址再详细了解技能点配置描述符、接口描述符最后决定录用设置配置并开始分配任务。这一串过程任何一步出错电脑端就会报“未知 USB 设备”或者“设备描述符请求失败”。实际调试中我建议先用软件方式观察枚举流程的哪一步断了。Windows 的设备管理器、Linux 的 dmesg 都会提示枚举失败的具体阶段比盲猜省时间得多。信号层面的问题再上逻辑分析仪或者抓包工具后面我会细说。2.3 CDC、HID、MSC三种最常见的 USB 设备类模型USB 设备类Device Class是协议组织定义的标准范本它规定了某类设备必须有哪些接口、哪些端点、数据格式如何。这样操作系统内置通用驱动就能即插即用不用每台设备都装专属驱动。CDC 类Communications Device Class用来做虚拟串口。树莓派 Pico 的 MicroPython 固件默认就是用 CDC 实现 REPL你可以理解成用一个 USB 口虚拟出了一个串口。很多开发板、4G 模块、TTL 转 USB 工具内部也都在用 CDC但注意“USB 转串口”的小工具和“板载 USB CDC”不是一回事。前者是芯片把 UART 信号翻译成 USB后者是设备直接通过 USB 协议模拟串口行为。用起来都叫串口但底层的物理链路完全不同。HID 类Human Interface Device用在键盘、鼠标、游戏手柄上。它的优势是操作系统内置驱动、无需安装延迟低而且实现起来足够简单。做快捷宏键盘、模拟按键的工具就非常适合用 HID。MSC 类Mass Storage Class就是 U 盘。如果 Pico 暴露一个 MSC 接口电脑上会出现一个盘符你可以往里面拖文件。这对做日志记录、配置参数管理很实用但需要在软件里实现块设备读写接口复杂度比 HID 高一个量级。3. MicroPython 下的 USB 设备玩法从默认串口到自定义 HID3.1 固件默认行为上电就是一个 USB 串口把官方的 MicroPython 固件烧进 Pico然后插到电脑上系统会枚举出一个 CDC 设备在设备管理器或者 Linux 的 /dev 下能看到一个串口比如 ttyACM0。打开串口终端你进入的就是 MicroPython 的 REPL 交互环境直接在命令行敲代码就能控制 GPIO、读传感器。同时程序里所有 print() 的输出也会走这个虚拟串口调试非常方便。很多人刚开始会把“板载 USB 串口”和“USB 转串口模块”搞混。板载 USB 是 RP2040 芯片自己在跟电脑通信不需要额外驱动Windows 10 以上基本免驱。而像 FT231X、FT232R、CH340 这类 USB 转 UART 芯片本质是另一块独立的 USB 设备芯片把单片机的 UART 引脚数据映射成 USB 信号。如果你手里是一个 USB 转 TTL 模块连到 Pico 的 UART0 引脚那么电脑上看到的串口其实是那个模块的 USB 设备而不是 Pico 直接虚拟出来的。这时候要装对应芯片的驱动比如 FTDI 的 VCP 驱动。搞不清这一点你会在排查串口问题时绕很大弯路。还有一个小知识点按住 Pico 的 BOOTSEL 键再插 USB会进入芯片 ROM 引导模式电脑上出现一个名为 “RP1-RP2” 的可移动 U 盘这是烧录固件的通道和 MicroPython 运行时的 USB 设备完全不是一回事。我之前见过有人以为这个 U 盘是 MicroPython 提供的折腾半天想往里存文件显然是把两套机制搞混了。3.2 让 Pico 伪装成键盘usb.device 模块实战MicroPython 官方的 Pico 固件默认只暴露 CDC 串口不能直接在 Python 代码里改成 HID 键盘。要实现自定义 USB 设备类早期只能改固件源码里 TinyUSB 的配置重新编译对新手来说门槛很高。较新版本的 MicroPython 引入了 usb.device 模块开发者可以在 Python 层面定义 USB 设备比如创建 HID 键盘、MSC 存储甚至组合设备。用 usb.device 创建 HID 键盘的思路大致是这样的先导入 usb.device 和对应的 HID 接口类创建 HID 对象然后把这个 HID 接口挂载到 Device 对象上初始化 Device 后程序就能通过 HID 相关接口发送按键事件。需要注意版本差异我在 MicroPython 1.23 版本上测试过API 基本稳定但如果你用的固件比较老可能没有这个模块。那时候最省事的方案是直接刷 CircuitPython 固件。CircuitPython 的 USB HID 支持非常成熟使用 adafruit_hid 库几行代码就能模拟键盘输出文本。下面这段代码我实际跑过插上 Pico 后电脑会自动识别为 USB 键盘程序运行三秒后自动敲出 “Hello from Pico”import time import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS keyboard Keyboard(usb_hid.devices) layout KeyboardLayoutUS(keyboard) time.sleep(3) layout.write(Hello from Pico)用这种方式做一个一键输入密码、自动填写表单、快捷回复的小工具非常合适。实际使用中我踩过两个坑一是睡眠时间不能太短电脑还没来得及完成枚举、加载驱动你发送的按键事件就丢了二是发送字符串时要注意当前输入法状态如果是中文输入法layout.write 发出来的英文字符可能会直接进了输入法的组词框所以用之前最好先手动切到英文输入法。3.3 进阶玩法组合设备与 MSC 存储模拟单个 HID 键盘当然不过瘾很多项目需要的是组合设备既想用 USB 串口调试又想模拟键盘鼠标甚至还想让电脑把它识别成 U 盘来拖文件。从 USB 协议层面讲这可以通过一个配置描述符挂多个接口实现TinyUSB 也支持复合设备。在 MicroPython 里做复合设备的 API 还在演进我更建议直接改固件配置或者在 CircuitPython 里看官方对 usb_hid、usb_cdc、usb_mass_storage 的支持。CircuitPython 允许通过 boot.py 里的配置自由启用或关闭哪些 USB 类比如同时启用 CDC 串口和 HID 键盘非常灵活。下面是一个 boot.py 的例子import usb_cdc import usb_hid usb_cdc.enable(consoleTrue, dataTrue) usb_hid.enable((usb_hid.Device.KEYBOARD,))MSC 存储模拟则更硬核一些需要实现块设备接口也就是一个能让文件系统读写的底层存储对象。你可以把 Pico 外挂的 SPI Flash 或者 SD 卡暴露成 U 盘也可以通过 CircuitPython 的 storage 模块将内部文件系统的一部分做成只读盘符给电脑访问。如果你只是想做一个“USB 日志导出器”MSC 是体验最好的方案拔下 Pico插到电脑上直接就有一个文件夹可以拷贝日志不用装任何配套软件。4. 把 Pico 变成 USB Host能读 U 盘、接键盘的硬核玩法4.1 USB Host 的硬件改造从板载口到 A 口母座前面讲的都是 Pico 作为 USB 设备反向的场景是让 Pico 当主机。RP2040 的 USB 控制器原生支持 Host 模式也就是说你可以让 Pico 主动去读取 U 盘里的文件或者接收 USB 键盘传入的键值。但官方 Pico 板上的 USB 口是设备口当 Host 用的时候不能直接插外设。你得把 GP0D-和 GP1D引出来接到一个 USB A 型母座上同时保证地和电源也接好。接线关系如下Pico GP0 / USB_DM → USB A 母座 D-Pico GP1 / USB_DP → USB A 母座 DPico GND → USB A 母座 GND外部 5V 电源 → USB A 母座 VBUS要特别留意供电。Host 模式下Pico 要为外部设备提供 5V 电源如果设备是 USB 键盘电流不大用 Pico 板上的 VBUS 引出来勉强能用如果是 U 盘或者带 RGB 灯的手柄最好单独用稳压模块给外设供电。很多人做 Host 失败不是协议问题而是外设供电不足导致反复复位。4.2 固件选择官方 MicroPython 之外的三条路截至我写这篇文章官方 Pico MicroPython 固件默认是不带 USB Host 支持的你想在 Python 里直接枚举外设基本没戏。要玩 Host 模式主要有三条路可以走。第一条是用树莓派官方的 C SDK。pico-examples 仓库里有完整的 USB Host 示例比如 usb/host/tusb_host_hid编译后 Pico 会自动枚举连接在上面的 HID 键盘并把键值打印到串口。这是最稳定、最推荐的方式毕竟底层是 TinyUSB 在维护。第二条是用 Arduino-Pico 第三方核心。它把 RP2040 的 USB Host 能力封装好Arduino IDE 里可以直接写 C 代码实现 USB 键盘读取、游戏手柄读取等对不熟悉 C SDK 的朋友友好很多。缺点是第三方库更新有滞后性遇到问题需要自己查源码。第三条是社区里有人编译过支持 USB Host 的 MicroPython 固件但我试下来的兼容性参差不齐。如果你确实想在 Python 环境里玩 Host可以先找社区固件试一试但不要对稳定性抱太高期望。我个人还是倾向 C SDK 或者 Arduino毕竟 USB Host 的时序要求比 Device 模式更严格Python 解释层的开销会让问题排查变得复杂。4.3 一个可复现案例用 Pico 读取 USB 键盘输入我实际玩过的第一个 Host 项目是用 Pico 读取 USB 键盘的按键然后在 OLED 屏幕上显示出来原理上就是跑通一个 HID Host 例程。在 pico-sdk 环境下进入 pico-examples 目录编译 usb/host/tusb_host_hid 示例生成 uf2 文件按住 Pico 的 BOOTSEL 键烧录进去。把自制的 USB A 母座接上键盘上电后电脑端打开串口监视器按下键盘任意键你会在串口输出里看到类似 “HID report: keycode 04” 这样的数据。到这里Pico 就成功扮演了一个 USB 主机角色。这个项目虽然简单但打通了 Host 模式最关键的数据通路。后续要扩展成 USB 键盘记录器、USB 手柄转无线模块、甚至通过 Host 模式和电脑之间的 USB 设备通信都是在这个基础上做功能叠加。有一点值得提醒USB Host 代码里经常要处理设备的拔出和插入异常一定要在程序里加上对总线事件的轮询否则设备意外掉线后程序很容易死锁。我在第一次测试时就是没有处理设备拔出导致 Pico 直接卡死只能拔电重启。5. 调试、抓包与常见问题排查实录5.1 在没有专业设备的情况下给 USB 抓包USB 调试最头疼的问题是“看不见”。它不像串口那样拿逻辑分析仪一搭就能看到全部数据USB 是差分双线高速信号普通工具抓起来不方便。但实际工作中也不需要一上来就买几万块的专业 USB 分析仪有几个低成本方案非常有效。第一个方案是软件抓包适合抓设备与电脑之间的通信。Linux 下用 usbmon 加 Wireshark几行命令就能看到设备枚举过程、控制传输的内容、描述符数据。具体操作是sudo modprobe usbmon sudo wireshark打开 Wireshark 后在接口列表里选择 usbmon0就能开始抓包。用过滤表达式usb.device_address 1 usb.transfer_type 0x00可以只看某个地址的控制传输。做 USB 枚举问题分析时这套方案几乎是免费的 Wireshark 协议解析器强烈推荐。第二个方案是硬件抓包适合抓采样点不在电脑上的场景比如两个嵌入式设备之间的 USB 通信。普通逻辑分析仪只要采样率足够至少 24Msps 以上直接夹在 D / D- 上抓原始波形再用软件解码出 USB 包。不过逻辑分析仪解码 USB 的效果看设备品牌采样率不够高会把差分信号误判成毛刺建议优先选支持 USB 协议解码的型号。第三个思路是用另一个 Pico 的 PIO 功能做简易逻辑分析仪然后抓第一个 Pico 的 USB 信号。这个玩法很酷RP2040 的 PIO 可以在 100MHz 以下稳定采样USB Full Speed 的 12Mbps 波形完全抓得住。只不过 PIO 程序需要自己写适合想深入理解 USB 物理层的同学折腾。5.2 设备描述符请求失败的排查思路“未知 USB 设备设备描述符请求失败”是 Pico USB 相关文章里被问得最多的问题几乎所有人都遇到过。这个报错说明电脑已经检测到设备插入但在枚举过程的第 4 步也就是第一次读取设备描述符时没有得到有效响应。排查思路要按从软件到硬件、从简单到复杂的顺序来。第一步先按住 BOOTSEL 键插 USB。如果 BOOTSEL 模式能正常出现 U 盘盘符说明 RP2040 芯片本身没问题USB 物理层也没问题问题大概率出在你烧录的固件上——很可能是应用程序初始化 USB 后进入死循环或者时钟配置不对导致 USB 外设没有正常工作。第二步重新烧录一份干净的官方 MicroPython 或 blink 例程排除固件损坏的可能。第三步检查自定义板卡上的 D / D- 走线、串联电阻、TVS 管是否正常特别要确认 D 上拉是否已经开启。第四步检查 48MHz 时钟是否稳定。可以用示波器看晶振波形或者先在程序里点个灯确认系统时钟起来了再测 USB。我遇到过最隐蔽的一个情况是 USB 座的地线虚焊D / D- 信号看起来正常但地回路阻抗过大导致信号幅度异常最后电脑端同样报设备描述符请求失败。这种问题靠肉眼看不出来只能拿万用表量 USB 座外壳与板子 GND 之间的导通电阻。5.3 常见问题速查表这里整理一份我实际使用 Pico USB 时遇到的典型问题和解决方法做成表格方便查阅。现象可能原因解决方向插上 Pico 后电脑提示“设备描述符请求失败”固件异常、时钟配置错误、D 上拉未开启按住 BOOTSEL 重新烧录官方固件检查自定义板卡的 D / D- 与阻容元件电脑能识别 USB但串口工具无法打开串口被其他程序占用、驱动未正确加载关闭串口监视器重新插拔检查是否有多个 CDC 设备同时存在运行程序后 USB 断开且无法恢复程序死循环或主频配置导致 USB 中断无法响应代码中加入看门狗避免在 while 循环里长期关闭中断USB 键盘模拟偶尔失效枚举未完成就开始发数据、供电不稳程序启动后 sleep 2 到 3 秒再发送检查 USB 座供电USB Host 模式下外设无反应外设供电不足、枚举失败、接线错误单独给外设供 5V确认 GP0/GP1 对应 D-/D查看串口打印的枚举日志系统没有自动安装 USB 驱动系统版本过旧、CDC 驱动文件缺失更新系统部分精简版 Windows 需要手动安装 WinUSB/CDC 驱动用 USB 转 TTL 模块连接 Pico 没反应模块与 Pico 的 UART 引脚没接对或模块本身需要驱动确认模块芯片型号FT232R 等安装对应 VCP 驱动交叉连接 TX/RX最后一个额外提醒USB 线材对调试影响真的很大。有些线看着是 USB 线实际上只有电源线没有数据线还有些线质量差信号衰减严重导致枚举时好时坏。常备两根不同品牌的优质 USB 线排查问题时先换线排除掉这个变量能省下大量时间。我在实际项目里最常干的还是两件事一是把 Pico 变成虚拟键盘做自动化快捷键工具二是用 Host 模式配合 HID 设备做数据采集。USB 这套东西说复杂也复杂但只要抓住枚举、描述符、端点、传输类型这几个核心概念大部分问题都能自己拆解。最后分享一个小技巧不管用什么固件调试 USB 永远先跑官方最小例程再叠加自己的逻辑不要一上来就写完整应用否则 USB 出问题的时候你根本不知道是协议栈的问题还是自己业务代码的问题。先把水管打通再往里面灌水这是 USB 开发最朴素也最有效的思路。
返回列表