首页 / 资讯中心 / 文章详情

USB 2.0 Full Speed协议深度解析与嵌入式调试实战

USB 2.0 Full Speed协议深度解析与嵌入式调试实战 ★ FEATURED ARTICLE
1. 为什么还要啃USB 2.0 Full Speed这块老骨头搞嵌入式的人多少都有过这种经历拿到一个USB设备插上电脑系统识别了但传输数据就是不稳定偶尔丢包偶尔枚举失败。抓包一看满屏的Token、Data、Handshake时序图上全是细节一时半会儿理不清头绪。这时候如果对USB 2.0 Full Speed的协议底层没有足够理解基本就只能靠猜和试。USB 2.0规范从2000年发布到现在已经二十多年了但Full Speed全速12 Mbps依然是大量嵌入式设备的主力通信速率。原因很直接低速Low Speed1.5 Mbps太慢只能应付键鼠这类HID设备高速High Speed480 Mbps对MCU的时钟要求和PHY成本又上了一个台阶很多低成本方案扛不住。Full Speed刚好卡在一个甜蜜点上——12 Mbps的速率足够应付音频、CDC串口、自定义HID、MIDI、部分大容量存储等场景同时MCU只需要48 MHz时钟就能跑USB PHY的模拟部分也相对好做。但问题在于很多人对Full Speed的理解停留在“插上能用就行”的层面。一旦遇到需要自己写USB协议栈、调试枚举流程、优化批量传输吞吐量、或者排查间歇性通信故障的时候底层细节的缺失就会变成拦路虎。比如为什么Setup事务之后必须有Data0为什么批量传输的DATA PID要在Data0和Data1之间翻转为什么SOF帧起始包对Full Speed设备这么重要这些问题不搞清楚调试基本靠运气。这篇内容面向的是有一定单片机基础、正在或准备做USB设备开发的工程师也适合那些想从“会用库”进阶到“理解协议”的开发者。我会从协议分层、物理层信号、事务结构、传输类型、枚举流程、实际调试经验几个维度展开把Full Speed的核心机制拆开讲透。不会只讲理论每个关键点都会配上实际调试中会遇到的现象和排查思路。2. USB 2.0 Full Speed的协议分层与核心架构2.1 从物理层到应用层的完整栈结构USB协议不是单一层的东西它是一套分层体系。理解这个分层是搞清楚后面所有细节的前提。最底层是物理层负责差分信号D和D的电气特性。Full Speed使用3.3V差分信号D线上拉1.5kΩ电阻到3.3V是Full Speed设备的标志低速设备则是D-上拉。主机通过检测哪根线上有上拉来判断设备是全速还是低速。这个细节很关键——很多自己画板子的人忘了加上拉电阻或者上拉到了错误的电压导致主机根本不识别。往上一层是协议层定义了Token包、Data包、Handshake包的格式和时序。这一层是Full Speed最核心的部分也是调试时抓包工具看到的主要内容。协议层规定了事务Transaction的组成方式一个完整的事务通常包含Token包、Data包和Handshake包三个阶段。再往上是配置层负责设备的枚举和配置管理。包括设备描述符、配置描述符、接口描述符、端点描述符的读取和解析。这一层决定了主机怎么认识设备、给设备分配什么资源。最上面是应用层也就是具体的设备类协议比如HID、CDC、MSC、Audio等。应用层建立在下面三层的基础上但很多开发者只关注应用层忽略了底层的约束结果就是应用层代码看起来没问题实际跑起来各种异常。注意很多人调试USB时只盯着应用层代码改但80%的问题其实出在协议层和配置层。抓包工具能看到的东西远比你在代码里printf出来的信息有价值。2.2 Full Speed与Low Speed、High Speed的关键差异Full Speed不是简单地把Low Speed提速它在协议机制上有本质区别。特性Low SpeedFull SpeedHigh Speed速率1.5 Mbps12 Mbps480 Mbps上拉电阻位置D-DD枚举时先以Full Speed识别最大包长控制传输8 字节8/16/32/64 字节64 字节最大包长批量传输不支持8/16/32/64 字节512 字节最大包长中断传输8 字节1-64 字节1-1024 字节SOF包不支持每1ms一帧每125μs一个微帧是否支持批量传输否是是是否支持高速握手否否是Chirp握手这个表格里最值得注意的是Low Speed不支持批量传输。如果你做一个低速设备想用批量端点协议上就不允许。另外Full Speed的SOF包是每1ms一个这个SOF包不只是用来同步帧的它还是中断传输和同步传输的调度基准。还有一个容易混淆的点High Speed设备在枚举初期是以Full Speed模式运行的通过Chirp握手后才切换到高速模式。所以即使用的是高速设备理解Full Speed的枚举流程也是基础。2.3 端点与管道的概念为什么端点0这么特殊USB通信的基本单位是端点Endpoint每个端点本质上是一个单向的数据缓冲区。主机和设备之间的通信通过**管道Pipe**进行管道是端点和主机端缓冲区之间的逻辑连接。端点0是所有USB设备必须支持的它专门用于控制传输负责枚举和配置。端点0的特殊之处在于它是双向的既能接收也能发送而且它在设备上电后就默认存在不需要配置。其他端点端点1、2、3……必须在配置描述符中声明主机配置设备后才生效。每个端点有自己的传输类型控制、中断、批量、同步和方向IN或OUT。IN方向是从设备到主机OUT方向是从主机到设备。注意这个方向是站在主机角度定义的不是站在设备角度。很多初学者在这里搞反导致端点配置错误。端点的**最大包长wMaxPacketSize**也是一个关键参数。Full Speed下控制端点0的最大包长可以是8、16、32或64字节批量端点可以是8、16、32或64字节中断端点可以是1到64字节。这个值不是随便设的设大了主机可能不接受设小了传输效率低。实操心得端点0的最大包长在设备描述符的bMaxPacketSize0字段中声明。如果你用的是现成的USB库这个值通常默认是64但有些低端MCU的USB IP核只支持8或16这时候必须改对否则枚举会失败。3. 物理层信号与包结构深度拆解3.1 差分信号、位填充与NRZI编码Full Speed的物理层用差分信号传输数据D和D-两根线的电压差决定逻辑电平。差分传输的好处是抗共模干扰能力强适合短距离高速通信。但USB的物理层不只是差分那么简单。它用了NRZI编码Non-Return-to-Zero Inverted规则是数据为0时电平翻转数据为1时电平保持不变。这种编码方式的好处是自带时钟信息接收方可以从数据流中恢复时钟。NRZI编码有一个问题如果连续发送很多个1电平一直不翻转接收方就可能丢失时钟同步。所以USB协议规定每连续6个1之后必须插入一个0这就是位填充Bit Stuffing。接收方在收到连续6个1之后的0时会自动把这个0去掉恢复原始数据。这个机制在调试时很重要。如果你用逻辑分析仪抓USB信号看到的原始波形是经过NRZI编码和位填充的不能直接当二进制数据读。需要用支持USB协议解码的分析仪或者自己写解码脚本。3.2 Token包、Data包、Handshake包的格式与作用Full Speed的包结构分为三类Token包、Data包、Handshake包。每个包都以SOPStart of Packet开始以EOPEnd of Packet结束。Token包由主机发出用来启动一个事务。Token包包含PID字段8位、地址字段7位、端点字段4位和CRC5校验5位。PID字段标识Token类型常见的有SETUP0x2D启动控制传输的Setup阶段IN0x69主机请求从设备读取数据OUT0xE1主机要向设备发送数据SOF0xA5帧起始包每1ms发送一次Data包携带实际数据包含PID字段、数据字段0到1024字节和CRC16校验。Data包的PID有四种DATA0、DATA1、DATA2、MDATA。Full Speed下主要用DATA0和DATA1用于数据翻转同步。Handshake包只有PID字段没有数据和CRC。常见的Handshake有ACK接收方正确收到数据NAK接收方暂时无法处理主机稍后重试STALL端点出错或请求不支持NYET仅High Speed使用Full Speed不用一个典型的事务流程是主机发Token包 → 设备识别地址和端点 → 数据阶段如果有→ Handshake阶段。比如一个IN事务主机发IN Token → 设备发DATA0/DATA1 → 主机回ACK。3.3 SOF包的作用与帧同步机制SOF包是Full Speed协议里一个容易被忽视但很重要的东西。它每1ms由主机发出一次包含一个11位的帧号Frame Number从0到2047循环。SOF包的作用有三个第一帧同步。中断传输和同步传输的调度是以帧为单位的。主机在每一帧开始时决定这一帧要处理哪些中断端点和同步端点。设备端如果要做同步传输必须根据SOF包来同步自己的数据节奏。第二时钟参考。有些设备比如音频设备需要精确的1ms时间基准SOF包提供了这个基准。设备可以通过检测SOF包的间隔来校准自己的内部时钟。第三保持总线活跃。即使没有数据传输SOF包也会定期出现防止总线进入空闲状态过久。注意SOF包不是必须被设备响应的。设备收到SOF包后不需要回ACK也不需要做任何Handshake。但设备可以选择监听SOF包来获取帧号信息。在实际调试中如果你发现设备的同步传输有周期性抖动第一件事就是检查SOF包的接收是否正常。有些MCU的USB IP核在SOF中断处理上有延迟导致同步节奏不准。4. 四种传输类型的机制与实操差异4.1 控制传输枚举的基石控制传输是USB里最复杂的传输类型也是最重要的。所有USB设备在枚举阶段都通过控制传输来交换描述符。控制传输由三个阶段组成Setup阶段、Data阶段可选、Status阶段。Setup阶段固定是一个SETUP事务主机发送8字节的Setup数据包含请求类型、请求码、值、索引和长度。设备必须回ACK。这8字节的格式是固定的偏移字段长度说明0bmRequestType1字节请求方向、类型、接收者1bRequest1字节具体请求码2wValue2字节请求参数4wIndex2字节索引或偏移6wLength2字节数据阶段长度Data阶段的方向由bmRequestType的bit7决定。如果bit7为1表示设备到主机IN如果为0表示主机到设备OUT。Data阶段的数据包PID从DATA1开始每成功传输一个包翻转一次。Status阶段的方向与Data阶段相反。如果Data阶段是INStatus阶段就是OUT主机发一个空的DATA1包设备回ACK。如果Data阶段是OUTStatus阶段就是IN设备发一个空的DATA1包主机回ACK。控制传输的关键点在于Setup阶段永远用DATA0Data阶段的第一个包用DATA1Status阶段永远用DATA1。这个规则不能错错了设备可能不响应。4.2 中断传输小数据、低延迟的保障中断传输适合小数据量、需要保证延迟的场景比如键盘、鼠标、游戏手柄。Full Speed下中断端点的最大包长是1到64字节轮询间隔是1到255毫秒。中断传输的“中断”这个词容易让人误解。它不是设备主动打断主机而是主机定期轮询设备。主机在每个帧1ms中根据中断端点的轮询间隔来决定是否发起IN或OUT事务。如果设备没有数据要发就回NAK主机下一帧再试。中断传输的延迟保证来自于轮询间隔。如果你设置轮询间隔为1ms主机每1ms就会来问一次最坏情况下的延迟就是1ms加上传输时间。但轮询间隔设得太小会占用总线带宽设得太大又保证不了延迟需要根据实际需求权衡。实操心得很多人在做自定义HID设备时把中断端点的轮询间隔设成1ms结果发现总线带宽被大量占用其他端点传输变慢。实际上如果数据更新率不需要那么高设成5ms或10ms完全够用还能给其他传输留出带宽。4.3 批量传输大数据量、无延迟保证批量传输适合大数据量、对延迟不敏感的场景比如U盘、打印机。Full Speed下批量端点的最大包长是8、16、32或64字节。批量传输利用总线的空闲带宽当总线忙时批量传输会被推迟。批量传输的可靠性通过数据翻转Data Toggle和重试机制来保证。发送方每成功发送一个DATA0或DATA1包并收到ACK后就翻转PID。如果收到NAK就保持当前PID重试。接收方通过检查PID来判断是否收到重复包。批量传输没有固定的带宽保证。如果总线上有大量中断传输和同步传输批量传输可能被严重推迟。所以在设计系统时如果批量传输的吞吐量很关键要确保总线负载不要太高。4.4 同步传输实时性的代价同步传输用于音频、视频等需要恒定速率、容忍一定误码的场景。Full Speed下同步端点的最大包长是1023字节但没有Handshake阶段——也就是说同步传输没有ACK、NAK、STALL发送方发出去就不管了。同步传输的可靠性靠CRC校验和帧同步来保证。接收方如果发现CRC错误直接丢弃该包不做重传。同步传输的带宽是在配置阶段预留的主机保证每帧为同步端点分配足够的时间槽。同步传输的PID只有DATA0不翻转。这是因为同步传输不需要重传也就不需要PID来区分重复包。注意同步传输虽然叫“同步”但它不保证数据一定到达。如果总线质量差或者干扰大同步传输会丢包。所以在实际应用中音频设备通常会在应用层加一些容错机制比如缓冲和插值。5. 枚举流程全解析从上电到可用5.1 设备插入检测与复位设备插入主机后主机的根集线器通过检测D或D-上的上拉电阻来判断有设备接入。Full Speed设备在D上有1.5kΩ上拉主机检测到D变高后就知道有Full Speed设备插入。接下来主机发送复位信号复位信号是D和D-都拉低至少10ms。复位后设备进入默认状态地址为0端点0可用其他端点都不可用。复位完成后主机开始枚举。枚举的第一步是读取设备描述符的前8个字节目的是获取端点0的最大包长。这一步用的是默认地址0控制传输的Setup包请求的是设备描述符但wLength只设成8。5.2 地址分配与描述符读取主机拿到端点0的最大包长后会给设备分配一个唯一的地址1到127。地址分配通过SET_ADDRESS请求完成这个请求没有Data阶段直接进入Status阶段。地址分配完成后主机用新地址重新读取完整的设备描述符18字节。设备描述符里包含了厂商ID、产品ID、设备类、端点0最大包长、配置数量等信息。接下来主机读取配置描述符。配置描述符的长度不固定取决于接口和端点的数量。主机先读9字节的配置描述符头部从中获取wTotalLength然后再读完整的配置描述符集合。配置描述符集合里包含了配置描述符、接口描述符、端点描述符可能还有类特定描述符。主机解析这些描述符后就知道设备有哪些接口、每个接口有哪些端点、端点的传输类型和最大包长。5.3 配置设备与端点使能主机读取完所有描述符后会发送SET_CONFIGURATION请求把设备配置成某个配置值。设备收到这个请求后使能对应的端点进入配置状态。配置完成后设备就可以正常通信了。主机根据接口的类协议来加载对应的驱动比如HID设备加载HID驱动MSC设备加载大容量存储驱动。整个枚举流程看起来简单但实际调试中问题很多。常见的问题包括描述符长度不对、端点最大包长设置错误、配置描述符集合的总长度不对、设备响应超时等。实操心得枚举失败时第一件事是用抓包工具看主机读到了什么。如果主机在读设备描述符时就失败了问题通常出在端点0的响应上如果读到描述符但配置失败问题通常出在描述符内容上。抓包工具能看到主机发了什么请求、设备回了什么数据比在代码里猜高效得多。6. 实际调试中的常见问题与排查技巧6.1 枚举失败的五种典型原因枚举失败是USB开发中最常见的问题。根据我的经验原因通常集中在以下五个方面第一上拉电阻问题。Full Speed设备需要在D上拉1.5kΩ到3.3V。如果忘了加上拉或者上拉到了5V主机可能检测不到设备或者检测到但枚举失败。第二端点0最大包长不匹配。设备描述符里声明的bMaxPacketSize0必须和实际端点0的缓冲区大小一致。如果声明64但实际只有8主机发64字节的数据包时设备就处理不了。第三描述符内容错误。描述符的bLength字段必须和实际长度一致wTotalLength必须等于配置描述符集合的总字节数。这些字段错了主机会解析失败。第四响应超时。主机发请求后设备必须在规定时间内响应。如果设备固件处理太慢或者中断被禁用太久就会超时。Full Speed下控制传输的超时时间通常是几毫秒到几十毫秒。第五电源问题。有些设备在枚举阶段电流需求就比较大如果主机供电不足设备可能复位或工作异常。可以在设备端加一个大电容缓冲或者用自供电方案。6.2 数据传输不稳定的排查思路数据传输不稳定通常表现为偶尔丢包、传输速率远低于预期、间歇性NAK或STALL。排查时可以从以下几个角度入手检查总线负载如果总线上有多个端点同时传输带宽可能不够。用抓包工具看每帧的传输情况如果帧内时间槽用满了就需要降低轮询频率或减少端点数量。检查数据翻转DATA0和DATA1的翻转规则必须严格遵守。如果设备端翻转逻辑写错了主机会把重复包当成新包或者把新包当成重复包丢弃。检查NAK处理设备回NAK是正常的流控机制但如果NAK太频繁说明设备处理速度跟不上。可以增大端点缓冲区或者优化固件处理逻辑。检查时钟精度Full Speed要求时钟精度在±0.25%以内。如果MCU的USB时钟偏差太大会导致位填充错误或CRC错误。6.3 抓包工具的选择与使用要点调试USB离不开抓包工具。常见的选择有工具类型优点缺点适用场景硬件协议分析仪时序精确能看到物理层细节价格高深度调试、物理层问题软件抓包工具免费或低成本使用方便看不到物理层依赖主机驱动协议层调试、枚举分析逻辑分析仪解码插件成本适中能看到原始波形解码插件质量参差不齐信号完整性、时序分析使用抓包工具时重点看几个东西枚举阶段的描述符请求和响应、数据传输阶段的PID翻转、Handshake包的类型和频率、SOF包的间隔是否稳定。注意软件抓包工具通常只能看到主机侧的数据看不到设备侧的响应细节。如果怀疑设备端有问题最好用硬件分析仪同时抓D和D-两根线。7. 从理解协议到优化性能的进阶思路7.1 批量传输吞吐量的优化方法Full Speed下批量传输的理论最大吞吐量是12 Mbps减去协议开销。实际能达到的吞吐量通常在800 KB/s到1 MB/s左右。如果实测远低于这个值可以从以下方面优化增大端点缓冲区端点最大包长设成64字节比设成8字节效率高很多。因为每个包都有Token和Handshake开销包越大有效数据占比越高。减少NAK次数设备端如果处理速度慢主机会频繁收到NAK导致重试。可以增大双缓冲让设备在处理当前包的同时接收下一个包。优化固件中断处理USB中断的优先级要足够高处理时间要尽量短。如果USB中断被其他中断打断太久会导致响应超时。合理分配总线带宽如果同时有中断传输和批量传输中断传输的轮询间隔不要设得太小给批量传输留出足够的时间槽。7.2 中断传输延迟的测量与调优中断传输的延迟由轮询间隔和传输时间决定。测量延迟的方法是在设备端打时间戳在主机端也打时间戳两者相减。如果延迟比预期大检查以下几点轮询间隔是否设对了、主机是否按时发起IN事务、设备是否及时准备了数据、总线上是否有其他高优先级传输抢占了时间槽。调优的方向是在满足延迟要求的前提下尽量增大轮询间隔减少总线占用。如果延迟要求很严格可以考虑用同步传输替代中断传输但同步传输没有重传机制可靠性会下降。7.3 低功耗场景下的USB挂起与恢复USB协议支持挂起Suspend和恢复Resume机制。当总线空闲超过3ms时设备应该进入挂起状态电流降到2.5mA以下。主机可以通过发送恢复信号来唤醒设备。挂起和恢复的调试要点设备要正确检测总线空闲状态、挂起时保存好上下文、恢复时能正确重新同步。有些MCU的USB IP核在挂起和恢复时有硬件bug需要仔细看勘误手册。实操心得低功耗产品中USB挂起电流是一个硬指标。如果实测挂起电流偏大先检查USB PHY是否真正进入了低功耗模式再检查MCU的其他外设是否关闭了。有时候一个没关的LED就能让挂起电流超标。8. 写在最后一些踩坑后的真实体会USB 2.0 Full Speed的协议细节很多但真正调试起来出问题的地方往往集中在几个关键点上上拉电阻、端点0配置、描述符内容、数据翻转、时钟精度。把这几个点吃透大部分问题都能定位。我自己的习惯是拿到一个新的USB方案先用抓包工具把枚举流程完整抓一遍确认描述符读取正常、配置成功。然后再测数据传输看PID翻转和Handshake是否正常。最后再测边界条件比如总线负载高的时候、挂起恢复的时候、热插拔的时候。还有一点很重要不要迷信库。现成的USB库能帮你省很多事但库也有bug也有配置不对的时候。理解协议底层才能在库出问题的时候知道去哪里找原因。最后分享一个小技巧如果你在调试一个自定义USB设备不妨先把它枚举成一个标准的HID设备用系统自带的HID驱动来验证基础通信是否正常。基础通信没问题了再换成自定义驱动和自定义协议。这样能把问题范围缩小避免一上来就面对一堆未知因素。
阅读完成 · 觉得有帮助?
咨询建站