正点原子 发表于 2019-7-13 16:40:01

【正点原子FPGA连载】第三十五章 OV7725摄像头VGA显示实验--摘自【正点原子】开拓者 FPGA 开发指南

本帖最后由 正点原子 于 2020-10-24 15:17 编辑

1)实验平台:正点原子开拓者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=579749209820
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-281143-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:712557122点击加入:
5)关注正点原子公众号,获取最新资料更新


第三十五章 OV7725摄像头VGA显示实验

OV7725是OmniVision(豪威科技) 公司生产的一颗CMOS图像传感器, 该传感器功耗低、 可靠性高以及采集速率快, 主要应用在玩具、 安防监控、 电脑多媒体等领域。本章我们将使用FPGA开发板实现对OV7725的数字图像采集并通过VGA实时显示。
本章包括以下几个部分:
35.1 OV7725简介
35.2 实验任务
35.3 硬件设计
35.4 程序设计
35.5 下载验证
35.1 OV7725简介
OV7725是一款1/4英寸单芯片图像传感器, 其感光阵列达到640*480, 能实现最快60fps VGA分辨率的图像采集。 传感器内部集成了图像处理的功能, 包括自动曝光控制(AEC) 、 自动增益控制(AGC) 和自动白平衡(AWB) 等。 同时传感器具有较高的感光灵敏度, 适合低照度的应
用, 下图为OV7725的功能框图。

图 35.1.1 OV7725功能框图

由上图可知, 感光阵列(image array) 在XCLK时钟的驱动下进行图像采样, 输出640*480阵列的模拟数据; 接着模拟信号处理器在时序发生器(video timing generator) 的控制下对模拟数据进行算法处理(analog processing) ; 模拟数据处理完成后分成G(绿色) 和R/B(红
色/蓝色) 两路通道经过AD转换器后转换成数字信号, 并且通过DSP进行相关图像处理, 最终输出所配置格式的10位视频数据流。 模拟信号处理以及DSP等都可以通过寄存器(registers) 来配置, 配置寄存器的接口就是SCCB接口, 该接口协议兼容IIC协议。SCCB(Serial Camera Control Bus, 串行摄像头控制总线) 是由OV(OmniVision的简称)公司定义和发展的三线式串行总线, 该总线控制着摄像头大部分的功能, 包括图像数据格式、分辨率以及图像处理参数等。 OV公司为了减少传感器引脚的封装, 现在SCCB总线大多采用两线式接口总线。
OV7725使用的是两线式接口总线, 该接口总线包括SIO_C串行时钟输入线和SIO_D串行双向数据线, 分别相当于IIC协议的SCL信号线和SDA信号线。 我们在前面提到过SCCB协议兼容IIC协议, 是因为SCCB协议和IIC协议非常相似, 有关IIC协议的详细介绍请大家参考“EEPROM读写
实验” 章节。SCCB的写传输协议如下图所示:



图 35.1.2 SCCB写传输协议

上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0: 写 1: 读) , OV7725的器件地址为7’ h21, 所以在写传输协议中, ID Address(W) = 8’ h42(器件地址左移1位,低位补0) ; Sub-address为8位寄存器地址, 在OV7725的数据手册中定义了0x00~0xAC共173个
寄存器, 有些寄存器是可改写的, 有些是只读的, 只有可改写的寄存器才能正确写入; WriteData为8位写数据, 每一个寄存器地址对应8位的配置数据。 上图中的第9位X表示Don’ t Care(不必关心位), 该位是由从机(此处指OV7725)发出应答信号来响应主机表示当前ID Address、
Sub-address和Write Data是否传输完成, 但是从机有可能不发出应答信号, 因此主机(此处指FPGA) 可不用判断此处是否有应答, 直接默认当前传输完成即可。我们可以发现, SCCB和IIC写传输协议是极为相似的, 只是在SCCB写传输协议中, 第9位为不必关心位, 而IIC写传输协议为应答位。 SCCB的读传输协议和IIC有些差异, 在IIC读传输协议中, 写完寄存器地址后会有restart即重复开始的操作; 而SCCB读传输协议中没有重复开始的概念, 在写完寄存器地址后, 发起总线停止信号, 下图为SCCB的读传输协议。



图 35.1.3 SCCB读传输协议

由上图可知, SCCB读传输协议分为两个部分。 第一部分是写器件地址和寄存器地址, 即先进行一次虚写操作, 通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置, 当然虚写操作也可以通过前面介绍的写传输协议来完成。 第二部分是读器件地址和读数据, 此时读取
到的数据才是寄存器地址对应的数据, 注意ID Address(R) = 8’ h43(器件地址左移1位,低位补1) 。 上图中的NA位由主机(这里指FPGA) 产生, 由于SCCB总线不支持连续读写, 因此NA位必须为高电平。
在OV7725正常工作前, 必须先对传感器进行初始化, 即通过配置寄存器使其工作在预期的工作模式, 以及得到较好画质的图像。 因为SCCB的写传输协议和IIC几乎相同, 因此我们可以直接使用IIC的驱动程序来配置摄像头。 当然这么多寄存器也并非都需要配置, 很多寄存器可
以采用默认的值。 OV公司提供了OV7725的软件使用手册(OV7725 Software Application Note,位于开发板所随附的资料“7_硬件资料/6_OV7725资料/OV7725 Software Application
Note.pdf” ) , 如果某些寄存器不知道如何配置可以参考此手册, 下表是本程序用到的关键寄存器的配置说明。
表 35.1.1 OV7725关键寄存器配置说明













OV7725的寄存器较多, 对于其它寄存器的描述可以参OV7725的数据手册。下图为OV7725的一些特性。



图 35.1.4 OV7725的特性


