主机/外设角色协商协议归纳总结
做 OTG 驱动这么多年,真正让我在半夜惊醒的 bug 有一半出在角色协商上。你想想看,一根线插进来,谁当主机谁当设备?这个决策看似简单,底下的协议细节却能把人绕晕。
我习惯把 OTG 角色协商拆成两个阶段来看:
- 设备发现阶段 —— 通过 ID 引脚和 Session Request Protocol (SRP) 判断线缆方向,确定最初的主从关系。
- 动态切换阶段 —— 通过 Host Negotiation Protocol (HNP) 在运行中交换角色。
这两套协议是 OTG 的核心,也是驱动加载失败的常见雷区。下面我按实际排查顺序来展开。
1. ID 引脚与初始角色判定
先聊最基础的东西。OTG 的 Micro-USB 或 Type-C 接口里有个 ID 引脚。这个引脚的电气状态决定了你的设备一上电是当 Host 还是当 Peripheral:
| ID 引脚状态 | 默认角色 | 典型场景 |
|---|---|---|
| 接地 (GND) | Host(主机) | 插了标准 A 型线或 OTG 电缆 |
| 悬空 (Hi-Z) | Peripheral(外设) | 插了标准 B 型线 |
| 浮空 + 上/下拉 | 根据 OTG 能力决定 | Type-C 的 CC 引脚逻辑 |
嗯,这里要注意:很多低端硬件把 ID 检测做到 GPIO 里,电平抖动或者 RC 时间常数没调好,就会导致角色识别出错。我记得有一次项目,ID 引脚电容漏电,结果设备总把自己当成 Peripheral,SRP 永远起不来。
2. SRP —— 会话请求协议
SRP 是 OTG 真正开谈的第一句话。当 A 设备(默认 Host)检测到总线空闲超过一段时间,而 B 设备(默认 Peripheral)想发起会话时,B 设备需要主动用 SRP 来唤醒总线。
SRP 的完整流程可以概括为:
- B 设备拉低 D+ 持续至少 T1(规范为 30~100 ms),产生数据线脉冲 (Data Line Pulse)。
- 或者拉低 VBUS 持续至少 T2(20~40 ms),产生电源线脉冲 (VBus Pulse)。
- A 设备检测到任一脉冲后,恢复 VBUS 到 5V,并开始主机枚举。
为什么要有两路脉冲?说白了就是为了兼容性。有些 Host 只监控 VBUS,有些只监控 D+,两根线都试一遍能提高成功率。我在一个平板项目里遇到过 B 设备只发了数据线脉冲,但 Host 的 VBUS 检测电路已经进入低功耗模式,根本不响应。最后只好改成先发 VBus 脉冲再发数据线脉冲。
3. HNP —— 主机协商协议
SRP 解决的是“谁先说话”的问题,HNP 解决的是“谁当老大”的问题。当一个 OTG 双角色设备刚开始作为 Peripheral 接入后,如果需要切换成 Host(比如想插个 U 盘),就得走 HNP。
HNP 的握手步骤我画了个简化图:
HNP 的关键在于总线释放的时序。A 设备必须进入挂起 (Suspend) 状态后再释放 D+ 的上拉电阻,B 设备才能成功将 D+ 拉高。如果 A 设备驱动没实现正确的挂起流程,B 设备拉 D+ 时就会发生冲突。
4. 角色协商的常见失败场景
我在实际项目中把角色协商失败归纳为三类,下面用表格对比:
| 失败类型 | 典型现象 | 根因 | 我的修复经验 |
|---|---|---|---|
| SRP 无应答 | B 设备发脉冲后 VBUS 不恢复 | Host 端 VBUS 检测 de-bounce 过长 | 改驱动定时器从 50ms 降到 25ms |
| HNP 切换失败 | B 拉高 D+ 后 A 无响应 | A 没有正确释放 D+ 上拉 | 检查 OTG 控制器的 DP/DM 上拉使能寄存器 |
| 角色乒乓 | 两个设备反复切换 Host | HNP 协议中的超时与重试逻辑错误 | 限制单方向切换次数,增加稳定期 |
5. 实战中的驱动配置要点
讲到这里,我想强调两个在实践中容易忽略的点:
- VBUS 的供电能力 —— OTG 设备作为 Host 时要能提供至少 100mA 电流。很多嵌入式平台用 GPIO 直接控制 VBUS 开关,外接的负载电容一大,VBUS 上升沿过慢,B 设备的 VBus 检测就会超时。
- ID 引脚的中断处理 —— 我建议在驱动中为 ID 变化注册一个高优先级线程化 IRQ,而不是在中断上下文直接做角色切换。否则你可能遇到死锁,尤其是当切换过程中要释放 USB 主机栈资源的时候。
好了,角色协商协议这部分,简单说就是:ID 引脚定初态,SRP 唤醒总线,HNP 动态换角色。调试时抓波形、看寄存器、卡时序,三步走完大部分问题都能浮出水面。