4. 图像传感器接口协议(二):DVP并行接口时序分析
上一章我们聊了串行接口,这一章来谈谈DVP——也就是并行接口。说实话,DVP在现在的手机摄像头里已经很少见了,但在工业相机、安防监控、还有FPGA开发板上,它依然是个主力。为什么?因为简单、直接、好调试。
DVP的全称是Digital Video Port,说白了就是一组并行数据线加上几个控制信号。我刚开始接触图像采集时,用的就是DVP接口。那时候觉得这东西太直观了,不像MIPI那样还要做串并转换。嗯,但后来发现,越是简单的东西,时序上越容易出问题。
4.1 DVP接口信号定义
一个标准的DVP接口,通常包含以下几根线:
| 信号名 | 方向 | 说明 |
|---|---|---|
| PCLK | 输出 | 像素时钟,由传感器提供 |
| VSYNC | 输出 | 帧同步信号,高/低电平有效 |
| HSYNC | 输出 | 行同步信号,高/低电平有效 |
| DATA[7:0] | 输出 | 8位或10位并行数据总线 |
| XCLK | 输入 | 主时钟,由FPGA提供 |
这里有个细节要注意:VSYNC和HSYNC的有效电平,不同厂商的传感器可能不一样。有的用高电平有效,有的用低电平有效。我在项目中就吃过这个亏——换了一款传感器,结果采集出来的图像全是乱的,查了半天才发现是极性搞反了。
4.2 DVP时序分析
DVP的时序其实不复杂。你想想看,它本质上就是一个同步并行总线。传感器在PCLK的上升沿(或下降沿)把数据打出来,FPGA在同一个时钟沿去采样。就这么简单。
但简单归简单,有几个关键点必须搞清楚:
4.2.1 帧同步时序
VSYNC信号用来标志一帧图像的开始和结束。通常的时序是这样的:
- VSYNC拉高(或拉低)表示一帧开始
- VSYNC拉低(或拉高)表示一帧结束
- 在VSYNC有效期间,传感器会输出整帧的所有行
我个人习惯的做法是:用VSYNC的上升沿作为帧开始的标志,然后开始计数行数。等VSYNC再次到来时,说明这一帧结束了。
4.2.2 行同步时序
HSYNC信号用来标志一行像素的开始和结束。每一行里,HSYNC有效期间,PCLK会连续输出该行的所有像素数据。
这里有个常见的坑:HSYNC有效期间,并不是每个PCLK都有有效数据。有些传感器会在行首和行尾插入一些空白像素(blanking)。我曾经调试一款OV系列的传感器,发现每行数据比预期的多了几十个像素,后来查手册才知道是行消隐区。
4.2.3 数据采样时序
数据采样的核心就是一句话:在正确的时钟沿,采正确的数据。DVP接口通常有两种采样方式:
- PCLK上升沿采样:传感器在PCLK上升沿输出数据,FPGA也在上升沿采样
- PCLK下降沿采样:传感器在PCLK下降沿输出数据,FPGA在下降沿采样
具体用哪种,要看传感器的数据手册。我建议你拿到新传感器后,先用示波器抓一下PCLK和DATA的相位关系,确认后再写代码。别问我为什么知道——我曾经因为没看波形,直接按上升沿采样,结果图像全是雪花。
4.3 Verilog实现像素数据采集
好了,理论说完了,咱们直接上代码。下面是一个典型的DVP接口采集模块:
module dvp_capture (
input wire pclk, // 像素时钟
input wire rst_n, // 复位,低有效
input wire vsync, // 帧同步
input wire hsync, // 行同步
input wire [7:0] data_in, // 像素数据
output reg frame_valid, // 帧有效标志
output reg line_valid, // 行有效标志
output reg [7:0] pixel_data // 采集到的像素数据
);
// 边沿检测
reg vsync_d1, vsync_d2;
reg hsync_d1, hsync_d2;
wire vsync_pos = vsync_d1 & ~vsync_d2; // VSYNC上升沿
wire vsync_neg = ~vsync_d1 & vsync_d2; // VSYNC下降沿
wire hsync_pos = hsync_d1 & ~hsync_d2; // HSYNC上升沿
always @(posedge pclk or negedge rst_n) begin
if (!rst_n) begin
vsync_d1 <= 1'b0;
vsync_d2 <= 1'b0;
hsync_d1 <= 1'b0;
hsync_d2 <= 1'b0;
end else begin
vsync_d1 <= vsync;
vsync_d2 <= vsync_d1;
hsync_d1 <= hsync;
hsync_d2 <= hsync_d1;
end
end
// 帧状态机
reg frame_active;
always @(posedge pclk or negedge rst_n) begin
if (!rst_n) begin
frame_active <= 1'b0;
frame_valid <= 1'b0;
end else begin
if (vsync_pos) begin
frame_active <= 1'b1; // 帧开始
end else if (vsync_neg) begin
frame_active <= 1'b0; // 帧结束
end
frame_valid <= frame_active;
end
end
// 行数据采集
always @(posedge pclk or negedge rst_n) begin
if (!rst_n) begin
line_valid <= 1'b0;
pixel_data <= 8'd0;
end else begin
if (frame_active && hsync) begin
// 在帧有效且行有效期间采集数据
line_valid <= 1'b1;
pixel_data <= data_in;
end else begin
line_valid <= 1'b0;
pixel_data <= 8'd0;
end
end
end
endmodule
核心要点:
- 用两级寄存器做边沿检测,这是FPGA设计的标准做法
- 帧状态机用VSYNC的上升沿和下降沿来控制
- 数据采集只在frame_active和hsync同时有效时进行
4.4 避坑指南
我曾经踩过的坑,你一定要注意:
- 极性搞反:VSYNC和HSYNC的有效电平一定要确认。有些传感器用高电平有效,有些用低电平。搞反了图像就是乱的。
- 数据对齐:PCLK的上升沿和下降沿,哪个沿采数据?一定要看手册+示波器确认。
- 消隐区:行首和行尾可能有blanking像素,采集时要跳过。
- 跨时钟域:如果PCLK和FPGA内部时钟不同步,采集到的数据需要做跨时钟域处理。
我的调试小技巧:
调试DVP接口时,我习惯先抓一帧数据存到BRAM里,然后用串口或者以太网发到PC上,用Python或者Matlab把图像还原出来看看。如果图像是正常的,说明采集逻辑没问题。如果图像有错位或者颜色不对,那就回头查时序。
4.5 本章小结
DVP并行接口虽然看起来简单,但实际调试中容易出各种小问题。核心就是三点:
- 搞清楚VSYNC和HSYNC的极性
- 确认PCLK的采样沿
- 处理好行消隐区
把这三点搞定了,DVP接口的采集就稳了。下一章我们会讲怎么把采集到的数据缓存到DDR里,那又是另一个故事了。