FPGA采集攝像頭數據,經過中間緩存,最后輸出到屏幕上,這個工程幾乎是所有FPGAer都要經歷的工程。曾聽人說過,如果能獨立的做出攝像頭顯示工程,那么就代表他的FPGA終於入門了。
這次,我准備將目前市面上最常用的三款攝像頭——OV7670、OV7725、OV5640的開發全過程全部記錄下來,並且提供所有代碼,若日后需要做相關項目,也方便自己回顧,迅速撿起來。OV7670和OV7725都是30w像素級攝像頭,其典型輸出為640x480@30fps(VGA),各方面時序也完全一致,僅僅是攝像頭配置不同,OV7725可以說是OV7670的升級版,前者比后者的成像效果好很多。而OV5640為500w像素級攝像頭,最高支持 2592x1944@15fps(QSXGA)的圖像輸出。
一、硬件電路(by小梅哥AC620)
如下是常用的CMOS硬件電路。
共有20個引腳,其解釋如下所示:

二、內部結構
內部結構有些小復雜,直接說重點。攝像頭采集圖像,經過內部一系列的處理,最終通過端口輸出,輸出端口有幾種,如DVP、MIPI、LVDS、CSI等,我們一般用的是DVP接口,有些模塊的DVP是10位的,我們取高8位即可,舍棄掉了低2位。
三、上電配置時序分析
1、OV7670/OV7725
OV7670 和 OV7725 的數據手冊中並沒有出現上電的時序圖,但是給出了一條信息:Setting time after software/hardware reset:1ms。所謂軟件復位說的是寄存器復位,攝像頭寄存器很多,有一個寄存器有復位功能,對其寫入復位操作可以達到軟件復位效果。而硬件復位指的就是硬件電路圖上的 RST 信號的復位操作。
在一本名為《OV7670 照相模組硬件應用指南》的PDF文件中倒是給出了上電時序圖,但是感覺參數和原版的 datasheet 有些出入,所以我沒有采用。
經過上板我得出一些結論:
(1)cmos_pwdn 信號直接賦 0 即可。
(2)cmos_rst_n 信號直接賦 1 即可。
(3)rst_n 賦 1 后,必須延時 1ms 后再進行 SCCB 配置。
2、OV5640
OV5640的上電時序在其 datasheet 中明確給出了,如下所示:
注意 DOVDD 和 AVDD 是 OV5640 器件內部就已經設計好的,不用自己設計。
經過上板發現,cmos_pwdn 信號不延時直接賦 0 也是可以的。總結如下:
(1)cmos_pwdn直接賦0即可。
(2)cmos_rst_n 信號延時1ms后賦 1 即可。
(3)cmos_rst_n信號賦 1 后,延時 20ms 后才能再進行SCCB配置。
3、時鍾Xclk
OV7670、OV7725、OV5640的輸入時鍾Xclk,一般都建議為 24Mhz,用 FPGA 的 PLL 分頻到 24Mhz 給它就行,攝像頭內部有自己的 PLL,會按照內部設計供給其內部各個模塊使用,使得攝像頭能正常工作。關於Pclk,我們后面再說。
四、代碼展現
1、總體架構
總體架構如上所示,解釋如下:
(1)pll:時鍾分頻模塊
(2)ov7725_top:攝像頭的頂層模塊
(3)sdram_top:SDRAM圖像緩存模塊
(4)TFT_driver:TFT屏顯示模塊
(5)SEG_driver:數碼管顯示幀率模塊
本系列模塊只重點討論第二個 ov7725_top 模塊,其他模塊前面的博客都有說過,其實就是搭積木而已。
2、工程頂層代碼
頂層模塊都差不多,就是端口和 pll ,其他的都是別的部分了。
1 //************************************************************************** 2 // *** 名稱 : top.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2020-5-20 6 // *** 工具 : Quartus 13.0 7 // *** 芯片 : Cyclone IV E 8 // *** 型號 : EP4CE10F17C8 9 // *** 描述 : 工程的頂層模塊 10 //************************************************************************** 11 module top 12 //========================< 端口 >========================================== 13 ( 14 input clk , 15 input rst_n , 16 //ov7670 -------------------------------------------- 17 input cmos_pclk , 18 output cmos_xclk , 19 input cmos_vsync , 20 input cmos_href , 21 input [ 7:0] cmos_data , 22 output cmos_pwdn , 23 output cmos_rst_n , 24 output cmos_scl , 25 inout cmos_sda , 26 //sdram --------------------------------------------- 27 output sdram_clk , 28 output sdram_cke , 29 output sdram_cs_n , 30 output sdram_we_n , 31 output sdram_cas_n , 32 output sdram_ras_n , 33 output [ 1:0] sdram_dqm , 34 output [ 1:0] sdram_ba , 35 output [12:0] sdram_addr , 36 inout [15:0] sdram_dq , 37 //TFT ----------------------------------------------- 38 output TFT_clk , 39 output TFT_de , 40 output TFT_pwm , 41 output TFT_hsync , 42 output TFT_vsync , 43 output [15:0] TFT_data , 44 //Segment ------------------------------------------- 45 output SH_CP , 46 output ST_CP , 47 output DS 48 ); 49 //========================< 信號 >========================================== 50 wire clk_100m ; 51 wire clk_100m_shift ; 52 wire clk_24m ; 53 wire clk_10m ; 54 //SDRAM --------------------------------------------- 55 wire sdram_init_done ; 56 wire wr_en ; //SDRAM 寫使能 57 wire [15:0] wr_data ; //SDRAM 寫數據 58 wire rd_en ; //SDRAM 讀使能 59 wire [15:0] rd_data ; //SDRAM 讀數據 60 //Segment ------------------------------------------- 61 wire [ 7:0] fps_rate ; 62 //========================================================================== 63 //== PLL 64 //========================================================================== 65 pll pll 66 ( 67 .inclk0 (clk ), 68 .c0 (clk_24m ), //CMOS xclk 69 .c1 (clk_100m ), //SDRAM 70 .c2 (clk_100m_shift ), //SDRAM 71 .c3 (clk_10m ) //TFT 72 ); 73 //========================================================================== 74 //== ov7725,已裁剪為480x272 75 //========================================================================== 76 ov7725_top u_ov7725_top 77 ( 78 .clk_24m (clk_24m ), 79 .rst_n (rst_n & sdram_init_done), //SDRAM復位后 80 //cmos ------------------------------------------ 81 .cmos_pclk (cmos_pclk ), 82 .cmos_xclk (cmos_xclk ), 83 .cmos_vsync (cmos_vsync ), 84 .cmos_href (cmos_href ), 85 .cmos_data (cmos_data ), 86 .cmos_rst_n (cmos_rst_n ), 87 .cmos_pwdn (cmos_pwdn ), 88 .cmos_scl (cmos_scl ), 89 .cmos_sda (cmos_sda ), 90 //rgb565 ---------------------------------------- 91 .rgb_vld (wr_en ), //rgb數據指示 92 .rgb_data (wr_data ), //rgb數據 93 .fps_rate (fps_rate ) //fps幀率 94 ); 95 //========================================================================== 96 //== SDRAM 97 //========================================================================== 98 sdram_top u_sdram_top 99 ( 100 .ref_clk (clk_100m ), //SDRAM 控制器參考時鍾 101 .out_clk (clk_100m_shift ), //給SDRAM器件的偏移時鍾 102 .rst_n (rst_n ), //系統復位 103 //用戶寫端口 ------------------------------------ 104 .wr_clk (cmos_pclk ), //寫端口FIFO: 寫時鍾 105 .wr_en (wr_en ), //寫端口FIFO: 寫使能 106 .wr_data (wr_data ), //寫端口FIFO: 寫數據 107 .wr_min_addr (24'd0 ), //寫SDRAM的起始地址 108 .wr_max_addr (480*272 ), //寫SDRAM的結束地址 109 .wr_len (10'd512 ), //寫SDRAM時的數據突發長度 110 .wr_load (~rst_n ), //寫端口復位: 復位寫地址,清空寫FIFO 111 //用戶讀端口 ------------------------------------ 112 .rd_clk (clk_10m ), //讀端口FIFO: 讀時鍾 113 .rd_en (rd_en ), //讀端口FIFO: 讀使能 114 .rd_data (rd_data ), //讀端口FIFO: 讀數據 115 .rd_min_addr (24'd0 ), //讀SDRAM的起始地址 116 .rd_max_addr (480*272 ), //讀SDRAM的結束地址 117 .rd_len (10'd512 ), //從SDRAM中讀數據時的突發長度 118 .rd_load (~rst_n ), //讀端口復位: 復位讀地址,清空讀FIFO 119 //用戶控制端口 ---------------------------------- 120 .sdram_init_done (sdram_init_done ), //SDRAM 初始化完成標志 121 .sdram_pingpang_en (1'b1 ), //SDRAM 乒乓操作使能,1開0關 122 //SDRAM 芯片接口 -------------------------------- 123 .sdram_clk (sdram_clk ), //SDRAM 芯片時鍾 124 .sdram_cke (sdram_cke ), //SDRAM 時鍾有效 125 .sdram_cs_n (sdram_cs_n ), //SDRAM 片選 126 .sdram_ras_n (sdram_ras_n ), //SDRAM 行有效 127 .sdram_cas_n (sdram_cas_n ), //SDRAM 列有效 128 .sdram_we_n (sdram_we_n ), //SDRAM 寫有效 129 .sdram_ba (sdram_ba ), //SDRAM Bank地址 130 .sdram_addr (sdram_addr ), //SDRAM 行/列地址 131 .sdram_dq (sdram_dq ), //SDRAM 數據 132 .sdram_dqm (sdram_dqm ) //SDRAM 數據掩碼 133 ); 134 //========================================================================== 135 //== TFT 136 //========================================================================== 137 TFT_driver u_TFT_driver 138 ( 139 .clk (clk_10m ), 140 .rst_n (rst_n ), 141 .TFT_req (rd_en ), 142 .TFT_x ( ), 143 .TFT_y ( ), 144 .TFT_din (rd_data ), 145 .TFT_clk (TFT_clk ), 146 .TFT_de (TFT_de ), 147 .TFT_pwm (TFT_pwm ), 148 .TFT_hsync (TFT_hsync ), 149 .TFT_vsync (TFT_vsync ), 150 .TFT_data (TFT_data ) 151 ); 152 //========================================================================== 153 //== 數碼管顯示幀率 154 //========================================================================== 155 SEG_driver u_SEG_driver 156 ( 157 .clk (clk_100m ), 158 .rst_n (rst_n ), 159 .en (1 ), 160 .value (fps_rate ), //fps幀率值 161 .SH_CP (SH_CP ), 162 .ST_CP (ST_CP ), 163 .DS (DS ) 164 ); 165 166 167 168 169 endmodule
這里補充一個知識點:FPGA時鍾為50Mhz,攝像頭需要 24Mhz,SDRAM需要兩個100Mhz,而 TFT 屏的推薦時鍾是 9Mhz,但 pll 已經無法分出 9Mhz 了,因此我分了一個近視的 10Mhz 代替 9Mhz,最終顯示也沒有任何問題。
給出的是 ov7725_top,其實 ov7670、ov5640的頂層例化也是完全一樣的。
很多人的攝像頭工程喜歡把一些簡單的信號如 cmos_pwdn 和 cmos_rst_n 信號,在頂層模塊中就直接賦出去,這也是可以的。而我的本次設計中,工程頂層模塊中沒有任何代碼,不管信號復雜與否都嚴格分塊設計,全都寫進了內部模塊里,這樣生成的 rtl 視圖更簡潔,各個模塊的配合也更直觀。
3、攝像頭頂層代碼
(1)OV7670和OV7725
前面說過多次,這兩貨是一樣的,cmos_pwdn 信號和 cmos_rst_n 信號都可以直接賦值,而 cmos_rst_n 信號拉高后,必須延時 1ms 后才能進行 SCCB 配置,代碼如下所示:
//========================================================================== //== cmos簡單信號 //========================================================================== assign cmos_xclk = clk_24m; //24MHz CMOS XCLK output assign cmos_pwdn = 1'b0; //非節電模式,即正常模式 assign cmos_rst_n = 1'b1; //復位信號,可不用延時 /* always @(posedge clk_24m or negedge rst_n) begin if(!rst_n) cmos_rst_n <= 1'b0; else cmos_rst_n <= 1'b1; end */ //========================================================================== //== SCCB驅動和配置 //========================================================================== //延時1ms再進行SCCB配置 //--------------------------------------------------- always @(posedge clk_24m or negedge rst_n) begin if(!rst_n) delay_cnt <= 'b0; else if(delay_cnt <= 24000) delay_cnt <= delay_cnt + 1'b1; end assign sccb_vld = delay_cnt == 24001;
(2)OV5640
OV5640的 cmos_pwdn 信號可以直接賦值,cmos_rst_n 信號卻必須延時 1ms 后才能拉高,拉高后再延時 20ms 后才能進行 SCCB 配置,代碼如下所示:
//========================================================================== //== cmos簡單信號 //========================================================================== //24MHz CMOS XCLK output //--------------------------------------------------- assign cmos_xclk = clk_24m; //非節電模式,即正常模式,不延時也有用 //--------------------------------------------------- assign cmos_pwdn = 1'b0; //延時計數器 //--------------------------------------------------- always @(posedge clk_24m or negedge rst_n) begin if(!rst_n) delay_cnt <= 'b0; else if(delay_cnt <= 504000) delay_cnt <= delay_cnt + 1'b1; end //復位信號,至少延時1ms //--------------------------------------------------- always @(posedge clk_24m or negedge rst_n) begin if(!rst_n) cmos_rst_n <= 1'b0; else if(delay_cnt==240000) cmos_rst_n <= 1'b1; end //========================================================================== //== SCCB驅動和配置 //========================================================================== //至少再延時20ms再進行SCCB配置 //--------------------------------------------------- assign sccb_vld = delay_cnt==504001;
OK,本篇博客就到這,下一篇講解 SCCB 配置是怎么回事。
參考資料:
[1]正點原子FPGA教程
[2]小梅哥《OV5640圖像采集從原理到應用》
[3]開源騷客《SDRAM那些事兒》
[4]韓彬, 於瀟宇, 張雷鳴. FPGA設計技巧與案例開發詳解[M]. 電子工業出版社, 2014.