总:基于FPGA的OV7670摄像头显示
目錄
?
前言:
一、整體系統(tǒng)設(shè)計
二、各部分模塊設(shè)計
1、時鐘模塊
2、OV7670初始化模塊
3、DVP協(xié)議數(shù)據(jù)流模塊
4、寫FIFO模塊
5、讀FIFO模塊
6、寫FIFO控制模塊
7、讀FIFO控制模塊
8、SDRAM控制模塊
9、VGA控制模塊
10、頂層模塊
三、仿真測試
四、上板驗(yàn)證
五、總結(jié)
工程文件下載鏈接:https://download.csdn.net/download/qq_33231534/13010542
前言:
這個專題的博客中寫的都是關(guān)于OV7670攝像頭顯示所需要的模塊,并對每個模塊進(jìn)行仿真驗(yàn)證,最后再對每個模塊進(jìn)行整合,本篇就是對整個攝像頭系統(tǒng)進(jìn)行整合和匯總。在過程中遇到很多問題,應(yīng)該也會是很多人會遇到的問題,涉及到調(diào)試和代碼的問題,在下邊也會加以講解。
一、整體系統(tǒng)設(shè)計
如下系統(tǒng)框圖:
圖:整體系統(tǒng)框圖?如圖所示,FPGA中主要模塊包含:時鐘模塊、OV7670初始化模塊、DVP協(xié)議數(shù)據(jù)流模塊、寫FIFO模塊、寫FIFO控制模塊、SDRAM控制模塊、讀FIFO模塊、讀FIFO控制模塊、VGA控制模塊。
其中OV7670初始化模塊、DVP協(xié)議數(shù)據(jù)流模塊和VGA控制模塊都在本專題博客中寫過,這里不再贅述。寫FIFO和讀FIFO模塊使用的IP核,都是寬度16位,長度256,其中讀FIFO使用的是showahead模式。SDRAM控制器漆面的博客也寫過,這邊做了一些改動,添加了一些需要的信號。
其整體流程為:啟動時先對攝像頭進(jìn)行初始化設(shè)置,初始化完成后,FPGA從攝像頭獲取一幀一幀的圖像數(shù)據(jù),根據(jù)數(shù)據(jù)手冊將ov7670數(shù)據(jù)流轉(zhuǎn)換成我們需要的RGB565數(shù)據(jù)流,隨后存入寫FIFO模塊;(寫控制模塊)當(dāng)寫FIFO模塊中存儲的數(shù)據(jù)大于等于8時,發(fā)出SDRAM寫請求,SDRAM寫請求通過后,讀取FIFO數(shù)據(jù)存儲起來;(讀FIFO模塊)當(dāng)讀FIFO數(shù)據(jù)小于等于8時,讀取SDRAM中的數(shù)據(jù)經(jīng)過讀FIFO緩存后送入VGA顯示模塊進(jìn)行顯示。同時寫控制模塊和讀控制模塊控制SDRAM讀寫地址的增加。
二、各部分模塊設(shè)計
1、時鐘模塊
這里使用PLL的IP核,以50MHz時鐘生成25MHz和100MHz時鐘,其中攝像頭初始化模塊和VGA控制模塊使用的是25MHz,SDRAM控制模塊、寫FIFO控制模塊和讀FIFO控制模塊使用的是100MHz,寫FIFO和讀FIFO模塊都是異步FIFO,使用25MHz和100MHz時鐘。
2、OV7670初始化模塊
前面博客講的很詳細(xì),也沒有改動,這里不再贅述。
3、DVP協(xié)議數(shù)據(jù)流模塊
前面博客講的很詳細(xì),也沒有改動,這里不再贅述。
4、寫FIFO模塊
這里使用的IP核,數(shù)據(jù)寬度為16,長度為256,普通模式。
5、讀FIFO模塊
這里使用的IP核,數(shù)據(jù)寬度為16,長度為256,showahead模式。showahead模式是為了在讀出FIFO數(shù)據(jù)時先出一個數(shù)據(jù),和VGA顯示的數(shù)據(jù)有效信號好對齊。
6、寫FIFO控制模塊
主要實(shí)現(xiàn)兩個功能:
(1)當(dāng)寫FIFO中數(shù)據(jù)大于等于8時,向SDRAM控制器發(fā)出寫請求信號
(2)發(fā)送SDRASM控制器寫地址(行地址和列地址),當(dāng)SDRAM控制器寫完數(shù)據(jù)后,寫地址進(jìn)行相應(yīng)變化。
代碼如下:
// 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 ,//系統(tǒng)復(fù)位input [7:0] rdusedw ,//寫FIFO中數(shù)據(jù)個數(shù)input [9:0] row_addr_max ,//行地址最大input [9:0] col_addr_max ,//列地址最大input sdram_wdata_done,//SDRAM寫數(shù)據(jù)結(jié)束標(biāo)志input aclr ,//一幀結(jié)束清零信號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;endendendmodule7、讀FIFO控制模塊
主要實(shí)現(xiàn)兩個功能:
(1)當(dāng)讀FIFO中數(shù)據(jù)小于等于8時,向SDRAM控制器發(fā)出讀請求信號
(2)發(fā)送SDRASM控制器讀地址(行地址和列地址),當(dāng)SDRAM控制器讀完數(shù)據(jù)后,讀地址進(jìn)行相應(yīng)變化。
代碼如下:
// 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 ,//復(fù)位信號input [7:0] rdusedw ,//讀FIFO中數(shù)據(jù)個數(shù)input [9:0] row_addr_max ,//行地址最大input [9:0] col_addr_max ,//列地址最大input sdram_rdata_done,//SDRAM讀數(shù)據(jù)結(jié)束標(biāo)志input aclr ,//一幀結(jié)束清零標(biāo)志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;endendendmodule8、SDRAM控制模塊
前面有個專題專門講的SDRAM控制器原理和實(shí)現(xiàn)方法,這里在原來的代碼上,根據(jù)現(xiàn)在系統(tǒng)的需求,進(jìn)行了略微的修改,主要功能沒有修改,只是增加幾個外部需要的信號,讀優(yōu)先級還是大于寫優(yōu)先級(寫大于讀也可以)。
這里給出代碼:
// 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 ,//復(fù)位input wr_en ,//寫使能信號input [15:0] wr_data ,//寫數(shù)據(jù)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 ,//讀出的數(shù)據(jù)output reg rd_data_vld ,//讀出數(shù)據(jù)有效位output wire wr_data_vld ,//寫入數(shù)據(jù)有效位output wire wdata_done ,//寫數(shù)據(jù)結(jié)束標(biāo)志output wire rdata_done ,//讀數(shù)據(jù)結(jié)束標(biāo)志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 ,//數(shù)據(jù)線屏蔽信號output reg [11:0] sdram_addr ,//SDRAM地址線output reg [1:0] sdram_bank ,//SDRAM bank選取inout wire[15:0] sdram_dq , //SDRAM數(shù)據(jù)輸出輸入總線output wire state_wr_req , //新增,用于讀寫地址判斷output wire state_rd_req //新增,用于讀寫地址判斷);//延時localparam TWAIT_200us = 15'd20000 ;//上電等待時間localparam TRP = 2'd3 ;//預(yù)充電周期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 ;//狀態(tài)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;//預(yù)充電地址線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;//讀有效標(biāo)志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//刷新請求計數(shù)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//初始化標(biāo)志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個周期計數(shù)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);//讀寫信號標(biāo)志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 //讀優(yōu)先級高于寫優(yōu)先級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//延遲一拍,時序?qū)Ralways @(posedge clk or negedge rst_n)beginif (!rst_n) beginfifo_rdreq <= 0;endelse beginfifo_rdreq <= fifo_rdreq_f;endendendmodule9、VGA控制模塊
本專題博客講的很詳細(xì),也沒有改動,這里不再贅述。
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 ,//系統(tǒng)時鐘50MHzinput rst_n ,//系統(tǒng)復(fù)位input vsync ,//OV7670模塊輸入場同步信號input href ,//OV7670模塊輸入行同步信號input [7:0] din ,//OV7670模塊攝像頭數(shù)據(jù)輸入input pclk ,//OV7670模塊像素時鐘輸入output scl ,//OV7670模塊配置SCCB協(xié)議時鐘線inout sda ,//OV7670模塊配置SCCB協(xié)議數(shù)據(jù)線output xclk ,//OV7670模塊輸入時鐘output pwdn ,//OV7670模塊模式選擇 0:工作 1:POWER DOWNoutput reset ,//OV7670模塊初始化所有寄存器到默認(rèn)值 0:RESET 模式 1:一般模式output wire VGA_clk ,//25MHzoutput wire[23:0] VGA_RGB ,//VGA模塊圖像數(shù)據(jù){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 ,//數(shù)據(jù)線屏蔽信號output wire[11:0] sdram_addr ,//SDRAM地址線output wire[1:0] sdram_bank ,//SDRAM bank選取inout wire[15:0] sdram_dq //SDRAM數(shù)據(jù)輸出輸入總線);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三、仿真測試
仿真的時候沒有加入攝像頭數(shù)據(jù)流部分,從寫FIFO開始,數(shù)據(jù)自己設(shè)置寫入寫FIFO,經(jīng)過SDRAM存儲,這里用SDRAM的仿真模型模擬SDRAM,然后讀出數(shù)據(jù)到讀FIFO中,再從讀FIFO中讀出數(shù)據(jù)。
測試模型的頂層代碼為:
// 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 ;//重點(diǎn)出錯 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測試代碼為:發(fā)送兩幀數(shù)據(jù)
`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數(shù)據(jù)讀寫的記錄,從中觀察是否有錯誤。如圖:
這里容易出錯的地方是SDRAM讀寫地址,仿真中容易在讀數(shù)據(jù)時用的是寫數(shù)據(jù)的行地址,如圖:
SDRAM讀寫地址代碼如下,這里高了挺久,很容易出錯,對時序要求挺高,只能用組合電路實(shí)現(xiàn)。
四、上板驗(yàn)證
剛開始寫好代碼上板圖:
調(diào)好焦距以后(很大部分?jǐn)?shù)據(jù)錯亂):
一次修改后上板圖(少部分?jǐn)?shù)據(jù)錯亂):
最終圖(無數(shù)據(jù)丟失錯亂):
?
五、總結(jié)
這個代碼寫好挺久了,但有問題。兩三個星期,然后因?yàn)楦鞣N事情耽擱了,就一直沒有修改,找問題。一開始出的圖數(shù)據(jù)丟失錯亂嚴(yán)重,通過quartus軟件的sjgnal tap logic analyzer工具抓取數(shù)據(jù)分析,由于抓取數(shù)據(jù)有限,根本看不出來哪里的問題,因此后邊做了挺久也沒找到問題在哪。自己分析應(yīng)該是SDRAM控制器寫的有問題,于是就對SDRAM控制器進(jìn)行了仿真測試,當(dāng)然測試情況要多一些,然后還是發(fā)現(xiàn)沒什么問題。然后昨天寫了個測試代碼,就是上邊給出來的,仿真測試的時候,在modelsim控制臺輸出信息有sdram讀取數(shù)據(jù)的信息,就發(fā)現(xiàn)讀數(shù)據(jù)時行地址有時候會用上一次讀數(shù)據(jù)的行地址,這個問題從這里就找出來了,通過定位發(fā)現(xiàn),在頂層模塊中,對SDRAM讀寫地址部分代碼寫的有問題,對時序要求較高,才出現(xiàn)問題,這里對這個問題不做細(xì)講,第一次修改后明顯圖像好了很多,還是有數(shù)據(jù)錯亂丟失,其實(shí)還是這里沒寫好,經(jīng)過修改后就沒有這些問題了。
在代碼出現(xiàn)問題的時候千萬不要嫌麻煩,多動手多測試,問題總會解決的。
工程文件下載鏈接:https://download.csdn.net/download/qq_33231534/13010542
?
總結(jié)
以上是生活随笔為你收集整理的总:基于FPGA的OV7670摄像头显示的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu mint 开机启动项管理
- 下一篇: 从苹果到索尼:六大生态系统的崛起