
聊USB协议绕不开一个词端点Endpoint。上次我们把USB从1.0到4.0的演进路线和总线架构整体过了一遍这一篇我们继续往下钻专门聊端点通信——数据在USB总线上到底是怎么按“包”流转的主机怎么找到一个设备上的某个端点四种传输类型各自解决什么问题以及实际调试时怎么抓包、怎么看描述符、怎么对付各种驱动和线材的“幺蛾子”。这篇文章适合嵌入式开发者、驱动工程师也适合刚接触USB协议想系统搞明白的硬件爱好者。读完这套内容再回去看USB抓包数据心里基本就有谱了。1. 从架构图里走出来的数据通道端点是什么1.1 把端点想象成USB设备里的“邮局窗口”端点并不是设备上的某个物理引脚而是一个逻辑上的收发缓冲区。USB主机和设备之间的通信不是一根管子直接插到底而是按端点拆成多条独立的逻辑通道。每个端点都可以看成邮局里的一个窗口窗口号就是端点号方向决定了窗口是“收信”还是“发信”业务类型则对应了你寄的是平邮、挂号还是特快。主机往端点0发数据和往端点1发数据走的不是同一条通道优先级、可靠性和带宽保障也可能完全不同。一个USB设备可以拥有最多16个端点每个方向各算一份再加上特殊端点0功能复杂的设备端点数量还不少。举个例子一个USB摄像头图像数据传输用一个等时端点控制参数调整用端点0如果还带麦克风音频流又要再配等时端点而一个键盘鼠标一体设备键盘和鼠标往往各自占用一个中断端点。把端点想象成窗口窗口后面是谁、做什么由接口描述符来定义主机在传输数据时只认端点地址不关心里面具体是哪颗芯片在做处理。这也是理解USB协议最关键的思维转变USB不是一个“读写寄存器”的简单总线而是一个“面向端点”的包传输网络。驱动程序做IO操作时本质上就是往某个端点扔一批数据或者从某个端点收一批数据至于设备内部怎么把数据拆分、缓存、转发那是设备固件和芯片的事。把这个模型装进脑子后面看任何USB log都不会再晕。1.2 端点的属性表地址、方向、传输类型一个端点离不开四个关键属性端点地址、传输方向、传输类型、最大包大小。端点地址用一个字节表示bit7是方向位1表示IN设备到主机0表示OUT主机到设备低4位是端点号范围0到15。比如0x81就是端点1的IN方向0x01就是端点1的OUT方向。很多初学者看到0x81和0x01会以为是“两个不同端点”实际上它们是同一个端点号的两个方向通道底层对应设备里不同的FIFO或缓存区。传输类型决定可靠性、延迟和带宽占用。USB 2.0定义了控制、批量、中断、等时四种传输类型USB 3.x保留了这些类型但做了一些参数扩展。最大包大小则与速度有关全速下批量端点最大64字节高速下512字节超速下能到1024字节等时端点在高速下最大1024字节超速下可以更大。端点描述符里的wMaxPacketSize字段就是干这个的它决定了单个数据包最多能塞多少数据超过部分必须拆包。这里必须单独提端点0它是所有USB设备的“默认控制通道”方向固定但传输类型只能是控制地址是0x00OUT和0x80IN。设备一上电主机还没给它分配地址之前只能通过端点0来枚举。端点0的最大包大小不是描述符里的通用值而是设备描述符第7字节bMaxPacketSize0常见取值是8、16、32、64低速设备经常是8字节全速设备常用64字节。很多枚举失败的问题恰恰是设备说好的64字节实际却回了超过8字节的数据导致主机状态机崩掉。2. 四种传输类型谁在什么场景下用哪种2.1 控制传输所有设备都躲不开的“握手环节”控制传输是USB协议里最基础、也最绕的一种传输。它专门用来让主机“认识”设备并给设备下发配置命令。所有设备默认都支持控制传输因为枚举过程就是通过端点0上的控制传输完成的。控制传输的特点是可靠、低速、低带宽但它是双向的一个完整的控制传输包含SETUP阶段、DATA阶段可选和STATUS阶段三个阶段每个阶段都由令牌包、数据包、握手包组成。SETUP阶段里主机先发一个SETUP令牌然后紧跟着一个DATA0数据包这个8字节的标准请求包长这样bmRequestType表示请求方向、类型和接收者bRequest是具体的请求码wValue、wIndex和wLength是请求参数。拿最常见的GET_DESCRIPTOR请求来说wValue高字节代表描述符类型低字节是描述符索引wIndex一般是0或接口号wLength是期望接收的字节数。设备收到这个请求后在DATA阶段把对应描述符返回给主机最后STATUS阶段用于确认整个传输完成还是出错。控制传输为什么分这么多阶段因为USB是半双工共享总线必须有严格的时序来避免冲突。主机发完SETUP后如果设备回应了ACK说明设备收到了请求如果设备无法处理会回STALL比如请求了一个不支持的描述符。STATUS阶段还有方向反转的逻辑如果是“主机获取数据”的控制读传输STATUS阶段由设备发出IN数据包再由主机回ACK如果是“主机下发数据”的控制写传输STATUS阶段由设备发一个零长度IN包表示处理完毕。这些细节在抓包时看得特别清楚也是判断设备是否合规的切入点。2.2 批量传输数据量大、不赶时间的搬运工批量传输Bulk是为大块数据传输设计的。U盘读写、USB转串口的数据流向、打印机的打印内容底层走的基本都是批量传输。批量传输的特点是保证数据不丢失但“什么时候轮到它”不保证这有点像一个物流仓库里的普通货车货一定要送到路上跑多快看路况路上堵了就先等一等。批量传输使用错误检测和自动重传主机和设备通过数据包里的CRC和握手包来确认是否收到。收方如果数据正确回ACK表示确认如果缓冲区满了回NAK表示“先别发”如果端点出错或不再支持回STALL表示“这条路堵死”。NAK机制是批量传输的流量阀门设备处理不过来时就让主机不停重试直到设备有空闲缓冲区。抓包时如果看到大量连续的IN令牌加NAK基本可以判断设备端处理速度跟不上主机。批量传输的包大小和速度等级强相关全速下最大64字节高速下最大512字节超速下是1024字节。这里有个实用经验U盘写入慢很多时候不是闪存颗粒的问题而是批量端点频繁NAK或者主机驱动做了过多重试导致有效带宽被协议开销吃掉。调试时用USB抓包工具看NAK次数比看文件传输速度直观得多。2.3 中断传输与等时传输一个求稳一个准时中断传输的名字很有迷惑性它并不是设备主动向主机“发中断”而是主机在固定的时间间隔里去主动轮询设备。键盘、鼠标、游戏手柄这类HID设备数据量不大但要求延迟可控所以用中断传输。主机根据端点描述符里的bInterval字段每隔一段时间就发一个IN令牌设备有数据就回数据包没数据就回NAK。全速中断端点的轮询间隔可以设成1到255毫秒高速下还可以按125微秒的微帧为单位来算。中断传输有错误重传机制传输可靠性高但带宽上限比等时传输低。这就像一个“定时定点取件”的快递员每到一个窗口就问一句“有信吗”取到就送取不到下次再来可靠是可靠但频率受路线限制。等时传输则是USB里最“叛逆”的一种它保证带宽和时间点但允许丢数据。USB摄像头、音频麦克风、扬声器这类实时音视频设备必须用等时传输。想象一个现场直播的主播如果画面有一帧数据损坏了为了等重传而卡住画面反而比直接丢掉这一帧更糟糕。等时传输没有重传、没有ACK/NAK握手主机按照预留好的带宽在每个帧/微帧里发送或接收固定数量的字节错就错了画面糊一帧就糊一帧但时间轴绝不能断。一个常见误区是认为等时传输不校验完整性。其实USB协议里的等时传输照样带CRC校验只是不重传而已。抓到等时数据包时如果CRC错误分析仪会标出来应用程序收到的数据可能是坏的。所以很多专业音视频设备会在应用层再加一道校验和序号机制把错误帧丢到最前面。3. 端点通信的完整流程从枚举到一次读写3.1 枚举过程主机如何认识一个USB设备把U盘插进电脑那一刻主机并不是立即就能读写文件它要先做一套被称为“枚举”的流程。枚举的本质是主机通过端点0上的控制传输对设备完成“你是谁、你要多少电、你有哪些接口和端点、我该怎么配置你”的完整问询。没有这套流程设备连地址都没有更谈不上后续通信。第一步是设备插入检测。USB全速/高速设备在D线上接1.5kΩ上拉电阻低速设备在D-线上接上拉电阻主机检测到某根数据线被拉高就知道有设备接进来了。随后主机向设备发送复位信号设备检测到总线复位后把地址重置为0做好接收控制请求的准备。第二步是地址分配。主机通过端点0发一个SET_ADDRESS请求设备收到后记住这个新地址一般是1到127从此以后主机就只往新地址发包。接下来主机会重新读取设备描述符、配置描述符集合和字符串描述符最后根据配置信息发送SET_CONFIGURATION请求把设备切换到“已配置”状态。到这里枚举才算完成操作系统开始加载对应驱动设备才算真正“工作”起来。这里有个很经典的坑为什么主机第一次GET_DESCRIPTOR时只读前8字节因为设备描述符中的bMaxPacketSize0位于第8字节主机此时还不知道设备端点0最大包大小所以只敢先读8字节拿到这个字段后再用正确的包大小重新读完整描述符。有些新手写固件时把设备描述符长度填错或者返回数据超出当前包大小就会出现“枚举到一半设备消失”的灵异事件抓包一看全是STALL或超时。3.2 描述符设备的“身份证”与“说明书”描述符是USB协议最核心的静态信息结构。它们按层级组织设备描述符是“身份证”说明设备类型、厂商ID、产品ID、USB版本、端点0最大包大小配置描述符是“套餐说明”说明这个设备有几种配置、消耗多少电流、包含多少个接口接口描述符是“功能说明”说明某个接口是HID、CDC还是Mass Storage以及这个接口下有哪些端点端点描述符则是“通道说明”具体描述每个端点的地址、方向、传输类型、最大包大小和轮询间隔。字符串描述符提供可读的厂商名、产品名和序列号不是必须的但建议加否则操作系统里显示“未知设备”会让人头大。HID设备还有HID描述符用来描述报表结构。调试时我用Windows的USBView或Linux的lsusb -v看描述符树基本一眼就能判断设备固件有没有写对。描述符之间不是孤立的接口描述符必须挂在某个配置描述符下面端点描述符又必须挂在接口描述符下面。主机读取配置描述符集合时实际上是把配置、接口、端点和类特殊描述符全部拼在一起形成一个连续的缓冲区。一个典型的配置描述符集合大概是配置描述符9字节 接口描述符9字节 HID描述符9字节 端点描述符7字节 端点描述符7字节最早一批USB学习者在手写描述符时最常犯的错误就是长度字段和层级顺序对不上导致主机解析到一半直接报设备描述符请求失败。3.3 一次实际读写的现场还原只看概念容易飘我们把一次具体的数据传输拆开看。假设一个USB鼠标设备中断传输端点1 IN轮询间隔8毫秒最大包大小4字节。主机每8毫秒发一个IN令牌包包里带有目标地址和端点号。如果鼠标这时候有位移或按键数据就把4字节数据放在DATA包返回主机收到后回ACK如果鼠标没动设备直接回NAK主机什么都不用做等下一个轮询周期。再看U盘写一个4KB文件。主机驱动会把4KB数据拆成若干个512字节的批量数据包逐个发往批量OUT端点。每一个数据包在线上包含OUT令牌、数据包、ACK/NAK/STALL握手。由于USB协议用DATA0/DATA1交替来避免丢包场景下的重传重复发送方会在每个成功ACK之后切换数据包PID。抓包时会看到DATA0、DATA1、DATA0这样交替出现如果第一次发DATA0被NAK重发还是DATA0直到收到ACK才切换。这个机制保证了接收方不会把同一个包当成两个不同的包处理是USB可靠传输的基石。做上位机联调时我经常让应用一次性下发很大的缓冲区然后抓包看底层到底被拆成多少个DATA包每个包间隔多大。这能很直观地看到批量带宽利用率也能反过来验证驱动是不是把请求拆分成了合理的大小而不是一次性发一个超大块导致设备缓冲区溢出。把这些现场数据理解透了再去优化性能或者定位奇怪的数据错乱问题心里就有底了。4. 实战抓包、驱动、带宽分配与排错4.1 USB抓包工具与分析方法学习USB协议或者排查通信问题只看代码和文档是不够的一定要学会抓包。软件方案首推Wireshark加USB监控驱动Windows下可以用USBPcap或微软的usbmon老版本需要装WinUSB/DDKLinux下直接挂载usbmon内核模块抓出来的pcapng文件用Wireshark打开过滤条件可以写成usb.bmRequestType、usb.endpoint_number、usb.transfer_type等。如果你用的是Windows 10以上系统USBPcap虽然“年久失修”但多数平台还能用在不支持USBPcap的环境我会用一个叫Zadig的工具把设备的USB驱动切换成WinUSB这样Wireshark也能抓到该设备的URB。软件抓包的优势是成本低、方便缺点是只能看到主机侧发出的请求和URB看不到总线上的电气时序。要真正分析协议完整状态比如高速握手、电气信号质量、链路异常复位还是得上硬件协议分析仪。市面上的USB分析仪不便宜但对做USB协议栈、USB控制器验证的团队来说是刚需。个人学习阶段可以先用Wireshark看URB层面理解端点通信流程完全够用。抓包得出经验是遇到枚举失败优先用Wireshark捕获并过滤找到SET_ADDRESS前后的控制传输遇到“设备能识别但读写失败”就过滤目标设备的端点号看返回的是NAK还是STALL同时关注是否有CRC错误遇到性能问题就看批量传输的NAK数量和平均事务间隔。把这三个方向固定下来很多问题不用翻代码就能锁定大概范围。4.2 USB转串口设备与驱动FT232R/FT231X那些事USB转串口是USB协议在嵌入式领域最典型的应用场景之一热搜里天天有人问FT232R、FT231X的驱动就是因为这类芯片用得实在太多。简单说USB转串口芯片在主机侧表现为一个标准的串口设备在设备侧则通过UART接口连接MCU或传感器。主机驱动把应用程序发来的串口数据包封装成USB批量传输送到芯片的批量端点芯片再把数据解析出来按设定的波特率从UART引脚发出去接收方向正好反过来。FTDI的FT232R和FT231X外观不同但核心功能一致都是USB转UART桥接芯片系统驱动基本兼容。FT232R是老牌经典封装相对大广泛见于各种USB转TTL模块FT231X封装更小、功耗更低新款开发板用得更多。在Windows下驱动装好后设备管理器会出现一个“USB Serial Port (COMx)”如果没出现先检查是不是用了劣质Type-C线——不少“只能充电不能传数据”的线D/D-根本没连通芯片再正常也白搭。用USB转串口时最常踩的坑是乱码。一提到乱码很多人第一反应是波特率但USB转串口方案的乱码往往还有两个容易被忽略的原因一是芯片的FIFO缓冲和驱动流控没匹配高频大数据量传输时出现丢字节二是用了带FTDI“山寨芯片”的模块驱动识别到假ID后会直接禁止枚举或者把数据全填成0x00。我个人的经验是不要贪便宜买来路不明的模块尽量选印有“FT”标志的正品或国产替代芯片比如CH340、CP2102这些芯片虽然不如FTDI好用但胜在驱动稳定、没有ID校验问题。4.3 端点的带宽分配与常见错误排查USB总线上所有传输都是共享带宽的所以主机在配置阶段要给每个等时和中断端点预留带宽。以USB 2.0全速为例一帧是1毫秒每帧最多传输1500字节左右的实际数据其中等时端点最早预留中断端点其次批量传输只能使用剩下没有预留的带宽。这也是为什么一个USB总线上挂很多摄像头和鼠标后U盘速度会明显下降——带宽被前者占光了批量传输只能在空档里挤。排查USB问题有一套固定顺序。第一检查供电USB 2.0标准端口最大500mAUSB 3.0是900mA很多移动硬盘带不动就是因为供电不足导致设备反复枚举。第二检查线材和连接器插线板似的HUB、超过3米的延长线都可能导致信号质量恶化表现为传输速度突然掉到12Mbps甚至断连。第三查描述符用USBView或lsusb -v对照规范看配置看看端点最大包大小是否合法、bInterval是否合理、接口类是否匹配驱动。第四抓包定位协议错误看有没有设备发出STALL主机请求是否有超时数据包CRC错误率是否过高。还有一个很多人忽略的坑高速设备插入USB 2.0端口时会先做高速握手Chirp J/K序列如果线缆质量太差或者上拉电阻不标准握手不成功设备会降级到全速模式。这时候设备依然能用但速度从480Mbps掉到12Mbps表现就是“明明换了高速U盘却慢得像十年前”。遇到这个问题先换一根短线、换一个直连端口不要急着怀疑U盘或主板。4.4 写在最后的调试经验最后再分享几条我做USB开发多年的实际体会。第一USB枚举失败永远是第一优先级排查项因为枚举不通过后面所有传输都不会发生而大部分枚举失败都是描述符长度、端点地址、包大小这几个字段写错导致的抓包一眼就能看出来。第二NAK和STALL是两个完全不同的问题NAK是“我没准备好请重试”STALL是“这个请求我不支持请别再问”排错思路完全不同。第三全速/高速模式下因为微帧和帧的结构不一样中断端点bInterval的单位也不一样千万不要直接照抄网上的范例要对照USB 2.0规范的表来换算。USB协议内容很多但真正最核心的其实是“端点”这个抽象模型。把端点想清楚控制、批量、中断、等时四种传输就是四种不同的窗口业务逻辑后面的枚举、驱动、抓包都是围绕它展开的。你现在回去看任何一份USB抓包文件应该已经能区分出SETUP、DATA、握手也能明白为什么某些端点NAK这么多。希望这篇内容能帮你把USB这条“通用串行总线”彻底跑通少踩几个当年我踩过的坑。