1、视频流采集设计
在对 CMOS Sensor 进行了寄存器的初始化配置后,并行数据总线上便开始持续的输出视频数据流。如图1所示,这是 CMOS Sensor 输出 VGA(640*480 分辨率)并行数据视频流协议的时序波形。我们可以看到,场同步信号 VSYNC 的每一个高脉冲表示新的一场图像(或者说是新的一帧图像)马上要开始传输;行同步信号 HREF 为高电平时,表示目前的数据总线 D[7:0]上的数据是有效的视频流。
图1 并行数据视频流协议
VSYNC 是高电平有效或低电平有效,通常是可以通过 I2C 设置 CMOS Sensor 的寄存器进行更改。OV5640 的 VSYNC 实际上是低电平有效的,即 VSYNC 实际的波形和图 1 正好是反向的。在 image_capture.v 模块内部,我们把 image_sensor_vsync 作为高电平有效信号,这是因为在 zstar.v 模块输入 image_sensor_vsync 信号时,我们做了一下反向操作。
如图2所示,视频时钟 PCLK 的每个上升沿,有效数据 D[7:0]、行同步信号 HREF 和场同步信号 VSYNC 被锁存到 FPGA 中。
图2 并行数据视频流时序
一个有效的行将传输 640*2Bytes 的数据,也就是说,一个像素点会有 2Bytes 即 16bits的有效色彩值。对应 R、G、B 的位数分别为 5bits、6bits、5bits。传输的数据总线是 8bits,那么一个像素点对应就有 2 个 8bits 需要传输。每两个字节中的 R、G、B 格式定义如图 3 所示。
图3 RGB 565 输出时序框图
理解了时序波形,我们再来看看代码中是如何对 CMOS Sensor送来的这组源同步信号进行采集的。先看一下实现图像缓存和时钟域切换的image_capture.v模块的接口定义。从CMOS Sensor 接口的同步信号 image_sensor_vsync , image_sensor_href , 数据总线 image_sensor_data 都是和时钟 image_sensor_pclk 同步的;在经过 DC FIFO 缓存后,产生的控制信号 image_ddr3_wren、image_ddr3_clr,数据总线 image_ddr3_wrdb 将和系统时钟 clk 同步。
module image_capture(
input clk, //时钟信号
input rst_n, //复位信号
//ImageSensor图像采集接口
input image_sensor_pclk, //视频时钟
input image_sensor_vsync, //视频场同步信号,高电平有效(有效视频传输时该信号拉低)
input image_sensor_href, //视频行同步信号
input[7:0] image_sensor_data, //视频数据总线
//ImageSensor数据写入DDR3接口
output reg image_ddr3_wren,
output image_ddr3_clr,
output[15:0] image_ddr3_wrdb
);
如图4 所示,这里通过一个异步 FIFO 来同步 CMOS Sensor 和 FPGA 内部逻辑。我们只要把image_sensor_pck、image sensor dataimage sensor href 分别作为 FIFO 的写入时钟写入数据和写入使能信号。此外,image sensor vsync 作为这个FIFO 的复位信号,每一新图像前 FIFO 进行一次清空,实际操作中,为了得到稳定有效的复位信号,我们使用内部时钟对这个复位信号打了一拍。这样,我们便把持续不断的视频流有效数据缓存到了 FIFO中。在 FIFO 的读端,判定数据大等于 16*16bit 时,就连续读出这 16 个数据,送到后续模块的DDR3 写缓存 FIFO 中。
图4 image_capture 模块功能框图
以下逻辑对image_sensor_vsync 信号打一拍,同步到本地时钟域。image_sensor_vsync_l信号将作为 DC FIFO 的复位信号。
//image_sensor_vsync同步到clk时钟域
wire image_sensor_vsync_r; //帧同步信号,高电平
register_diff_clk register_diff_clk_dc2(
.clk(clk),
.rst_n(rst_n),
.in_a(image_sensor_vsync),
.out_b(image_sensor_vsync_r)
);
DC FIFO 例化如下。
//数据缓存FIFO例化
//将数据从Image Sensor的PCLK时钟域转换到FPGA内的50MHz时钟域
wire[8:0] image_fifo_rd_data_count; //数据有效个数
reg image_fifo_rd_en; //FIFO读请求信号
fifo_generator_1 uut_image_cache_fifo (
.rst(image_sensor_vsync_r), // input wire rst
.wr_clk(image_sensor_pclk), // input wire wr_clk
.rd_clk(clk), // input wire rd_clk
.din(image_sensor_data), // input wire [7 : 0] din
.wr_en(image_sensor_href), // input wire wr_en
.rd_en(image_fifo_rd_en), // input wire rd_en
.dout(image_ddr3_wrdb), // output wire [15 : 0] dout
.full(), // output wire full
.empty(), // output wire empty
.rd_data_count(image_fifo_rd_data_count) // output wire [8 : 0] rd_data_count
);
FIFO IP核设置如图5所示。write width 是数据位宽,选择8。而write depth 指的是深度,即FPGA的FIFO使用多少个位宽为8的寄存器。由于分辨率是 640×480 ,也就是水平方向需要640个寄存器,所以需要至少640深度,但是可以留多点余量,比如1024,以防不够。之所以选择行数据为深度,是因为方便时序的设计。输出read width为16。
图5
下面的代码设计了一个状态机。在 RFIFO_IDLE 状态下,判断 FIFO 中的可读数据量大于 16 个时(image_fifo_rd_data_count >= 9’d16),进入 RFIFO_RDDB 状态从 FIFO 中连续读取 16 个数据并送往后续模块。
//连续读出16个数据计数控制状态机
parameter RFIFO_IDLE = 3'd0;
parameter RFIFO_RDDB = 3'd1;
reg[2:0] rfifo_state;
reg[8:0] dcnt; //读FIFO数据个数计数器
always @(posedge clk or negedge rst_n)
if(!rst_n) rfifo_state <= RFIFO_IDLE;
else begin
case(rfifo_state)
RFIFO_IDLE: if(image_fifo_rd_data_count >= 9'd16) rfifo_state <= RFIFO_RDDB;
else rfifo_state <= RFIFO_IDLE;
RFIFO_RDDB: if(dcnt >= 9'd16) rfifo_state <= RFIFO_IDLE;
else rfifo_state <= RFIFO_RDDB;
default: rfifo_state <= RFIFO_IDLE;
endcase
end
RFIFO_RDDB 状态下,计数器 dcnt 工作,在 dcnt 取值在 1~16 期间,FIFO 读使能信号image_fifo_rd_en 拉高,以读取 FIFO 中的数据。
//读FIFO数据个数计数器
always @(posedge clk or negedge rst_n)
if(!rst_n) dcnt <= 9'd0;
else if(rfifo_state == RFIFO_IDLE) dcnt <= 9'd0;
else if(rfifo_state == RFIFO_RDDB) dcnt <= dcnt+1'b1;
else dcnt <= 9'd0;
//读FIFO使能信号产生逻辑
always @(posedge clk or negedge rst_n)
if(!rst_n) image_fifo_rd_en <= 1'b0;
else if((dcnt > 9'd0) && (dcnt <= 9'd16)) image_fifo_rd_en <= 1'b1;
else image_fifo_rd_en <= 1'b0;
register_diff_clk.v
module register_diff_clk(
input clk,
input rst_n,
input in_a,
output out_b
);
reg[1:0] temp;
always @(posedge clk or negedge rst_n)
if(!rst_n) temp <= 2'b00;
else temp <= {temp[0],in_a};
assign out_b = temp[1];
endmodule
image_capture.v
module image_capture(
input clk, //时钟信号
input rst_n, //复位信号
//ImageSensor图像采集接口
input image_sensor_pclk, //视频时钟
input image_sensor_vsync, //视频场同步信号,高电平有效(有效视频传输时该信号拉低)
input image_sensor_href, //视频行同步信号
input[7:0] image_sensor_data, //视频数据总线
//ImageSensor数据写入DDR3接口
output reg image_ddr3_wren,
output image_ddr3_clr,
output[15:0] image_ddr3_wrdb
);
//image_sensor_vsync同步到clk时钟域
wire image_sensor_vsync_r; //帧同步信号,高电平
register_diff_clk register_diff_clk_dc2(
.clk(clk),
.rst_n(rst_n),
.in_a(image_sensor_vsync),
.out_b(image_sensor_vsync_r)
);
//数据缓存FIFO例化
//将数据从Image Sensor的PCLK时钟域转换到FPGA内的50MHz时钟域
wire[8:0] image_fifo_rd_data_count; //数据有效个数
reg image_fifo_rd_en; //FIFO读请求信号
fifo_generator_1 uut_image_cache_fifo (
.rst(image_sensor_vsync_r), // input wire rst
.wr_clk(image_sensor_pclk), // input wire wr_clk
.rd_clk(clk), // input wire rd_clk
.din(image_sensor_data), // input wire [7 : 0] din
.wr_en(image_sensor_href), // input wire wr_en
.rd_en(image_fifo_rd_en), // input wire rd_en
.dout(image_ddr3_wrdb), // output wire [15 : 0] dout
.full(), // output wire full
.empty(), // output wire empty
.rd_data_count(image_fifo_rd_data_count) // output wire [8 : 0] rd_data_count
);
//连续读出16个数据计数控制状态机
parameter RFIFO_IDLE = 3'd0;
parameter RFIFO_RDDB = 3'd1;
reg[2:0] rfifo_state;
reg[8:0] dcnt; //读FIFO数据个数计数器
always @(posedge clk or negedge rst_n)
if(!rst_n) rfifo_state <= RFIFO_IDLE;
else begin
case(rfifo_state)
RFIFO_IDLE: if(image_fifo_rd_data_count >= 9'd16) rfifo_state <= RFIFO_RDDB;
else rfifo_state <= RFIFO_IDLE;
RFIFO_RDDB: if(dcnt >= 9'd16) rfifo_state <= RFIFO_IDLE;
else rfifo_state <= RFIFO_RDDB;
default: rfifo_state <= RFIFO_IDLE;
endcase
end
//读FIFO数据个数计数器
always @(posedge clk or negedge rst_n)
if(!rst_n) dcnt <= 9'd0;
else if(rfifo_state == RFIFO_IDLE) dcnt <= 9'd0;
else if(rfifo_state == RFIFO_RDDB) dcnt <= dcnt+1'b1;
else dcnt <= 9'd0;
//读FIFO使能信号产生逻辑
always @(posedge clk or negedge rst_n)
if(!rst_n) image_fifo_rd_en <= 1'b0;
else if((dcnt > 9'd0) && (dcnt <= 9'd16)) image_fifo_rd_en <= 1'b1;
else image_fifo_rd_en <= 1'b0;
//送给DDR3的数据使能信号和清楚信号产生
always @(posedge clk) begin
image_ddr3_wren <= image_fifo_rd_en;
end
assign image_ddr3_clr = image_sensor_vsync_r;
endmodule
image_controller.v
module image_controller(
input clk, //50MHz时钟
input rst_n, //复位信号,低电平有效
//ImageSensor图像采集接口
(*mark_debug = "true"*) input image_sensor_pclk, //视频时钟
(*mark_debug = "true"*) input image_sensor_vsync, //视频场同步信号,高电平有效(有效视频传输时该信号拉低)
(*mark_debug = "true"*) input image_sensor_href, //视频行同步信号
(*mark_debug = "true"*) input[7:0] image_sensor_data, //视频数据总线
//ImageSensor串行配置接口
(*mark_debug = "true"*) output image_sensor_scl, //串行配置IIC时钟信号
(*mark_debug = "true"*) inout image_sensor_sda, //串行配置IIC数据信号
//ImageSensor复位与低功耗接口
output image_sensor_reset_n, //复位接口,低电平有效
output image_sensor_pwdn, //低功耗使能信号,高电平有效
//ImageSensor数据写入DDR3接口
(*mark_debug = "true"*) output image_ddr3_wren,
output image_ddr3_clr,
(*mark_debug = "true"*) output[15:0] image_ddr3_wrdb
);
//低功耗使能信号,高电平有效
assign image_sensor_pwdn = ~rst_n;
//复位接口,低电平有效
assign image_sensor_reset_n = rst_n;
//IIC寄存器初始化配置
wire tiic_init_done; //IIC配置完成标志位,高电平有效
I2C_OV5640_Init_RGB565 uut_I2C_OV5640_Init_RGB565(
.clk(clk), //100MHz
.rst_n(rst_n), //Global Reset
.i2c_sclk(image_sensor_scl), //I2C CLOCK
.i2c_sdat(image_sensor_sda), //I2C DATA
.config_done(tiic_init_done) //Config Done
);
//视频输入缓存控制
image_capture uut_image_capture(
.clk(clk), //时钟信号
.rst_n(rst_n & tiic_init_done), //复位信号
//ImageSensor采集接口
.image_sensor_pclk(image_sensor_pclk), //视频时钟
.image_sensor_vsync(image_sensor_vsync), //视频场同步信号,高电平有效(有效视频传输时该信号拉低)
.image_sensor_href(image_sensor_href), //视频行同步信号
.image_sensor_data(image_sensor_data), //视频数据总线
//ImageSensor数据写入DDR3接口
.image_ddr3_wren(image_ddr3_wren),
.image_ddr3_clr(image_ddr3_clr),
.image_ddr3_wrdb(image_ddr3_wrdb)
);
endmodule
至此整个image_controller已完成,接下来将解析AXI HP 总线读写 DDR3。