
做嵌入式开发这些年我跟USB 2.0打过太多交道。从最初照着参考设计改电路到后来被枚举失败折磨到半夜再到最后能靠示波器波形直接判断问题出在哪一段这一路踩过的坑比看协议规范学到的多得多。如果你想搞懂USB 2.0或者正在被USB设备开发折磨这篇关于基本概念的文章应该能帮你把地基打牢。我尽量不照搬协议文档而是配合实际开发经验来聊让你看完能理解USB到底是怎么工作的也知道出了问题该往哪个方向查。1. USB 2.0到底是什么——先把这个协议放进历史坐标系里1.1 一个2000年发布的协议为什么现在还没退休USB 2.0规范在2000年4月发布到现在已经二十多年了。你可能会问都这么多年了USB3.x甚至USB4都出来了为什么我们还在谈USB 2.0原因特别现实大量设备根本不需要更高的带宽。鼠标、键盘、游戏手柄、U盾、调试接口、充电握手这些设备用USB 2.0完全够用而且USB 2.0的硬件成本低、设计成熟、兼容性极好。你去翻现在的单片机哪怕是很高端的型号原生USB接口多数也还是2.0的比如STM32F4系列、i.MX RT系列板载USB基本都是2.0高速或全速。而且USB 2.0和USB 3.x的兼容设计是共存的USB 3.0的Type-A口内部还保留了D/D-这两根USB 2.0信号线。也就是说你插一个USB 2.0设备到USB 3.0的口上系统走的仍然是USB 2.0那一套协议。搞懂USB 2.0是理解整个USB生态的基石。1.2 USB到底解决了什么问题在没有USB之前电脑外设接口是群雄割据串口、并口、PS/2、Game Port每个接口长得不一样协议不一样供电方式不一样还不能带电插拔。USB当初的设计目标我总结下来就四个统一接口形态一个口能接多种设备支持热插拔即插即用由总线提供电源小型设备不用外接电源一套协议支持从低速键盘到高速硬盘的多种速率需求这四个目标基本都实现了。但USB也带来了新的复杂度比如拓扑结构要分主从通信时序要求非常严格电气信号对线缆和连接器有明确要求。这些复杂度正是我们学习和调试时要面对的坎。2. 物理层那些事——连接器、线缆与D/D-差分信号2.1 连接器形态和引脚定义USB 2.0时代最常见的就是Type-A和Type-B两种标准连接器后来因为便携设备体积限制又衍生出Mini和Micro系列。Type-A就是我们插电脑的那个扁口Type-B是方口打印机上常见Mini-USB和Micro-USB是早期手机和嵌入式开发板上的常客。不管外形怎么变USB 2.0的信号定义只有4根线引脚信号名线缆颜色作用1VBUS红5V电源2D-白差分数据负端3D绿差分数据正端4GND黑电源地这四根线里VBUS和GND负责供电D/D-才是真正的数据线。D/D-是差分对数据通过两根线上的电压差来传输这样设计的好处是抗共模干扰能力强。要注意USB 2.0的差分信号逻辑电平并不是标准的TTL电平它工作在3.3V逻辑域而VBUS是5V两者千万不能混淆。我之前见过有人把D接到5V上结果设备直接被烧连电脑主板上的保护电路都救不回来。2.2 为什么D/D-是差分信号而不是单端信号有读者可能不理解既然D和D-两根线传同一份数据为什么不只用一根线把另一根省下来这正是我在实际面试中经常问的问题。答案很简单抗干扰。在PCB上或者线缆里外部电磁干扰通常以共模形式同时耦合到两根线上差分接收端比较的是两根线的电压差共模干扰会被抵消掉。而且差分信号对地弹噪声也不敏感这让USB能用比串口低得多的电压摆幅传输高速数据。换句话说同样的干扰环境下差分信号能跑更高的频率并且保持低误码率。代价是布线要保证两根线等长、等阻抗并且成对走线。这也是为什么USB线缆里的D/D-是一对双绞线而不是两根平行线。2.3 VBUS电源一个容易被忽略的约束条件VBUS在USB 2.0规范里标称5V允许范围是4.75V到5.25V。但这里有几个约束很多人没注意设备在枚举成功之前只能从VBUS抽取最大100mA电流枚举完成并通过配置之后才允许抽取最大500mA如果超过500mAHost端USB控制器会断电甚至把主机主板上的保险丝或者PTC保护元件打爆我见过不少开发板板上外设全开的时候待机电流就有200多毫安接上主机之后频繁被断开就是因为枚举还没完成电流就超了。正确做法是板载外设供电和USB VBUS之间加负载开关USB枚举成功后再打开外设电源让总线电流曲线符合规范预期。3. 三种速率和一个握手过程——USB 2.0是怎么识别设备身份的3.1 低速、全速、高速到底差在哪USB 2.0向下兼容USB 1.1所以实际包含三种速率速率信号速率典型设备D/D-状态低速1.5 Mbps鼠标、键盘D-上拉全速12 Mbps音频、HID设备、虚拟串口D上拉高速480 MbpsU盘、移动硬盘、摄像头D上拉随后握手切换低速和全速阶段的识别非常简单设备通过把D或者D-上拉到3.3V来表示自己的速率。D有上拉电阻就是全速设备D-有上拉电阻就是低速设备主机检测到对应信号线的静态电平就知道设备是什么速率。注意这里说的上拉是设备端的行为不是主机端。3.2 高速识别chirp握手是怎么发生的高速设备比较特殊它一开始被主机当作全速设备识别然后通过一个叫chirp握手的流程切换到高速模式。高速设备上电后先像全速设备一样把D上拉主机检测到后进入复位流程。复位期间主机把D和D-都拉低并保持一段时间高速设备在复位期间收到这个状态后会向主机发出一系列高速chirp信号表示“我是高速设备我要切高速”。主机如果支持高速就会回应chirp序列双方完成速度协商然后设备断开D上拉以高速模式继续运行。如果主机不支持高速比如某些老Hub设备就保持在全速模式继续工作。理解这个过程的实际意义在于排查问题。你可能会遇到一种情况USB 2.0高速设备被识别成了全速设备传输速度慢了一半以上。原因通常是chirp握手失败可能是线缆质量差、连接器接触不良、或者设备端上拉电阻/阻抗匹配有问题。这些问题在普通使用中不一定会表现出来但一旦出现速率异常就要往这个方向查。3.3 为什么不能通过软件配置设备速率有个常见误区认为设备的速率是枚举过程中通过描述符告诉主机的。不是这样。速率识别完全是物理层行为发生在任何软件通信之前。主机先通过硬件检测到设备速率然后才对这个速率发起后续的枚举通信。描述符里的内容可以声明设备是高速还是全速但主机信任的是物理层握手结果而不是描述符。所以做硬件设计时D/D-的上拉电阻选型和连接方式必须正确一旦这个地方错了软件层面怎么改都救不回来。4. 端点、管道与四种传输——USB数据传输的基本单元4.1 端点设备内部的数据出入口如果说USB设备是一栋房子那端点就是这栋房子开出来的门。每个端点有唯一的地址由端点号和方向组成。端点号从0到15方向有IN和OUTIN是设备到主机的方向OUT是主机到设备的方向。设备端点的数量不是无限的USB规范规定控制端点必须在端点0其他类型端点的数量和配置由设备描述符说明。我拿一个典型的USB串口芯片比如CH340或者CP2102来举例这类芯片通常有两个批量端点一个批量IN设备数据往主机发一个批量OUT主机数据往设备发再加上端点0做控制和设备描述符访问。你在电脑上打开串口收发数据走的其实就是这两个批量端点。4.2 管道端点和主机之间的约定通道管道是主机和设备端点之间的逻辑连接它关联了三件事端点的地址、传输类型、以及约定的数据包大小。管道在设备配置完成后建立在配置改变或设备断开时销毁。你可以把管道理解成一条水管水管本身不存数据数据从一端流入从另一端流出水管的粗细由最大包大小决定。USB的每一笔事务都是主机发起的设备永远是被动响应方即使设备要往主机发数据也必须等主机来索取。4.3 四种传输类型各有各的脾气USB 2.0定义了四种传输类型适应不同设备的需求这个必须背熟开发时选错类型会出大问题传输类型主要用途特点典型设备控制传输枚举、设备配置、命令双向的、可靠性最高、开销最大所有USB设备端点0批量传输数据量大、对延迟不敏感保证可靠传输带宽不保证U盘、串口、打印机中断传输小数据量、周期性、保证延迟保证轮询周期数据可靠鼠标、键盘、游戏手柄同步传输实时性要求高、允许丢数据不重传错误带宽有保证摄像头、麦克风、音频设备控制传输是USB的立身之本所有设备必须在端点0支持控制传输。批量传输有CRC校验和重试机制数据错了一定会重传但是实时性没保障——如果你写一个USB转串口的应用数据在系统繁忙时可能会延迟但不会丢。中断传输名字叫“中断”实际上不是硬件中断它只是主机保证定期去设备读取数据。同步传输不重传延迟最低但偶尔丢一帧数据对音视频来说是可以接受的。设计USB设备时你必须在可靠性和实时性之间做权衡。4.4 传输、事务、包一次通信的三层结构USB 2.0通信有个很重要的层级概念传输由若干个事务组成事务由若干个包组成。拿批量传输举例一个批量事务包含令牌包、数据包、握手包三个阶段。令牌包由主机发出说明这次要操作哪个端点、什么方向数据包携带实际数据握手包由接收方回复表示ACK成功、NAK忙或STALL错误。传输层和事务层的关系可以类比成寄包裹传输是“我要寄这个快递”事务是“快递车跑一趟”包则是“车上的一个包裹袋”。搞清楚这个层次你才能读懂USB分析仪抓到的数据流。比如设备NAK得很频繁说明设备来不及处理数据你要优化的是固件处理速度而不是协议本身。5. 枚举过程——USB设备从插上到开始通信的几百毫秒5.1 枚举到底做了什么枚举是USB协议里最核心、也最容易出问题的环节。很多开发者在调试时遇到“设备无法识别”本质都是枚举流程没走完。我用几个步骤来说明主机和设备之间到底发生了什么设备插入Host检测到D或D-上拉电平判断设备速率Host向设备发出复位信号SE0状态即D和D-均为低持续至少10ms复位结束后Host在地址0和设备端点0建立控制通信读取设备描述符的前8字节主要是最大数据包大小Host再次发出复位信号然后通过SET_ADDRESS请求给设备分配唯一地址Host使用新地址获取完整的设备描述符、配置描述符、字符串描述符等Host根据描述符加载合适的驱动程序Host发出SET_CONFIGURATION请求设备进入配置状态开始正常的数据传输这个流程中任何一步失败都会导致设备无法使用。我做了个表方便你在调试时根据现象判断是哪一步出了问题故障现象可能出错的环节排查方向系统完全没有反应物理层/速率识别查VBUS、D/D-上拉、线缆连接设备反复断开重连Reset或地址分配查D/D-短路、复位时间过长、供电不足提示“未知设备”描述符读取失败查端点0最大包大小、固件枚举代码设备能识别但无功能驱动加载或配置失败查VID/PID、描述符内容、配置描述符长度5.2 描述符设备的身份证和说明书描述符是USB设备主动暴露给主机的数据结构主机依靠它们了解设备是什么、能干什么、需要什么样的驱动。常用的描述符有设备描述符Device Descriptor包含VID、PID、设备版本、端点0最大包大小等配置描述符Configuration Descriptor包含接口数量、供电方式、最大电流等接口描述符Interface Descriptor描述设备的功能比如是HID、Mass Storage还是CDC虚拟串口端点描述符Endpoint Descriptor描述端点的地址、传输类型、最大包大小、轮询间隔VID和PID特别重要。VID是厂商识别码需要向USB-IF购买PID是产品编号由厂商自己分配。Windows和Linux都会根据VID/PID来匹配驱动。如果你的设备在电脑上显示“未知设备”大概率是VID/PID和驱动不匹配或者设备在枚举时无法完整返回描述符。5.3 一个调试枚举失败的实操记忆我当年调试第一块USB开发板现象是插上电脑后提示“无法识别的USB设备”。排查过程让我记忆深刻先用万用表量VBUS和GND之间电压5V正常再用示波器看D/D-发现D上拉电平不对只有1.8V左右。量了一圈电阻发现板子上的1.5k上拉电阻焊接偏位焊到了旁边的电阻网络里导致上拉电阻被并联拉低了。这个问题纯粹是硬件问题但如果没有示波器光靠查软件代码是永远查不出来的。所以调试USB设备示波器或逻辑分析仪是刚需不要试图只靠printf打印来定位问题。6. 实用排查经验——从硬件设计到总线信号常见坑一次说清6.1 硬件设计中的几个红线USB 2.0硬件设计说简单也简单说复杂也复杂但有几条红线绝对不能碰D/D-必须是差分对保持等长、等阻抗默认走线阻抗控制在90Ω±15%上拉电阻必须接在设备端而不是主机端电阻阻值必须是1.5kΩ±5%对应3.3V上拉电压VBUS和GND要加足够容量的滤波电容通常4.7uF-10uF靠近连接器放置如果需要ESD保护TVS管的结电容要选小于2pF的否则会吃掉信号边沿导致高速模式不稳定我见过有人直接把一个模块的D/D-引两根杜邦线出来接设备这种飞线方式在全速模式下还能跑一旦切到高速信号质量会恶化到无法握手。USB 2.0高速模式的信号上升沿不到500ps任何额外的寄生电容和线路长度都会造成信号振铃和误码。所以如果你在做板级设计D/D-走线尽量短过孔越少越好。6.2 线缆隐蔽的背锅侠USB线缆质量参差不齐这是我踩坑最深的地方之一。USB 2.0高速模式要求D/D-是阻抗匹配的双绞线屏蔽层要良好接地线径不能太细。便宜线缆往往用普通平行线代替双绞线短距离用用没感觉一旦超过1米高速设备就可能掉到全速模式甚至完全无法识别。我试过一个案例客户反馈说产品插某些电脑无法识别但换一台电脑就行。排查到最后问题出在客户提供的USB线缆上那根线里面的D/D-居然是断了一根靠屏蔽层地线碰巧偶尔导通。这种故障用万用表测未必测得出来因为时断时续。所以建议做产品验证时至少备几根正规品牌线缆作为基准出现问题时先排除线缆因素再排查设备本体。6.3 枚举失败的软件调试思路如果硬件看着没问题那就需要抓USB总线数据来看了。常用工具有几种商用USB分析仪比如Teledyne LeCroy、Total Phase、开源方案比如用FPGA或单片机自己抓、或者软件层工具比如Windows上的USBLyzer、Linux上的usbmonWireshark。对于基本概念学习和常规开发我建议先用软件层工具因为它们不额外花钱而且能抓到协议层的请求和响应大部分枚举问题在协议层就能定位。真正常见的软件类枚举问题包括设备描述符里的bMaxPacketSize0与实际硬件配置不符导致主机后续读取失败端点描述符里的最大包大小和端点实际发送/接收能力不符SET_ADDRESS后设备没有实际切换地址导致主机后续请求全部超时配置描述符里声明的接口和端点和固件实际初始化的不一致我遇到过最离奇的一个是固件里配置的端点号是0x81但主机一直枚举失败后来发现USB规范里端点号有效范围是1到150x81的低4位是1地址是对的但固件里端点号定义用了完整字节导致某个中间转换环节把0x81解析成了129号端点越界出错。这类问题看代码半天看不出来用总线分析仪一眼就能定位是哪个端点没有响应。6.4 电源噪声与设备复位循环还有一种特别隐蔽的问题设备不断循环枚举表现为设备连接提示音反复响过一会又断。排除线缆和软件问题后务必要测一下VBUS在上电瞬间和枚举瞬间的波形。很多供电不足的USB Hub在设备拉高电流时VBUS跌落超过规范允许范围设备内部电压监控触发复位然后设备又重新开始枚举形成振荡。这种情况在笔记本电脑上特别常见因为笔记本USB口供电本身就比较敏感。解决方法是设备端板载大容量储能电容例如100uF以上或者在固件里做上电软启动避免瞬时电流冲击过大。你看有时候USB问题根本不是协议问题而是电源设计问题。7. 学习USB 2.0的几条建议——给刚开始接触的人7.1 不要上来就背协议规范USB 2.0规范文档有六百多页你要是照着从头读到尾大概率坚持不了一周就会放弃。我的建议是先建立整体框架理解上面说的物理层、协议层、传输类型、枚举流程这几个概念然后找一个小目标练手。比如用一个带USB接口的单片机开发板实现一个HID键盘——不需要驱动Windows和Linux都能直接识别是练手USB最好的入门项目。7.2 开发环境和工具精简到刚好够用做USB开发不需要一开始就上高端仪器。我自己的起步配置特别朴素一块带USB外设的STM32开发板F103或F407都行一个能测波形的100MHz示波器USB转串口模块用于调试日志Linux开发环境usbmon和Wireshark抓包很方便几根质量靠得住的USB线这套配置应付基本的枚举调试和协议分析绰绰有余。等你真的开始调高速信号完整性了再考虑投资更高端的设备也不迟。7.3 源码阅读比看文档更高效再次强调一下USB的学习不能只停留在阅读层面。建议去读市面主流协议栈源码比如STM32的USB Device库、Linux内核的usb core、或者Zephyr的USB device stack。重点观察一个设备从插上到枚举完成软件到底做了哪些事情。对照源码去理解规范里的概念效率比自己死磕文档高得多。7.4 遇到问题先分层再定位最后分享一个实操方法论USB调试的核心是分层定位。遇到任何USB问题先问自己——问题出在物理层还是协议层还是软件驱动层物理层就看波形协议层就抓包软件层就看日志。不要一上来就怀疑固件哪儿写错了更不要一上来就怀疑电脑主板坏了。我见过太多同事纠结了几天的问题最后发现是线缆接触不良。分层排查的思路看起来很基础但真的能省掉无数冤枉时间。这套思路我做USB开发到现在一直在用几乎适用所有USB问题。希望这篇基本概念能帮你把USB 2.0的大框架立住下一步就可以找个小项目实践起来了。