总:基于FPGA的OV7670摄像头显示

目录

 

前言:

一、整体系统设计

二、各部分模块设计

1、时钟模块

2、OV7670初始化模块

3、DVP协议数据流模块

4、写FIFO模块

5、读FIFO模块

6、写FIFO控制模块

7、读FIFO控制模块

8、SDRAM控制模块

9、VGA控制模块

10、顶层模块

三、仿真测试

四、上板验证

五、总结


工程文件下载链接:https://download.csdn.net/download/qq_33231534/13010542


前言:

这个专题的博客中写的都是关于OV7670摄像头显示所需要的模块,并对每个模块进行仿真验证,最后再对每个模块进行整合,本篇就是对整个摄像头系统进行整合和汇总。在过程中遇到很多问题,应该也会是很多人会遇到的问题,涉及到调试和代码的问题,在下边也会加以讲解。

一、整体系统设计

如下系统框图:

图:整体系统框图

 如图所示,FPGA中主要模块包含:时钟模块、OV7670初始化模块、DVP协议数据流模块、写FIFO模块、写FIFO控制模块、SDRAM控制模块、读FIFO模块、读FIFO控制模块、VGA控制模块。

其中OV7670初始化模块、DVP协议数据流模块和VGA控制模块都在本专题博客中写过,这里不再赘述。写FIFO和读FIFO模块使用的IP核,都是宽度16位,长度256,其中读FIFO使用的是showahead模式。SDRAM控制器漆面的博客也写过,这边做了一些改动,添加了一些需要的信号。

其整体流程为:启动时先对摄像头进行初始化设置,初始化完成后,FPGA从摄像头获取一帧一帧的图像数据,根据数据手册将ov7670数据流转换成我们需要的RGB565数据流,随后存入写FIFO模块;(写控制模块)当写FIFO模块中存储的数据大于等于8时,发出SDRAM写请求,SDRAM写请求通过后,读取FIFO数据存储起来;(读FIFO模块)当读FIFO数据小于等于8时,读取SDRAM中的数据经过读FIFO缓存后送入VGA显示模块进行显示。同时写控制模块和读控制模块控制SDRAM读写地址的增加。

二、各部分模块设计

1、时钟模块

这里使用PLL的IP核,以50MHz时钟生成25MHz和100MHz时钟,其中摄像头初始化模块和VGA控制模块使用的是25MHz,SDRAM控制模块、写FIFO控制模块和读FIFO控制模块使用的是100MHz,写FIFO和读FIFO模块都是异步FIFO,使用25MHz和100MHz时钟。

2、OV7670初始化模块

前面博客讲的很详细,也没有改动,这里不再赘述。

3、DVP协议数据流模块

前面博客讲的很详细,也没有改动,这里不再赘述。

4、写FIFO模块

这里使用的IP核,数据宽度为16,长度为256,普通模式。

5、读FIFO模块

这里使用的IP核,数据宽度为16,长度为256,showahead模式。showahead模式是为了在读出FIFO数据时先出一个数据,和VGA显示的数据有效信号好对齐。

6、写FIFO控制模块

主要实现两个功能:

(1)当写FIFO中数据大于等于8时,向SDRAM控制器发出写请求信号

(2)发送SDRASM控制器写地址(行地址和列地址),当SDRAM控制器写完数据后,写地址进行相应变化。

代码如下:


// Company  : 
// Engineer : 
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534    PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date    : 2020-09-27 12:44:59
// Revise Data    : 2020-09-27 12:45:39
// File Name      : wr_control.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions  : Vivado 2019.2
// Revision       : V1.1
// Editor         : sublime text3, tab size (4)
// Description    : 写FIFO控制模块