从上图可以看出, OV7725的输入时钟频率的范围是10Mhz~48Mhz; SCCB总线的SIO_C的时钟频率最大为400KHz; 配置寄存器软件复位(寄存器地址0x12 Bit位)和硬件复位(cam_rst_n引脚) 后需要等待最大1ms才能配置其它寄存器; 每次配置完寄存器后, 需要最大300ms时间的
延迟, 也就是10帧图像输出的时间才能输出稳定的视频流。OV7725支持多种不同分辨率图像的输出, 包括VGA(640*480) 、 QVGA(320*240) 以及CIF(一种常用的标准化图像格式,分辨率为352*288)到40*30等任意尺寸。可通过寄存器地址0x12(COM7) 、 0x17(HSTART) 、 0x18(HSIZE) 、 0x19(VSTRT) 、 0x1A(VSIZE) 、 0x32(HREF)、0x29(HoutSize) 、 0x2C(VOutSize) 、 0x2A(EXHCH) 来配置输出图像的分辨率。OV7725支持多种不同的数据像素格式, 包括YUV(亮度参量和色度参量分开表示的像素格
式) 、 RGB(其中RGB格式包含RGB565、 RGB555等) 以及8位的RAW(原始图像数据) 和10位的RAW,通过寄存器地址0x12(COM7) 配置不同的数据像素格式。由于摄像头采集的图像最终要通过VGA接口在显示器上显示, 且开拓者开发板上的VGA接口为RGB565格式(详情请参考“VGA彩条显示实验” 章节) , 因此我们将OV7725摄像头输出的图像像素数据配置成RGB565格式。 本次实验采用OV7725支持的最大分辨率640*480, 下图为摄像头输出的VGA帧模式时序图。



图 35.1.5 VGA帧模式输出时序图

在介绍时序图之前先了解几个基本的概念。
VSYNC: 场同步信号, 由摄像头输出, 用于标志一帧数据的开始与结束。 上图中VSYNC的高电平作为一帧的同步信号, 在低电平时输出的数据有效。 需要注意的是场同步信号是可以通过设置寄存器0x15 Bit位进行取反的, 即低电平同步高电平有效, 本次实验使用的是和上图
一致的默认设置;HREF/HSYNC: 行同步信号, 由摄像头输出, 用于标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的, 只是数据的同步方式不一样。 本次实验使用的是HREF格式输出,当HREF为高电平时, 图像输出有效, 可以通过寄存器0x15 Bit进行配置。 本次实验使用的是HREF格式输出;
D: 数据信号, 由摄像头输出, 在RGB格式输出中, 只有高8位D是有效的;tPCLK: 一个像素时钟周期;tp: 单个数据周期, 这里需要注意的是上图中左下角红框标注的部分, 在RGB模式中, tp代表两个tPCLK(像素时钟) 。 以RGB565数据格式为例, RGB565采用16bit数据表示一个像素点,而OV7725在一个像素周期(tPCLK) 内只能传输8bit数据, 因此需要两个时钟周期才能输出一个RGB565数据;tLine: 摄像头输出一行数据的时间, 共784个tp, 包含640tp个高电平和144tp个低电平,其中640tp为有效像素数据输出的时间。 以RGB565数据格式为例, 640tp实际上是640*2=1280个tPCLK;由图 35.1.5可知, VSYNC的上升沿作为一帧的开始, 高电平同步脉冲的时间为4*tLine,紧接着等待18*tLine时间后,HREF开始拉高,此时输出有效数据;HREF由640tp个高电平和144tp个低电平构成; 输出480行数据之后等待8*tLine时间一帧数据传输结束。 所以输出一帧图像的时间实际上是tFrame =(4 + 18 + 480 + 8) *tLine = 510tLine。由此我们可以计算出摄像头的输出帧率, 以PCLK=25Mhz(周期为40ns)为例, 计算出OV7725输出一帧图像所需的时间如下:
一帧图像输出时间: tFrame = 510*tLine = 510*784tp = 510*784*2tPCLK = 799680*40ns= 31.9872ms;
摄像头输出帧率: 1000ms/31.9872ms ≈ 31Hz。
如果把像素时钟频率提高到摄像头的最大时钟频率48Mhz, 通过上述计算方法, 摄像头的输出帧率约为60Hz。下图为OV7725输出RGB565格式的时序图:



图 35.1.6 RGB565模式时序图

上图中的PCLK为OV7725输出的像素时钟, HREF为行同步信号, D为8位像素数据。OV7725最大可以输出10位数据, 在RGB565输出模式中, 只有高8位是有效的。 像素数据在HREF为高电平时有效, 第一次输出的数据为RGB565数据的高8位, 第二次输出的数据为RGB565数据
的低8位, first byte和second byte组成一个16位RGB565数据。 由上图可知, 数据是在像素时钟的下降沿改变的, 为了在数据最稳定的时刻采集图像数据, 所以我们需要在像素时钟的上升沿采集数据。
35.2 实验任务
本节实验任务是使用开拓者开发板及OV7725摄像头实现图像采集, 并通过VGA显示器实时显示。
35.3 硬件设计
我们的开拓者FPGA开发板上有一个摄像头扩展接口, 该接口可以用来连接OV7725/OV5640等摄像头模块, 摄像头扩展接口原理图如图 35.3.1所示:


图 35.3.1 摄像头扩展接口原理图

ATK-OV7725是ALIENTEK推出的一款高性能30W像素高清摄像头模块。 该模块通过2*9排针(2.54mm间距) 同外部连接, 我们将摄像头的排针直接插在开发板上的摄像头接口即可, 如图
35.3.2所示:



图 35.3.2 ATK-OV7725摄像头连接开发板图

我们在前面说过, OV7725在RGB565模式中只有高8位数据是有效的即D, 而我们的摄像头排针上数据引脚的个数是8位。 实际上, 摄像头排针上的8位数据连接的就是OV7725传感器的D, 所以我们直接使用摄像头排针上的8位数据引脚即可。
需要注意的是, 由图 35.3.1可知, 摄像头扩展口的第18个引脚定义为CMOS_PWDN, 而我们的OV7725摄像头模块的PWDN引脚固定为低电平, 也就是一直处于正常工作模式。 OV7725摄像头模块的第18个引脚定义为SGM_CTRL, 这个引脚是摄像头驱动时钟的选择引脚。 OV7725摄像头模
块内部自带晶振的, 当SGM_CTRL引脚为低电平时, 选择使用摄像头的外部时钟, 也就是FPGA需要输出时钟给摄像头; 当SGM_CTRL引脚为高电平时, 选择使用摄像头的晶振提供时钟。 本次实验将SGM_CTRL引脚驱动为高电平, 这样就不用为摄像头提供驱动时钟, 即不用在CMOS_XCLK
引脚上输出时钟。由于VGA接口和SDRAM引脚数目较多且在前面相应的章节中已经给出它们的管脚列表, 这里
只列出摄像头相关管脚分配, 如下表所示:
表 35.3.1 OV7725摄像头管脚分配






