目录

前言:

一、整体系统设计

二、各部分模块设计

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             ,//100MHzinput              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) beginif (!rst_n) beginsdram_wr_en <= 0;endelse if (rdusedw>=8) beginsdram_wr_en <= 1;endelse beginsdram_wr_en <= 0;endendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginwr_col_addr <= 9'd0;endelse if (sdram_wdata_done) beginif (wr_col_addr==col_addr_max-4'd8) beginwr_col_addr <= 9'd0;endelse beginwr_col_addr <= wr_col_addr + 4'd8;              endendelse if (aclr) beginwr_col_addr <= 0;endendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginwr_row_addr <= 12'd0;endelse if (sdram_wdata_done && wr_col_addr==col_addr_max-4'd8) beginif (wr_row_addr==row_addr_max-1) beginwr_row_addr <= 12'd0;endelse beginwr_row_addr <= wr_row_addr + 1'b1;endendelse if (aclr) beginwr_row_addr <= 0;endendendmodule

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             ,//时钟100MHzinput                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) beginif (!rst_n) beginsdram_rd_en <= 0;endelse if (rdusedw<=8) beginsdram_rd_en <= 1;endelse beginsdram_rd_en <= 0;endendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginrd_col_addr <= 9'd0;endelse if (sdram_rdata_done) beginif (rd_col_addr==col_addr_max-4'd8) beginrd_col_addr <= 9'd0;endelse beginrd_col_addr <= rd_col_addr + 4'd8;             endendelse if (aclr) beginrd_col_addr <= 0;endend always @(posedge clk or negedge rst_n) beginif (!rst_n) beginrd_row_addr <= 12'd0;endelse if (sdram_rdata_done && rd_col_addr==col_addr_max-4'd8) beginif (rd_row_addr==row_addr_max-1) beginrd_row_addr <= 9'd0;endelse beginrd_row_addr <= rd_row_addr + 1'b1;endendelse if (aclr) beginrd_row_addr <= 0;endendendmodule

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为3module SDRAM_control(input                   clk             ,//100MHZinput                  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=3localparam TWRITE_8       = 4'd8            ;//burst=8localparam 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) beginif (!rst_n) beginstate_c <= NOP;            endelse beginstate_c <= state_n;endendalways @(*)begincase(state_c)NOP :beginif (nop_to_pre_start) beginstate_n = PRECHARGE;endelse beginstate_n = state_c;endendPRECHARGE:beginif (pre_to_ref_start) beginstate_n = REF;endelse if (pre_to_idle_start) beginstate_n = IDLE;endelse beginstate_n = state_c;endendREF:beginif (ref_to_mode_start) beginstate_n = MODE;endelse if (ref_to_idle_start) beginstate_n = IDLE;endelse if (ref_to_ref_start) beginstate_n = REF;endelse beginstate_n = state_c;endendMODE:beginif (mode_to_idle_start) beginstate_n = IDLE;endelse beginstate_n =state_c;endendIDLE:beginif (idle_to_active_start) beginstate_n = ACTIVE;endelse if (idle_to_ref_start) beginstate_n = REF;endelse beginstate_n = state_c;endendACTIVE:beginif (active_to_write_start) beginstate_n = WRITE;endelse if (active_to_read_start) beginstate_n = READ;endelse beginstate_n = state_c;endendWRITE:beginif(write_to_pre_start)beginstate_n = PRECHARGE;endelse beginstate_n = state_c;endendREAD:beginif (read_to_pre_start) beginstate_n = PRECHARGE;endelse beginstate_n = state_c;endenddefault:state_n = IDLE;endcaseendassign 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)beginif (!rst_n) beginsdram_commond <= NOP_CMD;endelse if (nop_to_pre_start || write_to_pre_start || read_to_pre_start) beginsdram_commond <= PRECHARGE_CMD;endelse if (pre_to_ref_start || ref_to_ref_start || idle_to_ref_start) beginsdram_commond <= REF_CMD;endelse if (ref_to_mode_start) beginsdram_commond <= MODE_CMD;endelse if (idle_to_active_start) beginsdram_commond <= ACTIVE_CMD;endelse if (active_to_write_start) beginsdram_commond <= WRITE_CMD;endelse if (active_to_read_start) beginsdram_commond <= READ_CMD;endelse beginsdram_commond <= NOP_CMD;endend//cke信号保持拉高assign sdram_cke = 1;//dqm信号always  @(posedge clk or negedge rst_n)beginif(!rst_n)beginsdram_dqm <= 2'b11;endelse if(init_done)beginsdram_dqm <= 2'b00;endend//地址线always  @(posedge clk or negedge rst_n)beginif(!rst_n)beginsdram_addr <= 12'b0;endelse if(nop_to_pre_start || write_to_pre_start || read_to_pre_start)beginsdram_addr <= ALL_BANK;             endelse if(ref_to_mode_start)beginsdram_addr <= MODE_CONFIG;               endelse if(idle_to_active_start)beginsdram_addr <= row_addr;                                     endelse if (active_to_read_start || active_to_write_start) beginsdram_addr <= {3'b000,col_addr};endelse beginsdram_addr <= 12'b0;               endend//sdram_bankalways  @(posedge clk or negedge rst_n)beginif(!rst_n)beginsdram_bank <= 2'b00;endelse if (idle_to_active_start || active_to_write_start || active_to_read_start) beginsdram_bank <= bank_addr;endelse beginsdram_bank <= 2'b00;endend//sdram_dqassign 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)beginif (!rst_n) beginrd_data <= 16'd0;endelse beginrd_data <= sdram_dq;endend// assign rd_data = state_c==READ ? sdram_dq:16'hzzzz;//读有效标志always @(posedge clk or negedge rst_n)beginif (!rst_n) beginrd_data_vld_ff0 <= 0;endelse if (active_to_read_start) beginrd_data_vld_ff0 <= 1;endelse if (state_c==READ && cnt0==TREAD_11-4) beginrd_data_vld_ff0 <= 0;endendalways @(posedge clk or negedge rst_n)beginif (!rst_n) beginrd_data_vld_ff1 <= 0;rd_data_vld_ff2 <= 0;rd_data_vld_ff3 <= 0;rd_data_vld     <= 0;endelse beginrd_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;endend//刷新请求计数always @(posedge clk or negedge rst_n)beginif (!rst_n) beginauto_ref_cnt <= 0;endelse if (add_auto_ref_cnt) beginif (end_auto_ref_cnt) beginauto_ref_cnt <= 0;endelse beginauto_ref_cnt <= auto_ref_cnt + 1'b1;endendendassign 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)beginif (!rst_n) beginref_req <= 0;endelse if (end_auto_ref_cnt) beginref_req <= 1;endelse if (state_c==IDLE && ref_req==1) beginref_req <= 0;endend//初始化标志assign init_done = (state_c==MODE && end_cnt0);always @(posedge clk or negedge rst_n)beginif (!rst_n) begininit_flag <= 1;endelse if (init_done) begininit_flag <= 0;endendalways @(posedge clk or negedge rst_n)beginif (!rst_n) begincnt0 <= 0;endelse if (add_cnt0) beginif (end_cnt0) begincnt0 <= 0;endelse begincnt0 <= cnt0 + 1'b1;endendendassign add_cnt0 = state_c!=IDLE;assign end_cnt0 = add_cnt0 && cnt0==x-1'b1;always @(*)begincase(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;endcaseend//初始化自刷新8个周期计数always @(posedge clk or negedge rst_n)beginif (!rst_n) beginref_cnt1 <= 0;endelse if (add_cnt1) beginif (end_cnt1) beginref_cnt1 <= 0;endelse beginref_cnt1 <= ref_cnt1 + 1'b1;endendendassign 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)beginif (!rst_n) beginflag_rd <= 0;endelse if (state_c==IDLE && rd_en && ref_req==0) beginflag_rd <= 1;endelse if (pre_to_idle_start && flag_rd==1) beginflag_rd <= 0;endendalways @(posedge clk or negedge rst_n)beginif (!rst_n) beginflag_wr <= 0;endelse if (state_c==IDLE && wr_en && rd_en==0 && ref_req==0) begin  //读优先级高于写优先级flag_wr <= 1;endelse if (pre_to_idle_start && flag_wr==1) beginflag_wr <= 0;endend//写FIFO读请求信号always @(posedge clk or negedge rst_n)beginif (!rst_n) beginfifo_rdreq_f <= 0;endelse if (state_c==IDLE && wr_en && rd_en==0 && ref_req==0) beginfifo_rdreq_f <= 1;endelse if (fifo_rdreq_cnt>=7) beginfifo_rdreq_f <= 0;endendalways @(posedge clk or negedge rst_n)beginif (!rst_n) beginfifo_rdreq_cnt <= 0;endelse if (fifo_rdreq_f) beginif (fifo_rdreq_cnt >= 7) beginfifo_rdreq_cnt <= 0;endelse beginfifo_rdreq_cnt <= fifo_rdreq_cnt + 1'b1;endendend//延迟一拍,时序对齐always @(posedge clk or negedge rst_n)beginif (!rst_n) beginfifo_rdreq <= 0;endelse beginfifo_rdreq <= fifo_rdreq_f;endendendmodule

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             ,//系统时钟50MHzinput               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 DOWNoutput               reset           ,//OV7670模块初始化所有寄存器到默认值 0:RESET 模式 1:一般模式output   wire        VGA_clk         ,//25MHzoutput  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 @(*) beginif (!rst_n) beginrow_addr <= 12'd0;col_addr <= 9'd0;endelse if (wr_addr_req) beginrow_addr <= wr_row_addr;col_addr <= wr_col_addr;endelse if (rd_addr_req) beginrow_addr <= rd_row_addr;col_addr <= rd_col_addr;endelse beginrow_addr <= row_addr;col_addr <= col_addr;endendassign 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/1nsmodule SDRAM_control_test_tb (); /* this is automatically generated */reg rst_n;reg clk_100M;reg clk_25M;// (*NOTE*) replace reset, clock, othersreg [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)beginrepeat(512)begin#40;data_rgb565 = data_rgb565 + 1;endenddata_rgb565_vld = 0;#20000;data_rgb565_vld = 1;repeat(600)beginrepeat(512)begin#40;data_rgb565 = data_rgb565 + 1;endend#200;$stop;endendmodule

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

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

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

四、上板验证

刚开始写好代码上板图:

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

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

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

五、总结

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

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


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


总:基于FPGA的OV7670摄像头显示相关推荐

  1. 【OV7670】基于FPGA的OV7670摄像头介绍和使用

    1.软件版本 quartusii12.1 2.本算法理论知识 OV7670摄像头模块 带384Kb FIFO  数字摄像头  手动变焦 OV7670总共有656*488个像素,其中640*480个有效 ...

  2. 基于FPGA的OV7670摄像头实时检测

    目录 前言:整体系统框图 一.OV7670摄像头简介 二.OV7670 SCCB协议简介 三.OV7670初始化寄存器配置 四.OV7670初始化代码编写 五.什么是DVP? 六.摄像头写数据请求 七 ...

  3. OV7670摄像头显示

    第1节 –作者:小黑同学 本文为明德扬原创及录用文章,转载请注明出处! 1.1 总体设计 1.1.1 概述 OV7670是一种图像传感器,图像传感器,体积小,工作电压低,提供单片VGA摄像头和影像处理 ...

  4. 基于FPGA的数码管显示出租车计费器

    基于FPGA简易出租车计价器设计技术规范 专业:集成电路设计与集成系统 班级:电路1401班 姓名:童峥 学号:05146034 一. 功能描述: 本设计基于FPGA设计并使用Verilog HDL硬 ...

  5. 基于FPGA的HDMI图片显示

    第一次写博客,就当是练手了吧QAQ 无病呻吟[滑稽] 1.导出图片数据 2.将数据存入ROM中 3.程序设计 运行结果 总结 无病呻吟[滑稽] 首先说一下我用的板子,是XILINX的zynq-7020 ...

  6. 基于STM32的OV7670摄像头总结

    一.OV7670模块:      介绍一下OV7670传感器:CMOS器件:标准的SCCB接口,兼容IIC接口:内置感光阵列,时序发生器,AD转换器,模拟信号处理,数字信号处理器.....      ...

  7. 基于 FPGA 的 HDMI/DVI 显示

    文章目录 前言 一.HDMI 与 DVI 的区别与联系 1.1 DVI 接口含义 1.2 HDMI 接口含义 1.3 HDMI 与 DVI 的区别 1.4 HDMI 与 DVI 的兼容性 1.5 HD ...

  8. 【接口协议】基于 FPGA 的 HMDI 彩条显示实验

    目录 HDMI 介绍 HDMI 引脚定义 TMDS 介绍 编码模块 代码实现 并转串模块 视频时序标准 传输通道顶层 顶层模块 工程搭建 HDMI 介绍 HDMI,高清晰度多媒体接口(High Def ...

  9. 基于FPGA的数字时钟显示(万年历lcd1602)

    一.  lcd1602a的驱动和配置 (1)  lcd1602a的管脚分配图,在程序中,我们需要对相应的管脚进行操作,才能使其正确显示 (2) 由于我使用的板子EP4CE6上的晶振是50MHZ,而lc ...

最新文章

  1. 过程即奖励(The Journey is the Reward)
  2. 网络拓扑手工绘制不可或缺
  3. 系列文章--oracle简单入门教程
  4. 如何将Pytorch生成的模型进行CPU部署
  5. 标签体系、用户分群、用户画像「玩味」解读,你沦为形式主义了吗?
  6. 微软职位内部推荐-Senior PM
  7. memset函数具体说明
  8. linux怎么远程命令,Linux远程命令
  9. CF605E-Intergalaxy Trips【期望dp】
  10. java ojdbc 还需要装 oracle client 吗,ojdbc连接数据库
  11. 20201221:力扣220场周赛题解
  12. 互评成绩 c语言,1077 互评成绩计算 (C语言)
  13. ubuntu生成pem证书连接服务器(已验证)
  14. 【学习笔记】powell法的python实现
  15. 汽车抛负载瞬态7637-5A/5B测试,您不知道的都在这里
  16. 番茄工作法 计划表格式
  17. 深度学习论文-Cyclical Learning Rates for Training Neural Networks
  18. 获取第二天凌晨12点时间
  19. java cms 垃圾回收_了解Java垃圾自动回收
  20. 发音程序c语言,用C语言发声

热门文章

  1. 常见的加解密及编码总结
  2. 基于h5的微课教学系统
  3. 使用 JDK 14 的 jpackage 打包 Java 应用
  4. Miku Miku Dance DxOpenNI for OpenNI 2
  5. 人工智能会取代人类的艺术创造力吗
  6. 这20件事千万不要对自己做!
  7. 企业内容管理软件的核心技术及应用方向(计世网)
  8. 清华优博论文丨物体检测中的特征构建与模型优化
  9. base64的图片处理技术
  10. amazon linux虚拟主机,如何在Amazon AWS上设置Linux服务器