FPGA采集摄像头数据,经过中间缓存,最后输出到屏幕上,这个工程几乎是所有FPGAer都要经历的工程。曾听人说过,如果能独立的做出摄像头显示工程,那么就代表他的FPGA终于入门了。

  这次,我准备将目前市面上最常用的三款摄像头——OV7670、OV7725、OV5640的开发全过程全部记录下来,并且提供所有代码,若日后需要做相关项目,也方便自己回顾,迅速捡起来。OV7670和OV7725都是30w像素级摄像头,其典型输出为640×480@30fps(VGA),各方面时序也完全一致,仅仅是摄像头配置不同,OV7725可以说是OV7670的升级版,前者比后者的成像效果好很多。而OV5640为500w像素级摄像头,最高支持 2592×1944@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.

 

版权声明:本文为xianyufpga原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xianyufpga/p/12267675.html