4. 设备状态机入门:状态定义与主机交互

各位好,欢迎来到第四章。设备状态机这词听起来挺吓人,其实就是描述 USB 设备从插上电到能被用起来,中间经历的几个“人生阶段”。我刚开始学 USB 的时候,总把一堆状态混在一起,调试时设备不工作,根本不知道它卡在哪一步。后来我把状态图贴在工位上,每次定位问题先看状态对不对——效率高多了。

4.1 设备有哪些状态?

USB 3.0 的设备状态有八个,比 USB 2.0 多了“复位(Reset)”和“功能(Function)”等细分。但你不用全背,核心就六个:连接(Attached)、供电(Powered)、默认(Default)、地址(Address)、配置(Configured)、挂起(Suspend)。下面这张图展示了它们之间的变换关系。

USB 3.0 设备状态机核心流程 Attached Powered Default (Reset) Address Configured Suspend 检测到VBUS 复位信号 主机分配地址 配置描述符 总线空闲>3ms 远程唤醒 图中实线为正常枚举流程,虚线为挂起后的唤醒路径。 每个状态转换都由主机事件触发,设备端被动响应。

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 后无法自己恢复,需要复位
避坑提醒: 我遇到过一台设备插上后主机能识别,但就是无法配置成功。抓包发现设备在 Address 状态回复了 STALL。后来查手册才知道,设备必须在收到 SET_ADDRESS 后立即用相同的地址回复主机,不能延迟——哪怕延迟一个微帧都不行。

4.3 状态机与主机交互的“暗线”

你想想看,主机根本不知道设备内部状态。它只能通过 USB 总线发送请求,然后看设备的回应。设备状态机,说白了就是设备内部的“实际状态”和主机认为的“期望状态”之间的契约。

举个例子:主机发送复位的本质是什么?它是把设备强制拉回 Default,清空所有之前的配置。但设备硬件有没有真的做这件事?我看过一个项目,复位后设备的端点寄存器保留了旧值,结果下次枚举时地址冲突——设备返回了自己旧地址的数据,主机直接报错。所以设备状态机一定要在复位信号到来时干净地初始化所有状态相关的硬件逻辑。

再比如 Address 状态。主机发送 SET_ADDRESS(7) 后,设备要立刻把内部地址寄存器改为 7,然后用地址 7 回应 ACK。我刚开始写固件时,以为等主机发下一个 再更新地址也行,结果抓分析仪一看,设备回复了 NAK——主机没收到 ACK 就会重试三次后放弃,枚举失败。设备状态机的“状态提交”是很严格的,主机不接收延迟反馈。

小经验: 如果你的设备在枚举时总是“配置成功但数据传不了”,十有八九是 Configured 状态下的端点配置没生效。可以试试在配置完成后,用主机工具发一个清空端点(CLEAR_FEATURE)看设备是否正常响应。

4.4 状态机为什么要有“挂起”这个状态?

USB 3.0 的挂起状态和 2.0 不太一样。2.0 靠 D+ 下拉电阻判断挂起,3.0 靠发送 LFPS 的暂停序列。设备进入 Suspend 后,所有非必要时钟必须关掉,只保留远程唤醒检测电路。

我记得有一次做低功耗项目,设备进入 Suspend 后电流只有 200μA,我觉得完美了。结果主机复位时,设备因为时钟恢复太慢,错过了复位序列——直接卡死在 Powered 状态,机主以为插坏了。后来我在固件里加了个“快速时钟锁定”阶段,保证 Suspend 唤醒后 1ms 内能锁上 PLL。

4.5 状态机实际调试怎么用?

当你手边没有协议分析仪时,可以通过观察设备状态来定位问题。

  1. 看电源指示灯: 上电后灯亮但主机没反应,可能是 Attached->Powered 没问题,但后续状态卡住了。
  2. 看主机的设备管理器: 如果显示“未知设备(设备描述符请求失败)”,那设备大概率停在 Default 或 Address 之前。
  3. 看固件调试串口: 在我参与的项目里,我都会在状态机每个入口打一个日志,方便跟踪。
  4. 看示波器测量 DP/DM(或 SSTX/SSRX): 复位阶段的 LFPS 应该是 1ms 左右的高低脉冲,如果没有或者形状不对,就是 PHY 没初始化好。
一句话总结: USB 设备状态机不是课本上的虚概念,它就是枚举过程的“骨架”。设备从 Attached 走到 Configured,每一步都是主机和设备握手的结果。只要状态对了,数据就能通;状态乱了,一切白搭。