1、概述
本文FPGA通过SCCB接口初始化OV7725摄像头寄存器,然后采集OV7725的摄像头数据,使用DDR3对数据进行暂存,最后将数据输出到HDMI显示器上进行显示。
该工程对应系统框图如下所示,主要包含OV7725驱动及数据处理模块、DDR3读写控制模块、HDMI控制模块。其中DDR3读写控制模块和HDMI显示控制模块在前文设计中已经进行详细讲解过了,本文直接使用即可,不再对其功能的实现进行赘述,不了解的可以参考前文。
由于只需要向OV7725的寄存器中写入数据,所以可以直接使用前文编写的I2C驱动模块代替SCCB驱动模块。iic_drive模块是I2C的驱动模块,兼容SCCB的写时序。init_contrl模块主要功能是控制初始化OV7725的哪些寄存器。Capture_data模块将摄像头输出的8位数据拼接为16位数据,然后传输给DDR3读写模块,将数据存入DDR3中。
后面DDR3控制模块和HDMI显示模块的设计思路与以太网传输图片中两个模块的设计基本一致。
外部的晶振输入100MHz时钟,通过锁相环进行倍频、分频处理,给OV7725提供12MHz的系统时钟XCLK,给DDR3控制模块提供200MHz的参考时钟和MIG IP工作时钟。由于显示器分辨率为1024*768,所以还需要给HDMI模块提供65MHz时钟和325MHz的参考时钟信号。
对于异步信号,首先DDR3读写控制模块的读、写两侧都是异步FIFO,相应的数据就能够异步FIFO交换数据,不需要做额外处理。
2、OV7725初始化控制模块
根据官方提供的初始化参数,对70个寄存器进行初始化,最终输出PCLK时钟频率为24MHz,640*480分辨率的RGB565像素数据。根据前文时序的介绍,首先对8’h12寄存器进行设置,使所有寄存器复位,发出该命令之后,需要等待1ms才能开始配置其余寄存器。
因此通过一个复位计数器和复位标志信号来对复位状态进行指示,为了简化电路,此处使用的计数器依靠溢出清零。
如下代码所示,复位指示信号初始值为0,wdata_cnt计数器用于表示已经初始化的寄存器个数。因为最先初始化的是复位计数器,因此当wdata_cnt等于1并且I2C驱动模块空闲的时候,证明复位寄存器已经被写入数据了,此时复位指示信号拉高,当复位计数器所有位均为高电平时,表示复位的持续时间大于1ms,此时可以配置后续寄存器了。
//生成复位指示信号,初始为低电平,当复位寄存器且等待1ms之后拉低,表示复位完成;
always@(posedge clk)begin
if(rst_n==1'b0)begin//初始值为0;
rst_flag <= 1'b0;
end//当延时计数器全为1时,表示延时1.3ms,复位完成。
else if(&delay_cnt)begin
rst_flag <= 1'b0;
end//当配置第0个数据之后,如果I2C驱动模块空闲,表示复位寄存器初始化完成,之后需要延时1ms才能继续配置其他寄存器。
else if(wdata_cnt == 7'd1 && rdy)begin
rst_flag <= 1'b1;
end
end
下面代码就是复位的延时计数器,当复位指示信号有效时,对系统时钟进行计数,当溢出时清零,系统时钟周期为10ns,17位计数器从0计数到最大值需要131071个时钟,即1.31ms。
//为了便于计数,直接使用一个17位计数器对100MHz时钟进行计数。
always@(posedge clk)begin
if(rst_n==1'b0)begin//初始值为0;
delay_cnt <= 17'd0;
end
else if(rst_flag)begin//处于复位状态下,对系统时钟进行计数。
delay_cnt <= delay_cnt + 17'd1;
end
end
之后就是计数器wdata_cnt,对初始化的寄存器个数进行计数。初始值为0,当初始化复位计数器时,要等延时计数器计数结束才能加一,如果初始化的不是复位寄存器,且已配置寄存器个数小于需要配置的寄存器个数,且I2C驱动模块处于空闲时加1。
//wdata_cnt计数器用于记录配置的寄存器个数;
always@(posedge clk)begin
if(rst_n==1'b0)begin//
wdata_cnt <= 0;
end
else if(add_wdata_cnt)begin
wdata_cnt <= wdata_cnt + 1;
end
end
//记录初始化寄存器的个数,由于第0个寄存器初始化之后需要延时1ms,所以当计数器等于1时,需要等待延时计数器计数结束才能加1。
//其余时间只需要等待I2C驱动模块空闲,就可以发送数据。
assign add_wdata_cnt = (((wdata_cnt == 7'd1) && (&delay_cnt)) || ((wdata_cnt != 7'd1) && rdy && (wdata_cnt < REG_NUM)));
将计数器加一条件延时一个时钟周期作为I2C驱动模块的开始信号,由于只需要向寄存器写入数据,所以读写标志信号一直拉低即可。根据计数器的数值生成需要写入寄存器的地址和数据。
//写开始信号,当计数器加一条件有效且没有配置完所有寄存器时拉高;
always@(posedge clk)begin
if(rst_n==1'b0)begin//初始值为0;
start <= 1'b0;
end
else begin
start <= add_wdata_cnt;
end
end
assign rw_flag = 1'b0;//读写控制信号置为低电平,因为只需要对寄存器进行初始化,不会有读操作。
//设置寄存器的地址和数据;
always@(posedge clk)begin
if(rst_n==1'b0)begin//初始值为0;
{reg_addr,wdata} <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位;
end
else if(add_wdata_cnt)begin
case(wdata_cnt)
//先对寄存器进行软件复位,使寄存器恢复初始值;
//寄存器软件复位后,需要延时1ms才能配置其它寄存器;
7'd0 : {reg_addr,wdata} <= {8'h12, 8'h80}; //COM7 BIT[7]:复位所有的寄存器;
7'd1 : {reg_addr,wdata} <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿;
7'd2 : {reg_addr,wdata} <= {8'h15, 8'h02}; //COM10 href/vsync/pclk/data信号控制;
7'd3 : {reg_addr,wdata} <= {8'h17, 8'h23}; //HSTART 水平起始位置;
7'd4 : {reg_addr,wdata} <= {8'h18, 8'ha0}; //HSIZE 水平尺寸;
7'd5 : {reg_addr,wdata} <= {8'h19, 8'h07}; //VSTRT 垂直起始位置;
7'd6 : {reg_addr,wdata} <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸;
7'd7 : {reg_addr,wdata} <= {8'h32, 8'h00}; //HREF 图像开始和尺寸控制,控制低位;
7'd8 : {reg_addr,wdata} <= {8'h29, 8'ha0}; //HOutSize 水平输出尺寸;
7'd9 : {reg_addr,wdata} <= {8'h2a, 8'h00}; //EXHCH 虚拟像素MSB;
7'd10 : {reg_addr,wdata} <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB;
7'd11 : {reg_addr,wdata} <= {8'h2c, 8'hf0}; //VOutSize 垂直输出尺寸;
7'd12 : {reg_addr,wdata} <= {8'h0d, 8'h41}; //COM4 PLL倍频设置(multiplier);
7'd13 : {reg_addr,wdata} <= {8'h11, 8'h00}; //CLKRC 内部时钟配置;
7'd14 : {reg_addr,wdata} <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式;
7'd15 : {reg_addr,wdata} <= {8'h0c, 8'h10}; //COM3 Bit[0]: 0:图像数据 1:彩条测试;
//DSP 控制
7'd16 : {reg_addr,wdata} <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值;
7'd17 : {reg_addr,wdata} <= {8'h4d, 8'h09}; //FixGain 模拟增益放大器;
7'd18 : {reg_addr,wdata} <= {8'h63, 8'hf0}; //AWB_Ctrl0 自动白平衡控制字节0;
7'd19 : {reg_addr,wdata} <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字节1;
7'd20 : {reg_addr,wdata} <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字节2;
7'd21 : {reg_addr,wdata} <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字节3;
7'd22 : {reg_addr,wdata} <= {8'h67, 8'h00}; //DSP_Ctrl4 DSP控制字节4;
//AGC AEC AWB
//COM8 Bit[2]:自动增益使能 Bit[1]:自动白平衡使能 Bit[0]:自动曝光功能;
7'd23 : {reg_addr,wdata} <= {8'h13, 8'hff}; //COM8;
7'd24 : {reg_addr,wdata} <= {8'h0f, 8'hc5}; //COM6;
7'd25 : {reg_addr,wdata} <= {8'h14, 8'h11};
7'd26 : {reg_addr,wdata} <= {8'h22, 8'h98};
7'd27 : {reg_addr,wdata} <= {8'h23, 8'h03};
7'd28 : {reg_addr,wdata} <= {8'h24, 8'h40};
7'd29 : {reg_addr,wdata} <= {8'h25, 8'h30};
7'd30 : {reg_addr,wdata} <= {8'h26, 8'ha1};
7'd31 : {reg_addr,wdata} <= {8'h6b, 8'haa};
7'd32 : {reg_addr,wdata} <= {8'h13, 8'hff};
//matrix sharpness brightness contrast UV
7'd33 : {reg_addr,wdata} <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1;
//DNSOff 降噪阈值下限,仅在自动模式下有效
7'd34 : {reg_addr,wdata} <= {8'h91, 8'h01}; //DNSOff;
7'd35 : {reg_addr,wdata} <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限;
7'd36 : {reg_addr,wdata} <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限;
7'd37 : {reg_addr,wdata} <= {8'h94, 8'h5f}; //MTX1 矩阵系数1;
7'd38 : {reg_addr,wdata} <= {8'h95, 8'h53}; //MTX1 矩阵系数2;
7'd39 : {reg_addr,wdata} <= {8'h96, 8'h11}; //MTX1 矩阵系数3;
7'd40 : {reg_addr,wdata} <= {8'h97, 8'h1a}; //MTX1 矩阵系数4;
7'd41 : {reg_addr,wdata} <= {8'h98, 8'h3d}; //MTX1 矩阵系数5;
7'd42 : {reg_addr,wdata} <= {8'h99, 8'h5a}; //MTX1 矩阵系数6;
7'd43 : {reg_addr,wdata} <= {8'h9a, 8'h1e}; //MTX_Ctrl 矩阵控制;
7'd44 : {reg_addr,wdata} <= {8'h9b, 8'h3f}; //BRIGHT 亮度;
7'd45 : {reg_addr,wdata} <= {8'h9c, 8'h25}; //CNST 对比度;
7'd46 : {reg_addr,wdata} <= {8'h9e, 8'h81};
7'd47 : {reg_addr,wdata} <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制;
7'd48 : {reg_addr,wdata} <= {8'ha7, 8'h65}; //USAT "U"饱和增益;
7'd49 : {reg_addr,wdata} <= {8'ha8, 8'h65}; //VSAT "V"饱和增益;
7'd50 : {reg_addr,wdata} <= {8'ha9, 8'h80}; //VSAT "V"饱和增益;
7'd51 : {reg_addr,wdata} <= {8'haa, 8'h80}; //VSAT "V"饱和增益;
//伽马控制
7'd52 : {reg_addr,wdata} <= {8'h7e, 8'h0c};
7'd53 : {reg_addr,wdata} <= {8'h7f, 8'h16};
7'd54 : {reg_addr,wdata} <= {8'h80, 8'h2a};
7'd55 : {reg_addr,wdata} <= {8'h81, 8'h4e};
7'd56 : {reg_addr,wdata} <= {8'h82, 8'h61};
7'd57 : {reg_addr,wdata} <= {8'h83, 8'h6f};
7'd58 : {reg_addr,wdata} <= {8'h84, 8'h7b};
7'd59 : {reg_addr,wdata} <= {8'h85, 8'h86};
7'd60 : {reg_addr,wdata} <= {8'h86, 8'h8e};
7'd61 : {reg_addr,wdata} <= {8'h87, 8'h97};
7'd62 : {reg_addr,wdata} <= {8'h88, 8'ha4};
7'd63 : {reg_addr,wdata} <= {8'h89, 8'haf};
7'd64 : {reg_addr,wdata} <= {8'h8a, 8'hc5};
7'd65 : {reg_addr,wdata} <= {8'h8b, 8'hd7};
7'd66 : {reg_addr,wdata} <= {8'h8c, 8'he8};
7'd67 : {reg_addr,wdata} <= {8'h8d, 8'h20};
7'd68 : {reg_addr,wdata} <= {8'h0e, 8'h65}; //COM5;
7'd69 : {reg_addr,wdata} <= {8'h09, 8'h00}; //COM2 Bit[1:0] 输出电流驱动能力;
//只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写;
default:{reg_addr,wdata} <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位;
endcase
end
end
当所有寄存器配置完成后,将配置完成信号拉高。
//初始化完成信号;
always@(posedge clk)begin
if(rst_n==1'b0)begin//初始值为0;
init_done <= 1'b0;
end//当所有寄存器写入数据,且I2C驱动模块处于空闲状态时,表示初始化完成;
else if((wdata_cnt == REG_NUM) && rdy)begin
init_done <= 1'b1;
end
end
由于该模块的逻辑比较简单,因此没有进行仿真,后续直接上板即可,参考代码如下:
3、摄像头数据处理模块
由于配置摄像头寄存器之后,需要10帧图像数据的时间,才能正常传输数据。当寄存器初始化完成之后,对场同步信号的上升沿进行检测,直到检测到10个为止。
//将场同步信号延迟两个时钟周期,用于检测其上升沿;
always@(posedge cam_pclk)begin
cam_vsync_r <= {cam_vsync_r[0],cam_vsync};
cam_href_r <= {cam_href_r[0],cam_href};
end
//检测场同步信号上升沿;
assign cam_vsync_pos = cam_vsync_r[0] & (~cam_vsync_r[1]);
assign cam_href_neg = (~cam_href_r[0]) & cam_href_r[1];
//延时计数器,用于记录复位后,前几帧数据;
always@(posedge cam_pclk)begin
if(rst_n==1'b0)begin//
delay_cnt <= 0;
end//当寄存器初始化完成,且检测到场同步上升沿,且小于10帧时加1;
else if(ov7725_init_done && cam_vsync_pos && (delay_cnt < WAIT_FRAME - 1))begin
delay_cnt <= delay_cnt + 1;
end
end
//等待完成信号;
always@(posedge cam_pclk)begin
if(rst_n==1'b0)begin//初始值为0;
delay_done <= 1'b0;
end//
else if(ov7725_init_done && cam_vsync_pos && (delay_cnt >= WAIT_FRAME - 1))begin
delay_done <= 1'b1;
end
end
由于需要把输入的8位像素数据拼接为16位像素数据输出,需要一个标志信号byte_flag,每行结束或者每帧数据开始时清零该信号,当像素有效信号HREF为高电平时,byte_flag翻转。
//相当于一个计数器,用于记录采集数据的个数,当数据有效时翻转,为1时表示采集了两个数据;
always@(posedge cam_pclk)begin
if(rst_n==1'b0)begin//初始值为0;
byte_flag <= 1'b0;
end
else if(cam_href_neg | cam_vsync_pos)begin//在一帧数据开始或者一行数据接收结束时清零,便于下次计数的正确;
byte_flag <= 1'b0;
end
else if(cam_href)begin
byte_flag <= ~byte_flag;
end
end
当输入像素有效(HREF位高电平),如果byte_flag为低电平,表示输入高字节数据,如果byte_flag为高电平,表示输入低字节数据。之后将像素输出有效指示信号拉高。最后可以把场同步信号输出,作为DDR3读、写控制模块写FIFO的复位信号,每帧数据开始时会对写FIFO进行复位,保证下一帧数据正确存储。
//将8位数据转换为16位数据输出,摄像头先传输高字节数据,后传输低字节数据;
always@(posedge cam_pclk)begin
if(rst_n==1'b0)begin//初始值为0;
cmos_frame_data <= 16'd0;
end
else if(cam_href)begin
if(byte_flag)//采集低字节数据;
cmos_frame_data[7:0] <= cam_data;
else//采集高字节数据;
cmos_frame_data[15:8] <= cam_data;
end
end
//输出像素有效指示信号,当采集完两次数据后输出一次有效数据;
always@(posedge cam_pclk)begin
if(rst_n==1'b0)begin//初始值为0;
cmos_frame_valid <= 1'b0;
end
else begin//当采集完两次数据且已经过了延时时输出一次有效数据
cmos_frame_valid <= cam_href & byte_flag & delay_done;
end
end
//输出场同步信号,延时完成后,将行同步信号延时一个时钟输出;
assign cmos_frame_vsync = delay_done ? cam_vsync_r[0] : 1'b0;
该模块的设计就这么简单,起始可以不用关心前10帧图像数据,可以继续简化模块设计。
4、顶层模块
本次设计由于生成的时钟比较多,需要例化两个锁相环模块才能完成。另外摄像头采集的场同步信号作为DDR3读写控制模块的写FIFO复位,由于场同步信号拉高会提前有效像素几千个时钟,这段时间完成FIFO的复位完全没问题。
DDR3读写控制模块使用乒乓模式,因为读速率比较快,所以始终读与写地址相反的地址区域即可。
另外需要对HDMI数据请求信号修改,由于显示器的像素为1024*768,而摄像头采集数据的像素是640*480,需要把图像显示在显示器的中心区域。
则水平像素显示区域为191831,垂直像素显示区域为143623,这段时间才能把DDR3读写控制模块的读使能信号拉高,一个时钟后得到像素数据。
将HDMI的场同步信号作为DDR3读写控制模块的读FIFO的复位信号,每读取一帧数据就对读FIFO的数据清零,确保下一帧图像正确显示。
由于摄像头采集的数据一行包含640个像素点,为了DDR3读侧FIFO不溢出,FIFO深度设置为2048。
整个模块的RTL视图如下所示:
参考代码如下:
assign power_en = 1'b1;//使能模块电源,仅对此模块有用;
//例化锁相环,输出200MHZ时钟,作为DDR的参考时钟;
//生成65MHz时钟作为HDMI的参考时钟信号;
clk_wiz_0 u_clk_wiz_0(
.clk_out1 ( clk_200m ),//output clk_out1
.clk_out2 ( dvi_clk ),//output clk_out2
.clk_out3 ( dvi_clk_5x ),//output clk_out3
.resetn ( rst_n ),//input resetn
.locked ( sys_rst_n ),//output locked
.clk_in1 ( clk ) //input clk_in1
);
//还要生成一路频率位12MHz的时钟输出给摄像头模块;
clk_wiz_1 u_clk_wiz_1(
.clk_out1 ( cam_xclk ),//output clk_out1
.resetn ( rst_n ),//input resetn
.clk_in1 ( clk ) //input clk_in1
);
//例化OV7725摄像头采集模块;
ov7725_top u_ov7725_top (
.clk ( clk ),//系统时钟信号,100MHz;
.rst_n ( sys_rst_n ),//系统复位信号,低电平有效;
.cam_pclk ( cam_pclk ),//摄像头数据像素时钟;
.cam_vsync ( cam_vsync ),//摄像头场同步信号;
.cam_href ( cam_href ),//摄像头行同步信号;
.cam_data ( cam_data ),//摄像头输入数据信号;
.cmos_frame_vsync ( cmos_frame_vsync ),//帧有效信号;
.cmos_frame_valid ( cmos_frame_valid ),//数据有效使能信号;
.cmos_frame_data ( cmos_frame_data ),//有效数据;
.scl ( scl ),//SCCB串行时钟信号;
.sda ( sda ) //SCCB双向串行数据信号;
);
//例化DDR3顶层模块
ddr3_top u_ddr3_top (
.sys_clk_i ( clk_200m ),//MIG IP核输入时钟,200MHz;
.rst_n ( sys_rst_n ),//复位,低有效;
.ddr3_init_done ( ddr3_init_done ),//ddr3初始化完成信号;
//DDR3接口信号
.ddr3_addr ( ddr3_addr ),//ddr3 地址;
.ddr3_ba ( ddr3_ba ),//ddr3 banck地址;
.ddr3_ras_n ( ddr3_ras_n ),//ddr3 行选择;
.ddr3_cas_n ( ddr3_cas_n ),//ddr3 列选择;
.ddr3_we_n ( ddr3_we_n ),//ddr3 读写选择;
.ddr3_reset_n ( ddr3_reset_n ),//ddr3 复位;
.ddr3_ck_p ( ddr3_ck_p ),//ddr3 时钟正;
.ddr3_ck_n ( ddr3_ck_n ),//ddr3 时钟负;
.ddr3_cke ( ddr3_cke ),//ddr3 时钟使能;
.ddr3_cs_n ( ddr3_cs_n ),//ddr3 片选;
.ddr3_dm ( ddr3_dm ),//ddr3_dm;
.ddr3_odt ( ddr3_odt ),//ddr3_odt;
.ddr3_dq ( ddr3_dq ),//ddr3 数据;
.ddr3_dqs_n ( ddr3_dqs_n ),//ddr3 dqs负;
.ddr3_dqs_p ( ddr3_dqs_p ),//ddr3 dqs正;
//复位及突发读写长度设置信号;
.app_addr_wr_min ( 29'd0 ),//读ddr3的起始地址;
.app_addr_wr_max ( 29'd307200 ),//读ddr3的结束地址;
.app_wr_bust_len ( 8'd80 ),//从ddr3中读数据时的突发长度;
.app_addr_rd_min ( 29'd0 ),//读ddr3的起始地址;
.app_addr_rd_max ( 29'd307200 ),//读ddr3的结束地址;
.app_rd_bust_len ( 8'd80 ),//从ddr3中读数据时的突发长度;
.wr_rst ( cmos_frame_vsync ),//写复位信号,上升沿有效,持续时间必须大于ui_clk的周期;
.rd_rst ( video_vs ),//读复位信号,上升沿有效,持续时间必须大于ui_clk周期;
//写数据相关信号;
.wfifo_wclk ( cam_pclk ),//写FIFO写时钟信号;
.wfifo_wren ( cmos_frame_valid ),//写FIFO写使能信号;
.wfifo_wdata ( cmos_frame_data ),//写FIFO写数据信号;
.wfifo_wcount ( ),//写FIFO中的数据个数;
.wfifo_full ( wfifo_full ),//写FIFO满指示信号;
.wfifo_wrst_busy ( wfifo_wrst_busy ),//写FIFO复位完成指示信号,低电平表示复位完成;
//读数据相关信号
.rfifo_rclk ( dvi_clk ),//读FIFO读时钟;
.rfifo_rden ( data_req ),//读FIFO读使能信号;
.rfifo_rdata ( pixel_data ),//读FIFO读数据;
.rfifo_rcount ( rfifo_rcount ),//读FIFO中的数据个数;
.rfifo_empty ( ),//读FIFO空指示信号;
.rfifo_rrst_busy ( rfifo_rrst_busy ) //读FIFO复位状态指示信号,高电平表示处于复位过程中。
);
//例化DVI接口驱动模块
dvi_top u_dvi_top (
.dvi_clk ( dvi_clk ),//DVI时钟信号,1024*768分辨率时为65MHz
.dvi_clk_5x ( dvi_clk_5x ),//DVI的5倍参考时钟信号,325MHz.
.rst_n ( sys_rst_n ),//复位信号,低电平有效。
.ddr3_init_done ( ddr3_init_done ),//DDR3初始化完成;
.rfifo_rrst_busy ( rfifo_rrst_busy ),//读FIFO的复位状态指示信号;
.pixel_data ( pixel_data ),
.data_req ( data_req ),
.video_vs ( video_vs ),
//HDMI接口信号
.tmds_oen ( tmds_oen ),
.tmds_clk_p ( tmds_clk_p ),
.tmds_clk_n ( tmds_clk_n ),
.tmds_data_p ( tmds_data_p ),
.tmds_data_n ( tmds_data_n )
);
5、上板测试
综合工程之后,下载到开发板实测结果如下图所示;
由于我手里这颗摄像头年代太过久远,且镜头上有一些杂质,导致摄像头成像比较差。
图5 显示器显示摄像头数据
可以通过修改配置寄存器的数值,如下图所示,将摄像头改成输出彩条图像。即寄存器8’h0c的最低位改为1,摄像头被设置为输出彩条测试模式。
最终得到的彩条显示结果如下图所示,证明FPGA整个工程的数据处理是没有问题的,后续更换一个摄像头就好了。
本次工程设计到此结束了,有前文的DDR3设计和HDMI设计作为保障,摄像头采集数据通过HDMI显示在显示器上的工程其实并不复杂。
积少成多,量变终究引发质变,对于每个细节都要找到问题原因,一起加油!!!
本工程可以在公众号后台回复“基于OV7725摄像头的HDMI显示”(不包括引号)获取,工程项目使用vivado2021.1在zynq7030上进行开发。
如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!
如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!