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

it2025-08-17  13

目录

 

前言:

一、整体系统设计

二、各部分模块设计

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 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 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 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 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 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 ; //重点出错 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; 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


 

最新回复(0)