尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深度解析USB端点通信:四大传输类型与实战调试要点

深度解析USB端点通信:四大传输类型与实战调试要点 1. 从“四大传输类型”看懂 USB 的数据流动如果说上次聊 USB 的历史和物理架构时是把通用串行总线当成一条高速公路来理解那么这回来聊“端点通信”就得进入这条路上的每一个收费站、车道和交通规则了。上篇我们讲到 USB 的主从结构、设备地址和描述符体系但真正让 USB 跑起来的核心机制其实是它的四种传输类型控制传输、批量传输、中断传输和等时传输。很多刚接触 USB 协议栈的朋友对着datasheet看半天脑子里还是混的为什么同一个设备里要同时存在好几种端点这些端点到底怎么选为什么有些端点传数据老要“重试”有些却允许丢包这些问题本质上都是没有把“USB 是一个以主机为中心的轮询总线”这句话吃透。主机是唯一能发起事务的角色设备永远只能被动应答所以端点通信的每一种类型都是主机根据设备需要在不同场景下采取的“调度策略”。1.1 四种传输类型各自解决什么问题先讲一个生活化的类比。把 USB 主机想象成一个快递调度中心设备就是分布在各个片区的收发点。每个收发点有多个窗口——这些窗口就是“端点”Endpoint每个窗口有编号有方向有自己擅长处理的包裹类型控制传输Control Transfer相当于调度中心给每个收发点发的工作手册。包裹不大但必须“确保收到且按顺序理解”。设备刚插上时主机就是通过控制传输读取描述符、配置地址、设置配置以及在运行过程中发送各类类请求。所有 USB 设备必须至少有端点 0 支持控制传输这是一个跑不掉的规定。批量传输Bulk Transfer适合“数据量大、不能丢”的场景比如 U 盘拷贝文件、USB 网卡收发以太网帧。特点是带宽不保底谁急主机先服务谁但协议上通过 CRC 校验和重传机制保证每个字节都准确到达。中断传输Interrupt Transfer名字带“中断”但 USB 里压根没有硬件中断线。它是主机以固定周期主动去设备“轮询”有没有新数据。适合鼠标、键盘、游戏手柄这类小数据量、但要求延迟稳定的设备。等时传输Isochronous Transfer适合音视频这类对时间敏感、但偶尔丢一两帧无所谓的流数据。没有重传机制带宽预先分配好保证每个周期都能发送预定量的数据。这四种传输类型不是 USB 发明者脑洞大开而是实打实地映射了外设世界的真实需求。如果只是顺序读写大块数据批量传输就够了但如果要让一个 USB 声卡播放 48kHz 采样率的音频数据必须严格按每秒 48000 次的节奏送到 DAC这时候任何“重传”造成的延迟抖动都是灾难等时传输就成了唯一合理的选择。下表是四种传输类型的核心对比做底层驱动或固件开发的朋友建议直接存下来选型时经常用到传输类型方向数据量传输保证延迟/周期典型设备控制双向Endpoint 0小≤64字节全速≤8字节低速保证送达有重试主机按需发起所有设备枚举阶段、类请求批量单向或双向单端点大最大 512 字节高速/ 64 字节全速保证送达有重试与流控不保证空闲带宽全给它U盘、USB转串口、网卡中断单向或双向中/小全速最大 64高速最大 1024保证送达有重试固定轮询周期1~255ms全速或 1~65535µs高速鼠标、键盘、HID 自定义设备等时单向多数场景全速最大 1023 字节/帧高速最大 1024 字节/微帧不保证送达无重试固定带宽预分配USB 摄像头、声卡、音频采集1.2 端点描述符到底在描述什么刚才反复提到了“端点”那么主机是怎么知道一个设备有哪些端点、每个端点用什么传输类型、最大能传多大的包呢答案全在端点描述符里。端点描述符是配置描述符集合的一部分设备在枚举阶段通过控制传输把它们逐条返回给主机。一个标准的端点描述符只有 7 个字节关键字段包括bEndpointAddress端点号和方向打包在一个字节里。Bit 7 是方向位0 表示 OUT主机到设备1 表示 IN设备到主机Bit 3~0 是端点号。所以 0x81 表示端点 1 的 IN 方向0x01 表示端点 1 的 OUT 方向。bmAttributes这个字节的低两位决定传输类型0控制1等时2批量3中断。等时传输还有额外的同步属性和使用类型位。wMaxPacketSize端点单次事务能承载的最大字节数。全速批量传输固定 64 字节高速批量传输可以到 512 字节等时传输则要看帧/微帧的预算限制。bInterval轮询周期。中断传输用这个值告诉主机“你每隔多久来看我一次”全速设备的单位是毫秒高速设备的单位是 125 微秒。等时传输同样也要设置这个值。初学者最容易忽略的是方向与端点的组合概念。一个物理端点号可以有 IN 和 OUT 两个方向它们共享端点号但逻辑独立由bEndpointAddress的方向位区分。特别需要注意的是有些控制器实现中端点号 0 的 IN 和 OUT 是固定绑定到控制传输的而 1~15 的端点则可以根据描述符自由配置为 IN 或 OUT。2. 控制传输所有 USB 设备的第一堂必修课控制传输是整个 USB 协议里最特殊、也最重要的一种传输方式。为什么所有设备都必须支持因为设备插上主机的那一刻连地址都没有主机想查询任何信息都只能使用默认的端点 0而这个端点必须无条件支持控制传输。它就像新员工入职第一天领到的工牌和岗位说明书没有这套流程后面的工作和沟通都无法开始。2.1 控制传输的四个阶段很多教材讲控制传输时会画复杂的时序图但实际操作中只要记住一条主线建立阶段 - 数据阶段可变- 状态阶段。其中数据阶段根据请求方向和数据量可能是 OUT 方向也可能是 IN 方向也可能没有数据阶段。建立阶段由主机发送一个 SETUP 事务这个事务固定使用 8 字节的标准请求结构包含bmRequestType、bRequest、wValue、wIndex、wLength五个字段。设备收到 SETUP 后必须解析这 8 个字节然后判断主机想干什么。比如主机想知道设备描述符就会发送一个GET_DESCRIPTOR请求bmRequestType0x80设备到主机方向bRequest0x06wValue0x0100描述符类型为设备描述符索引为 0wLength0x40最多允许返回 64 字节。数据阶段是实际搬运数据的阶段。如果是GET_DESCRIPTOR数据阶段就是设备把描述符数据用 IN 事务发给主机如果是SET_CONFIGURATION数据阶段就可能是一个 OUT 事务不过这个请求本身没有数据阶段直接进入状态阶段。这里的关键点是数据阶段的第一个包必须小于等于wMaxPacketSize如果恰好等于最大值主机还会再发一个 0 长度的 IN 包来确认数据结束。2.2 状态阶段最容易出问题状态阶段用来报告整个控制传输成功还是失败。方向和数据阶段相反数据阶段是 IN 的请求状态阶段就是 OUT数据阶段是 OUT 的请求状态阶段就是 IN。设备如果处理成功就返回一个 0 长度的数据包如果设备发现请求不合法或参数错误可以在状态阶段返回 STALL 握手主机随即结束此次控制传输。我在实际的嵌入式开发中踩过一个典型的坑某些 USB 分析仪抓包时如果设备在状态阶段返回 STALL看起来像是主机发送了 RESET 或 CLEAR_FEATURE但根因设备并没有主动复位而是固件里的命令解析器在状态阶段之前就已经对未知请求返回了 STALL。排查这种问题关键在于用逻辑分析仪或 USB 分析仪确认 STALL 出现在哪个阶段——是在数据阶段还是状态阶段。2.3 标准请求速查控制传输承载了所有 USB 标准请求作为开发者至少需要背下来以下 8 个常用请求请求名bRequest 值作用GET_STATUS0x00查询设备/端点/接口的状态CLEAR_FEATURE0x01清除某个特性如端点暂停SET_FEATURE0x03设置特性如远程唤醒SET_ADDRESS0x05给设备分配唯一地址GET_DESCRIPTOR0x06读取设备/配置/字符串等描述符SET_DESCRIPTOR0x07更新描述符很少用GET_CONFIGURATION0x08查询当前配置值SET_CONFIGURATION0x09激活某个配置使设备进入工作状态如果做的是 HID 设备还有额外的GET_REPORT(0x01)、SET_REPORT(0x09) 等类特定请求。这些请求同样走端点 0只是bmRequestType的类型位不同。枚举完成后主机仍然会时不时通过端点 0 发送请求比如查询设备状态、处理远程唤醒等所以控制传输不只是枚举阶段的工作而是伴随设备整个生命周期。3. 批量传输与端点流控如何保证一个字节都不丢批量传输是 U 盘、USB 转串口、USB 打印机等设备的核心。它最大的特点是“只要带宽有剩余就往死里传”但一旦总线繁忙它可能要等很久。很多做嵌入式的人第一次调 USB 虚拟串口时都会困惑一件事为什么明明说好是“批量传输”我却要一包一包地等 ACK3.1 批量传输的事务机制一次批量传输由 IN 或 OUT 事务组成。以 OUT 事务为例主机先发一个 OUT 令牌包紧接着发数据包然后设备在收到正确数据后返回 ACK 握手包如果设备因为内部缓冲未准备好可以返回 NAK主机会在后续的调度周期里再次尝试如果设备检测到数据错误或端点 halt 了返回 STALL。IN 事务类似主机发 IN 令牌包设备如果准备好了就发数据主机校验成功后返回 ACK如果设备暂时没数据就回 NAK。这个“NAK-重试”机制是批量传输可靠性的基石。但 NAK 也带来一个隐含的副作用主机反复 NAK 会占用总线带宽。对于高速设备协议规定了 NAK 重试的比例上限NAK Limit以防止某个端点长期霸占总线。做固件时如果发现批量传输吞吐率上不去第一反应应该是查自己的端点是不是一直在 NAK而不是怀疑总线速度。3.2 Ping 协议与高速批量传输的流控全速 USB 只有 OUT 令牌包这一个机制设备没准备好就回 NAK主机等下一轮。但到了高速模式如果还按这种方式NAK 风暴会导致极大的带宽浪费和延迟增加。于是协议里引入了 PING 协议主机先发送 PING 令牌包设备如果缓冲区有空位就返回 ACK主机随后才发 OUT 数据包如果设备缓冲区满了返回 NAK 或 NYET。这样主机可以在不发送数据的情况下先试探设备是否就绪避免了无效的数据传输。这个细节在数据手册里往往只有一页但调试高速 USB 外设时极其重要。比如我遇到过一款 USB 3.0 转千兆以太网的芯片在某些 Linux 内核版本下批量读吞吐量一直上不去抓包发现主机在不停地 PING设备却一直在回 NAK。最后确认是硬件 FIFO 配置过小驱动把MaxBurstSize设置得太激进导致每个突发传输后设备缓冲区都瞬间被填满。调整端点描述符里的突发参数后吞吐量立刻恢复正常。3.3 端点 FIFO 与流控的硬件实现无论端点描述符写得多么漂亮最终数据都要落到硬件 FIFO 里。控制器的端点 FIFO 深度是固定的如果固件没有及时读取 IN 端点 FIFO 中的数据或者没有及时向 OUT 端点 FIFO 写入数据硬件会自动返回 NAK。很多开发者会困惑明明我配置对了端点数据却一包都发不出去。这时候八成是中断处理太慢FIFO 溢出或下溢了。对于全速/低速 USB一个最直接的优化方式是使用双缓冲。双缓冲的本质是让硬件在两个 FIFO 之间轮流切换CPU 在处理一个缓冲区的同时USB 控制器在填充/清空另一个缓冲区。这意味着即使 CPU 处理速度略微跟不上总线速度也能通过交错操作把有效带宽拉满。有些高端控制器甚至支持三缓冲但收益递减明显一般双缓冲就足够。4. 中断传输与等时传输低延迟和确定性延迟的差别中断传输和等时传输经常一起出现因为 HID 键盘、鼠标和音频设备都依赖它们。但两者的设计哲学完全不同中断传输保证“数据最终送达且延迟有限”但允许主机调度等时传输保证“固定时间点必有一次传输”但允许数据丢失。4.1 中断传输其实也是轮询USB 的中断传输和硬件中断没关系主机是周期性发起 IN/OUT 事务。这个周期由端点描述符的bInterval决定。全速设备的最小周期是 1ms一帧最大 255ms高速设备的最小周期是 125µs一个微帧最大可以到 4,096 个微帧。在低速设备1.5Mbps中中断传输的数据包最大只有 8 字节。对于键盘鼠标这类设备8 字节足够装了按键码、修饰键状态、滚轮增量一共也就 3 到 6 个字节。所以一个低速键盘只需要 8 字节端点即可。印象很深的是某次做一个 HID 游戏手柄明明用的全速端点、上报频率也设置到了 1ms实际测试时却发现事件延迟忽高忽低。后来抓包发现是因为我把中断 IN 端点描述符里的bInterval设置成了 10主机就真的每 10ms 才来轮询一次。这个问题卡了我快一天才定位因为当时心里默认“中断传输嘛不就是随时传嘛”根本没意识到 USB 里它本质是轮询。这个教训后来一直提醒我拿到 HID 描述符和端点描述符后先查bInterval别想当然。4.2 等时传输的带宽预分配与数据完整性问题等时传输在主机调度器中有预先分配好的带宽。全速模式下每个 1ms 帧最多可以有 1023 字节的等时数据高速模式下每个 125µs 微帧最多可以有 1024 字节。这个带宽是主机在枚举阶段根据端点描述符的wMaxPacketSize和bInterval算出来的。如果总请求带宽超过总线可用带宽主机可能会拒绝配置设备。等时传输没有 ACK 机制也没有重传数据错误通过 CRC 检测后直接丢弃。这意味着音频偶尔会有爆音视频偶尔会出现马赛克但不会造成音视频播放的卡顿和迟滞。对摄像头和声卡这类实时设备来说一个偶尔损坏的帧远比重传带来的时序中断更容易接受。做 USB Audio 设备时还要注意同步问题。常见的同步方式有三种异步、自适应和同步。异步模式下设备端有自己的时钟主机跟着设备走自适应模式下设备端时钟跟随主机的帧率同步模式下设备直接恢复主机帧率作为时钟。USB 音频的 bit-perfect 输出通常推荐异步模式这样设备的 DAC 时钟不跟随 USB 总线的抖动音质才稳定。4.3 中断传输与等时传输的对比速查项目中断传输等时传输数据完整性有 ACK 和重试保证送达无 ACK 无重试可能丢包带宽优先级高于批量低于等时最高优先级预分配固定带宽延迟表现周期轮询有少量抖动严格按帧/微帧调度延迟稳定最大包长全速64 字节1023 字节最大包长高速1024 字节1024 字节典型应用鼠标、键盘、自定义 HID 上报音频流、视频帧、实时传感器选型建议很简单如果你要传的数据“丢失一个字节就不能正常工作”用中断或批量如果数据“晚到一毫秒比丢失更严重”用等时。只是现实中很多人把 HID 和音频的需求搞混导致调试体验极差。5. 端点通信的实战调试经验讲了一堆协议理论最后落到工程实践。端点通信调不通、枚举失败、传输超时这些问题在 USB 开发中实在太常见。分享几个我实际踩过、也帮别人排查过的典型问题。5.1 枚举失败设备描述符请求超时设备插入后主机发出GET_DESCRIPTOR(Device)请求设备没有响应。常见的硬件原因有D 或 D- 的上拉电阻没接对、晶振频率偏差太大、Vbus 供电不稳。软件原因更多固件里没有在 USB 复位后正确初始化端点 0、中断处理里没有处理RESET中断、或者SETUP包解析逻辑有 bug。排查时建议用 USB 分析仪抓包先确认主机是否发起了总线复位和SETUP包。如果根本没有SETUP说明物理层有问题如果SETUP发了但设备没有响应就要查设备固件的枚举状态机。很多人喜欢一上来就改代码其实先抓包再定位会高效很多。5.2 批量传输吞吐率不达标影响批量吞吐率的因素很多端点wMaxPacketSize设置过小、NAK 率过高、控制器不支持高带宽模式、DMA 配置不对。在高速模式下想要达到理论最大值必须确保每个微帧内事务数量足够多端点描述符的wMaxPacketSize也要尽可能接近 512 字节。我调试过一个项目批量传输只有 20MB/s而理论值应该接近 40MB/s。抓包发现隔几个事务就有一个 NAK根因是 DMA 描述符环太小导致 USB 控制器在 DMA 还没来得及搬运上一包数据时FIFO 就满了。把 DMA 描述符数量翻倍后吞吐率直接提升到 35MB/s 左右。5.3 中断传输丢事件如果你的 HID 设备上报的是瞬时事件比如一个按钮按下而主机轮询周期太长设备可能在下一次轮询之前状态已经被覆盖事件就丢了。解决方法是设置更短的bInterval或者在固件里做事件排队。内核缓冲区溢出也会丢事件这往往是主机驱动的read线程没有及时消费而不是 USB 层面的问题。5.4 等时传输过载导致设备被拒绝配置描述符里如果等时端点的带宽请求太大主机在SET_CONFIGURATION阶段可能直接返回错误设备根本无法启动。解决方式是减小wMaxPacketSize或者降低采样率/分辨率给其他等时设备留出带宽。这里也想提醒一点同一台主机上挂多个高清摄像头时USB 2.0 总线的等时带宽会被迅速耗尽。不要试图在一个 USB 2.0 口上接两个 1080p30 的摄像头它们的带宽需求加起来已经快顶满整条总线了。USB 3.0 或者分开插到不同控制器是更理性的做法。6. 把端点通信放进更大的图景里到现在USB 四大传输类型、端点描述符、流控机制、枚举流程和实战调试都串了一遍。回头看USB 能统治外设总线这么多年根本原因不是速率最快而是它在设计之初就把“多样性”考虑得足够周全既有保证可靠交付的控制和批量传输又有保证时间确定性的中断和等时传输既有简单到只有 4 根线的低速键盘鼠标又有复杂到支持端到端数据流控制的高速网卡。做 USB 开发的这几年我最大的体会是USB 协议看似庞大但只要抓住“主机轮询”“端点”“传输类型”这三条主线大部分问题都能在几分钟内定位到层。遇到问题别再盲猜先抓包再对照端点描述符和传输类型排查省下的时间能让你多调十个功能。最后再分享一个小技巧调试的时候把设备的描述符完整 dump 一份打印出来包括设备、配置、接口、端点和字符串描述符对照 USB 规范逐个字段检查。这台设备“是什么、能干什么、每个端点怎么通信”一张表就全清楚了。这个习惯救过我无数次也建议你试试。
返回列表