4. 图像采集接口:OV5640摄像头配置、MIPI与DVP接口、Sensor数据读取与同步、ITU-R BT.656协议

图像采集是整个实时处理流水线的第一关。说白了,如果这步没做好,后面再牛的算法也是白搭。我做过好几个项目,最后发现图像花屏、丢帧、颜色不对,根因往往就出在Sensor配置或接口时序上。

这一章我们聚焦OV5640这颗经典Sensor,把配置、接口、同步、协议这些硬骨头啃下来。

4.1 OV5640摄像头配置:从上电到输出图像

OV5640是一颗500万像素的CMOS Sensor,支持MIPI和DVP两种输出接口。我个人习惯先把它配置成DVP模式做调试,稳定后再切到MIPI。

配置流程大致分三步:

  1. 硬件初始化:上电时序、复位、PLL锁定
  2. 寄存器写入:通过SCCB(类似I2C)总线配置分辨率、帧率、输出格式
  3. 输出验证:检查VSYNC、HREF、PCLK是否正常

关键寄存器速查表(我常用的几个):

寄存器地址 功能 典型值
0x3008 软件复位 0x80
0x3034 PLL分频配置 0x1A
0x3035 PLL倍频配置 0x21
0x3036 PLL控制 0x62
0x3820 ISP垂直翻转 0x00
0x3821 ISP水平翻转 0x00
0x4300 输出格式控制 0x30 (RGB565)

小技巧:配置PLL时,先算好目标像素时钟。OV5640的PLL公式是:PCLK = XVCLK × M / (N × P1 × P2)。我习惯先写个Python脚本算一遍,再写到FPGA的配置状态机里。

4.2 MIPI与DVP接口:两种路,各有千秋

OV5640同时支持MIPI CSI-2和DVP(Digital Video Port)两种接口。怎么选?看你的FPGA资源和项目需求。

对比项 MIPI CSI-2 DVP
信号线数量 2~4对差分对 + 时钟 10~12根单端信号
最高速率 每lane可达1Gbps 一般不超过150MHz PCLK
抗干扰能力 强(差分信号) 弱(单端易受干扰)
FPGA资源消耗 需要SerDes或专用IP 普通IO即可
适用场景 高分辨率、长距离 低分辨率、短距离、低成本

我在一个工业检测项目里用过DVP接口,当时Sensor和FPGA在同一块板子上,距离不到5cm,DVP完全够用。但后来换到另一个需要线缆连接的项目,DVP信号衰减严重,最后老老实实切回了MIPI。

4.2.1 DVP接口时序

DVP接口的信号包括:

  • PCLK:像素时钟,每个上升沿锁存一个像素数据
  • VSYNC:帧同步,高电平表示一帧开始
  • HREF:行同步,高电平表示一行有效数据
  • DATA[9:0]:像素数据总线

典型的DVP读取时序是这样的:

// 伪代码:DVP数据采集状态机
always @(posedge pclk) begin
    if (vsync) begin
        frame_start <= 1;
        row_cnt <= 0;
    end
    else if (href) begin
        // 每个PCLK上升沿锁存一个像素
        pixel_data <= {data[9:3], data[2:0]}; // RGB565格式
        pixel_valid <= 1;
        col_cnt <= col_cnt + 1;
    end
    else begin
        pixel_valid <= 0;
    end
end

注意:DVP接口的PCLK频率不能太高。我曾经试过把PCLK跑到200MHz,结果FPGA的IO延时导致数据采样出错。后来查手册才发现,OV5640的DVP模式PCLK最大只支持140MHz。嗯,这个坑我替你们踩过了。

4.2.2 MIPI CSI-2接口

MIPI接口复杂得多。它使用差分信号传输,每个lane包含一对D+和D-。数据在时钟的上升沿和下降沿都采样(DDR模式)。

MIPI接收端需要做这几件事:

  1. 时钟恢复:从差分时钟恢复出采样时钟
  2. 数据对齐:每个lane内部做bit对齐
  3. lane对齐:多个lane之间做word对齐
  4. 协议解析:解析CSI-2包格式,提取像素数据

说实话,MIPI的FPGA实现比DVP麻烦不少。如果用的是Xilinx或Intel的器件,我建议直接用原厂提供的MIPI IP核。自己写的话,光是一个D-PHY的模拟前端就够折腾的。