35.4 程序设计
OV7725在VGA帧模式下, 以RGB565格式输出最高帧率可达60Hz, VGA显示器的刷新频率也可以达到60Hz, 那么是不是直接将采集到的图像数据连接到VGA的输入数据端口就行了呢? 答案是不可以。 我们在前面说过, OV7725帧率如果要达到60Hz, 那么像素时钟频率必须为48Mhz,
而VGA操作时钟为25Mhz, 首先是跨时钟域处理问题, 当然跨时钟域处理可通过异步fifo解决;最重要的是时序方面的不匹配, VGA驱动对时序有着严格的要求。 我们在“VGA彩条显示实验”的章节中可以获知, VGA一行或一场分为四个部分: 低电平同步脉冲、 显示后沿、 有效数据段
以及显示前沿, 各个部分的时序参数很显然跟OV7725并不是完全一致的。 因此必须先把一帧图像缓存下来, 然后再把图像数据按照VGA的时序发送到VGA显示器上显示。 OV7725在VGA帧模式输出下, 一帧图像的数据量达到640*480*16bit = 4915200bit = 4800kbit = 4.6875Mbit, 我
们开拓者FPGA开发板芯片型号为EP4CE10F17C8, 从Altera提供的Cyclone IV器件手册可以发现,EP4CE10的片内存储资源为414Kbit, 远不能达到存储要求。 因此我们只能使用板载的外部存储器SDRAM来缓存图像数据, 开拓者板载的SDRAM容量为256Mbit, 足以满足缓存图像数据的需求。
OV7725在正常工作之前必须通过配置寄存器进行初始化, 而配置寄存器的SCCB协议和I2C协议在写操作时几乎一样, 所以我们需要一个I2C驱动模块; 为了使OV7725在期望的模式下运行并且提高图像显示效果, 需要配置较多的寄存器, 这么多寄存器的地址与参数需要单独放在
一个模块, 因此还需要一个寄存配置信息的I2C配置模块; 在摄像头配置完成后, 开始输出图像数据, 因此需要一个摄像头图像采集模块来采集图像; 外接SDRAM存储器当然离不开SDRAM控制器模块的支持, 最后VGA驱动模块读取SDRAM缓存的数据以达到最终实时显示的效果。
OV7725摄像头VGA显示系统框图如下图所示:



图 35.4.1 OV7725摄像头VGA显示系统框图


顶层模块的原理图如下图所示:


图 35.4.2 顶层模块原理图由上图可知, PLL时钟模块(pll_clk) 为VGA驱动模块、 SDRAM读写控制模块以及I2C驱动模块提供驱动时钟, I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束, 传感器初始化完成后图像采集模块将采集到的数据写入SDRAM读写控制模块, VGA驱动模块从SDRAM控制模块中读出数据, 完成了数据的采集、 缓存与显示。 需要注意的是图像数据采集模块是在SDRAM和传感器都初始化完成之后才开始输出数据的, 避免了在SDRAM初始化过程中向里面写入数据。FPGA顶层模块(ov7725_rgb565_640x480_vga) 例化了以下六个模块: PLL时钟模块(pll_clk) 、 I2C驱动模块(i2c_dri) 、 I2C配置模块(i2c_ov7725_rgb565_cfg) 、 摄像头图像采集模块(cmos_capture_data) 、 SDRAM读写控制模块(sdram_top) 和VGA驱动模块
(vga_driver) 。PLL时钟模块(pll_clk) : PLL时钟模块通过调用锁相环(PLL) IP核实现, 共输出3个时钟, 频率分别为100Mhz、 100Mhz(SDRAM相位偏移时钟) 和25Mhz时钟。 100Mhz时钟和100Mhz相位偏移时钟作为SDRAM读写控制模块的驱动时钟, 25Mhz时钟作为I2C驱动模块和VGA驱动模块的驱动时钟。
I2C驱动模块(i2c_dri) : I2C驱动模块负责驱动OV7725 SCCB接口总线, 用户可根据该模块提供的用户接口可以很方便的对OV7725的寄存器进行配置, 该模块和“EEPROM读写实验” 章节中用到的I2C驱动模块为同一个模块, 有关该模块的详细介绍请大家参考“EEPROM读写实验”
章节。I2C配置模块(i2c_ov7725_rgb565_cfg) : I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的, 这样方便了I2C驱动模块和I2C配置模块之间的数据交互。 该模块寄存需要配置的寄存器地址、 数据以及控制初始化的开始与结束, 同时该模块输出OV7725的寄存器地址和数
据以及控制I2C驱动模块开始执行的控制信号, 直接连接到I2C驱动模块的用户接口, 从而完成对OV7725传感器的初始化。摄像头图像采集模块(cmos_capture_data) : 摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成SDRAM读写控制模块的写使能信号和16位写数据信号, 完成对OV7725传感器图像的采集。SDRAM读写控制模块(sdram_top) : SDRAM读写控制器模块负责驱动SDRAM片外存储器, 缓存图像传感器输出的图像数据。 该模块将SDRAM复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。 在“SDRAM读写测试实验” 的程序中, 读写操作地址都是SDRAM的同一存储空间, 如果只使用一个存储空间缓存图像数据, 那么同一存储空间中会出现两帧图像叠加的情况, 为了避免这一情况, 我们在SDRAM的其它BANK中开辟一个相同大小的存储空间, 使用乒乓操作的方式来写入和读取数据, 所以本次实验在“SDRAM读写测试实验” 的程序里做了一个小小的改动, 有关该模块的详细介绍请大家参考“SDRAM读写测试实验” 章节, 本章只对改动的地方作介绍。VGA驱动模块(vga_driver) : VGA驱动模块负责驱动VGA显示器, 该模块通过读取SDRAM读写控制模块来输出像素数据。 本次实验将模块内部信号data_req(数据请求信号) 输出至端口, 方便从SDRAM控制器中读取数据, 有关VGA驱动模块的详细介绍请大家参考“VGA彩条显示实验” 章节。
顶层模块的代码如下:
1 module ov7725_rgb565_640x480_vga(
2 input sys_clk , //系统时钟
3 input sys_rst_n , //系统复位, 低电平有效
4 //摄像头接口
5 input cam_pclk , //cmos 数据像素时钟
6 input cam_vsync , //cmos 场同步信号
7 input cam_href , //cmos 行同步信号
8 input cam_data , //cmos 数据
9 output cam_rst_n , //cmos 复位信号, 低电平有效
10 output cam_sgm_ctrl, //cmos 时钟选择信号, 1:使用摄像头自带的晶振
11 output cam_scl , //cmos SCCB_SCL线
12 inout cam_sda , //cmos SCCB_SDA线
13 //SDRAM接口
14 output sdram_clk , //SDRAM 时钟
15 output sdram_cke , //SDRAM 时钟有效
16 output sdram_cs_n , //SDRAM 片选
17 output sdram_ras_n , //SDRAM 行有效
18 output sdram_cas_n , //SDRAM 列有效
19 output sdram_we_n , //SDRAM 写有效
20 output sdram_ba , //SDRAM Bank地址
21 output sdram_dqm , //SDRAM 数据掩码
22 output sdram_addr , //SDRAM 地址
23 inout sdram_data , //SDRAM 数据
24 //VGA接口
25 output vga_hs , //行同步信号
26 output vga_vs , //场同步信号
27 output vga_rgb //红绿蓝三原色输出
28 );
29
30 //parameter define
31 parameter SLAVE_ADDR = 7'h21 ; //OV7725的器件地址7'h21
32 parameter BIT_CTRL = 1'b0 ; //OV7725的字节地址为8位 0:8位 1:16位
33 parameter CLK_FREQ = 26'd25_000_000; //i2c_dri模块的驱动时钟频率 25MHz
34 parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率,不超过400KHz
35 parameter CMOS_H_PIXEL = 24'd640 ; //CMOS水平方向像素个数,用于设置SDRAM缓存大小
36 parameter CMOS_V_PIXEL = 24'd480 ; //CMOS垂直方向像素个数,用于设置SDRAM缓存大小
37
38 //wire define
39 wire clk_100m ; //100mhz时钟,SDRAM操作时钟
40 wire clk_100m_shift ; //100mhz时钟,SDRAM相位偏移时钟
41 wire clk_25m ; //25mhz时钟,提供给vga驱动时钟
42 wire locked ;
43 wire rst_n ;
44
45 wire i2c_exec ; //I2C触发执行信号
46 wire i2c_data ; //I2C要配置的地址与数据(高8位地址,低8位数据)
47 wire cam_init_done ; //摄像头初始化完成
48 wire i2c_done ; //I2C寄存器配置完成信号
49 wire i2c_dri_clk ; //I2C操作时钟
50
51 wire wr_en ; //sdram_ctrl模块写使能
52 wire wr_data ; //sdram_ctrl模块写数据
53 wire rd_en ; //sdram_ctrl模块读使能
54 wire rd_data ; //sdram_ctrl模块读数据
55 wire sdram_init_done ; //SDRAM初始化完成
56 wire sys_init_done ; //系统初始化完成(sdram初始化+摄像头初始化)
57
58 //*****************************************************
59 //** main code
60 //*****************************************************
61
62 assign rst_n = sys_rst_n & locked;
63 //系统初始化完成: SDRAM和摄像头都初始化完成
64 //避免了在SDRAM初始化过程中向里面写入数据
65 assign sys_init_done = sdram_init_done & cam_init_done;
66 //不对摄像头硬件复位,固定高电平
67 assign cam_rst_n = 1'b1;
68 //cmos 时钟选择信号, 0:使用引脚XCLK提供的时钟 1:使用摄像头自带的晶振
69 assign cam_sgm_ctrl = 1'b1;
70
71 //锁相环
72 pll_clk u_pll_clk(
73 .areset (~sys_rst_n),
74 .inclk0 (sys_clk),
75 .c0 (clk_100m),
76 .c1 (clk_100m_shift),
77 .c2 (clk_25m),
78 .locked (locked)
79 );
80
81 //I2C配置模块
82 i2c_ov7725_rgb565_cfg u_i2c_cfg(
83 .clk (i2c_dri_clk),
84 .rst_n (rst_n),
85 .i2c_done (i2c_done),
86 .i2c_exec (i2c_exec),
87 .i2c_data (i2c_data),
88 .init_done (cam_init_done)
89 );
90
91 //I2C驱动模块
92 i2c_dri
93 #(
94 .SLAVE_ADDR (SLAVE_ADDR), //参数传递
95 .CLK_FREQ (CLK_FREQ ),
96 .I2C_FREQ (I2C_FREQ )
97 )
98 u_i2c_dri(
99 .clk (clk_25m ),
100 .rst_n (rst_n ),
101 //i2c interface
102 .i2c_exec (i2c_exec ),
103 .bit_ctrl (BIT_CTRL ),
104 .i2c_rh_wl (1'b0), //固定为0, 只用到了IIC驱动的写操作
105 .i2c_addr (i2c_data),
106 .i2c_data_w (i2c_data),
107 .i2c_data_r (),
108 .i2c_done (i2c_done ),
109 .scl (cam_scl ),
110 .sda (cam_sda ),
111 //user interface
112 .dri_clk (i2c_dri_clk) //I2C操作时钟
113 );
114
115 //CMOS图像数据采集模块
116 cmos_capture_data u_cmos_capture_data(
117 .rst_n (rst_n & sys_init_done), //系统初始化完成之后再开始采集数据
118 .cam_pclk (cam_pclk),
119 .cam_vsync (cam_vsync),
120 .cam_href (cam_href),
121 .cam_data (cam_data),
122 .cmos_frame_vsync (),
123 .cmos_frame_href (),
124 .cmos_frame_valid (wr_en), //数据有效使能信号
125 .cmos_frame_data (wr_data) //有效数据
126 );
127
128 //SDRAM 控制器顶层模块,封装成FIFO接口
129 //SDRAM 控制器地址组成: {bank_addr,row_addr,col_addr}
130 sdram_top u_sdram_top(
131 .ref_clk (clk_100m), //sdram 控制器参考时钟
132 .out_clk (clk_100m_shift), //用于输出的相位偏移时钟
133 .rst_n (rst_n), //系统复位
134
135 //用户写端口
136 .wr_clk (cam_pclk), //写端口FIFO: 写时钟
137 .wr_en (wr_en), //写端口FIFO: 写使能
138 .wr_data (wr_data), //写端口FIFO: 写数据
139 .wr_min_addr (24'd0), //写SDRAM的起始地址
140 .wr_max_addr (CMOS_H_PIXEL*CMOS_V_PIXEL), //写SDRAM的结束地址
141 .wr_len (10'd512), //写SDRAM时的数据突发长度
142 .wr_load (~rst_n), //写端口复位: 复位写地址,清空写FIFO
143
144 //用户读端口
145 .rd_clk (clk_25m), //读端口FIFO: 读时钟
146 .rd_en (rd_en), //读端口FIFO: 读使能
147 .rd_data (rd_data), //读端口FIFO: 读数据
148 .rd_min_addr (24'd0), //读SDRAM的起始地址
149 .rd_max_addr (CMOS_H_PIXEL*CMOS_V_PIXEL), //读SDRAM的结束地址
150 .rd_len (10'd512), //从SDRAM中读数据时的突发长度
151 .rd_load (~rst_n), //读端口复位: 复位读地址,清空读FIFO
152
153 //用户控制端口
154 .sdram_read_valid (1'b1), //SDRAM 读使能
155 .sdram_pingpang_en (1'b1), //SDRAM 乒乓操作使能
156 .sdram_init_done (sdram_init_done), //SDRAM 初始化完成标志
157
158 //SDRAM 芯片接口
159 .sdram_clk (sdram_clk), //SDRAM 芯片时钟
160 .sdram_cke (sdram_cke), //SDRAM 时钟有效
161 .sdram_cs_n (sdram_cs_n), //SDRAM 片选
162 .sdram_ras_n (sdram_ras_n), //SDRAM 行有效
163 .sdram_cas_n (sdram_cas_n), //SDRAM 列有效
164 .sdram_we_n (sdram_we_n), //SDRAM 写有效
165 .sdram_ba (sdram_ba), //SDRAM Bank地址
166 .sdram_addr (sdram_addr), //SDRAM 行/列地址
167 .sdram_data (sdram_data), //SDRAM 数据
168 .sdram_dqm (sdram_dqm) //SDRAM 数据掩码
169 );
170
171 //VGA驱动模块
172 vga_driver u_vga_driver(
173 .vga_clk (clk_25m),
174 .sys_rst_n (rst_n),
175
176 .vga_hs (vga_hs),
177 .vga_vs (vga_vs),
178 .vga_rgb (vga_rgb),
179
180 .pixel_data (rd_data),
181 .data_req (rd_en), //请求像素点颜色数据输入
182 .pixel_xpos (),
183 .pixel_ypos ()
184 );
185
186 endmodule
在程序的第124至第125行, CMOS图像数据采集模块输出的cmos_frame_valid(数据有效使能信号) 和cmos_frame_data(有效数据) 连接到SDRAM读写控制模块的写fifo接口, 实现了图像数据的缓存; 在程序的第180至第181行, VGA驱动模块输出data_req(请求像素点颜色数据
输入) 连接到SDRAM读写控制模块的读fifo接口, 将读出的数据连接到VGA驱动模块的输入颜色数据请求信号, 从而实现了VGA实时显示的功能。 需要注意的是顶层模块中第33至第34行定义了两个变量: CLK_FREQ(i2c_dri模块的驱动时钟频率) 和I2C_FREQ(I2C的SCL时钟频率) ,
I2C_FREQ的时钟频率不能超过400KHz, 否则有可能导致摄像头配置不成功。我们可以发现, 在对SDRAM进行读写操作时, 并没有使用摄像头的场同步信号和行同步信号。 为了保证图像数据在VGA显示器上正确显示, 我们在SDRAM中开辟出一个存储空间(大小为
640*480) 用于缓存一帧图像。 在摄像头初始化结束后输出的第一个数据对应图像的第一个像素点, 将其写入存储空间的首地址中。 通过在SDRAM读写控制模块中对输出的图像数据进行计数, 从而将它们分别写入相应的地址空间。 计数达640*480后, 完成一帧图像的存储, 然后回
到存储空间的首地址继续下一帧图像的存储。 在显示图像时, VGA驱动模块从SDRAM存储空间的首地址开始读数据, 同样对读过程进行计数, 并将读取的图像数据分别显示到显示器相应的像素点位置。
上述的操作保证了在没有行场同步信号下数据不会出现错乱的问题, 但是会导致当前读取的图像与上一次存入的图像存在交错, 如下图所示:



图 35.4.3 SDRAM单个BANK缓存图像机制

由上图的t2时刻可知, SDRAM存储空间中会出现缓存两帧图像交错的情况。 为了解决这一问题, 在顶层模块代码的第155行, 使能了SDRAM读写控制器的乒乓操作(sdram_pingpang_en)。SDRAM乒乓操作使能之后, 内部使用了两个存储空间(大小为640*480) 分别缓存两帧图像。 图
像数据总是在两个存储空间之间不断切换写入, 而读请求信号在读完当前存储空间后判断哪个存储空间没有被写入, 然后去读取没有被写入的存储空间。 对于本次程序设计来说, 数据写入较慢而读出较快, 因此会出现同一存储空间被读取多次的情况, 但保证了读出的数据一定是一
帧完整的图像而不是两帧数据拼接的图像。 当正在读取其中一个缓存空间, 另一个缓存空间已经写完, 并开始切换写入下一个缓存空间时, 由于图像数据读出的速度总是大于写入的速度,因此, 读出的数据仍然是一帧完整的图像。本 次 实 验 的 SDRAM 控 制 器 模 块 在 “SDRAM 读 写 测 试 实 验 ” 程 序 的 基 础 上 增 加 了sdram_pingpang_en信号, 用于控制是否增加乒乓存储操作, 高电平有效。 主要修改了SDRAM控制器的sdram_fifo_ctrl模块, 修改后的核心源代码如下。
73 reg sw_bank_en; //切换BANK使能信号
74 reg rw_bank_flag; //读写bank的标志
省略部分源代码……
156 //sdram写地址产生模块
157 always @(posedge clk_ref or negedge rst_n) begin
158 if (!rst_n) begin
159 sdram_wr_addr <= 24'd0;
160 sw_bank_en <= 1'b0;
161 rw_bank_flag <= 1'b0;
162 end
163 else if(wr_load_flag) begin //检测到写端口复位信号时, 写地址复位
164 sdram_wr_addr <= wr_min_addr;
165 sw_bank_en <= 1'b0;
166 rw_bank_flag <= 1'b0;
167 end
168 else if(write_done_flag) begin //若突发写SDRAM结束, 更改写地址
169 //若未到达写SDRAM的结束地址, 则写地址累加
170 if(sdram_pingpang_en) begin //SDRAM 读写乒乓使能
171 if(sdram_wr_addr < wr_max_addr - wr_length)
172 sdram_wr_addr <= sdram_wr_addr + wr_length;
173 else begin //切换BANK
174 rw_bank_flag <= ~rw_bank_flag;
175 sw_bank_en <= 1'b1; //拉高切换BANK使能信号
176 end
177 end
178 //若突发写SDRAM结束, 更改写地址
179 else if(sdram_wr_addr < wr_max_addr - wr_length)
180 sdram_wr_addr <= sdram_wr_addr + wr_length;
181 else //到达写SDRAM的结束地址, 回到写起始地址
182 sdram_wr_addr <= wr_min_addr;
183 end
184 else if(sw_bank_en) begin //到达写SDRAM的结束地址, 回到写起始地址
185 sw_bank_en <= 1'b0;
186 if(rw_bank_flag == 1'b0) //切换BANK
187 sdram_wr_addr <= {1'b0,wr_min_addr};
188 else
189 sdram_wr_addr <= {1'b1,wr_min_addr};
190 end
191 end
192
193 //sdram读地址产生模块
194 always @(posedge clk_ref or negedge rst_n) begin
195 if(!rst_n) begin
196 sdram_rd_addr <= 24'd0;
197 end
198 else if(rd_load_flag) //检测到读端口复位信号时, 读地址复位
199 sdram_rd_addr <= rd_min_addr;
200 else if(read_done_flag) begin //突发读SDRAM结束, 更改读地址
201 //若未到达读SDRAM的结束地址, 则读地址累加
202 if(sdram_pingpang_en) begin //SDRAM 读写乒乓使能
203 if(sdram_rd_addr < rd_max_addr - rd_length)
204 sdram_rd_addr <= sdram_rd_addr + rd_length;
205 else begin //到达读SDRAM的结束地址, 回到读起始地址
206 //读取没有在写数据的bank地址
207 if(rw_bank_flag == 1'b0) //根据rw_bank_flag的值切换读BANK地址
208 sdram_rd_addr <= {1'b1,rd_min_addr};
209 else
210 sdram_rd_addr <= {1'b0,rd_min_addr};
211 end
212 end
213 //若突发写SDRAM结束, 更改写地址
214 else if(sdram_rd_addr < rd_max_addr - rd_length)
215 sdram_rd_addr <= sdram_rd_addr + rd_length;
216 else //到达写SDRAM的结束地址, 回到写起始地址
217 sdram_rd_addr <= rd_min_addr;
218 end
219 end
程序中定义了两个用于切换BANK的寄存器(sw_bank_en信号和rw_bank_flag信号) 。sdram_wr_addr和sdram_rd_addr分别代表SDRAM的写入地址和读出地址, 其最高两位表示BANK的 地 址 , 切 换 BANK 时 改 变 sdram_wr_addr 和 sdram_rd_addr 的 最 高 位 , 相 当 于 数 据 在
BANK0(2’ b00)和BANK2(2’ b10) 之间切换。 当rw_bank_sw=0时, 数据写入BANK0, 从BANK2中读出数据; 当rw_bank_sw=1时, 数据写入BANK2, 从BANK0中读出数据。I2C配置模块寄存需要配置的寄存器地址、 数据以及控制初始化的开始与结束, 代码如下所示:
1 module i2c_ov7725_rgb565_cfg(
2 input clk , //时钟信号
3 input rst_n , //复位信号, 低电平有效
4 5
input i2c_done , //I2C寄存器配置完成信号
6 output reg i2c_exec , //I2C触发执行信号
7 output reg i2c_data , //I2C要配置的地址与数据(高8位地址,低8位数据)
8 output reg init_done //初始化完成信号
9 );
10
11 //parameter define
12 parameter REG_NUM = 7'd70 ; //总共需要配置的寄存器个数
13
14 //reg define
15 reg start_init_cnt; //等待延时计数器
16 reg init_reg_cnt ; //寄存器配置个数计数器
17
18 //*****************************************************
19 //** main code
20 //*****************************************************
21
22 //cam_scl配置成250khz,输入的clk为1Mhz,周期为1us,1023*1us = 1.023ms
23 //寄存器延时配置
24 always @(posedge clk or negedge rst_n) begin
25 if(!rst_n)
26 start_init_cnt <= 10'b0;
27 else if((init_reg_cnt == 7'd1) && i2c_done)
28 start_init_cnt <= 10'b0;
29 else if(start_init_cnt < 10'd1023) begin
30 start_init_cnt <= start_init_cnt + 1'b1;
31 end
32 end
33
34 //寄存器配置个数计数
35 always @(posedge clk or negedge rst_n) begin
36 if(!rst_n)
37 init_reg_cnt <= 7'd0;
38 else if(i2c_exec)
39 init_reg_cnt <= init_reg_cnt + 7'b1;
40 end
41
42 //i2c触发执行信号
43 always @(posedge clk or negedge rst_n) begin
44 if(!rst_n)
45 i2c_exec <= 1'b0;
46 else if(start_init_cnt == 10'd1022)
47 i2c_exec <= 1'b1;
48 //只有刚上电和配置第一个寄存器增加延时
49 else if(i2c_done && (init_reg_cnt != 7'd1) && (init_reg_cnt < REG_NUM))
50 i2c_exec <= 1'b1;
51 else
52 i2c_exec <= 1'b0;
53 end
54
55 //初始化完成信号
56 always @(posedge clk or negedge rst_n) begin
57 if(!rst_n)
58 init_done <= 1'b0;
59 else if((init_reg_cnt == REG_NUM) && i2c_done)
60 init_done <= 1'b1;
61 end
62
63 //配置寄存器地址与数据
64 always @(posedge clk or negedge rst_n) begin
65 if(!rst_n)
66 i2c_data <= 16'b0;
67 else begin
68 case(init_reg_cnt)
69 //先对寄存器进行软件复位, 使寄存器恢复初始值
70 //寄存器软件复位后, 需要延时1ms才能配置其它寄存器
71 7'd0 : i2c_data <= {8'h12, 8'h80}; //COM7 BIT:复位所有的寄存器
72 7'd1 : i2c_data <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿
73 7'd2 : i2c_data <= {8'h15, 8'h00}; //COM10 href/vsync/pclk/data信号控制
74 7'd3 : i2c_data <= {8'h17, 8'h23}; //HSTART 水平起始位置
75 7'd4 : i2c_data <= {8'h18, 8'ha0}; //HSIZE 水平尺寸
76 7'd5 : i2c_data <= {8'h19, 8'h07}; //VSTRT 垂直起始位置
77 7'd6 : i2c_data <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸
78 7'd7 : i2c_data <= {8'h32, 8'h00}; //HREF 图像开始和尺寸控制, 控制低位
79 7'd8 : i2c_data <= {8'h29, 8'ha0}; //HOutSize 水平输出尺寸
80 7'd9 : i2c_data <= {8'h2a, 8'h00}; //EXHCH 虚拟像素MSB
81 7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB
82 7'd11 : i2c_data <= {8'h2c, 8'hf0}; //VOutSize 垂直输出尺寸
83 7'd12 : i2c_data <= {8'h0d, 8'h41}; //COM4 PLL倍频设置(multiplier)
84 //Bit: 0:1x 1:4x 2:6x 3:8x
85 7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 内部时钟配置
86 //Freq=multiplier/[(CLKRC+1)*2]
87 7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式
88 7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3 Bit: 0:图像数据 1:彩条测试
89 //DSP 控制
90 7'd16 : i2c_data <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值
91 7'd17 : i2c_data <= {8'h4d, 8'h09}; //FixGain 模拟增益放大器
92 7'd18 : i2c_data <= {8'h63, 8'hf0}; //AWB_Ctrl0 自动白平衡控制字节0
93 7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字节1
94 7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字节2
95 7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字节3
96 7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4 DSP控制字节4
97 //AGC AEC AWB
98 //COM8 Bit:自动增益使能 Bit:自动白平衡使能 Bit:自动曝光功能
99 7'd23 : i2c_data <= {8'h13, 8'hff}; //COM8
100 7'd24 : i2c_data <= {8'h0f, 8'hc5}; //COM6
101 7'd25 : i2c_data <= {8'h14, 8'h11};
102 7'd26 : i2c_data <= {8'h22, 8'h98};
103 7'd27 : i2c_data <= {8'h23, 8'h03};
104 7'd28 : i2c_data <= {8'h24, 8'h40};
105 7'd29 : i2c_data <= {8'h25, 8'h30};
106 7'd30: i2c_data <= {8'h26, 8'ha1};
107 7'd31: i2c_data <= {8'h6b, 8'haa};
108 7'd32: i2c_data <= {8'h13, 8'hff};
109 //matrix sharpness brightness contrast UV
110 7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1
111 //DNSOff 降噪阈值下限,仅在自动模式下有效
112 7'd34 : i2c_data <= {8'h91, 8'h01}; //DNSOff
113 7'd35 : i2c_data <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限
114 7'd36 : i2c_data <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限
115 7'd37 : i2c_data <= {8'h94, 8'h5f}; //MTX1 矩阵系数1
116 7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩阵系数2
117 7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩阵系数3
118 7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩阵系数4
119 7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩阵系数5
120 7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩阵系数6
121 7'd43 : i2c_data <= {8'h9a, 8'h1e}; //MTX_Ctrl 矩阵控制
122 7'd44 : i2c_data <= {8'h9b, 8'h3f}; //BRIGHT 亮度
123 7'd45 : i2c_data <= {8'h9c, 8'h25}; //CNST 对比度
124 7'd46 : i2c_data <= {8'h9e, 8'h81};
125 7'd47 : i2c_data <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制
126 7'd48 : i2c_data <= {8'ha7, 8'h65}; //USAT "U"饱和增益
127 7'd49 : i2c_data <= {8'ha8, 8'h65}; //VSAT "V"饱和增益
128 7'd50 : i2c_data <= {8'ha9, 8'h80}; //VSAT "V"饱和增益
129 7'd51 : i2c_data <= {8'haa, 8'h80}; //VSAT "V"饱和增益
130 //伽马控制
131 7'd52 : i2c_data <= {8'h7e, 8'h0c};
132 7'd53 : i2c_data <= {8'h7f, 8'h16};
133 7'd54 : i2c_data <= {8'h80, 8'h2a};
134 7'd55 : i2c_data <= {8'h81, 8'h4e};
135 7'd56 : i2c_data <= {8'h82, 8'h61};
136 7'd57 : i2c_data <= {8'h83, 8'h6f};
137 7'd58 : i2c_data <= {8'h84, 8'h7b};
138 7'd59 : i2c_data <= {8'h85, 8'h86};
139 7'd60 : i2c_data <= {8'h86, 8'h8e};
140 7'd61 : i2c_data <= {8'h87, 8'h97};
141 7'd62 : i2c_data <= {8'h88, 8'ha4};
142 7'd63 : i2c_data <= {8'h89, 8'haf};
143 7'd64 : i2c_data <= {8'h8a, 8'hc5};
144 7'd65 : i2c_data <= {8'h8b, 8'hd7};
145 7'd66 : i2c_data <= {8'h8c, 8'he8};
146 7'd67 : i2c_data <= {8'h8d, 8'h20};
147
148 7'd68 : i2c_data <= {8'h0e, 8'h65}; //COM5
149 7'd69 : i2c_data <= {8'h09, 8'h00}; //COM2 Bit 输出电流驱动能力
150 //只读存储器,防止在case中没有列举的情况, 之前的寄存器被重复改写
151 default:i2c_data <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位
152 endcase
153 end
154 end
155
156 endmodule
图像传感器刚开始上电时电压有可能不够稳定, 所以程序中定义了一个延时计数器(start_init_cnt) 等待传感器工作在稳定的状态。 当计数器计数到预设值之后, 开始第一次配置传感器即软件复位, 目的是让所有的寄存器复位到默认的状态。 从前面介绍的OV7725的特
性可知, 软件复位需要等待1ms的时间才能配置其它的寄存器, 因此发送完软件复位命令后,延时计数器清零, 并重新开始计数。 当计数器计数到预设值之后, 紧接着配置剩下的寄存器。只有软件复位命令需要1ms的等待时间, 其它寄存器不需要等待时间, 直接按照程序中定义的
顺序发送即可。 在代码的第12行定义了总共需要配置的寄存器的个数, 如果增加或者删减了寄存器的配置, 需要修改此参数。摄像头接口输出8位像素数据, VGA显示的数据格式是16位的RGB565数据, 所以需要在图像采集模块实现8位数据转16位数据的功能。 CMOS图像数据采集模块的代码如下所示:
1 module cmos_capture_data(
2 input rst_n , //复位信号
3 //摄像头接口
4 input cam_pclk , //cmos 数据像素时钟
5 input cam_vsync , //cmos 场同步信号
6 input cam_href , //cmos 行同步信号
7 input cam_data , //cmos 数据
8 //用户接口
9 output cmos_frame_vsync, //帧有效信号
10 output cmos_frame_href , //行有效信号
11 output cmos_frame_valid, //数据有效使能信号
12 output cmos_frame_data //有效数据
13 );
14
15 //寄存器全部配置完成后, 先等待10帧数据
16 //待寄存器配置生效后再开始采集图像
17 parameter WAIT_FRAME = 4'd10 ; //寄存器数据稳定等待的帧个数
18
19 //reg define
20 reg cam_vsync_d0 ;
21 reg cam_vsync_d1 ;
22 reg cam_href_d0 ;
23 reg cam_href_d1 ;
24 reg cmos_ps_cnt ; //等待帧数稳定计数器
25 reg frame_val_flag ; //帧有效的标志
26
27 reg cam_data_d0 ;
28 reg cmos_data_t ; //用于8位转16位的临时寄存器
29 reg byte_flag ;
30 reg byte_flag_d0 ;
31
32 //wire define
33 wire pos_vsync ;
34
35 //*****************************************************
36 //** main code
37 //*****************************************************
38
39 //采输入场同步信号的上升沿
40 assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
41
42 //输出帧有效信号
43 assign cmos_frame_vsync = frame_val_flag ? cam_vsync_d1 : 1'b0;
44 //输出行有效信号
45 assign cmos_frame_href = frame_val_flag ? cam_href_d1 : 1'b0;
46 //输出数据使能有效信号
47 assign cmos_frame_valid = frame_val_flag ? byte_flag_d0 : 1'b0;
48 //输出数据
49 assign cmos_frame_data = frame_val_flag ? cmos_data_t : 1'b0;
50
51 //采输入场同步信号的上升沿
52 always @(posedge cam_pclk or negedge rst_n) begin
53 if(!rst_n) begin
54 cam_vsync_d0 <= 1'b0;
55 cam_vsync_d1 <= 1'b0;
56 cam_href_d0 <= 1'b0;
57 cam_href_d1 <= 1'b0;
58 end
59 else begin
60 cam_vsync_d0 <= cam_vsync;
61 cam_vsync_d1 <= cam_vsync_d0;
62 cam_href_d0 <= cam_href;
63 cam_href_d1 <= cam_href_d0;
64 end
65 end
66
67 //对帧数进行计数
68 always @(posedge cam_pclk or negedge rst_n) begin
69 if(!rst_n)
70 cmos_ps_cnt <= 4'd0;
71 else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
72 cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
73 end
74
75 //帧有效标志
76 always @(posedge cam_pclk or negedge rst_n) begin
77 if(!rst_n)
78 frame_val_flag <= 1'b0;
79 else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
80 frame_val_flag <= 1'b1;
81 else;
82 end
83
84 //8位数据转16位RGB565数据
85 always @(posedge cam_pclk or negedge rst_n) begin
86 if(!rst_n) begin
87 cmos_data_t <= 16'd0;
88 cam_data_d0 <= 8'd0;
89 byte_flag <= 1'b0;
90 end
91 else if(cam_href) begin
92 byte_flag <= ~byte_flag;
93 cam_data_d0 <= cam_data;
94 if(byte_flag)
95 cmos_data_t <= {cam_data_d0,cam_data};
96 else;
97 end
98 else begin
99 byte_flag <= 1'b0;
100 cam_data_d0 <= 8'b0;
101 end
102 end
103
104 //产生输出数据有效信号(cmos_frame_valid)
105 always @(posedge cam_pclk or negedge rst_n) begin
106 if(!rst_n)
107 byte_flag_d0 <= 1'b0;
108 else
109 byte_flag_d0 <= byte_flag;
110 end
111
112 endmodule
CMOS图像采集模块第17行定义了参数WAIT_FRAME(寄存器数据稳定等待的帧个数) , 我们在前面介绍寄存器时提到过配置寄存器生效的时间最长为300ms, 约为摄像头输出10帧图像数据。 所以这里采集场同步信号的上升沿来统计帧数, 计数器计数超过10次之后产生数据有效的
标志, 开始采集图像; 在程序的第85行开始的always块实现了8位数据转16位数据的功能。 需要注意的是摄像头的图像数据是在像素时钟(cam_pclk) 下输出的, 因此摄像头的图像数据必须使用像素钟来采集, 否则会造成数据采集错误。图 35.4.4为摄像头图像采集过程中SignalTap抓取的行场同步信号的波形图, cam_vsync(场同步信号) 的上升沿标志着一帧数据的开始, cam_href(行同步) 信号是在场同步信号拉低等待一段时间之后才开始有效的, 和我们前面分析的OV7725的时序图是一致的。



图 35.4.4 摄像头采集行场同步信号的SignalTap波形图

图 35.4.5为摄像头图像采集过程中SignalTap抓取的行同步信号与数据的波形图, 从图中可以看出, cam_data(8位图像数据) 在行同步信号为高电平时有效, cmos_frame_data为拼接后的16位RGB565数据, 该数据在cmos_frame_clk_en为高电平时有效。



图 35.4.5 摄像头采集数据SignalTap波形图

35.5 下载验证
首 先 我 们 打 开 OV7725 摄 像 头 VGA 显 示 实 验 工 程 , 在 工 程 所 在 的 路 径 下 打 开ov7725_rgb565_640x480_vga/par文件夹, 在里面找到“ov7725_rgb565_640x480_vga.qpf” 并双击打开。 注意工程所在的路径名只能由字母、 数字以及下划线组成, 不能出现中文、 空格以
及特殊字符等。 工程打开后如图 35.5.1所示:



图 35.5.1 OV7725摄像头VGA显示实验工程

然后将OV7725摄像头插入开发板上的摄像头扩展接口(注意摄像头镜头朝外) , 将VGA连接线一端连接显示器, 另一端与开发板上的VGA接口连接。 再将下载器一端连电脑, 另一端与开发板上对应端口连接, 最后连接电源线并打开电源开关。


图 35.5.2 摄像头连接实物图

接下来我们下载程序, 验证OV7725摄像头VGA实时显示的功能。 工程打开后通过点击工具栏 中 的 “Programmer” 图 标 打 开 下 载 界 面 , 通 过 “Add File” 按 钮 选 择ov7725_rgb565_640x480_vga/par/output_files目录下的“ov7725_rgb565_640x480_vga.sof”
文件。 开发板电源打开后, 在程序下载界面点击“Hardware Setup” , 在弹出的对话框中选择当前的硬件连接为“USB-Blaster” 。然后点击“Start” 将工程编译完成后得到的sof文件下载到开发板中, 如图 35.5.3所示:


图 35.5.3 程序下载完成界面

下载完成后观察显示器的显示图像如图 35.5.4所示, 说明OV7725摄像头VGA显示程序下载验证成功。


图 35.5.4 VGA实时显示图像

hugohehuan 发表于 2019-7-25 02:17:00

木有附件?
页: [1]
查看完整版本: 【正点原子FPGA连载】第三十五章 OV7725摄像头VGA显示实验--摘自【正点原子】开拓者 FPGA 开发指南