module wr_control(
	input				clk				,//100MHz
	input				rst_n			,//系统复位
	input	[7:0]		rdusedw			,//写FIFO中数据个数
	input	[9:0]		row_addr_max	,//行地址最大
	input	[9:0]		col_addr_max	,//列地址最大
	input				sdram_wdata_done,//SDRAM写数据结束标志
	input				aclr			,//一帧结束清零信号

	output	reg			sdram_wr_en		,//SDRAM写使能信号
	output	reg	[11:0]	wr_row_addr		,//SDRAM 行地址
	output	reg	[8:0]	wr_col_addr		//SDRAM 列地址
	);

	always @(posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			sdram_wr_en <= 0;
		end
		else if (rdusedw>=8) begin
			sdram_wr_en <= 1;
		end
		else begin
			sdram_wr_en <= 0;
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			wr_col_addr <= 9'd0;
		end
		else if (sdram_wdata_done) begin
			if (wr_col_addr==col_addr_max-4'd8) begin
				wr_col_addr <= 9'd0;
			end
			else begin
				wr_col_addr <= wr_col_addr + 4'd8;				
			end
		end
		else if (aclr) begin
			wr_col_addr <= 0;
		end
	end
	
	always @(posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			wr_row_addr <= 12'd0;
		end
		else if (sdram_wdata_done && wr_col_addr==col_addr_max-4'd8) begin
			if (wr_row_addr==row_addr_max-1) begin
				wr_row_addr <= 12'd0;
			end
			else begin
				wr_row_addr <= wr_row_addr + 1'b1;
			end
		end
		else if (aclr) begin
			wr_row_addr <= 0;
		end
	end

endmodule

7、读FIFO控制模块

主要实现两个功能:

(1)当读FIFO中数据小于等于8时,向SDRAM控制器发出读请求信号

(2)发送SDRASM控制器读地址(行地址和列地址),当SDRAM控制器读完数据后,读地址进行相应变化。

代码如下:


// Company  : 
// Engineer : 
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534    PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date    : 2020-09-27 16:40:08
// Revise Data    : 2020-09-27 16:40:08
// File Name      : rd_control.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions  : Vivado 2019.2
// Revision       : V1.1
// Editor         : sublime text3, tab size (4)
// Description    : 读FIFO控制模块

module rd_control(
	input				clk				,//时钟100MHz
	input				rst_n			,//复位信号
	input	[7:0]		rdusedw			,//读FIFO中数据个数
	input	[9:0]		row_addr_max	,//行地址最大
	input	[9:0]		col_addr_max	,//列地址最大
	input				sdram_rdata_done,//SDRAM读数据结束标志
	input				aclr			,//一帧结束清零标志

	output	reg			sdram_rd_en		,//SDRAM读使能
	output	reg	[11:0]	rd_row_addr		,//SDRAM 行地址
	output	reg	[8:0]	rd_col_addr		 //SDRAM 列地址
	);

	always @(posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			sdram_rd_en <= 0;
		end
		else if (rdusedw<=8) begin
			sdram_rd_en <= 1;
		end
		else begin
			sdram_rd_en <= 0;
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			rd_col_addr <= 9'd0;
		end
		else if (sdram_rdata_done) begin
			if (rd_col_addr==col_addr_max-4'd8) begin
				rd_col_addr <= 9'd0;
			end
			else begin
				rd_col_addr <= rd_col_addr + 4'd8;				
			end
		end
		else if (aclr) begin
			rd_col_addr <= 0;
		end
	end 
	
	always @(posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			rd_row_addr <= 12'd0;
		end
		else if (sdram_rdata_done && rd_col_addr==col_addr_max-4'd8) begin
			if (rd_row_addr==row_addr_max-1) begin
				rd_row_addr <= 9'd0;
			end
			else begin
				rd_row_addr <= rd_row_addr + 1'b1;
			end
		end
		else if (aclr) begin
			rd_row_addr <= 0;
		end
	end

endmodule

8、SDRAM控制模块

前面有个专题专门讲的SDRAM控制器原理和实现方法,这里在原来的代码上,根据现在系统的需求,进行了略微的修改,主要功能没有修改,只是增加几个外部需要的信号,读优先级还是大于写优先级(写大于读也可以)。

这里给出代码:


// Company  : 
// Engineer : 
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534    PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date    : 2020-09-18 14:20:22
// Revise Data    : 2020-10-20 15:30:16
// File Name      : SDRAM_control.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions  : Vivado 2019.2
// Revision       : V1.1
// Editor         : sublime text3, tab size (4)
// Description    : SDRAM控制器,支持读写冲突长度为8,cas为3

module SDRAM_control(
	input					clk				,//100MHZ
	input					rst_n			,//复位
	input					wr_en			,//写使能信号
	input	[15:0]			wr_data			,//写数据
	input					rd_en			,//读使能信号
	input	[1:0]			bank_addr		,//bank地址
	input	[11:0]			row_addr		,//行地址
	input	[8:0]			col_addr		,//列地址
	
	output	reg				fifo_rdreq		,//写FIFO读请求信号
	output	reg	[15:0]		rd_data			,//读出的数据
	output	reg				rd_data_vld		,//读出数据有效位
	output	wire			wr_data_vld		,//写入数据有效位
	output	wire			wdata_done		,//写数据结束标志
	output	wire			rdata_done		,//读数据结束标志

	output	wire			sdram_clk		,//SDRAM时钟信号
	output	reg	[3:0]		sdram_commond	,//{cs,ras,cas,we}
	output	wire			sdram_cke		,//时钟使能信号
	output	reg	[1:0]		sdram_dqm		,//数据线屏蔽信号
	output	reg	[11:0]		sdram_addr		,//SDRAM地址线
	output	reg	[1:0]		sdram_bank		,//SDRAM bank选取
	inout	wire[15:0]		sdram_dq		, //SDRAM数据输出输入总线
	output	wire			state_wr_req	, //新增,用于读写地址判断
	output	wire			state_rd_req	  //新增,用于读写地址判断
	);

	//延时
	localparam TWAIT_200us 	= 15'd20000		;//上电等待时间
	localparam TRP			= 2'd3			;//预充电周期
	localparam TRC 			= 4'd10			;//自刷新周期
	localparam TRSC			= 2'd3			;//加载模式寄存器周期
	localparam TRCD			= 2'd2			;//激活命令周期
	localparam TREAD_11		= 4'd11			;//burst=8,cas=3
	localparam TWRITE_8		= 4'd8			;//burst=8
	localparam AUTO_REF_TIME= 11'd1562		;

	//状态
	localparam NOP			= 3'd0			;
	localparam PRECHARGE	= 3'd1			;
	localparam REF			= 3'd2			;
	localparam MODE			= 3'd3			;
	localparam IDLE			= 3'd4			;
	localparam ACTIVE		= 3'd5			;
	localparam WRITE		= 3'd6			;
	localparam READ			= 3'd7			;

	//操作命令
	localparam NOP_CMD      = 4'b0111		;
    localparam PRECHARGE_CMD= 4'b0010		;
    localparam REF_CMD      = 4'b0001		;
    localparam MODE_CMD     = 4'b0000		;
    localparam ACTIVE_CMD   = 4'b0011		;
    localparam WRITE_CMD    = 4'b0100		;
    localparam READ_CMD     = 4'b0101		;

    //初始化阶段地址线
    localparam ALL_BANK		= 12'b01_0_00_000_0_000;//预充电地址线
    localparam MODE_CONFIG	= 12'b00_0_00_011_0_011;//配置模式寄存器时地址线

    wire nop_to_pre_start 		;
	wire pre_to_ref_start 		;
	wire pre_to_idle_start 		;
	wire ref_to_mode_start 		;
	wire ref_to_idle_start 		;
	wire ref_to_ref_start 		;
	wire mode_to_idle_start 	;
	wire idle_to_active_start 	;
	wire idle_to_ref_start 		;
	wire active_to_write_start	;
	wire active_to_read_start 	;
	wire write_to_pre_start 	;
	wire read_to_pre_start 		;

	reg	[2:0]	state_c			;
	reg	[2:0]	state_n			;
    wire 		sdram_dq_en		;
	wire[15:0]	sdram_dq_r		;
	reg			rd_data_vld_ff0	;
	reg			rd_data_vld_ff1	;
	reg			rd_data_vld_ff2	;
	reg			rd_data_vld_ff3	;
	reg	[10:0]	auto_ref_cnt	;
	wire		add_auto_ref_cnt;
	wire		end_auto_ref_cnt;
	reg 		ref_req			;
	wire 		init_done		;
	reg  		init_flag		;
	reg	[14:0]	cnt0			;
	wire		add_cnt0		;
	wire		end_cnt0		;
	reg [14:0]	x				;
	reg	[3:0]	ref_cnt1		;
	wire		add_cnt1		;
	wire		end_cnt1		;
	reg			flag_rd			;		
	reg 		flag_wr			;		
	reg			fifo_rdreq_f	;
	reg	[2:0]	fifo_rdreq_cnt;

	assign state_wr_req = state_c==IDLE; 
	assign state_rd_req = state_c==IDLE;

    assign sdram_clk = ~clk;

	always @(posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			state_c <= NOP;			
		end
		else begin
			state_c <= state_n;
		end
	end

	always @(*)begin
		case(state_c)
			NOP	:begin
				if (nop_to_pre_start) begin
					state_n = PRECHARGE;
				end
				else begin
					state_n = state_c;
				end
			end
			PRECHARGE:begin
				if (pre_to_ref_start) begin
					state_n = REF;
				end
				else if (pre_to_idle_start) begin
					state_n = IDLE;
				end
				else begin
					state_n = state_c;
				end
			end
			REF:begin
				if (ref_to_mode_start) begin
					state_n = MODE;
				end
				else if (ref_to_idle_start) begin
					state_n = IDLE;
				end
				else if (ref_to_ref_start) begin
					state_n = REF;
				end
				else begin
					state_n = state_c;
				end
			end
			MODE:begin
				if (mode_to_idle_start) begin
					state_n = IDLE;
				end
				else begin
					state_n =state_c;
				end
			end
			IDLE:begin
				if (idle_to_active_start) begin
					state_n = ACTIVE;
				end
				else if (idle_to_ref_start) begin
					state_n = REF;
				end
				else begin
					state_n = state_c;
				end
			end
			ACTIVE:begin
				if (active_to_write_start) begin
					state_n = WRITE;
				end
				else if (active_to_read_start) begin
					state_n = READ;
				end
				else begin
					state_n = state_c;
				end
			end
			WRITE:begin
				if(write_to_pre_start)begin
					state_n = PRECHARGE;
				end
				else begin
					state_n = state_c;
				end
			end
			READ:begin
				if (read_to_pre_start) begin
					state_n = PRECHARGE;
				end
				else begin
					state_n = state_c;
				end
			end
			default:state_n = IDLE;
		endcase
	end

	assign nop_to_pre_start 		= (state_c==NOP && end_cnt0);
	assign pre_to_ref_start 		= (state_c==PRECHARGE && end_cnt0 && init_flag==1);
	assign pre_to_idle_start 		= (state_c==PRECHARGE && end_cnt0 && init_flag==0);
	assign ref_to_mode_start 		= (state_c==REF && init_flag==1 && end_cnt1);
	assign ref_to_idle_start 		= (state_c==REF && init_flag==0 && end_cnt0);
	assign ref_to_ref_start 		= (state_c==REF && init_flag==1 && end_cnt0 && ref_cnt1<7);
	assign mode_to_idle_start 		= (state_c==MODE && init_flag==1 && end_cnt0);
	assign idle_to_active_start 	= (state_c==IDLE && (wr_en || rd_en) && ref_req==0);
	assign idle_to_ref_start 		= (state_c==IDLE && ref_req==1);
	assign active_to_write_start	= (state_c==ACTIVE && end_cnt0 && flag_wr);
	assign active_to_read_start 	= (state_c==ACTIVE && end_cnt0 && flag_rd);
	assign write_to_pre_start 		= (state_c==WRITE && end_cnt0);
	assign read_to_pre_start 		= (state_c==READ && end_cnt0);

	//命令控制字 sdram_commond = {CS,RAS,CAS,WE};
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			sdram_commond <= NOP_CMD;
		end
		else if (nop_to_pre_start || write_to_pre_start || read_to_pre_start) begin
			sdram_commond <= PRECHARGE_CMD;
		end
		else if (pre_to_ref_start || ref_to_ref_start || idle_to_ref_start) begin
			sdram_commond <= REF_CMD;
		end
		else if (ref_to_mode_start) begin
			sdram_commond <= MODE_CMD;
		end
		else if (idle_to_active_start) begin
			sdram_commond <= ACTIVE_CMD;
		end
		else if (active_to_write_start) begin
			sdram_commond <= WRITE_CMD;
		end
		else if (active_to_read_start) begin
			sdram_commond <= READ_CMD;
		end
		else begin
			sdram_commond <= NOP_CMD;
		end
	end

	//cke信号保持拉高
	assign sdram_cke = 1;
    
    //dqm信号
    always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sdram_dqm <= 2'b11;
        end
        else if(init_done)begin
            sdram_dqm <= 2'b00;
        end
    end

    //地址线
    always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sdram_addr <= 12'b0;
        end
        else if(nop_to_pre_start || write_to_pre_start || read_to_pre_start)begin
            sdram_addr <= ALL_BANK;             
        end
        else if(ref_to_mode_start)begin
            sdram_addr <= MODE_CONFIG;               
        end
        else if(idle_to_active_start)begin
		    sdram_addr <= row_addr;  		       		                 
        end
        else if (active_to_read_start || active_to_write_start) begin
        	sdram_addr <= {3'b000,col_addr};
        end
        else begin
            sdram_addr <= 12'b0;               
        end
    end

    //sdram_bank
    always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
        	sdram_bank <= 2'b00;
        end
        else if (idle_to_active_start || active_to_write_start || active_to_read_start) begin
        	sdram_bank <= bank_addr;
        end
        else begin
        	sdram_bank <= 2'b00;
        end
    end

    //sdram_dq
    assign sdram_dq_en = (state_c==WRITE) ? 1'b1 : 1'b0;

	assign sdram_dq = sdram_dq_en ? sdram_dq_r : 16'hzzzz;

	assign sdram_dq_r = wr_data;

	assign wr_data_vld = state_c==WRITE;


	assign wdata_done = write_to_pre_start;
	assign rdata_done = read_to_pre_start;
	
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			rd_data <= 16'd0;
		end
		else begin
			rd_data <= sdram_dq;
		end
	end
	// assign rd_data = state_c==READ ? sdram_dq:16'hzzzz;

	//读有效标志
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			rd_data_vld_ff0 <= 0;
		end
		else if (active_to_read_start) begin
			rd_data_vld_ff0 <= 1;
		end
		else if (state_c==READ && cnt0==TREAD_11-4) begin
			rd_data_vld_ff0 <= 0;
		end
	end
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			rd_data_vld_ff1 <= 0;
			rd_data_vld_ff2 <= 0;
			rd_data_vld_ff3 <= 0;
			rd_data_vld     <= 0;
		end
		else begin
			rd_data_vld_ff1 <= rd_data_vld_ff0;
			rd_data_vld_ff2 <= rd_data_vld_ff1;
			rd_data_vld_ff3 <= rd_data_vld_ff2;
			rd_data_vld     <= rd_data_vld_ff3;
		end
	end

	//刷新请求计数
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			auto_ref_cnt <= 0;
		end
		else if (add_auto_ref_cnt) begin
			if (end_auto_ref_cnt) begin
				auto_ref_cnt <= 0;
			end
			else begin
				auto_ref_cnt <= auto_ref_cnt + 1'b1;
			end
		end
	end
	assign add_auto_ref_cnt = init_flag==0;
	assign end_auto_ref_cnt = (add_auto_ref_cnt && auto_ref_cnt==AUTO_REF_TIME-1);

	//ref_req 刷新请求
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			ref_req <= 0;
		end
		else if (end_auto_ref_cnt) begin
			ref_req <= 1;
		end
		else if (state_c==IDLE && ref_req==1) begin
			ref_req <= 0;
		end
	end

	//初始化标志
	assign init_done = (state_c==MODE && end_cnt0);

	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			init_flag <= 1;
		end
		else if (init_done) begin
			init_flag <= 0;
		end
	end

	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			cnt0 <= 0;
		end
		else if (add_cnt0) begin
			if (end_cnt0) begin
				cnt0 <= 0;
			end
			else begin
				cnt0 <= cnt0 + 1'b1;
			end
		end
	end
	assign add_cnt0 = state_c!=IDLE;
	assign end_cnt0 = add_cnt0 && cnt0==x-1'b1;
	

	always @(*)begin
		case(state_c)
			NOP			: x = TWAIT_200us;
			PRECHARGE	: x = TRP;
			REF			: x = TRC;
			MODE		: x = TRSC;
			ACTIVE		: x = TRCD;
			WRITE		: x = TWRITE_8;
			READ		: x = TREAD_11;
			default		: x = 0;
		endcase
	end

	//初始化自刷新8个周期计数
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			ref_cnt1 <= 0;
		end
		else if (add_cnt1) begin
			if (end_cnt1) begin
				ref_cnt1 <= 0;
			end
			else begin
				ref_cnt1 <= ref_cnt1 + 1'b1;
			end
		end
	end
	assign add_cnt1 = (state_c==REF && init_flag==1 && end_cnt0);
	assign end_cnt1 = (add_cnt1 && ref_cnt1== 8-1);

	//读写信号标志
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			flag_rd <= 0;
		end
		else if (state_c==IDLE && rd_en && ref_req==0) begin
			flag_rd <= 1;
		end
		else if (pre_to_idle_start && flag_rd==1) begin
			flag_rd <= 0;
		end
	end
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			flag_wr <= 0;
		end
		else if (state_c==IDLE && wr_en && rd_en==0 && ref_req==0) begin  //读优先级高于写优先级
			flag_wr <= 1;
		end
		else if (pre_to_idle_start && flag_wr==1) begin
			flag_wr <= 0;
		end
	end

	//写FIFO读请求信号
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			fifo_rdreq_f <= 0;
		end
		else if (state_c==IDLE && wr_en && rd_en==0 && ref_req==0) begin
			fifo_rdreq_f <= 1;
		end
		else if (fifo_rdreq_cnt>=7) begin
			fifo_rdreq_f <= 0;
		end
	end
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			fifo_rdreq_cnt <= 0;
		end
		else if (fifo_rdreq_f) begin
			if (fifo_rdreq_cnt >= 7) begin
				fifo_rdreq_cnt <= 0;
			end
			else begin
				fifo_rdreq_cnt <= fifo_rdreq_cnt + 1'b1;
			end
		end
	end
	//延迟一拍,时序对齐
	always @(posedge clk or negedge rst_n)begin
		if (!rst_n) begin
			fifo_rdreq <= 0;
		end
		else begin
			fifo_rdreq <= fifo_rdreq_f;
		end
	end

endmodule

9、VGA控制模块

本专题博客讲的很详细,也没有改动,这里不再赘述。

10、顶层模块


// Company  : 
// Engineer : 
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534    PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date    : 2020-09-27 11:04:42
// Revise Data    : 2020-09-30 11:11:00
// File Name      : camera_ov7670_top.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions  : Vivado 2019.2
// Revision       : V1.1
// Editor         : sublime text3, tab size (4)
// Description    : 

module camera_ov7670_top(
	input				clk				,//系统时钟50MHz
	input				rst_n			,//系统复位
	input				vsync			,//OV7670模块输入场同步信号
	input				href			,//OV7670模块输入行同步信号
	input	[7:0]		din				,//OV7670模块摄像头数据输入
	input				pclk			,//OV7670模块像素时钟输入
	
	output				scl				,//OV7670模块配置SCCB协议时钟线
	inout				sda				,//OV7670模块配置SCCB协议数据线
	output				xclk			,//OV7670模块输入时钟
	output				pwdn			,//OV7670模块模式选择 0:工作 1:POWER DOWN
	output				reset			,//OV7670模块初始化所有寄存器到默认值 0:RESET 模式 1:一般模式

	output	wire		VGA_clk			,//25MHz
	output	wire[23:0]	VGA_RGB			,//VGA模块图像数据{R[7:0],G[7:0],B[7:0]}
	output	wire		VGA_HS			,//VGA模块行同步信号
	output	wire		VGA_VS			,//VGA模块场同步信号
	output	wire		VGA_BLK			,//VGA模块消影信号

	output	wire		sdram_clk		,//SDRAM时钟信号
	output	wire[3:0]	sdram_commond	,//{cs,ras,cas,we}
	output	wire		sdram_cke		,//时钟使能信号
	output	wire[1:0]	sdram_dqm		,//数据线屏蔽信号
	output	wire[11:0]	sdram_addr		,//SDRAM地址线
	output	wire[1:0]	sdram_bank		,//SDRAM bank选取
	inout	wire[15:0]	sdram_dq		 //SDRAM数据输出输入总线
	);

	wire				clk_25M				;
	wire				clk_100M			;

	wire				init_done			;
	wire	[15:0]		data_rgb565			;
	wire				data_rgb565_vld		;
	wire				ov7670_vsync		;
	wire				fifo_rdreq			;
	wire	[15:0]		wr_data				;
	wire	[7:0]		rdusedw				;
	wire	[7:0]		rdusedw1			;
	wire				wdata_done			;
	wire				wr_en				;
	wire	[11:0]		wr_row_addr			;
	wire	[8:0]		wr_col_addr			;
	wire				rd_en				;
	reg		[11:0]		row_addr			;
	reg		[8:0]		col_addr			;
	wire	[15:0]		rd_data				;
	wire				rd_data_vld			;
	wire				rdata_done			;
	wire	[11:0]		rd_row_addr			;
	wire	[8:0]		rd_col_addr			;
	wire				dat_act				;
	wire	[15:0]		q					;

	wire				wr_addr_req			;
	wire				rd_addr_req			;
	wire				state_wr_req		;
	wire				state_rd_req		;

	assign xclk = clk_25M;
	assign pwdn = 0;
	assign reset = 1;

	always @(*) begin
		if (!rst_n) begin
			row_addr <= 12'd0;
			col_addr <= 9'd0;
		end
		else if (wr_addr_req) begin
			row_addr <= wr_row_addr;
			col_addr <= wr_col_addr;
		end
		else if (rd_addr_req) begin
			row_addr <= rd_row_addr;
			col_addr <= rd_col_addr;
		end
		else begin
			row_addr <= row_addr;
			col_addr <= col_addr;
		end
	end

	assign wr_addr_req = (wr_en&&!rd_addr_req&&state_wr_req)?1'b1:1'b0;
	assign rd_addr_req = (rd_en&&state_rd_req)?1'b1:1'b0;


	pll inst_pll
		(
			.inclk0(clk), 
			.c0(clk_100M), 
			.c1(clk_25M)
		);

	ov7670_init inst_ov7670_init 
		(
			.clk(clk_25M), 
			.rst_n(rst_n), 
			.scl(scl), 
			.sda(sda), 
			.init_done(init_done)
		);


	ov7670_data_16rgb565 inst_ov7670_data_16rgb565
		(
			.clk             (pclk),
			.rst_n           (rst_n),
			.vsync           (vsync),
			.href            (href),
			.din             (din),
			.init_done       (init_done),
			.data_rgb565     (data_rgb565),
			.data_rgb565_vld (data_rgb565_vld),
			.ov7670_vsync    (ov7670_vsync)  //用来给写FIFO清零
		);

	async_fifo wr_async_fifo
		(
			.aclr    (ov7670_vsync),
			.data    (data_rgb565),
			.rdclk   (clk_100M),
			.rdreq   (fifo_rdreq),
			.wrclk   (clk_25M),
			.wrreq   (data_rgb565_vld),
			.q       (wr_data),
			.rdempty (),
			.rdusedw (rdusedw),
			.wrfull  ()
		);

	wr_control inst_wr_control
		(
			.clk              (clk_100M),
			.rst_n            (rst_n),
			.rdusedw          (rdusedw),
			.row_addr_max     (10'd600),
			.col_addr_max     (10'd512),
			.sdram_wdata_done (wdata_done),
			.aclr             (ov7670_vsync),
			.sdram_wr_en      (wr_en),
			.wr_row_addr      (wr_row_addr),	//
			.wr_col_addr      (wr_col_addr)		//
		);


	SDRAM_control inst_SDRAM_control
		(
			.clk           (clk_100M),
			.rst_n         (rst_n),
			.wr_en         (wr_en),
			.wr_data       (wr_data),
			.rd_en         (rd_en),
			.bank_addr     (2'b00),
			.row_addr      (row_addr),
			.col_addr      (col_addr),

			.fifo_rdreq    (fifo_rdreq),
			.rd_data       (rd_data),
			.rd_data_vld   (rd_data_vld),
			.wr_data_vld   (),
			.wdata_done    (wdata_done),
			.rdata_done    (rdata_done),

			.sdram_clk     (sdram_clk),
			.sdram_commond (sdram_commond),
			.sdram_cke     (sdram_cke),
			.sdram_dqm     (sdram_dqm),
			.sdram_addr    (sdram_addr),
			.sdram_bank    (sdram_bank),
			.sdram_dq      (sdram_dq),
			.state_wr_req  (state_wr_req),
			.state_rd_req  (state_rd_req)
		);

	rd_control inst_rd_control
		(
			.clk              (clk_100M),
			.rst_n            (rst_n),
			.rdusedw          (rdusedw1),
			.row_addr_max     (10'd600),
			.col_addr_max     (10'd512),
			.sdram_rdata_done (rdata_done),
			.aclr			  (),
			.sdram_rd_en      (rd_en),
			.rd_row_addr      (rd_row_addr),	//
			.rd_col_addr      (rd_col_addr)		//
		);

	async_fifo_ahead inst_async_fifo_ahead
		(
			.aclr	 (),
			.data    (rd_data),
			.rdclk   (clk_25M),
			.rdreq   (dat_act),
			.wrclk   (clk_100M),
			.wrreq   (rd_data_vld),
			.q       (q),
			.rdempty (),
			.rdusedw (rdusedw1),
			.wrfull  ()
		);

	VGA_ctrl inst_VGA_ctrl 
		(
			.clk_25M (clk_25M),
			.rst_n   (rst_n),
			.data_in ({q[15:11],3'b000,q[10:5],2'b00,q[4:0],3'b000}),
			.VGA_clk (VGA_clk),
			.VGA_RGB (VGA_RGB),
			.VGA_HS  (VGA_HS),
			.VGA_VS  (VGA_VS),
			.VGA_BLK (VGA_BLK),
			.hcount  (),
			.vcount  (),
			.dat_act (dat_act)
		);

endmodule

三、仿真测试

仿真的时候没有加入摄像头数据流部分,从写FIFO开始,数据自己设置写入写FIFO,经过SDRAM存储,这里用SDRAM的仿真模型模拟SDRAM,然后读出数据到读FIFO中,再从读FIFO中读出数据。

测试模型的顶层代码为:


// Company  : 
// Engineer : 
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534    PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date    : 2020-10-20 17:19:56
// Revise Data    : 2020-10-20 17:19:56
// File Name      : SDRAM_control_tb.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions  : Vivado 2019.2
// Revision       : V1.1
// Editor         : sublime text3, tab size (4)
// Description    : 
】
module SDRAM_control_test(
	input		clk_25M,
	input		clk_100M,
	input		rst_n,
	input	[15:0]	data_rgb565,
	input			data_rgb565_vld,
	input			ov7670_vsync,
	output	[15:0]	q
	);

reg	[11:0]		row_addr;
reg	[8:0]		col_addr;

wire			fifo_rdreq;
wire	[15:0]	wr_data;
wire	[7:0]	rdusedw;
wire			wdata_done;
wire			wr_en;
wire	[11:0]	wr_row_addr;
wire	[8:0]	wr_col_addr;
wire			rd_en;
wire	[15:0]	rd_data;
wire			rd_data_vld;
wire			rdata_done;
wire			sdram_clk;
wire	[3:0]	sdram_commond;
wire			sdram_cke;
wire	[1:0]	sdram_dqm;
wire	[11:0]	sdram_addr;
wire	[1:0]	sdram_bank;
wire	[15:0]	sdram_dq;
wire	[7:0]	rdusedw1;
wire	[11:0]	rd_row_addr;
wire	[8:0]	rd_col_addr;
// wire	[15:0]	q;
wire				wr_addr_req			;
wire				rd_addr_req			;
wire				state_wr_req		;
wire				state_rd_req		;

//重点出错
assign row_addr = state_wr_req ? wr_row_addr : rd_row_addr;
assign col_addr = state_wr_req ? wr_col_addr : rd_col_addr;


async_fifo wr_async_fifo
		(
			.aclr    (ov7670_vsync),
			.data    (data_rgb565),
			.rdclk   (clk_100M),
			.rdreq   (fifo_rdreq),
			.wrclk   (clk_25M),
			.wrreq   (data_rgb565_vld),
			.q       (wr_data),
			.rdempty (),
			.rdusedw (rdusedw),
			.wrfull  ()
		);
wr_control inst_wr_control
		(
			.clk              (clk_100M),
			.rst_n            (rst_n),
			.rdusedw          (rdusedw),
			.row_addr_max     (10'd600),
			.col_addr_max     (10'd512),
			.sdram_wdata_done (wdata_done),
			.aclr             (ov7670_vsync),
			.sdram_wr_en      (wr_en),
			.wr_row_addr      (wr_row_addr),	//
			.wr_col_addr      (wr_col_addr)		//
		);

SDRAM_control inst_SDRAM_control
		(
			.clk           (clk_100M),
			.rst_n         (rst_n),
			.wr_en         (wr_en),
			.wr_data       (wr_data),
			.rd_en         (rd_en),
			.bank_addr     (2'b00),
			.row_addr      (row_addr),
			.col_addr      (col_addr),

			.fifo_rdreq    (fifo_rdreq),
			.rd_data       (rd_data),
			.rd_data_vld   (rd_data_vld),
			.wr_data_vld   (),
			.wdata_done    (wdata_done),
			.rdata_done    (rdata_done),

			.sdram_clk     (sdram_clk),
			.sdram_commond (sdram_commond),
			.sdram_cke     (sdram_cke),
			.sdram_dqm     (sdram_dqm),
			.sdram_addr    (sdram_addr),
			.sdram_bank    (sdram_bank),
			.sdram_dq      (sdram_dq),
			.state_wr_req  (state_wr_req),
			.state_rd_req  (state_rd_req)
		);

sdram_model_plus #(
		.addr_bits(12),
		.data_bits(16),
		.col_bits(9),
		.mem_sizes(640*500)
	) inst_sdram_model_plus (
		.Dq    (sdram_dq),
		.Addr  (sdram_addr),
		.Ba    (sdram_bank),
		.Clk   (sdram_clk),
		.Cke   (sdram_cke),
		.Cs_n  (sdram_commond[3]),
		.Ras_n (sdram_commond[2]),
		.Cas_n (sdram_commond[1]),
		.We_n  (sdram_commond[0]),
		.Dqm   (sdram_dqm),
		.Debug (1'b1)
	);

rd_control inst_rd_control
		(
			.clk              (clk_100M),
			.rst_n            (rst_n),
			.rdusedw          (rdusedw1),
			.row_addr_max     (10'd600),
			.col_addr_max     (10'd512),
			.sdram_rdata_done (rdata_done),
			.aclr			  (),
			.sdram_rd_en      (rd_en),
			.rd_row_addr      (rd_row_addr),	//
			.rd_col_addr      (rd_col_addr)		//
		);

async_fifo_ahead inst_async_fifo_ahead
		(
			.aclr	 (),
			.data    (rd_data),
			.rdclk   (clk_25M),
			.rdreq   (1'b1),
			.wrclk   (clk_100M),
			.wrreq   (rd_data_vld),
			.q       (q),
			.rdempty (),
			.rdusedw (rdusedw1),
			.wrfull  ()
		);

endmodule

测试代码为:发送两帧数据


`timescale 1ns/1ns

module SDRAM_control_test_tb (); /* this is automatically generated */

	reg rst_n;
	reg clk_100M;
	reg clk_25M;


	// (*NOTE*) replace reset, clock, others

	reg [15:0] data_rgb565;
	reg        data_rgb565_vld;
	reg        ov7670_vsync;
	wire [15:0] q;

	SDRAM_control_test inst_SDRAM_control_test
		(
			.clk_25M         (clk_25M),
			.clk_100M        (clk_100M),
			.rst_n           (rst_n),
			.data_rgb565     (data_rgb565),
			.data_rgb565_vld (data_rgb565_vld),
			.ov7670_vsync    (ov7670_vsync),
			.q               (q)
		);

initial clk_25M = 1;
always #20 clk_25M = ~clk_25M;
initial clk_100M = 1;
always #5 clk_100M = ~clk_100M;

initial begin
	#1;
	rst_n = 0;
	data_rgb565 = 0;
	data_rgb565_vld = 0;
	ov7670_vsync = 0;
	#200;
	rst_n = 1;
	#200;

	@(posedge inst_SDRAM_control_test.inst_SDRAM_control.mode_to_idle_start)
	#2000;
	data_rgb565_vld = 1;

	repeat(600)begin
		repeat(512)begin
		#40;
		data_rgb565 = data_rgb565 + 1;
		end
	end

	data_rgb565_vld = 0;
	#20000;

	data_rgb565_vld = 1;
	repeat(600)begin
		repeat(512)begin
		#40;
		data_rgb565 = data_rgb565 + 1;
		end
	end

	#200;
	$stop;

end

endmodule

仿真后可观察modelsim控制台信息,观察SDRAM数据读写的记录,从中观察是否有错误。如图:

这里容易出错的地方是SDRAM读写地址,仿真中容易在读数据时用的是写数据的行地址,如图:

SDRAM读写地址代码如下,这里高了挺久,很容易出错,对时序要求挺高,只能用组合电路实现。

四、上板验证

刚开始写好代码上板图:

调好焦距以后(很大部分数据错乱):

一次修改后上板图(少部分数据错乱):

最终图(无数据丢失错乱):

 

五、总结

这个代码写好挺久了,但有问题。两三个星期,然后因为各种事情耽搁了,就一直没有修改,找问题。一开始出的图数据丢失错乱严重,通过quartus软件的sjgnal tap logic analyzer工具抓取数据分析,由于抓取数据有限,根本看不出来哪里的问题,因此后边做了挺久也没找到问题在哪。自己分析应该是SDRAM控制器写的有问题,于是就对SDRAM控制器进行了仿真测试,当然测试情况要多一些,然后还是发现没什么问题。然后昨天写了个测试代码,就是上边给出来的,仿真测试的时候,在modelsim控制台输出信息有sdram读取数据的信息,就发现读数据时行地址有时候会用上一次读数据的行地址,这个问题从这里就找出来了,通过定位发现,在顶层模块中,对SDRAM读写地址部分代码写的有问题,对时序要求较高,才出现问题,这里对这个问题不做细讲,第一次修改后明显图像好了很多,还是有数据错乱丢失,其实还是这里没写好,经过修改后就没有这些问题了。

在代码出现问题的时候千万不要嫌麻烦,多动手多测试,问题总会解决的。


工程文件下载链接:https://download.csdn.net/download/qq_33231534/13010542


 

基于 FPGA 的 OV5640 摄像头显示例程可以分为以下几个步骤: 1. 硬件连接 将 OV5640 摄像头连接到 FPGA 板上的相应接口(一般为 MIPI CSI 接口),并将 FPGA 板连接到显示器上。 2. 寄存器配置 配置 OV5640 摄像头的寄存器,使其能够输出图像数据。可以使用 I2C 线与 OV5640 摄像头通信,通过写入寄存器来配置摄像头。具体的寄存器配置可以参考 OV5640 数据手册。 3. 数据传输 将 OV5640 摄像头输出的数据传输到 FPGA 板上。一般来说,数据传输方式有两种: - 并行传输:将 OV5640 摄像头输出的像素数据通过并行接口传输到 FPGA 板上。这种传输方式需要使用大量的 FPGA 引脚,因此不太常用。 - MIPI CSI-2 串行传输:将 OV5640 摄像头输出的像素数据通过 MIPI CSI-2 串行接口传输到 FPGA 板上。这种传输方式需要使用较少的引脚,因此比较常用。 4. 图像处理 将传输到 FPGA 板上的图像数据进行处理,以便在显示器上显示。具体的图像处理算法根据需求而定,可以包括缩放、旋转、滤波等操作。 5. 显示器输出 将处理后的图像数据输出到显示器上进行显示。可以使用 VGA、HDMI 等接口将 FPGA 板连接到显示器上。 需要注意的是,OV5640 摄像头的驱动和图像处理需要使用 FPGA 开发板上的软件进行实现。具体的软件实现方式要根据 FPGA 开发板和摄像头的具体情况来定。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值