CMOS摄像头(1):总体介绍和上电控制
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 // *** 日期 : 2019-10-10 6 // *** 描述 : 工程的顶层模块 7 //************************************************************************** 8 9 module top 10 //========================< 参数 >========================================== 11 #( 12 parameter H_DISP = 480 , //图像宽度 13 parameter V_DISP = 272 //图像高度 14 ) 15 //========================< 端口 >========================================== 16 ( 17 input sys_clk , //系统时钟,50Mhz 18 input sys_rst_n , //系统复位,低电平有效 19 //OV5640 -------------------------------------------- 20 output cmos_xclk , //CMOS 驱动时钟 21 output cmos_rst_n , //CMOS 复位信号 22 output cmos_pwdn , //CMOS 休眠模式 23 input cmos_pclk , //CMOS 数据时钟 24 input cmos_href , //CMOS 行同步信号 25 input cmos_vsync , //CMOS 场同步信号 26 input [ 7:0] cmos_data , //CMOS 像素数据 27 output cmos_scl , //CMOS SCCB_SCL 28 inout cmos_sda , //CMOS SCCB_SDA 29 //SDRAM --------------------------------------------- 30 output sdram_clk , //SDRAM 时钟 31 output sdram_cke , //SDRAM 时钟有效 32 output sdram_cs_n , //SDRAM 片选 33 output sdram_ras_n , //SDRAM 行有效 34 output sdram_cas_n , //SDRAM 列有效 35 output sdram_we_n , //SDRAM 写有效 36 output [ 1:0] sdram_ba , //SDRAM Bank地址 37 output [ 1:0] sdram_dqm , //SDRAM 数据掩码 38 output [12:0] sdram_addr , //SDRAM 地址 39 inout [15:0] sdram_dq , //SDRAM 数据 40 //TFT ----------------------------------------------- 41 output TFT_clk , //TFT 像素时钟 42 output TFT_rst , //TFT 复位信号 43 output TFT_blank , //TFT 背光控制 44 output TFT_hsync , //TFT 行同步信号 45 output TFT_vsync , //TFT 场同步信号 46 output [15:0] TFT_data , //TFT 像素输出 47 output TFT_de , //TFT 数据使能 48 //Segment ------------------------------------------- 49 output [ 5:0] seg_sel , //数码管位选 50 output [ 7:0] seg_led //数码管段选 51 ); 52 //========================< 信号 >========================================== 53 wire rst_n ; //稳定后的复位信号 54 //PLL ----------------------------------------------- 55 wire clk_24m ; //24Mhz时钟 56 wire clk_100m ; //100Mhz时钟 57 wire clk_100m_shift ; //100Mhz时钟 58 wire clk_10m ; //10Mhz时钟 59 wire locked ; //PLL稳定信号 60 //CMOS ---------------------------------------------- 61 wire [ 7:0] FPS_rate ; //FPS帧率 62 //SDRAM --------------------------------------------- 63 wire wr_en ; //SDRAM写使能 64 wire [15:0] wr_data ; //SDRAM写数据 65 wire rd_en ; //SDRAM读使能 66 wire [15:0] rd_data ; //SDRAM读数据 67 wire sdram_init_done ; //SDRAM初始化完成信号 68 //========================================================================== 69 //== PLL 70 //========================================================================== 71 pll_clk u_pll_clk 72 ( 73 .inclk0 (sys_clk ), //输入系统时钟50Mhz 74 .areset (~sys_rst_n ), //输入复位信号 75 //----------------------------------------------- 76 .c0 (clk_24m ), //输出时钟24Mhz 77 .c1 (clk_100m ), //输出时钟100mhz 78 .c2 (clk_100m_shift ), //输出时钟100Mhz,-75°偏移 79 .c3 (clk_10m ), //输出时钟10Mhz 80 .locked (locked ) //输出时钟稳定指示信号 81 ); 82 83 //复位信号 = 系统复位 + PLL稳定 + SDRAM初始化完成 84 assign rst_n = sys_rst_n & locked & sdram_init_done; 85 //========================================================================== 86 //== ov7725 87 //========================================================================== 88 ov7725_top 89 #( 90 .H_DISP (H_DISP ), //图像宽度 91 .V_DISP (V_DISP ) //图像高度 92 ) 93 u_ov5640_top 94 ( 95 .clk_24m (clk_24m ), //时钟信号 96 .rst_n (rst_n ), //复位信号 97 //cmos ------------------------------------------ 98 .cmos_xclk (cmos_xclk ), //CMOS 驱动时钟 99 .cmos_rst_n (cmos_rst_n ), //CMOS 复位信号 100 .cmos_pwdn (cmos_pwdn ), //CMOS 休眠模式 101 .cmos_pclk (cmos_pclk ), //CMOS 数据时钟 102 .cmos_href (cmos_href ), //CMOS 行同步信号 103 .cmos_vsync (cmos_vsync ), //CMOS 场同步信号 104 .cmos_data (cmos_data ), //CMOS 像素数据 105 .cmos_scl (cmos_scl ), //CMOS SCCB_SCL 106 .cmos_sda (cmos_sda ), //CMOS SCCB_SDA 107 //RGB ------------------------------------------- 108 .RGB_vld (wr_en ), //RGB数据使能 109 .RGB_data (wr_data ), //RGB数据 110 .FPS_rate (FPS_rate ) //FPS帧率 111 ); 112 //========================================================================== 113 //== SDRAM 114 //========================================================================== 115 sdram_top u_sdram_top 116 ( 117 .ref_clk (clk_100m ), //SDRAM 控制器参考时钟 118 .out_clk (clk_100m_shift ), //给SDRAM器件的偏移时钟 119 .rst_n (sys_rst_n ), //系统复位 120 //用户写端口 ------------------------------------ 121 .wr_clk (cmos_pclk ), //写端口FIFO: 写时钟 122 .wr_en (wr_en ), //写端口FIFO: 写使能 123 .wr_data (wr_data ), //写端口FIFO: 写数据 124 .wr_min_addr (24'd0 ), //写SDRAM的起始地址 125 .wr_max_addr (H_DISP * V_DISP ), //写SDRAM的结束地址 126 .wr_len (10'd512 ), //写SDRAM时的数据突发长度 127 .wr_load (~sys_rst_n ), //写端口复位: 复位写地址,清空写FIFO 128 //用户读端口 ------------------------------------ 129 .rd_clk (clk_10m ), //读端口FIFO: 读时钟 130 .rd_en (rd_en ), //读端口FIFO: 读使能 131 .rd_data (rd_data ), //读端口FIFO: 读数据 132 .rd_min_addr (24'd0 ), //读SDRAM的起始地址 133 .rd_max_addr (H_DISP * V_DISP ), //读SDRAM的结束地址 134 .rd_len (10'd512 ), //从SDRAM中读数据时的突发长度 135 .rd_load (~sys_rst_n ), //读端口复位: 复位读地址,清空读FIFO 136 //用户控制端口 ---------------------------------- 137 .sdram_init_done (sdram_init_done ), //SDRAM 初始化完成标志 138 .sdram_pingpang_en (1'b1 ), //SDRAM 乒乓操作使能,图片1视频0 139 //SDRAM 芯片接口 -------------------------------- 140 .sdram_clk (sdram_clk ), //SDRAM 芯片时钟 141 .sdram_cke (sdram_cke ), //SDRAM 时钟有效 142 .sdram_cs_n (sdram_cs_n ), //SDRAM 片选 143 .sdram_ras_n (sdram_ras_n ), //SDRAM 行有效 144 .sdram_cas_n (sdram_cas_n ), //SDRAM 列有效 145 .sdram_we_n (sdram_we_n ), //SDRAM 写有效 146 .sdram_ba (sdram_ba ), //SDRAM Bank地址 147 .sdram_addr (sdram_addr ), //SDRAM 行/列地址 148 .sdram_dq (sdram_dq ), //SDRAM 数据 149 .sdram_dqm (sdram_dqm ) //SDRAM 数据掩码 150 ); 151 //========================================================================== 152 //== TFT 153 //========================================================================== 154 TFT_driver u_TFT_driver 155 ( 156 .clk (clk_10m ), //时钟信号 157 .rst_n (rst_n ), //复位信号 158 //----------------------------------------------- 159 .TFT_req (rd_en ), //输出数据请求信号 160 .TFT_din (rd_data ), //得到图像数据 161 //----------------------------------------------- 162 .TFT_clk (TFT_clk ), //TFT数据时钟 163 .TFT_rst (TFT_rst ), //TFT复位信号 164 .TFT_blank (TFT_blank ), //TFT背光控制 165 .TFT_hsync (TFT_hsync ), //TFT行同步信号 166 .TFT_vsync (TFT_vsync ), //TFT场同步信号 167 .TFT_data (TFT_data ), //TFT数据输出 168 .TFT_de (TFT_de ) //TFT数据使能 169 ); 170 //========================================================================== 171 //== 数码管 172 //========================================================================== 173 SEG_driver u_SEG_driver 174 ( 175 .clk (sys_clk ), //时钟 176 .rst_n (rst_n ), //复位 177 //----------------------------------------------- 178 .en (1'b1 ), //数码管使能 179 .data (FPS_rate ), //数码管显示的数值 180 .point (6'b000000 ), //小数点具体显示的位置,高电平有效 181 .sign (1'b0 ), //符号位,为1则显示符号(-) 182 //----------------------------------------------- 183 .seg_sel (seg_sel ), //数码管位选 184 .seg_led (seg_led ) //数码管段选 185 ); 186 187 188 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; //复位信号,可不用延时 //========================================================================== //== 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简单信号 //========================================================================== //cmos_xclk要求24Mhz //--------------------------------------------------- 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.