1. 项目概述从“线缆”到“对话”的协议世界刚接触USB开发那会儿我总觉得它就是个“插上就能用”的简单玩意儿。直到第一次调试USB设备枚举失败看着逻辑分析仪上那一串串高低电平才意识到USB协议背后是一套极其严谨的“语言”体系。这根看似普通的线缆里流淌的不是简单的电流而是一套由主机和设备严格遵守的、格式化的数据包“对话”。今天我们就来深入拆解USB 2.0协议中最核心的“发令枪”——令牌包Token Packets。理解它是理解整个USB通信如何发起、如何寻址、如何控制流量的关键一步。无论你是正在调试USB外设的嵌入式工程师还是对硬件通信协议感兴趣的技术爱好者搞懂令牌包就等于拿到了解读USB数据流的第一把钥匙。2. USB 2.0通信基础与包结构总览在深入令牌包之前我们必须先建立对USB 2.0通信框架的基本认知。USB通信是一种基于主从架构、轮询式的总线协议。这意味着整个总线上的所有活动都由主机通常是你的电脑发起和控制设备只能被动响应。这种设计简化了设备端的逻辑但要求主机端协议栈必须足够健壮。2.1 事务Transaction通信的基本单元USB通信的基本单元不是单个字节或数据包而是事务。一个完整的事务通常由三个包按严格顺序组成令牌包由主机发出宣告一次事务的开始并指明事务类型、目标设备地址和端点号。数据包根据事务类型由主机或设备发出承载实际需要传输的数据。握手包由数据接收方发出向发送方反馈本次数据传输的成功ACK、失败NAK或暂停STALL状态。偶尔某些事务可能只包含令牌包和握手包如SETUP事务但令牌包作为“开场白”是绝对不可或缺的。你可以把它想象成快递员上门前打的电话“你好我是XX快递现在到X栋Y号送个包裹类型请准备签收”。这个电话就是令牌包它不包含包裹本身数据但指明了所有关键信息。2.2 包的通用格式与编码所有USB包都以一个同步字段SYNC开始这是一个8位的特定模式0x80但经过NRZI编码和位填充后实际波形为KJKJKJKK用于让接收方的锁相环电路与发送方的时钟同步确保后续每一位都能被正确采样。包的最后是结束标志包结束符在物理线路上表现为单端零SEO状态持续一定时间。最关键的是包内的数据USB使用NRZI编码和位填充来保证信号中有足够的电平翻转便于接收端恢复时钟。简单来说NRZI编码下“0”代表电平翻转“1”代表电平保持。如果连续出现多个“1”电平就会长时间不变导致时钟失步。因此协议规定当连续出现6个“1”后发送方必须强行插入一个“0”位填充来制造一次翻转。接收方则需要能识别并移除这个填充位。理解这一点对后续分析包内容至关重要因为你看逻辑分析仪原始解码时可能会发现数据长度比预期长那可能就是位填充造成的。3. 令牌包Token Packets深度解析令牌包是主机握在手中的指挥棒它决定了接下来要发生什么。USB 2.0规范定义了三种令牌包IN、OUT和SETUP。此外还有一种特殊的PING令牌包用于高速模式流控。所有令牌包都具有相同的固定格式。3.1 令牌包的标准格式一个标准的令牌包由以下字段组成总长度为3个字节24位包标识符8位用于标识包类型。设备地址7位指定目标设备的地址。端点号4位指定目标设备上的具体端点。CRC5校验5位用于校验令牌包中地址和端点字段的完整性。包标识符的8位又被细分为两部分低4位是包类型标识高4位是校验字段。对于令牌包其包类型标识为0001IN、1001OUT或1101SETUP。校验字段是类型标识的二进制反码例如IN令牌的标识符完整8位是10010110二进制1001是类型0001的反码1110吗这里需要纠正标准中PID的低4位是类型高4位是低4位的按位取反。所以IN的类型0001取反是1110组合起来PID字节是1110 0001即0xE1。同理OUT(0001取反1110) -0xE1不对OUT的类型码是1001。我们直接记十六进制值更可靠。根据USB 2.0规范常见的令牌包PID值如下IN Token:0x69(二进制0110 1001低4位1001是9代表IN这里混乱了。应以规范为准PID[3:0]是类型PID[7:4]是~PID[3:0]。IN的类型字段是0001取反后1110所以PID1110 00010xE1。我之前的记忆有误。我们重新梳理并采用最权威的十六进制值)为了避免混淆我们直接引用最常用的值OUT Token: PID 0xE1(类型0001取反1110)IN Token: PID 0x69(类型1001取反0110) - 等等1001取反是0110所以是0110 10010x69。对了。SETUP Token: PID 0x2D(类型1101取反0010所以是0010 11010x2D)PING Token: PID 0xB4(类型0100取反1011所以是1011 01000xB4)设备地址字段共7位可寻址0~127其中地址0保留用于设备枚举过程中的初始默认地址。端点号字段共4位理论上每个设备方向IN/OUT可支持16个端点但端点0总是控制端点且大多数设备只使用少数几个端点。CRC5校验是针对地址和端点字段共11位计算的5位循环冗余校验码。它的生成多项式是G(x) x⁵ x² 1。接收方设备会重新计算CRC5并与收到的CRC5比较。如果不匹配设备会直接忽略整个令牌包不作任何响应。这是一种静默丢弃机制避免了因令牌错误而引发的总线冲突。注意很多初学者在抓取USB数据包时会忽略CRC校验字段或者觉得它不重要。但在干扰较强的环境中一个因CRC错误而被静默丢弃的令牌包可能导致主机等待设备响应超时进而触发设备复位或重新枚举。这种故障现象隐蔽排查时务必检查CRC。3.2 IN令牌包主机“索要”数据当主机需要从某个设备端点读取数据时它会发送一个IN令牌包。这个包告诉目标设备的指定端点“请把你准备好的数据发给我”。例如当你移动USB鼠标时主机每隔一个轮询间隔如1ms就会向鼠标的中断IN端点发送一个IN令牌鼠标随后用数据包回复其移动和按键状态。实操场景在逻辑分析仪上抓取一个鼠标中断传输。你会看到主机先发出一个PID为0x69的包后面跟着设备地址比如0x01和端点号比如0x81注意端点号最高位表示方向1为IN所以这里端点号字段是0x01但结合PID的IN含义指向的是设备的IN端点1。紧接着你应该会看到设备回复一个数据包DATA0或DATA1最后主机回复一个ACK握手包。如果设备暂时没有数据比如鼠标没动它会回复一个NAK握手包告诉主机“暂无数据”如果该端点被挂起halted则回复STALL。3.3 OUT令牌包与SETUP令牌包主机“下发”数据与命令OUT令牌包用于主机向设备发送数据。其PID为0xE1。主机发送OUT令牌后会紧接着发送一个数据包然后等待设备回复握手包。例如向USB打印机发送打印任务或者向USB音频设备发送音频流数据都使用OUT事务。SETUP令牌包是一种特殊的OUT令牌专门用于控制传输中的建立阶段。其PID为0x2D。SETUP事务总是使用DATA0数据包而普通OUT事务的数据包在DATA0和DATA1之间切换并且设备必须接受SETUP数据包只能回复ACK不能回复NAK或STALL。SETUP数据包的内容是一个固定的8字节数据结构包含了USB标准请求命令如获取描述符、设置地址等。这是USB枚举和设备控制的核心。关键区别OUT令牌用于普通的数据下发设备可以用ACK、NAK或STALL响应。SETUP令牌仅用于控制传输的建立阶段携带的是标准请求命令设备必须用ACK响应除非发生严重错误且其后的事务阶段可能包含IN或OUT有特殊的语义。实操心得调试USB设备枚举过程时一定要重点抓取和分析SETUP令牌包及其后的数据包。这里面包含了主机查询设备描述符、配置描述符、设置地址等关键信息。很多枚举失败的问题根源在于设备对某个SETUP请求的响应不正确比如描述符格式错误、请求不支持等。使用USB协议分析软件如Ellisys、Beagle等可以直观地解析这些请求内容。3.4 PING令牌包高速模式的流控探针PING令牌包是USB 2.0高速模式引入的一种特殊令牌用于流控制。它的PID是0xB4。在高速批量传输和中断传输中当主机向一个端点发送OUT令牌和数据包后如果设备回复NAK表示端点缓冲区满暂时无法接收主机会记住这个端点“忙”。在后续的微帧中主机不会盲目地继续发送数据浪费带宽而是改为发送PING令牌来探询该端点状态。设备回复ACK表示“缓冲区已空可以接收数据了”回复NAK表示“还没准备好”回复NYET这是高速模式特有的握手包表示“本次数据包已接收但缓冲区即将满下次发数据前请先PING”。为什么需要PING在没有PING机制的USB 1.1全速模式下如果设备一直回复NAK主机会在每个调度周期都尝试发送一次数据这些被NAK的数据包白白占用了总线带宽。在高速模式下总线带宽非常宝贵PING机制避免了这种带宽浪费极大地提高了总线利用率。4. 令牌包在完整事务中的角色与抓包分析理论需要结合实际波形来理解。我们通过一个具体的抓包实例来看令牌包如何嵌入到完整的事务流中。4.1 实例分析控制传输的建立阶段假设主机正在枚举一个地址为0x00的新设备枚举初期设备使用默认地址0请求获取设备描述符。主机发送 SETUP 令牌包SYNCPID:0x2D(SETUP)ADDR:0x00(默认地址)ENDP:0x00(控制端点0)CRC5:0x0A(假设值根据地址和端点计算得出)EOP主机发送 DATA0 数据包SYNCPID:0xC3(DATA0 PID)Data: 8字节的SETUP数据0x80 0x06 0x00 0x01 0x00 0x00 0x40 0x000x80: bmRequestType - 主机到设备标准请求目标为设备0x06: bRequest - GET_DESCRIPTOR0x00 0x01: wValue - 描述符类型为设备描述符(0x01)索引为00x00 0x00: wIndex - 00x40 0x00: wLength - 请求返回64字节0x0040CRC16: (根据数据计算)EOP设备回复 ACK 握手包SYNCPID:0xD2(ACK)EOP这个SETUP事务完成后主机知道设备已经收到了请求。接下来主机会发起一个IN事务同样是发给地址0端点0去“取回”设备描述符数据。4.2 逻辑分析仪与软件解析实战使用Saleae逻辑分析仪配合USB协议分析软件或者专用的USB协议分析仪如Ellisys、LeCroy可以清晰地看到这些包。在软件视图中一个SETUP事务通常被折叠成一行显示展开后能看到三个子包SETUP Token, DATA0, ACK。排查技巧如果发现枚举卡住可以按以下步骤检查令牌包层面主机发令牌了吗首先确认主机是否发出了正确的令牌包SETUP/IN/OUT。如果没看到任何令牌包问题可能出在主机控制器驱动或软件栈。令牌内容对吗检查令牌包中的PID是否正确别把0xE1看成0x69设备地址和端点号是否与预期一致。一个常见的低级错误是设备固件中配置的端点地址与主机请求的不匹配。CRC校验过了吗虽然分析软件通常会帮你计算并高亮显示CRC错误但自己也要留意。如果总线上噪声大可能导致CRC错误设备静默丢弃令牌主机侧表现为超时。设备有响应吗看到令牌包后紧接着应该看到设备的响应数据包或握手包。如果没有检查设备是否已正确复位、时钟是否稳定、D/-上拉电阻是否正确配置全速设备需在D上拉1.5k电阻到3.3V。5. 令牌包相关的常见问题与深度排查理解了令牌包的格式和作用很多USB通信问题就有了清晰的排查思路。下面是一些典型问题及其与令牌包关联的根因分析。5.1 问题一设备枚举失败卡在获取描述符阶段现象设备插入后电脑提示“无法识别的USB设备”或设备管理器出现带感叹号的未知设备。抓包发现主机发送了SETUP令牌获取描述符请求但后续没有IN事务或者IN事务中设备返回的数据包异常。排查步骤与根因检查SETUP令牌的接收确认设备端是否收到了SETUP令牌。可以通过在设备固件的控制端点0回调函数中设置断点或打印日志来验证。如果没收到可能是物理连接问题或设备SOF帧起始包同步丢失。检查SETUP数据包解析设备收到了SETUP令牌和数据包但解析bmRequestType、bRequest等字段时出错导致无法正确理解主机请求。例如设备只实现了部分标准请求对GET_DESCRIPTOR请求的wValue字段判断错误误以为请求的是不支持的描述符类型。检查ACK握手包的发送设备解析SETUP请求后必须回复ACK。如果设备固件忘记发送ACK或者发送时序不对例如在高速模式下应答太慢主机会认为事务失败可能触发复位。检查后续IN令牌的响应SETUP阶段成功后主机会发起IN令牌来读取描述符。设备需要在收到IN令牌后及时返回描述符数据包。常见问题有数据包长度错误设备描述符长度固定为18字节。如果设备返回的字节数不对或者数据包CRC错误主机会丢弃数据并可能重试多次失败后放弃枚举。NAK过多设备在准备好描述符数据前可以对IN令牌回复NAK。但主机有超时机制。如果设备因为初始化慢如等待外部EEPROM读取而持续NAK可能导致主机超时。解决方案是在设备复位后、主机首次请求前尽可能快地完成最小化初始化将描述符常驻在RAM中。5.2 问题二批量传输Bulk Transfer速度不达标或时断时续现象进行大文件传输时速度远低于USB 2.0高速模式应有的理论值约35-40 MB/s且传输过程中速度波动大甚至中断。排查步骤与根因观察OUT/IN令牌的规律使用分析仪抓取传输过程。在高速批量OUT传输中理想状态是主机连续发送OUT令牌数据包设备回复ACK。如果频繁出现设备回复NAK说明设备端的FIFO缓冲区已满来不及处理数据。这可能是设备端固件处理数据太慢或者USB控制器DMA配置不当。关注PING令牌流控在高速模式下如果设备回复了NAK主机应转为发送PING令牌探询而不是继续发送数据。检查抓包结果中在设备NAK后是否出现了PID为0xB4的PING令牌包。如果没有可能是主机端驱动或HCD主机控制器驱动未正确实现高速流控。如果PING后设备一直回复NAK或NYET则瓶颈肯定在设备端。检查令牌间隔USB是轮询总线主机在每个微帧125μs内调度事务。如果总线上有其他设备如键盘、鼠标正在进行中断或同步传输会占用部分带宽导致批量传输的令牌间隔不均匀速度波动。这是正常现象属于总线共享的特性。5.3 问题三设备在特定操作后无响应现象设备正常工作一段时间后突然对主机的任何令牌都不再响应像是“死机”了。排查步骤与根因检查最后一次成功的事务定位设备停止响应前最后一个成功完成的事务。观察其令牌包类型和端点。是否是某个SETUP请求如设置接口、设置配置后开始的检查端点Halt状态USB端点有一个“Halt”特性通过标准请求SET_FEATURE设置。当一个端点被halt后它对任何指向该端点的IN或OUT令牌都必须回复STALL握手包。如果设备固件错误地将某个常用端点如批量传输端点设置为halt状态就会导致后续所有相关事务失败。主机软件可能会尝试通过CLEAR_FEATURE请求来清除halt但如果设备固件没有正确处理这个清除请求端点将一直处于stall状态。检查设备地址变更在枚举过程中主机会通过SET_ADDRESS请求给设备分配新地址。设备必须在完成该请求的数据阶段后在状态阶段之前就切换到新地址进行响应。如果设备切换地址的时序不对或者继续监听旧地址就会丢失主机后续发给新地址的令牌包表现为失联。抓包时对比主机发出的令牌包中的地址和设备当前应监听的地址可以快速定位此问题。6. 设计实现与调试中的核心要点无论是设计一个USB设备还是编写主机端驱动对令牌包的深刻理解都能让你避开很多坑。6.1 设备端固件设计要点在设备端MCU的USB控制器固件中通常需要实现一个令牌包解析器通常由硬件完成和事务调度器。正确配置地址过滤器USB控制器硬件通常有一个地址匹配寄存器。在收到任何令牌包时硬件会自动比较令牌中的7位地址字段与自身配置的地址。只有地址匹配或地址为0且设备处于默认状态硬件才会将令牌包内容送入FIFO并产生中断通知固件处理。务必确保在主机发送SET_ADDRESS请求后及时更新这个寄存器。端点方向与类型匹配硬件需要为每个端点号配置其类型控制、中断、批量、同步和方向IN/OUT。当收到一个IN令牌指向端点X时硬件应检查端点X是否已配置为IN端点否则应忽略该令牌或回复STALL。固件初始化时必须仔细配置这些端点描述符。握手包的及时生成对于IN令牌设备固件需要在数据就绪后将数据填入指定端点的TX FIFO并让硬件自动在令牌后发送数据包和等待主机的ACK。对于OUT令牌和SETUP令牌硬件在接收完数据包后会验证CRC并通知固件。固件应在中断服务程序中根据数据有效性和自身状态决定让硬件回复ACK、NAK还是STALL。这个决策必须快速不能有大的延迟否则可能错过主机期望的响应窗口。6.2 主机端驱动与协议分析在主机侧作为开发者你可能更关心如何确保发送正确的令牌。事务调度主机控制器驱动负责将上层请求如“从设备端点1 IN读取64字节”翻译成一系列总线事务。它需要合理安排不同端点、不同传输类型的事务在微帧中的位置确保满足中断和同步传输的延迟要求同时充分利用批量传输的剩余带宽。调试时可以借助工具查看事务调度列表确认IN/OUT令牌是否按预期发出。错误处理与重试健壮的主机驱动需要对令牌包无响应、设备回复NAK/STALL等情况进行妥善处理。例如对NAK进行有限次重试对STALL进行日志记录并通知上层应用对超时无响应触发设备复位等。理解令牌包和握手包的语义是设计这些错误处理逻辑的基础。协议分析工具的使用工欲善其事必先利其器。除了昂贵的专业协议分析仪也有一些开源或低成本方案。例如在Linux下可以通过usbmon捕获内核USB流量对于低速/全速设备可以用一块支持高速采样的逻辑分析仪如Saleae配合sigrok或PulseView软件进行解码。虽然解码高级协议如海量数据可能吃力但对于分析令牌包、握手包这种小流量控制信息完全足够。关键在于正确设置触发条件例如触发PID为0x2DSETUP的包来抓取枚举过程。6.3 硬件设计与信号完整性令牌包能否被正确识别底层依赖的是差分信号D和D-的完整性。阻抗匹配与布线USB 2.0高速信号对阻抗敏感差分阻抗要求为90Ω ±10%。在PCB设计时D/D-走线应等长、等距、尽量短并远离噪声源如时钟线、电源。阻抗不连续会导致信号反射可能使得接收端将令牌包中的某个位误判进而引起CRC错误或PID错误。上拉电阻配置设备端通过在D全速/高速或D-低速上接一个1.5kΩ电阻到3.3V来向主机宣告其存在和速度能力。这个电阻的阻值精度和连接位置很重要。电阻值偏差过大可能导致主机检测不到设备或速度识别错误。电阻应尽可能靠近设备的USB接口引脚。ESD与过流保护在实际产品中USB接口是暴露的必须考虑静电放电保护。选择低电容的TVS二极管阵列避免对高速信号造成过大的边沿衰减。过流保护电路也需谨慎设计确保在短路时能快速切断电源且不影响数据线信号。理解令牌包就像是掌握了USB这场精密交响乐的指挥谱。它虽不承载实际的数据内容却严格规定了每一段旋律何时开始、由谁演奏、以及节奏的快慢。从一次简单的鼠标点击到高速的硬盘读写所有动作都始于主机发出的那一个微小的、格式固定的令牌包。当你再次面对一个“无法识别的USB设备”提示时不妨从逻辑分析仪上抓取最初的几个SETUP和IN令牌包开始分析你会发现大部分问题的答案都藏在这些看似枯燥的二进制序列之中。