主机/外设角色协商协议归纳总结

做 OTG 驱动这么多年,真正让我在半夜惊醒的 bug 有一半出在角色协商上。你想想看,一根线插进来,谁当主机谁当设备?这个决策看似简单,底下的协议细节却能把人绕晕。

我习惯把 OTG 角色协商拆成两个阶段来看:

  1. 设备发现阶段 —— 通过 ID 引脚和 Session Request Protocol (SRP) 判断线缆方向,确定最初的主从关系。
  2. 动态切换阶段 —— 通过 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 永远起不来。

避坑指南:我曾经在一个量产板上遇到 5% 的返修率,最后定位到 ID 引脚滤波电容用了 X5R 而非 X7R,温漂导致电平误判。切记用示波器实测 ID 引脚的上电时序,不少于 100 个样本。

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 脉冲再发数据线脉冲。

调试技巧:排查 SRP 失败时,用 USB 分析仪抓 D+ 和 VBUS 的波形。如果只看到任一脉冲但 Host 没响应,去看看 Host 端 VBUS 检测电路的阈值和去抖时间。

3. HNP —— 主机协商协议

SRP 解决的是“谁先说话”的问题,HNP 解决的是“谁当老大”的问题。当一个 OTG 双角色设备刚开始作为 Peripheral 接入后,如果需要切换成 Host(比如想插个 U 盘),就得走 HNP。

HNP 的握手步骤我画了个简化图:

HNP 角色切换核心流程 A 设备 (Host) B 设备 (Peripheral) 1. 发起角色交换请求 A 同意切换 2. A 释放 D+/D- 总线 3. B 拉高 D+ 4. 总线重枚举 B 设备 → Host A 设备 → Peripheral 切换前 A 必须已经挂起总线

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 协议中的超时与重试逻辑错误 限制单方向切换次数,增加稳定期
深入一点:角色乒乓是 OTG 驱动中最隐蔽的 bug。我遇到过两次,一次是 Linux 内核 OTG 驱动里 HNP 的 tBST (Buss Scan Time) 和 tBIDLE (Bus Idle time) 配置反了,另一次是硬件上 D+ 的上拉电阻阻值偏大(用了 2.2kΩ 而非 1.5kΩ),导致电平上升沿超过规范。排查这种问题我通常直接读芯片的 OTG 状态寄存器,比对规范时序。

5. 实战中的驱动配置要点

讲到这里,我想强调两个在实践中容易忽略的点:

  • VBUS 的供电能力 —— OTG 设备作为 Host 时要能提供至少 100mA 电流。很多嵌入式平台用 GPIO 直接控制 VBUS 开关,外接的负载电容一大,VBUS 上升沿过慢,B 设备的 VBus 检测就会超时。
  • ID 引脚的中断处理 —— 我建议在驱动中为 ID 变化注册一个高优先级线程化 IRQ,而不是在中断上下文直接做角色切换。否则你可能遇到死锁,尤其是当切换过程中要释放 USB 主机栈资源的时候。
个人习惯:我会在驱动初始化时先假设自己是 Peripheral,然后等 ID 中断或 SRP 事件再动。这样即使硬件接反了,也不会在 probe 阶段死锁。你可以把这个策略写到头文件里作为默认行为。

好了,角色协商协议这部分,简单说就是:ID 引脚定初态,SRP 唤醒总线,HNP 动态换角色。调试时抓波形、看寄存器、卡时序,三步走完大部分问题都能浮出水面。