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 的流程我整理成了几个步骤,你对照图会更容易理解:

  1. A 设备(初始主机)检测到 B 设备支持 HNP(通过接收到的设备描述符中的 bDeviceProtocol 字段)。
  2. A 设备发起的会话结束时,A 设备将 VBUS 置于空闲状态(关掉电源)。
  3. B 设备检测到 VBUS 掉电,开始等待一个 T2 时间(典型 3 ms)。
  4. B 设备将 D+ 拉低超过 2.5 μs(表示复位信号),然后主动上拉 D+(全速)或 D-(低速)并开始 Host 操作。
  5. 此时 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 经常一起出现。协议栈的工作逻辑可以用下面这张流程图来表示:

OTG 双角色设备会话与角色协商流程 设备连接 / ID 检测 A 设备(初始主机) B 设备(初始外设) 枚举、数据传输 等待主机命令 关 VBUS,进入低功耗 检测 VBUS 跌落 B 设备发起 SRP 脉冲 A 设备重新提供 VBUS HNP:A 释放、B 接管 最终结果:B 变 Host,A 变 Device 实现外设访问主机存储等场景

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- 线上的几个电平变化和等待时间。只要手边有逻辑分析仪,跟着规范一步步走,没那么神秘。