3、USB协议分层架构:从物理层到应用层的分层模型、各层职责、协议栈的软件与硬件划分。
说到USB协议,很多人第一反应就是那四根线。但真正做产品的时候你会发现,USB远不止是D+和D-这么简单。我个人习惯把USB协议想象成一栋楼——从地基到装修,每一层都有自己的活要干。
这章我们就来拆开这栋楼,看看每一层到底在做什么。软件和硬件怎么分工,协议栈怎么搭起来。
3.1 分层模型总览:USB协议的五层结构
USB协议栈从上到下,我习惯分成五层:
- 应用层:你写的驱动程序、用户程序,比如U盘读写、鼠标移动。
- 设备类层:HID、Mass Storage、CDC等标准类协议。
- 协议层:事务处理、包结构、端点管理。
- 链路层:帧/微帧调度、SOF、带宽分配。
- 物理层:电气特性、信号编码、线缆连接。
你想想看,一个鼠标点击下去,数据是怎么从物理层一路爬到应用层的?这就是分层要解决的问题。
核心观点:分层不是为了好看,是为了解耦。硬件厂商只管物理层和链路层,驱动开发者只管协议层和应用层。谁都不用管对方怎么实现的。
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 一个实际的分层例子:鼠标上报坐标
说了这么多理论,我们看个实际例子。鼠标移动一下,数据怎么从物理层到应用层?
- 物理层:D+和D-上出现差分信号,NRZI编码的比特流。
- 链路层:硬件检测到SOP,开始接收包。SOF同步帧边界。
- 协议层:硬件解析出IN令牌包,设备响应DATA0包,主机发ACK。
- 设备类层:HID协议解析报告描述符,知道4字节数据里哪是X、哪是Y。
- 应用层:操作系统把坐标变化转换成鼠标指针移动。
你看,每一层只关心自己的事。物理层不知道什么是鼠标,协议层不知道什么是坐标。这就是分层的好处——各司其职,互不干扰。
3.5 协议栈实现的选择
做产品时,协议栈怎么选?我列几个常见方案:
- 裸机+寄存器操作:适合资源受限的MCU,比如8位单片机。你得自己处理中断、状态机。
- RTOS+USB驱动层:比如FreeRTOS + TinyUSB。适合有一定资源的产品。
- Linux USB Gadget/主机栈:Linux内核自带完整的USB协议栈,你只需要写设备驱动。
- 商业协议栈:比如Keil的USB库、ST的HAL库。省事,但出了问题不好调试。
避坑指南:我曾经在一个项目里用了某厂商的USB库,结果枚举时总是卡在Get Descriptor阶段。查了三天发现是库里的端点描述符长度写死了,跟芯片手册对不上。从那以后,我坚持至少把枚举流程的代码自己过一遍,不能全信库。