总:基于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 ,//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摄像头显示相关推荐
- 【OV7670】基于FPGA的OV7670摄像头介绍和使用
1.软件版本 quartusii12.1 2.本算法理论知识 OV7670摄像头模块 带384Kb FIFO 数字摄像头 手动变焦 OV7670总共有656*488个像素,其中640*480个有效 ...
- 基于FPGA的OV7670摄像头实时检测
目录 前言:整体系统框图 一.OV7670摄像头简介 二.OV7670 SCCB协议简介 三.OV7670初始化寄存器配置 四.OV7670初始化代码编写 五.什么是DVP? 六.摄像头写数据请求 七 ...
- OV7670摄像头显示
第1节 –作者:小黑同学 本文为明德扬原创及录用文章,转载请注明出处! 1.1 总体设计 1.1.1 概述 OV7670是一种图像传感器,图像传感器,体积小,工作电压低,提供单片VGA摄像头和影像处理 ...
- 基于FPGA的数码管显示出租车计费器
基于FPGA简易出租车计价器设计技术规范 专业:集成电路设计与集成系统 班级:电路1401班 姓名:童峥 学号:05146034 一. 功能描述: 本设计基于FPGA设计并使用Verilog HDL硬 ...
- 基于FPGA的HDMI图片显示
第一次写博客,就当是练手了吧QAQ 无病呻吟[滑稽] 1.导出图片数据 2.将数据存入ROM中 3.程序设计 运行结果 总结 无病呻吟[滑稽] 首先说一下我用的板子,是XILINX的zynq-7020 ...
- 基于STM32的OV7670摄像头总结
一.OV7670模块: 介绍一下OV7670传感器:CMOS器件:标准的SCCB接口,兼容IIC接口:内置感光阵列,时序发生器,AD转换器,模拟信号处理,数字信号处理器..... ...
- 基于 FPGA 的 HDMI/DVI 显示
文章目录 前言 一.HDMI 与 DVI 的区别与联系 1.1 DVI 接口含义 1.2 HDMI 接口含义 1.3 HDMI 与 DVI 的区别 1.4 HDMI 与 DVI 的兼容性 1.5 HD ...
- 【接口协议】基于 FPGA 的 HMDI 彩条显示实验
目录 HDMI 介绍 HDMI 引脚定义 TMDS 介绍 编码模块 代码实现 并转串模块 视频时序标准 传输通道顶层 顶层模块 工程搭建 HDMI 介绍 HDMI,高清晰度多媒体接口(High Def ...
- 基于FPGA的数字时钟显示(万年历lcd1602)
一. lcd1602a的驱动和配置 (1) lcd1602a的管脚分配图,在程序中,我们需要对相应的管脚进行操作,才能使其正确显示 (2) 由于我使用的板子EP4CE6上的晶振是50MHZ,而lc ...
最新文章
- 过程即奖励(The Journey is the Reward)
- 网络拓扑手工绘制不可或缺
- 系列文章--oracle简单入门教程
- 如何将Pytorch生成的模型进行CPU部署
- 标签体系、用户分群、用户画像「玩味」解读,你沦为形式主义了吗?
- 微软职位内部推荐-Senior PM
- memset函数具体说明
- linux怎么远程命令,Linux远程命令
- CF605E-Intergalaxy Trips【期望dp】
- java ojdbc 还需要装 oracle client 吗,ojdbc连接数据库
- 20201221:力扣220场周赛题解
- 互评成绩 c语言,1077 互评成绩计算 (C语言)
- ubuntu生成pem证书连接服务器(已验证)
- 【学习笔记】powell法的python实现
- 汽车抛负载瞬态7637-5A/5B测试,您不知道的都在这里
- 番茄工作法 计划表格式
- 深度学习论文-Cyclical Learning Rates for Training Neural Networks
- 获取第二天凌晨12点时间
- java cms 垃圾回收_了解Java垃圾自动回收
- 发音程序c语言,用C语言发声