
上篇把USB的诞生背景、版本迭代讲了一遍这次接着往下走。如果说上篇是“纵览历史”这篇就是把USB扒开看内里协议架构是怎么搭起来的、端点和管道到底是什么、四种传输类型分别适合干什么以及最关键的——真到了调板子、写固件、抓包排障的时候这些知识怎么落地。我之所以一直强调要搞懂协议而不只是“会用”是因为USB这玩意儿跟UART、SPI完全不一样。SPI从设备被动响应UART两边对着发都是在“已经建立好的连接”上干活。USB则复杂得多设备插上去之后要枚举、要分配地址、要选配置、要搞清传输类型任何一个环节断了结果就是设备管理器里那行刺眼的“未知USB设备设备描述符请求失败”。这块内容涉及的知识点非常密集但好消息是它是纯逻辑的理解了架构之后剩下的都能推导出来。1. USB协议架构这套“主从世界”到底是怎么运转的1.1 为什么USB一定要有个“主机”先问个问题U盘插到电脑上是U盘主动告诉电脑“我来了”吗答案是否定的。USB是严格的主从架构所有通信都由主机Host发起设备Device永远只能被动响应。主机说“我要数据”设备才把数据拿出来主机不说设备哪怕有再多话也只能憋着。这个设计和I2C有点像但比I2C更彻底。I2C至少还有多主机模式USB连这个口子都不留。主机控制着一切总线上的所有传输事务Transaction都由主机调度设备连“主动发个中断”的资格都没有。那鼠标动了、键盘按了系统怎么知道答案是主机周期性地轮询设备问“你有没有新数据”设备回答“有”或者“没有”。理解这个模型是理解后面所有内容的地基。实际调试中很多人遇到“设备收不到数据”或“上位机读不到设备发来的数据”时第一反应是查设备端代码其实很多时候问题出在主机根本就没发起轮询或者设备在枚举阶段就没通过主机压根不知道这个设备该怎么通信。USB总线上当然还有一类角色叫Hub集线器它的作用是把一个主机口扩展成多个。但Hub本质上也是个“特殊的设备”它同样受主机控制不具备任何主动调度能力。所以整个USB拓扑就是一棵以主机为根、向下一层层展开的树这一点和以太网那种对等网络有本质区别。1.2 物理层细节D/D-、上下拉电阻与速度识别USB用一对差分信号线D和D-传输数据配合VBUS供电和GND地线一共四根线USB 2.0时代。差分的好处是抗共模干扰强这也是USB线能做到几米传输的底气所在。但有意思的是USB的速度识别并不靠“看信号波形”而是靠一根电阻。设备接入主机后主机通过D或D-上的电平状态来判断设备是什么速度全速12Mbps和高速480Mbps设备在D上接一个1.5kΩ上拉电阻低速1.5Mbps设备在D-上接一个1.5kΩ上拉电阻主机端则在D和D-上分别有15kΩ的下拉电阻设备没插入时主机两条线都被拉低设备一插入对应线上的1.5kΩ上拉和主机15kΩ下拉分压线被拉高主机就知道“有设备来了是全速/低速”。高速设备的识别更特殊它先以全速身份被识别然后主机和设备通过一套chirp握手流程确认双方都支持高速再切到480Mbps。网上常看到有人问“D D-上要不要加电容、加多大”这其实是个误区。D和D-上通常不会靠并联电容来识别速度关键是上拉电阻和走线阻抗。D、D-需要做差分走线阻抗控制在90Ω左右走线要等长、远离干扰源。如果为了滤除EMI串联一个小电容pF级不是不行但加不好会直接毁了信号完整性造成枚举不稳定。我见过一块板子D线上加了个100nF电容结果高速设备完全识别不了低速全速勉强能跑问题就出在这。1.3 传输的最小单元包、事务与传输的三级结构USB协议有三层递进的概念包Packet、事务Transaction、传输Transfer。很多人被搞晕就是没捋清这三层。包是总线上可见的最小数据单元类似网络里的帧。一个包由SYNC同步字段、SOP包起始、PID包标识符、地址/帧号/数据等负载字段、CRC校验和EOP包结束组成。PID有好几大类令牌包SETUP、IN、OUT、SOF由主机发出相当于“命令”数据包DATA0、DATA1真正搬运数据握手包ACK、NAK、STALL接收方给发送方的回应事务是通信的基本动作通常由“令牌包数据包握手包”三段组成。主机发一个IN令牌设备回一个DATA包主机再回一个ACK这就完成了一个IN事务。控制传输、批量传输、中断传输、等时传输本质上就是不同规则组合出来的事务序列。传输则是更高层的概念比如控制传输里的一次“读设备描述符”操作可能包含多个事务。层与层之间的关系搞清楚了抓包时就不会被一堆PID刷花眼——你先看令牌判断主机在让谁干什么再看数据和响应整个通信过程就流畅了。2. 端点通信拆解管道模型与四种传输类型2.1 端点的本质设备内部的小信箱端点Endpoint是设备内部的一个数据缓冲实体也是主机与设备通信的唯一入口。你可以把端点想象成设备墙上开的一排小信箱每个信箱有编号、有方向IN还是OUT、有传输类型。主机往OUT端点里塞数据或者从IN端点里取数据。这里的方向要特别强调IN和OUT是站在主机的视角说的。IN端点是设备往主机方向发数据OUT端点是主机往设备方向写数据。很多第一次写USB固件的人在这里栽跟头觉得“我要把数据发出去所以用OUT端点”结果完全搞反。USB 2.0协议里一个设备最多可以有16个端点编号0到15每个端点都有方向属性。不过实际应用中一个端点号通常成对出现比如EP1 IN和EP1 OUT是两个不同的端点但因为编号相同常被大家合称“端点1”。端点0非常特殊它是每个USB设备必须有的“控制端点”双向的专门用来完成枚举和标准控制请求。设备一上电主机默认跟端点0通信其他端点要等枚举完成、驱动加载之后才会被使用。配置描述符、接口描述符、端点描述符这三级结构就是用来告诉主机“这个设备有哪些端点、每个端点怎么用”。2.2 控制传输枚举的基本功控制传输是USB协议的“元老”专门传输小量、但对可靠性要求极高的数据。它有三个阶段建立阶段主机发一个SETUP令牌跟一个DATA0数据包8字节的标准请求然后设备回ACK数据阶段可选的根据请求类型决定是否传输数据方向由请求决定状态阶段一个零长度的数据包用来确认整个操作完成枚举过程中主机就是用标准控制请求来“盘问”设备的。最重要的请求是GET_DESCRIPTOR读描述符和SET_ADDRESS分配地址。设备刚插入时地址是0主机先往地址0发GET_DESCRIPTOR设备把设备描述符前8字节包含端点0最大包长返回主机再发SET_ADDRESS给设备分配一个唯一地址通常是1到127之后所有通信都走新地址。设备描述符里最关键的几个字段每个写固件的人都应该烂熟于心字段含义典型值bLength描述符长度0x1218字节bDescriptorType描述符类型0x01bcdUSBUSB版本号0x0200USB 2.0bDeviceClass设备类0x00在接口描述符中定义bMaxPacketSize0端点0最大包长0x4064字节idVendor厂商ID如0x0403FTDIidProduct产品ID如0x6001FT232R配置描述符、接口描述符、端点描述符逐级展开最终形成一个完整的能力清单告诉主机这个设备有多少个功能、每个功能有哪些端点可以走。2.3 批量传输大块数据搬运工批量传输Bulk Transfer是干粗活的。它不保证实时性但保证数据一定送达错了就重传典型应用是U盘读写、USB转串口、打印机等。批量传输每次事务能搬运的数据量最大全速12Mbps下一个包最多64字节高速480Mbps下最多512字节超速USB 3.0下最高达到1024字节。批量传输的精髓在NAK机制。设备如果暂时缓冲满了、没准备好接收下一个包就会回一个NAK主机收到NAK之后过段时间再重试。这个机制让设备可以“慢一点”但代价是总线效率下降。调试时如果发现批量传输速度上不去很多时候不是因为协议瓶颈而是设备端来不及处理数据、频繁回NAK把总线拖垮了。高速批量传输还有个PING扩展协议主机先发PING令牌试探设备是否准备好设备回ACK表示可以发主机再发OUT和数据。这个机制避免了大数据包频繁被NAK的浪费。2.4 中断传输小数据、周期性的正解中断传输Interrupt Transfer名字很唬人实际上并不是设备“中断”主机而是主机周期性地来轮询。设备如果有数据就放进端点的缓冲里等主机来取没有数据就回NAK。中断传输适合传输量小但又要求低延迟的数据比如鼠标、键盘、游戏手柄。bInterval字段规定了轮询间隔全速设备是1到255毫秒高速设备是125微秒的倍数。别小看这个参数它决定了你USB鼠标的响应速度。把全速设备的bInterval设为1意味着主机每一毫秒来问一次这个延迟人眼根本感觉不到。中断传输的可靠性保证和批量传输一样出错就重传。它和批量传输的区别主要在于总线调度的优先级在每一帧或微帧里主机会先保证等时和中断传输的带宽剩下的带宽才分给批量传输。所以中断传输适合“数据量不大但必须及时”的场景。2.5 等时传输实时但允许丢包等时传输Isochronous Transfer是四种传输类型里最特别的。它没有握手阶段不支持错误重传发送方发完就走接收方接多接少看运气。但换来的是确定的带宽和低延迟因为主机在每一帧里都会给它预留固定时隙。典型应用是USB音频USB Audio和USB摄像头UVC。你拿USB麦克风连线开会偶尔一个音频包丢了听感上就是“啪”一个小杂音问题不大但如果用批量传输去传音频宁可等重传也要保证数据完整延迟就会大到没法用。等时传输就是“大风天快递扔进院子就走晚点没关系但一定给你扔到”和“宁可多跑几趟但每件都要交到你手上”这两种物流方式里选了前者。等时传输的端点描述符需要额外指定每帧传输的字节数主机据此预留带宽所以等时端点在配置时要明确告诉主机“我每帧要多少带宽”不然配置直接失败。3. 传输流程全景从设备插入到第一次通信3.1 枚举全过程设备从“无名之辈”到“有身份证”把枚举流程完整走一遍前面那些抽象概念就全部串起来了设备插入D/D-上的上拉电阻让主机检测到设备连接主机对总线复位SE0状态设备进入默认状态主机向地址0的设备发GET_DESCRIPTOR读取设备描述符前8字节获得端点0最大包长主机再次复位总线发送SET_ADDRESS给设备分配唯一地址主机用新地址重新GET_DESCRIPTOR拿到完整的18字节设备描述符主机继续GET_DESCRIPTOR读取配置描述符。注意这里配置描述符请求一次可能读不完因为配置描述符是一整块数据包含接口和端点描述符主机通常会先读前9字节拿到wTotalLength再按这个长度把整个配置描述符读回来主机解析配置描述符识别出接口类型加载对应驱动主机发SET_CONFIGURATION设备进入配置状态其他端点开始可用完成这八步设备才真正“活”了。很多USB固件问题集中在这几步之间设备描述符bMaxPacketSize0填错主机直接设备描述符请求失败配置描述符wTotalLength算错设备枚举后反复重置。3.2 枚举不成功优先级最高的排查思路设备管理器里“未知USB设备”是无数工程师和电子爱好者的噩梦。我的排查顺序永远是固定的第一看硬件。D或D-的上拉电阻有没有贴错、虚焊用万用表量一下设备端D/D-的对地电压全速设备D应该被拉到3V左右。这个是最高频的问题别一上来就翻固件。第二看供电。VBUS有没有到位很多USB设备是总线供电设备如果设备功耗超过500mAUSB 2.0规范主机可能直接拒绝供电或出现反复断开重连。第三看枚举响应。用逻辑分析仪或者USB分析仪抓包看主机发的GET_DESCRIPTOR有没有响应。如果设备根本没回包多半是固件根本没跑起来或者时钟有问题如果回了包但CRC错误、长度不对那就要仔细查描述符。第四看驱动。设备枚举成功但驱动装不上那是另一回事。设备管理器里显示“未知设备”和显示“设备有问题代码10”是完全不同的两码事。这套流程我用了很多年基本上覆盖了九成以上的枚举失败场景。4. 实战USB调试的一线方案与踩坑记录4.1 软件抓包三板斧usbmon、USBPcap、USBlyzer调试USB协议抓包是最有力的手段。硬件分析仪比如Beagle USB、Ellisys当然最专业能抓到底层电平信号和时序但价格动辄上万。好在软件方案对付绝大多数问题已经够了。Linux下最简单的是usbmon。内核默认编译了usbmon模块时mount -t debugfs none /sys/kernel/debug之后在/sys/kernel/debug/usb/usbmon/目录下就能看到0u、1u、2u等接口分别对应不同的USB控制器。cat /sys/kernel/debug/usb/usbmon/0u就能实时看到URB级别的通信记录在Wireshark里选择usbmon接口就能图形化分析每个URB的内容、方向、状态、数据长度排查枚举和端点通信问题非常直观。Windows下推荐USBPcap加Wireshark的组合。USBPcap装好后Wireshark里会出现USBPcap1之类的接口选择后就能抓取USB流量。它能看到的也是URB层的通信虽然没有Linux usbmon那么底层但对绝大多数问题是够用的。如果再往上走一层用USBlyzer这种商业工具能直接看到USB请求块URB和设备驱动的交互对驱动兼容性问题有奇效。说句实在话软件抓包的最大价值不是“看报文内容”而是“看主机有没有发请求、设备有没有响应、响应内容对不对”。4.2 FT232系列老芯片在新系统上的驱动问题真相FT232R、FT231X、FT232H这些FTDI的USB转UART芯片在调试嵌入式设备时几乎人手一块。但它们在Windows 10/11上经常出幺蛾子这是我收到私信问得最多的问题之一。经典症状有三种第一设备管理器识别出设备但显示黄色感叹号错误代码10或43第二端口能识别但打不开或者一打开就蓝屏第三芯片本身是“假货”——网上低价买的FT232R本质上是盗版芯片FTDI官方新驱动检测到后会故意把PID置为0000设备直接“死了”。处理方案按顺序来确认芯片是不是正品。用官方FT_PROG工具读芯片信息或者直接查芯片丝印卸载系统自动安装的驱动去FTDI官网下载最新CDM驱动FTDI VCP Driver手动指定inf安装如果还是不行检查设备管理器的“通用串行总线控制器”里是不是有其他冲突设备老芯片在新Windows上一直不稳定的话干脆换CH340或CP2102方案兼容性还更好FT232R之所以经典是因为它内部有EEPROM可以自定义PID/VID和序列号这对产品量产管理非常有用。但正因如此它也成了盗版重灾区。4.3 硬件设计容易忽略的细节从D/D-走线到上拉电阻自己做USB板子的人硬件设计这部分一定要收藏。软件层的问题再难都能通过调试解决PCB打样回来发现信号走线有问题只能改版重来。重点检查这几项D/D-要做差分走线阻抗90Ω两条线等长误差越小越好全速设备D上1.5kΩ上拉电阻不要省而且最好由稳定的3.3V或VBUS供电不是所有的MCU GPIO都能提供合适的上拉设备端D/D-要加ESD保护器件热插拔时的静电是芯片杀手高速设备还要考虑匹配电阻和过孔stub问题不是随便拉两根线就能跑480MbpsVBUS和GND的滤波电容要靠近设备端放置去耦不到位会出现枚举不稳定我曾经设计过一块STM32的全速USB板子D/D-两根线走了将近5厘米还没做等长处理结果设备插上之后有时能枚举、有时不能频率感人。后来把走线改成差分对、等长问题就消失了。低速和全速设备对走线要求不算苛刻但高速设备一点含糊不得。4.4 USB在嵌入式领域的延展玩法转串口、转网口与自制设备USB转串口UART是嵌入式调试最常用的手段本质上是USB设备枚举成CDC-ACM类设备主机把它识别成COM口然后通过批量端点传输数据。市面上常见的CH340、CP2102、FT232都是这个原理。再往下游延伸USB转TTL、USB转RS485在UART后加RS485收发器如SP485、USB转网口如AX88179这类支持CDC-ECM/RNDIS的芯片本质上都是“USB虚拟成另一种接口”。如果想自己做一个USB设备现代MCU基本都内置了USB外设。开发时有一点特别值得注意USB协议栈里端点描述符的配置、回调函数的处理跟串口那种“发就发收就收”完全是两套思路。串口是字节流USB是包加端点必须在主机和设备之间先建立好“管道”。再往深了走Linux下还有一种玩法叫FunctionFS属于USB Gadget子系统的功能之一允许用户态程序直接实现USB从设备功能。你在嵌入式Linux板子上插USB线连电脑想模拟成鼠标、键盘、网卡或自定义HID设备都可以通过FunctionFS在用户态写代码实现不需要改内核驱动。ESP32-S3这类带USB OTG的芯片也常常被用来实现USB摄像头、USB MIDI类设备这些都是“设备端”开发很好的练手项目。5. 常见问题速查表与调试心态5.1 一张表看懂典型故障现象可能原因排查方向设备插入完全无反应上拉电阻没接/虚焊、供电异常量D/D-电压、量VBUS枚举失败设备描述符请求失败端点0最大包长错误、时钟不对、上拉电阻问题usbmon抓包看是否回包枚举成功但驱动装不上描述符里的VID/PID不对、设备类声明错误检查设备管理器详细信息USB转串口能识别但通信乱码波特率不匹配、USB批量传输丢包检查串口助手波特率设置、抓包看数据高速设备插上只跑全速高速chirp握手失败、线缆质量差检查设备端高速使能配置、更换线缆Linux下usbmon看不到数据usbmon模块未加载、权限不足modprobe usbmon、sudo运行WiresharkFT232在Win11上报错驱动被替换、假芯片被锁重装官方CDM驱动、换芯片方案设备反复断开重连供电不足、ESD导致复位检查总线电流、加强供电5.2 调试USB这么多年最想提醒新人的三件事第一先确认“主机有没有发请求”再去查“设备为什么没回”。很多人在设备端代码里翻来覆去找问题结果cat一下usbmon发现主机压根没发过SETUP包。这个顺序搞反了会浪费大量时间。第二抓包数据一定要配套“看ACK/NAK比例”。如果设备频繁回NAK说明设备端处理不过来别急着怀疑协议栈有bug优先看固件里的中断响应时间和缓冲区管理。第三也是最实用的建议别在脱离总线的环境下做USB开发。我以前调试一个设备怎么都枚举不过后来发现是调试器占用了MCU的USB控制器引脚。把调试接口和USB引脚复用配好之后一切恢复正常。USB协议几十年来保持了惊人的向后兼容性但“向下兼容”也意味着调试复杂度始终不低。把这个协议彻底吃透往后不管是搞嵌入式、搞上位机通信还是自己DIY硬件都会顺手很多。这次的内容到这里就结束了如果你们在实际项目中遇到什么奇葩USB问题欢迎在后面留言交流。