### OTG 概念详解:协议、原理与角色切换

2. OTG 概念详解

讲 OTG 之前,我们得先搞清楚一个问题:为什么要有 OTG?

早年 USB 的世界很简单:电脑永远是 Host,键盘鼠标永远是 Device。可后来手机、平板这些便携设备出来了,用户想把 U 盘插手机上直接读文件,怎么办?手机平时是 Device,可这时候它得临时当一下 Host。OTG 就是干这个的——让一个 USB 口在 Host 和 Device 之间来回切换。

说白了,OTG 就是让一个 USB 口能当 Host 也能当 Device。但这个切换不是随便喊一句就能完成的,底层有一套严格的协议在撑着。

2.1 OTG 协议规范

OTG 规范最早由 USB-IF 在 2001 年发布,后来经历了几个版本迭代。目前主流设备基本都支持 OTG 2.0 和补充规范 ECN(工程变更通知)。核心就三个关键词:HNP、SRP、ADP。

我个人习惯把这三者称为 OTG 的“三板斧”——

  • HNP(Host Negotiation Protocol):主机协商协议,负责角色互换。
  • SRP(Session Request Protocol):会话请求协议,负责唤醒总线。
  • ADP(Attach Detection Protocol):连接检测协议,负责识别对端类型。
提示: 很多工程师只关注 HNP,觉得只要能把角色翻过来就完事了。但在实际项目中,SRP 和 ADP 才是坑最多的两个点,尤其是 ADP,芯片厂商的实现五花八门。

2.2 HNP / SRP / ADP 原理

HNP:主机协商协议

HNP 的流程,我画个图你就明白了。

默认情况下,OTG 设备插上后,谁的主机能力更强谁就当 Host。但 HNP 允许双方在运行中动态交换角色。举个例子:你的手机正在给鼠标供电(手机是 Host),这时候你想把手机切换成 Device 去连电脑,HNP 就派上用场了。

HNP 的过程可以分为三步:

  1. 初始状态: A 设备(默认 Host)供电,B 设备(默认 Device)等待。
  2. 请求切换: A 设备拉低 D+ 线,通知 B 设备“我想做 Device”。
  3. 角色翻转: B 设备检测到 D+ 拉低后,拉起 D+ 并释放 VBus 控制权,B 成为新 Host。
关键点: HNP 必须在 VBus 持续供电的前提下进行。如果 VBus 掉了,一切归零。

我记得最早调 HNP 时被坑过一次——代码里 HNP 请求发出去后,对方没反应。折腾了两天才发现,是 D+ 拉低的时间窗口没满足规范要求的 5ms 最小值。芯片厂商的参考代码给了个 1ms 的延时,但实际测试发现有些设备反应慢,必须拉到 5ms 以上。

SRP:会话请求协议

SRP 解决的是省电问题。当总线上长时间没有活动时,Host 可以关闭 VBus 进入挂起状态。这时候如果 Device 想干活——比方说鼠标动了一下——就得用 SRP 把 Host 叫醒。

SRP 的机制分两种:

  • 数据线脉冲(Data-line Pulsing): Device 在 D+ 或 D- 上发送一个脉冲,告诉 Host“我有事”。
  • VBus 脉冲(VBus Pulsing): Device 在 VBus 上拉一个短暂的高电平,Host 检测到后恢复供电。

我测过很多客户的设备,发现一个问题:很多嵌入式工程师只实现了数据线脉冲,忽略了 VBus 脉冲。结果就是——某些 Host 只认 VBus 脉冲,你的设备永远叫不醒它。

避坑: SRP 的实现必须同时支持两种脉冲方式。不要偷懒只做一种,否则兼容性会让你崩溃。我曾经在一个行车记录仪项目上吃过这个亏,量产了 5000 台才发现有 30% 的车机叫不醒,最后全部返工。

ADP:连接检测协议

