4. 设备状态机入门:状态定义与主机交互
各位好,欢迎来到第四章。设备状态机这词听起来挺吓人,其实就是描述 USB 设备从插上电到能被用起来,中间经历的几个“人生阶段”。我刚开始学 USB 的时候,总把一堆状态混在一起,调试时设备不工作,根本不知道它卡在哪一步。后来我把状态图贴在工位上,每次定位问题先看状态对不对——效率高多了。
4.1 设备有哪些状态?
USB 3.0 的设备状态有八个,比 USB 2.0 多了“复位(Reset)”和“功能(Function)”等细分。但你不用全背,核心就六个:连接(Attached)、供电(Powered)、默认(Default)、地址(Address)、配置(Configured)、挂起(Suspend)。下面这张图展示了它们之间的变换关系。
4.2 每个状态到底在干嘛?
我把每个状态的关键点整理成了一张表,你调试时可以直接对照。
| 状态 | 触发事件 | 设备行为 | 坑点(我实际遇到过的) |
|---|---|---|---|
| Attached | 物理插入 | 检测到 VBUS 和 D+(或 Rx) | 连接器接触不良时,设备反复进入 Attached 再掉电 |
| Powered | VBUS 稳定 | 内部上电、初始化 PHY | 有些劣质线缆 VBUS 压降太大,设备根本达不到 Powered 稳态 |
| Default (Reset) | 主机发送复位信号 | 清空地址、设置默认端点 0 | 我曾经有个 FPGA 工程,复位时序没配好,设备一直收不到有效的 LFPS |
| Address | 主机发送 SET_ADDRESS | 设备接收地址并状态提交 | 注意:USB 3.0 要求设备先应答再更新地址,顺序搞反就死锁 |
| Configured | 主机发送 SET_CONFIGURATION | 激活所有端点,准备好数据收发 | 配置描述符如果报错长度不对,设备会一直留在 Address 状态 |
| Suspend | 总线空闲超过 3ms | 进入低功耗模式,可以远程唤醒 | 很多 MCU 的 USB 外设在 Suspend 后无法自己恢复,需要复位 |
4.3 状态机与主机交互的“暗线”
你想想看,主机根本不知道设备内部状态。它只能通过 USB 总线发送请求,然后看设备的回应。设备状态机,说白了就是设备内部的“实际状态”和主机认为的“期望状态”之间的契约。
举个例子:主机发送复位的本质是什么?它是把设备强制拉回 Default,清空所有之前的配置。但设备硬件有没有真的做这件事?我看过一个项目,复位后设备的端点寄存器保留了旧值,结果下次枚举时地址冲突——设备返回了自己旧地址的数据,主机直接报错。所以设备状态机一定要在复位信号到来时干净地初始化所有状态相关的硬件逻辑。
再比如 Address 状态。主机发送 SET_ADDRESS(7) 后,设备要立刻把内部地址寄存器改为 7,然后用地址 7 回应 ACK。我刚开始写固件时,以为等主机发下一个 再更新地址也行,结果抓分析仪一看,设备回复了 NAK——主机没收到 ACK 就会重试三次后放弃,枚举失败。设备状态机的“状态提交”是很严格的,主机不接收延迟反馈。
4.4 状态机为什么要有“挂起”这个状态?
USB 3.0 的挂起状态和 2.0 不太一样。2.0 靠 D+ 下拉电阻判断挂起,3.0 靠发送 LFPS 的暂停序列。设备进入 Suspend 后,所有非必要时钟必须关掉,只保留远程唤醒检测电路。
我记得有一次做低功耗项目,设备进入 Suspend 后电流只有 200μA,我觉得完美了。结果主机复位时,设备因为时钟恢复太慢,错过了复位序列——直接卡死在 Powered 状态,机主以为插坏了。后来我在固件里加了个“快速时钟锁定”阶段,保证 Suspend 唤醒后 1ms 内能锁上 PLL。
4.5 状态机实际调试怎么用?
当你手边没有协议分析仪时,可以通过观察设备状态来定位问题。
- 看电源指示灯: 上电后灯亮但主机没反应,可能是 Attached->Powered 没问题,但后续状态卡住了。
- 看主机的设备管理器: 如果显示“未知设备(设备描述符请求失败)”,那设备大概率停在 Default 或 Address 之前。
- 看固件调试串口: 在我参与的项目里,我都会在状态机每个入口打一个日志,方便跟踪。
- 看示波器测量 DP/DM(或 SSTX/SSRX): 复位阶段的 LFPS 应该是 1ms 左右的高低脉冲,如果没有或者形状不对,就是 PHY 没初始化好。