4. USB3 Vision 协议详解:U3V 协议栈架构、设备发现与控制(GenCP)、流数据传输(Streaming)、U3V 描述符解析

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 协议栈分层架构 应用层 图像数据格式(Mono/Bayer/RGB)、触发模式、元数据(Chunk) 协议层 设备发现(GenCP)、控制通道、流数据封装(Payload Header) 传输层 USB Bulk 传输(CTRL/Bulk/Stream)、端点(Endpoint)管理 物理层 USB 3.0 SuperSpeed、5Gbps、差分信号、线缆长度限制 GenICam SFNC GenCP U3V USB Bulk USB 3.0 PHY

我的习惯:调试 U3V 设备时,先确认物理层和传输层通不通。用 USB 抓包工具看 Bulk 端点是否有数据,比直接调应用层快得多。

4.2 设备发现与控制:GenCP 协议

设备发现,说白了就是「主机怎么知道有个相机插上来了」。

U3V 用的是 GenCP(Generic Control Protocol)。它定义了一套标准的控制通道机制。我刚开始学的时候,总觉得 GenCP 和 U3V 是两回事。后来才明白——GenCP 是「通用控制协议」,U3V 是「具体实现」。U3V 把 GenCP 搬到了 USB Bulk 传输上。

4.2.1 设备发现流程

设备枚举分三步:

  1. USB 枚举:主机检测到 USB 设备,读取设备描述符。U3V 设备会暴露一个 U3V_DEVICE_CLASS 标识。
  2. GenCP 发现:主机通过控制端点发送 DISCOVERY_CMD,设备回复 DISCOVERY_ACK。这里面包含了设备名称、序列号、固件版本。
  3. 描述符解析:主机读取 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 描述符解析流程

我一般按这个顺序解析:

  1. 读取 USB 设备描述符,确认设备类是 U3V。
  2. 发送 GET_DESCRIPTOR 请求,获取 U3V 设备描述符。
  3. 解析 dwCapabilities,判断相机支持哪些功能。
  4. 读取流通道描述符,获取每个通道的像素格式、分辨率范围。
  5. 根据描述符信息,初始化控制通道和流通道。

我曾经踩过的坑:有些相机在描述符里声称支持 10 种像素格式,但实际只有 3 种能正常工作。解析描述符只是「能力声明」,真正能不能用,还得试。我建议在驱动初始化时,逐个格式做一次测试采集。

4.5 小结

U3V 协议栈,说白了就是四层结构:物理层管信号,传输层管数据搬运,协议层管发现和控制,应用层管图像格式。GenCP 负责设备发现和寄存器读写,Streaming 负责图像数据传输,描述符是设备的「自我介绍」。

这一章的内容,是驱动开发的基础。你把这些搞清楚了,后面写驱动代码、调图像质量,心里就有底了。