ADP 是后来补充的协议,主要为了解决一个问题:如何在不耗尽电池的情况下知道对端是什么东西?

传统的检测方式需要 Host 一直提供 VBus 电流,这在电池供电的场景下是灾难。ADP 的思路是:双方都只发一个极短的脉冲(微秒级),通过脉冲的反射情况判断对端是 Host、Device 还是空载。

ADP 有三种状态:

ADP 状态 行为 典型场景
ADP_PROBE 发送探测脉冲 设备刚插入,想知道对方是什么
ADP_SENSE 监听对端脉冲 设备等待对方响应
ADP_PRB 响应脉冲 已确定角色,保持连接

个人觉得 ADP 比纯粹检测 VBus 电平要优雅得多。VBus 电平检测只能判断“有没有电”,但 ADP 能判断“对端是什么类型的电”。举个例子:手机上插了一个充电器,VBus 有 5V,但如果你以为它是 Host 就去发起 HNP,那就错了——充电器根本不会响应 HNP。ADP 可以提前识别这是充电器,避免无谓的握手。

2.3 角色切换(Host / Device / OTG)

OTG 设备有且仅有三种角色:

  • Host: 提供 VBus,管理总线枚举和数据传输。
  • Device: 接收 VBus,响应 Host 的请求。
  • OTG: 两者皆可,通过 ID 引脚和协议协商确定当前角色。

这里有个容易混淆的地方:普通设备要么是 Host 要么是 Device,但 OTG 设备是“我全都要”。OTG 设备必须额外提供一个 ID 引脚。Micro-USB 的第五根线就是 ID 引脚,它用来识别插头的类型:

  • ID 引脚接地(GND)→ 该设备默认为 Host。
  • ID 引脚悬空(Float)→ 该设备默认为 Device。

但 ID 引脚只是初始值,真正的角色切换靠 HNP。也就是说,ID 引脚告诉你“我是什么”,HNP 告诉你“我现在想变成什么”。

核心公式: 角色 = ID 引脚初始值 + HNP 协商结果

我做过的项目里,最常见的角色切换拓扑就两种:

拓扑 初始 Host 初始 Device 切换后 Host
手机 ↔ U 盘 手机(ID 接地) U 盘(ID 悬空) 不切换,手机一直是 Host
手机 ↔ 电脑 电脑(ID 接地) 手机(ID 悬空) 可通过 HNP 让手机变成 Host

嗯,这里要注意:很多开发板的 USB-OTG 驱动默认只实现了 ID 引脚检测,没实现 HNP。也就是说,插什么头就固定当什么角色,不能动态切换。这在某些场景下足够了,但如果你要做的是像 USB-C 那种纯软件协商的解决方案,HNP 是绕不过去的。

为了帮你理清整个 OTG 系统的运作逻辑,我画了一张图——从系统初始化到 HNP 角色切换的全流程:

OTG 角色切换全流程 系统上电 ID 引脚检测 ID=GND → Host ID=Float → Device 提供 VBus 枚举 Device / 管理传输 接收 VBus 等待 Host 请求 HNP 请求(可选) 拉低 D+ → 对方接管 VBus 角色切换完成 ID 引脚仅决定初始角色

从这张图能看到,整个 OTG 的生命周期其实很简单:上电 → 读 ID → 确定初始角色 → 工作 → 需要切换时执行 HNP → 新角色继续工作。ADP 则是在 ID 检测之前悄悄完成的,用来判断对端是否值得唤醒。

最后补充一句:不要小看角色切换的时序。USB 2.0 规范要求角色切换必须在 100ms 内完成,否则总线会超时。我见过不少方案,软件协商是走通了,但切换时间花了 300ms,结果电脑端直接报“USB 设备无法识别”。所以如果你在做 OTG 嵌入式开发,一定在逻辑分析仪上卡一下 HNP 的时序,D+ 拉低到角色翻转完成,必须在 100ms 以内。