4. 图像采集接口:OV5640摄像头配置、MIPI与DVP接口、Sensor数据读取与同步、ITU-R BT.656协议
图像采集是整个实时处理流水线的第一关。说白了,如果这步没做好,后面再牛的算法也是白搭。我做过好几个项目,最后发现图像花屏、丢帧、颜色不对,根因往往就出在Sensor配置或接口时序上。
这一章我们聚焦OV5640这颗经典Sensor,把配置、接口、同步、协议这些硬骨头啃下来。
4.1 OV5640摄像头配置:从上电到输出图像
OV5640是一颗500万像素的CMOS Sensor,支持MIPI和DVP两种输出接口。我个人习惯先把它配置成DVP模式做调试,稳定后再切到MIPI。
配置流程大致分三步:
- 硬件初始化:上电时序、复位、PLL锁定
- 寄存器写入:通过SCCB(类似I2C)总线配置分辨率、帧率、输出格式
- 输出验证:检查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接收端需要做这几件事:
- 时钟恢复:从差分时钟恢复出采样时钟
- 数据对齐:每个lane内部做bit对齐
- lane对齐:多个lane之间做word对齐
- 协议解析:解析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配置到数据输出的完整链路:
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输出全是乱码。嗯,这个教训挺深刻的。