4.3 Sensor数据读取与同步:别让像素跑丢了

数据读取的核心问题就一个:如何保证每一帧、每一行的数据完整且有序。

我常用的方案是双缓冲+行缓存:

  • 双帧缓冲:一帧写入,一帧读出,避免帧撕裂
  • 行缓存FIFO:缓存一行数据,等待下游处理模块读取

同步逻辑的关键信号是VSYNC和HREF。我的做法是:

// 帧同步检测
always @(posedge pclk or negedge rst_n) begin
    if (!rst_n) begin
        vsync_d1 <= 0;
        vsync_d2 <= 0;
    end
    else begin
        vsync_d1 <= vsync;
        vsync_d2 <= vsync_d1;
    end
end

// 检测VSYNC上升沿:帧开始
assign frame_start = vsync_d1 & !vsync_d2;

经验之谈:VSYNC和HREF一定要做两级同步处理,否则亚稳态会让你怀疑人生。我早期一个项目就是没做同步,结果每过几分钟就出现一帧错位的数据,排查了整整两天。

4.4 ITU-R BT.656协议:数字视频的通用语言

BT.656是ITU制定的数字视频接口标准,广泛应用于标清视频领域。它把同步信号嵌入到数据流中,省去了VSYNC、HREF等独立信号线。

BT.656的数据流结构如下:

数据段 内容 长度
EAV(End of Active Video) 行结束标志 4字节
消隐区 行消隐数据 可变
SAV(Start of Active Video) 行开始标志 4字节
有效视频数据 YUV 4:2:2像素 720×2字节

EAV和SAV的格式固定为:FF 00 00 XY,其中XY字节包含场同步、行同步等信息。

在FPGA中解析BT.656,核心是检测FF 00 00这三个连续字节:

// BT.656同步码检测
always @(posedge clk) begin
    // 三级移位寄存器
    shift_reg <= {shift_reg[15:0], data_in};
    
    // 检测FF 00 00
    if (shift_reg[23:0] == 24'hFF0000) begin
        // 下一个字节是XY
        xy_byte <= data_in;
        // 根据XY判断是SAV还是EAV
        is_sav <= !xy_byte[7]; // bit7=0表示SAV
        is_eav <= xy_byte[7];  // bit7=1表示EAV
    end
end

BT.656 vs DVP 对比:

  • BT.656:信号线少(只需数据+时钟),但解析复杂,适合远距离传输
  • DVP:信号线多,但时序简单,适合板内短距离
  • MIPI:速率最高,但实现最复杂,需要专用硬件资源

4.5 本章知识结构图

下面这张图梳理了从Sensor配置到数据输出的完整链路:

图像采集接口知识结构图 图像采集接口知识结构 OV5640 Sensor SCCB配置 MIPI CSI-2 DVP接口 寄存器写入 数据对齐 VSYNC/HREF同步 BT.656协议解析 像素数据输出 💡 核心要点 • 配置Sensor时,先算PLL,再写寄存器,最后验证输出时钟 • MIPI适合高速长距离,DVP适合低速短距离,BT.656是标清视频的通用协议 • 数据同步的关键:VSYNC/HREF两级同步 + 双缓冲 + 行缓存FIFO

4.6 避坑指南

最后分享几个我踩过的坑:

  • 上电时序:OV5640对电源上电顺序有要求——先DVDD,再AVDD,最后IOVDD。顺序错了Sensor不工作。我有一块板子就是电源顺序反了,折腾了一周才发现。
  • 时钟抖动:MIPI时钟对抖动很敏感。如果时钟源质量不好,会出现随机丢帧。建议用专用的时钟Buffer,别直接从FPGA的PLL分出来就用。
  • 信号完整性:DVP接口的数据线尽量等长,长度差不要超过5mm。我见过一个项目因为数据线长度差太大,导致高分辨率下图像出现竖条纹。
  • 寄存器备份:每次修改寄存器配置前,先读出来备份。OV5640有些寄存器是只读的,写错了可能锁死Sensor。

特别提醒:如果你用的是MIPI接口,务必检查FPGA的Bank电压是否支持1.2V MIPI电平。我曾经把MIPI信号接到1.8V的Bank上,结果Sensor输出全是乱码。嗯,这个教训挺深刻的。