4. USB3 Vision协议详解
USB3 Vision(简称 U3V)是工业相机领域最主流的接口标准之一。说实话,我最早接触它的时候,觉得它就是个「USB 摄像头驱动规范」。后来踩了几个坑才明白——它背后是一整套严谨的协议栈。
这一章,我们把它拆开来看。
4.1 U3V 协议栈架构:从物理层到应用层
U3V 协议栈分四层。我习惯从下往上理解:
| 层级 | 作用 | 对应标准 |
|---|---|---|
| 物理层 | USB 3.0 电气信号、线缆、连接器 | USB 3.0 Spec |
| 传输层 | USB Bulk/Control/Isochronous 传输 | USB 3.0 Spec |
| 协议层 | 设备发现、控制、流数据封装 | U3V Spec + GenCP |
| 应用层 | 图像数据格式、触发、元数据 | U3V Spec + GenICam |
你想想看,物理层和传输层是 USB 标准已经定义好的。U3V 真正做的工作,是在协议层和应用层上「搭积木」。
核心观点:U3V 不是重新发明轮子,而是在 USB Bulk 传输之上,定义了工业相机如何被发现、如何被控制、如何传图。
下面这张图,是我自己画的一个协议栈分层结构。它帮你把各层的关系看清楚。
我的习惯:调试 U3V 设备时,先确认物理层和传输层通不通。用 USB 抓包工具看 Bulk 端点是否有数据,比直接调应用层快得多。
4.2 设备发现与控制:GenCP 协议
设备发现,说白了就是「主机怎么知道有个相机插上来了」。
U3V 用的是 GenCP(Generic Control Protocol)。它定义了一套标准的控制通道机制。我刚开始学的时候,总觉得 GenCP 和 U3V 是两回事。后来才明白——GenCP 是「通用控制协议」,U3V 是「具体实现」。U3V 把 GenCP 搬到了 USB Bulk 传输上。
4.2.1 设备发现流程
设备枚举分三步:
- USB 枚举:主机检测到 USB 设备,读取设备描述符。U3V 设备会暴露一个
U3V_DEVICE_CLASS标识。 - GenCP 发现:主机通过控制端点发送
DISCOVERY_CMD,设备回复DISCOVERY_ACK。这里面包含了设备名称、序列号、固件版本。 - 描述符解析:主机读取 U3V 描述符,获取相机的能力信息(支持的像素格式、分辨率范围、流通道数)。
我曾经踩过的坑:有一次设备枚举总是超时。查了两天,最后发现是 USB 线缆太长(超过 3 米),信号衰减导致 DISCOVERY_ACK 丢包。换短线就好了。U3V 官方建议线缆不超过 3 米,但实际项目里 2 米以内最稳。
4.2.2 GenCP 控制通道
控制通道用于读写相机寄存器。U3V 使用 Bulk 端点(而不是控制端点)来传输 GenCP 命令。为什么?因为 Bulk 传输更快,而且支持更大的数据包。
GenCP 命令格式很简单:
// GenCP 命令头结构(简化版)
typedef struct {
uint8_t command_id; // 0x01 = READ_REG, 0x02 = WRITE_REG
uint16_t length; // 数据长度
uint32_t address; // 寄存器地址
uint8_t data[]; // 数据载荷
} GenCP_Command;
我常用的控制通道操作流程:
- 读寄存器:发送 READ_REG 命令,设备返回寄存器值。用于读取传感器温度、帧计数器。
- 写寄存器:发送 WRITE_REG 命令,设置曝光时间、增益、触发模式。
- 事件通知:设备主动上报事件(如掉帧、温度告警)。
关键点:GenCP 是同步请求-响应模式。发送一个命令,必须等到响应才能发下一个。我见过有人用多线程并发发命令,结果设备直接挂死——因为 GenCP 不支持乱序处理。
4.3 流数据传输(Streaming)
流数据传输,是 U3V 最核心的部分。相机采集图像后,通过 Bulk 端点把数据推给主机。
4.3.1 流数据包格式
每一帧图像被拆成多个数据包。每个包包含一个 Payload Header,后面跟着图像数据块。
// U3V Payload Header(16 字节)
typedef struct {
uint32_t block_id; // 块 ID,从 0 开始递增
uint32_t pixel_format; // 像素格式(如 0x01080001 = Mono8)
uint32_t width; // 图像宽度
uint32_t height; // 图像高度
uint32_t offset_x; // ROI 起始 X
uint32_t offset_y; // ROI 起始 Y
uint32_t padding; // 对齐填充
uint32_t timestamp; // 时间戳(微秒级)
} U3V_PayloadHeader;
你想想看,主机收到数据包后,先解析 Header,就知道这一块数据属于哪一帧、是什么格式、多大尺寸。然后拼帧。
4.3.2 流通道管理
U3V 支持多个流通道。每个通道对应一个 Bulk 端点。我做过一个项目,需要同时输出彩色图和深度图——就用两个流通道,一个传 RGB,一个传 Depth。
| 通道 | 端点地址 | 用途 |
|---|---|---|
| 控制通道 | 0x01 (OUT) / 0x81 (IN) | GenCP 命令与响应 |
| 流通道 0 | 0x82 (IN) | 主图像数据 |
| 流通道 1 | 0x83 (IN) | 辅助数据(如深度图) |
我的建议:流通道的 Bulk 端点建议使用 USB_SS_BULK_MAX_PACKET_SIZE(1024 字节)作为包大小。太小了吞吐量上不去,太大了容易丢包。
4.3.3 帧同步与丢包处理
帧同步靠 block_id。每帧的第一个包 block_id 从 0 开始,递增到帧结束。主机检测到 block_id 回 0,就知道新帧开始了。
丢包怎么办?嗯,这里要注意:U3V 不保证 Bulk 传输不丢包。如果某个包丢了,主机检测到 block_id 不连续,就丢弃整帧。我遇到过一种情况——USB 控制器压力大时,偶尔丢一个包,导致整帧丢弃,画面出现「跳帧」。
解决方案:
- 增大主机端的 USB 缓冲区(至少 4MB)。
- 降低帧率或减小分辨率,减少瞬时带宽压力。
- 开启 USB 流式传输(USB Streams),让硬件自动管理多个 Bulk 流。
4.4 U3V 描述符解析
描述符是 U3V 设备的「身份证」。主机通过读取描述符,知道这个相机能干什么。
4.4.1 描述符结构
U3V 描述符放在 USB 设备描述符的 扩展描述符 区域。主机通过标准 USB 请求读取。
// U3V 设备描述符(简化版)
typedef struct {
uint8_t bLength; // 描述符长度
uint8_t bDescriptorType; // 0x41 = U3V 描述符
uint16_t bcdU3V; // U3V 版本号(如 0x0101 = 1.1)
uint32_t dwCapabilities; // 能力位掩码
uint16_t wMaxPower; // 最大功耗(mA)
uint8_t bNumberOfStreams; // 流通道数量
uint8_t bNumberOfEndpoints; // 端点数量
} U3V_DeviceDescriptor;
4.4.2 能力位掩码解析
dwCapabilities 是一个 32 位掩码。每一位代表一个能力。我常用的几个:
| 位 | 名称 | 含义 |
|---|---|---|
| Bit 0 | U3V_CAP_STREAMING | 支持流数据传输 |
| Bit 1 | U3V_CAP_EVENT | 支持事件通知 |
| Bit 2 | U3V_CAP_CHUNK | 支持元数据(Chunk) |
| Bit 3 | U3V_CAP_MULTI_STREAM | 支持多流通道 |
实战经验:解析描述符时,一定要检查 bcdU3V 版本号。U3V 1.0 和 1.1 在 Payload Header 格式上有细微差别。我遇到过一台老相机,版本号是 0x0100,结果按 1.1 的格式解析 Header,数据全乱了。
4.4.3 描述符解析流程
我一般按这个顺序解析:
- 读取 USB 设备描述符,确认设备类是 U3V。
- 发送
GET_DESCRIPTOR请求,获取 U3V 设备描述符。 - 解析
dwCapabilities,判断相机支持哪些功能。 - 读取流通道描述符,获取每个通道的像素格式、分辨率范围。
- 根据描述符信息,初始化控制通道和流通道。
我曾经踩过的坑:有些相机在描述符里声称支持 10 种像素格式,但实际只有 3 种能正常工作。解析描述符只是「能力声明」,真正能不能用,还得试。我建议在驱动初始化时,逐个格式做一次测试采集。
4.5 小结
U3V 协议栈,说白了就是四层结构:物理层管信号,传输层管数据搬运,协议层管发现和控制,应用层管图像格式。GenCP 负责设备发现和寄存器读写,Streaming 负责图像数据传输,描述符是设备的「自我介绍」。
这一章的内容,是驱动开发的基础。你把这些搞清楚了,后面写驱动代码、调图像质量,心里就有底了。