• OV7670/OV7725/OV5640开发记录(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 // *** 日期 : 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.

  • 相关阅读:
    IOS回调机制总结
    2.25~当svn服务器ip地址变了怎么办?
    ubuntu硬件信息,内存DDR详细信息
    关于JS相等比较算法(==)的原理
    ubuntu更改鼠标滚轮方向为自然方向(运动方向和滚轮滚动方向一致)
    C#模拟js的Json对象创建,操作
    关于json返回日期格式化的解决方案
    js定时器 timer
    ubuntu GUI界面复制文件没权限的解决方案
    CodeSmith 模板
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/12267675.html
Copyright © 2020-2023  润新知