3、USB协议分层架构:从物理层到应用层的分层模型、各层职责、协议栈的软件与硬件划分。

说到USB协议,很多人第一反应就是那四根线。但真正做产品的时候你会发现,USB远不止是D+和D-这么简单。我个人习惯把USB协议想象成一栋楼——从地基到装修,每一层都有自己的活要干。

这章我们就来拆开这栋楼,看看每一层到底在做什么。软件和硬件怎么分工,协议栈怎么搭起来。

3.1 分层模型总览:USB协议的五层结构

USB协议栈从上到下,我习惯分成五层:

  • 应用层:你写的驱动程序、用户程序,比如U盘读写、鼠标移动。
  • 设备类层:HID、Mass Storage、CDC等标准类协议。
  • 协议层:事务处理、包结构、端点管理。
  • 链路层:帧/微帧调度、SOF、带宽分配。
  • 物理层:电气特性、信号编码、线缆连接。

你想想看,一个鼠标点击下去,数据是怎么从物理层一路爬到应用层的?这就是分层要解决的问题。

核心观点:分层不是为了好看,是为了解耦。硬件厂商只管物理层和链路层,驱动开发者只管协议层和应用层。谁都不用管对方怎么实现的。

USB协议分层架构 应用层 驱动程序 / 用户程序 / 固件主循环 设备类层 HID / MSC / CDC / Audio / Video 协议层 事务 / 包结构 / 端点 / 管道 链路层 帧/微帧 / SOF / 带宽分配 物理层 电气特性 / NRZI编码 / 线缆 软件边界 固件 + 驱动 可编程 硬件边界 SIE + PHY 不可编程 软硬件分界线

3.2 各层职责详解

3.2.1 物理层:最底层的硬功夫

物理层管的是最实在的东西——电压、电流、阻抗、线缆。USB 2.0用的是差分信号,D+和D-两根线,电压摆幅约400mV。嗯,这里要注意,高速模式下终端匹配电阻是45Ω,不是标准50Ω。我在项目中遇到过因为终端电阻选错导致眼图闭合的情况,折腾了两天才找到原因。

速率模式 信号速率 编码方式 线缆最大长度
Low-Speed (1.5 Mbps) 1.5 MHz NRZI + 位填充 3 m
Full-Speed (12 Mbps) 12 MHz NRZI + 位填充 5 m
High-Speed (480 Mbps) 480 MHz NRZI + 位填充 5 m

避坑指南:我曾经在Layout时把D+和D-走线长度差了15mil,结果高速模式完全不通。差分对等长不是玄学,是硬指标。USB 2.0 HS要求差分对内时延差不超过50ps,换算成走线长度也就7-8mm。

3.2.2 链路层:谁在什么时候说话

链路层解决的是总线仲裁问题。USB是主从架构,主机说了算。主机每1ms(全速/低速)或125μs(高速)发一个SOF包,告诉所有设备:新的一帧开始了。

这一层还负责带宽分配。全速模式下每帧1500字节,高速模式下每微帧约187.5字节。你想想看,一个鼠标占多少带宽?一个U盘占多少?链路层得把这些算清楚。

3.2.3 协议层:包和事务的规矩

协议层是我觉得最有趣的一层。USB通信的基本单位是事务(Transaction),一个事务由多个包(Packet)组成:

  • 令牌包(Token):主机发,告诉谁要干什么。比如OUT、IN、SETUP。
  • 数据包(Data):传数据用的,DATA0/DATA1交替。
  • 握手包(Handshake):ACK/NAK/STALL,告诉对方收到没。

举个例子,主机想从端点1读数据:

主机发 IN令牌包(地址=设备1,端点=1)
设备发 DATA0包(携带数据)
主机发 ACK握手包(确认收到)

就这么简单?嗯,实际还有重传、超时、错误检测。我调试时经常用逻辑分析仪抓包,看到NAK满天飞就知道设备还没准备好。

3.2.4 设备类层:标准化接口

这一层是USB协议最聪明的地方。USB定义了一套标准设备类,比如:

  • HID:键盘、鼠标、游戏手柄
  • MSC:U盘、读卡器
  • CDC:虚拟串口、网卡
  • Audio:麦克风、音箱

每个类都有标准描述符和命令集。你写一个HID设备,Windows不用装驱动就能认。这就是标准化的力量。

3.2.5 应用层:业务逻辑

应用层就是你的业务代码。比如U盘里的文件系统、鼠标的坐标上报、虚拟串口的数据收发。这一层跟USB协议本身关系不大,但你要知道数据是怎么从物理层一路传上来的。

3.3 协议栈的软件与硬件划分

做嵌入式USB开发,最头疼的问题就是:哪些事交给硬件做,哪些事自己写代码?

我一般这么划分:

功能模块 硬件实现 软件实现
NRZI编码/解码 SIE内部硬件完成 不需要软件干预
位填充/去填充 硬件自动处理 不需要软件干预
CRC校验 硬件计算和验证 软件可读取校验结果
包检测(SOP/EOP) 硬件检测 不需要软件干预
事务调度 硬件维护帧/微帧 软件配置调度参数
端点缓冲区管理 硬件FIFO 软件读写FIFO
协议状态机 部分硬件支持 软件主导
描述符管理 硬件不参与 软件完全负责
枚举流程 硬件不参与 软件完全负责

经验之谈:我建议初学者先搞清楚硬件帮你做了什么。很多MCU的USB模块自带SIE(串行接口引擎),NRZI编码、位填充、CRC这些脏活累活硬件都干完了。你只需要配置寄存器、读写FIFO、处理中断。别一上来就想着自己实现协议栈,那是PCIE时代的事。

3.4 一个实际的分层例子:鼠标上报坐标

说了这么多理论,我们看个实际例子。鼠标移动一下,数据怎么从物理层到应用层?

  1. 物理层:D+和D-上出现差分信号,NRZI编码的比特流。
  2. 链路层:硬件检测到SOP,开始接收包。SOF同步帧边界。
  3. 协议层:硬件解析出IN令牌包,设备响应DATA0包,主机发ACK。
  4. 设备类层:HID协议解析报告描述符,知道4字节数据里哪是X、哪是Y。
  5. 应用层:操作系统把坐标变化转换成鼠标指针移动。

你看,每一层只关心自己的事。物理层不知道什么是鼠标,协议层不知道什么是坐标。这就是分层的好处——各司其职,互不干扰。

3.5 协议栈实现的选择

做产品时,协议栈怎么选?我列几个常见方案:

  • 裸机+寄存器操作:适合资源受限的MCU,比如8位单片机。你得自己处理中断、状态机。
  • RTOS+USB驱动层:比如FreeRTOS + TinyUSB。适合有一定资源的产品。
  • Linux USB Gadget/主机栈:Linux内核自带完整的USB协议栈,你只需要写设备驱动。
  • 商业协议栈:比如Keil的USB库、ST的HAL库。省事,但出了问题不好调试。

避坑指南:我曾经在一个项目里用了某厂商的USB库,结果枚举时总是卡在Get Descriptor阶段。查了三天发现是库里的端点描述符长度写死了,跟芯片手册对不上。从那以后,我坚持至少把枚举流程的代码自己过一遍,不能全信库。