2. USB OTG 规范:OTG 概念、双角色设备、HNP 与 SRP 协议详解
说到 USB OTG,我印象特别深。早年做嵌入式设备时,总被客户追着问:“为什么我的手机能插 U 盘,你们这块开发板就不行?” 说白了,传统 USB 协议里,主机就是主机,设备就是设备,角色定死了。但 OTG(On-The-Go)打破了这堵墙,让一个端口既能当 Host 又能当 Device,而且还能动态切换。
2.1 什么是 OTG?
OTG 是 USB 2.0 规范的补充,正式名称叫 USB On-The-Go Supplement。它的核心价值在于:双角色设备(Dual-Role Device,DRD)。一个 OTG 设备,插上 U 盘就是主机,连到电脑上就是外设。你想想看,这给消费电子带来了多大的灵活性。
OTG 的关键标识:OTG 设备使用特殊的 Mini-AB 或 Micro-AB 插座,并通过 ID 引脚(第 4 脚)来识别初始角色。ID 脚接地表示 A 设备(初始主机),ID 脚悬空表示 B 设备(初始外设)。
我在项目中遇到过因为 ID 引脚电平没处理好导致角色错乱的故障,排查了一天。大家设计时一定要留意 ID 引脚的上拉/下拉配置。
2.2 双角色设备(DRD)的硬件基础
一个 DRD 设备必须能同时支持 Host 和 Device 两套协议栈,并且能根据物理连接状态进行切换。它的内部结构大致如下:
| 模块 | Host 角色 | Device 角色 |
|---|---|---|
| USB 控制器 | 作为主机控制器(EHCI / xHCI) | 作为设备控制器(UDC) |
| VBUS 电源 | 对外提供 5V / 100mA(会话期) | 由外部主机供电 |
| D+/D- 线 | 检测设备连接(电平变化) | 上拉电阻指示速度 |
| ID 引脚 | 接到 GND(A 设备) | 悬空(B 设备) |
硬件上,一个典型的 DRD 方案会使用一颗支持 OTG 的 PHY(比如 NXP 的 ISP1507),外加一个切换开关来复用 D+/D- 和 VBUS 路径。软件上则要同时加载 Host 和 Gadget 驱动,并通过角色管理模块做仲裁。
2.3 HNP(主机协商协议)
HNP 全称 Host Negotiation Protocol,它解决了什么问题?想象一下:两台 OTG 设备直连,比如手机连打印机。按照初始规则,谁当 A 设备谁就当主机。但用户可能想把手机里的照片传输到打印机的存储卡,这时就需要打印机变成主机去读取手机。HNP 就是干这个的 — 动态交换主从角色。
HNP 的流程我整理成了几个步骤,你对照图会更容易理解:
- A 设备(初始主机)检测到 B 设备支持 HNP(通过接收到的设备描述符中的 bDeviceProtocol 字段)。
- A 设备发起的会话结束时,A 设备将 VBUS 置于空闲状态(关掉电源)。
- B 设备检测到 VBUS 掉电,开始等待一个 T2 时间(典型 3 ms)。
- B 设备将 D+ 拉低超过 2.5 μs(表示复位信号),然后主动上拉 D+(全速)或 D-(低速)并开始 Host 操作。
- 此时 A 设备切换到 Device 角色,枚举自己的设备描述符给 B 设备。
避坑指南:我曾经在实现 HNP 时忽略了 T2 超时。B 设备等待时间不够就发起复位,导致 A 设备还没有释放总线,两方冲突。后来严格对照 USB OTG 规范 1.3 中的时序表才调通。调试时建议用逻辑分析仪抓 D+/D- 和 VBUS 波形。
2.4 SRP(会话请求协议)
SRP 全称 Session Request Protocol。说白了,就是省电用的。默认情况下,OTG 设备在空闲时会把 VBUS 关掉来节省功耗。但如果 B 设备想发起一次数据传输(比如手机想访问打印机),它必须先唤醒 A 设备。SRP 提供了两种唤醒方法:
- 数据线脉冲(Data Line Pulsing):B 设备将 D+ 或 D- 拉高,持续一段时间再释放。
- VBUS 脉冲(VBUS Pulsing):B 设备通过自己内部的电荷泵在 VBUS 线上产生一个短暂的高电平(>2.0V)。
A 设备(通常是耗电的大设备,比如笔记本电脑)注意检测这些脉冲。一旦检测到,A 设备就会重新提供 VBUS 电源,然后双方通过 HNP 或直接进入标准枚举流程。
注意事项:B 设备不能随意发 SRP。它必须满足“无用电流模型”:在发 SRP 之前,先判断自己是否具备足够的电池电量,并且不超过 SRP 失败后的重试次数(最多 3 次)。我见过一个产品因为 SRP 重试太频繁导致 VBUS 反复开关,烧毁了 PHY 芯片。
2.5 HNP 与 SRP 的配合
在实际系统中,HNP 和 SRP 经常一起出现。协议栈的工作逻辑可以用下面这张流程图来表示:
2.6 协议栈实现要点
在 Linux 内核中,OTG 的支持主要分布在 drivers/usb/dwc2/、drivers/usb/otg/ 以及 Gadget 框架里。最关键的结构体是 struct usb_otg,它定义了角色切换的回调函数:
struct usb_otg {
u8 default_a; // 是否为 A 设备(初始主机)
struct usb_phy *phy; // 关联的 PHY
int (*set_host)(struct usb_otg *otg, struct usb_bus *host);
int (*set_peripheral)(struct usb_otg *otg, struct usb_gadget *gadget);
int (*start_hnp)(struct usb_otg *otg);
int (*start_srp)(struct usb_otg *otg);
};
实现 HNP 时,通常在 start_hnp() 里操作 PHY 寄存器来模拟 D+ 复位和上拉。SRP 则在 start_srp() 里先关闭内部下拉,然后给 D+ 或 VBUS 发送脉冲。每个步骤的时序都必须严格按照 OTG 规范表 5-3 和 5-4 来,差一点就会失败。
我的经验之谈:调试 OTG 角色切换时,先用 sysfs 接口手动强制角色(/sys/devices/platform/*/otg_role 写 0/1),走通后再做自动协商。这样能快速区分是硬件问题还是协议栈问题。
2.7 小结
OTG 的核心就是双角色 + 协商。HNP 让两个设备谁当主机都行,SRP 则让它们在不通信时省电、需要时快速唤醒。理解这两个协议,基本上就抓住了 OTG 规范的 80%。很多工程师刚开始看 OTG 时会被一堆时序吓到,但你想想看,说到底不过是 D+/D- 线上的几个电平变化和等待时间。只要手边有逻辑分析仪,跟着规范一步步走,没那么神秘。