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个引脚,其解释如下所示:

  这里先说明一点,OV7725和OV5640 芯片的 DVP 接口本身拥有 10 位的数据线,可以输出 10 位的 RAW 数据,但是在大多数情况下我们使用高 8 位数据即可,因此模组在设计时大多只涉及 D9~D2 这高 8 位,映射到模组上的OV_D7~OV_D0。注意:上述电路的 OV_SCL 和 OV_SDA 没有连接物理上拉电阻,直接使用会有问题,必须在 Quartus II 软件中对该 2 处引脚设置开启 FPGA 的 IO 片上上拉电阻,功能才能有效。
  此外注意到,20个引脚中,有一个引脚是 NC 空引脚,一个引脚是 OV_STROBE 预留引脚,因此很多摄像头电路会直接将这两个引脚删除,最终变成 18 个引脚,而且一般是会加上拉电阻的,这种摄像头硬件电路图如下所示:
  上述两种接口基本适配市面上卖的OV7670、OV7725、OV5640等摄像头,直接插上就行。

二、内部结构

  内部结构有些小复杂,直接说重点。摄像头采集图像,经过内部一系列的处理,最终通过端口输出,输出端口有几种,如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.

 

posted @ 2020-02-06 09:52  咸鱼IC  阅读(8108)  评论(0编辑  收藏  举报