8. ov7725摄像头vga图像显示

在各类信息中,图像含有最丰富的信息,作为机器视觉领域的核心部件,摄像头被广泛地应用在实时监控、视频会议、远程医疗、娱乐多媒体等诸多方面。摄像头按输出信号的类型来看可以分为数字摄像头和模拟摄像头,按照摄像头图像传感器材料构成来看可以分为 CCD 和 CMOS。现在智能手机的摄像头绝大部分都是CMOS类型的数字摄像头。

本章节我们将使用FPGA实现对OV7725的驱动控制,将摄像头采集的数字图像通过VGA实时显示。

8.1. 理论学习

8.1.1. 摄像头简介

8.1.1.1. 数字摄像头跟模拟摄像头区别

  • 输出信号类型:数字摄像头输出信号为数字信号,模拟摄像头输出信号为标准的模拟信号。

  • 接口类型:数字摄像头有 USB 接口(比如常见的PC端免驱摄像头)、 IEE1394 火线接口(由苹果公司领导的开发联盟开发的一种高速度传送接口,数据传输率高达 800Mbps)、千兆网接口(网络摄像头)。模拟摄像头多采用 AV 视频端子(信号线+地线)或 SVIDEO(即莲花头–SUPER

VIDEO, 是一种五芯的接口,由两路视频亮度信号、两路视频色度信号和一路公共屏蔽地线共五条芯线组成)。

  • 分辨率:模拟摄像头的感光器件,其像素指标一般维持在 752(H)*582(V)左右的水平,像素数一般情况下维持在 41W 左右。数字摄像头分辨率一般从数十万到数百万甚至数千万。但这并不能说明数字摄像头的成像分辨率就比模拟摄像头的高,原因在于模拟摄像头输出的是模拟视频信号,一般直接输入至电视或监视器

,其感光器件的分辨率与电视信号的扫描数呈一定的换算关系,图像的显示介质已经确定,因此模拟摄像头的感光器件分辨率不是不能做高,而是依据于实际情况没必要做这么高。

8.1.2. ccd-与-cmos-的区别

摄像头的图像传感器 CCD 与 CMOS 传感器主要区别如下:

  • 成像材料

CCD 与 CMOS 的名称跟它们成像使用的材料有关, CCD 是―电荷耦合器件‖(Charge Coupled Device)的简称,而 CMOS 是―互补金属氧化物半导体‖(Complementary Metal Oxide Semiconductor)的简称。

  • 功耗

由于 CCD 的像素由 MOS 电容构成,读取电荷信号时需使用电压相当大(至少 12V)的二相或三相或四相时序脉冲信号,才能有效地传输电荷。因此 CCD 的取像系统除了要有多个电源外,其外设电路也会消耗相当大的功率。有的 CCD 取像系统需消耗 2~5W 的功率。 而 CMOS 光电传感器件只需使用一个单电源 5V 或 3V,耗电量非常小,仅为 CCD 的 1/8~1/10,有的 CMOS 取像系统只消耗 20~50mW 的功率。

  • 成像质量

CCD 传感器件制作技术起步早,技术成熟,采用 PN 结或二氧化硅(sio2)隔离层隔离噪声,所以噪声低,成像质量好。与 CCD 相比, CMOS 的主要缺点是噪声高及灵敏度低, 不过现在随着 CMOS 电路消噪技术的不断发展,为生产高密度优质的CMOS 传感器件提供了良好的条件,现在的 CMOS 传感器已经占领了大部分的市场,主流的单反相机、智能手机都已普遍采用 CMOS 传感器。

8.1.3. OV7725简介

本小节主要讲解实验工程使用的OV7725摄像头,摄像头实物具体见图 58‑1。

OV7725002

图 58‑1 OV7725摄像头实物图

该摄像头主要由镜头、图像传感器、板载电路及下方的信号引脚组成。镜头部件包含一个镜头座和一个可旋转调节距离的凸透镜,通过旋转可以调节焦距,正常使用时,镜头座覆盖在电路板上遮光,光线只能经过镜头传输到正中央的图像传感器,它采集得的图像数据直接传输给FPGA芯片,FPGA将接收到的图像数据缓存到SDRAM 中,在VGA图像显示有效区域,VGA驱动自SDRAM读取图像数据在显示屏上进行显示。

若拆开摄像头座, 在摄像头的正下方可看到 PCB 板上的一个方形器件,它是摄像头的核心部件,型号为 OV7725 的 CMOS 类型数字图像传感器。该传感器支持输出最大为 30万像素的图像 (640x480 分辨率),它的体积小,工作电压低,支持使用 VGA 时序输出图像数据,输出图像的数据格式支持 YUV(422/420)、 YCbCr422 以及 RGB565 格式。它还可以对采集得的图像进行补偿,支持伽玛曲线、 白平衡、饱和度、色度等基础处理。

OV7725 传感器采用 BGA 封装,它的前端是采光窗口,引脚都在背面引出,引脚的分布见图 58‑2。

OV7725003

图 58‑2 OV7725管脚图

图中的非彩色部分是电源相关的引脚,彩色部分是主要的信号引脚,其介绍如表格 58‑1。

表格 58‑1 OV7725管脚描述

管脚名称

管脚类型

管脚描述

RSTB

输入

系统复位管脚,低电平有效

PWDN

输入

掉电/省电模式(高电平有效)

HREF

输出

行同步信号

VSYNC

输出

场同步信号

PCLK

输出

像素时钟

XCLK

输入

系统时钟输入端口

SCL

输入

SCCB 总线的时钟线

SDA

I/O

SCCB 总线的数据线

D0…D9

输出

像素数据端口

下面我们结合中的 OV7725 功能框图讲解这些信号引脚。

OV7725004

图 58‑3 OV7725功能框图

  1. 控制寄存器

标号处的是 OV7725 的控制寄存器,它根据这些寄存器配置的参数来运行,而这些参数是由外部控制器通过 SCL 和 SDA 引脚写入的, SCL 与 SDA 使用的通讯协议 SCCB 跟 I2C 十分类似,在 STM32 中我们完全可以直接用 I2C 硬件外设来控制。

  1. 通信、控制信号及时钟

标号处包含了 OV7725 的通信、控制信号及外部时钟,其中 PCLK、 HREF 及VSYNC 分别是像素同步时钟、行同步信号以及帧同步信号,这与液晶屏控制中的 VGA 信号是很类似的。 RSTB 引脚为低电平时,用于复位整个传感器芯片,PWDN 用于控制芯片进入低功耗模式。注意最后的一个 XCLK 引脚,它跟 PCLK是完全不同的, XCLK 是用于驱动整个传感器芯片的时钟信号,是外部输入到OV7725 的信号;而 PCLK 是 OV7725 输出数据时的同步信号,它是由 OV7725 输的信号。 XCLK 可以外接晶振或由外部控制器提供,若要类比 XCLK 之于OV7725 就相当于 HSE 时钟输入引脚与 STM32 芯片的关系, PCLK 引脚可类比STM32 的 I2C 外设的 SCL 引脚。

  1. 感光矩阵

标号处的是感光矩阵,光信号在这里转化成电信号,经过各种处理,这些信号存储成由一个个像素点表示的数字图像。

  1. 数据输出信号

标号处包含了 DSP 处理单元,它会根据控制寄存器的配置做一些基本的图像处理运算。这部分还包含了图像格式转换单元及压缩单元,转换出的数据最终通过D0-D9 引脚输出,一般来说我们使用 8 根据数据线来传输,这时仅使用 D2-D9 引脚。

外部控制器对 OV7725 寄存器的配置参数是通过 SCCB 总线传输过去的,而 SCCB 总线跟 I2C 十分类似,。关于SCCB 协议的完整内容可查看配套资料里的《SCCB 协议》文档,下面进行简单介绍。

SCCB 的起始、停止信号及数据有效性

SCCB 的起始信号、停止信号及数据有效性与I2C 完全一样,见。

  • 起始信号: 在 SCL(图中为 SIO_C) 为高电平时, SDA(图中为 SIO_D)出现一个下降沿,则 SCCB 开始传输。

  • 停止信号:在 SCL 为高电平时, SDA 出现一个上升沿,则 SCCB 停止传输。

  • 数据有效性:除了开始和停止状态, 在数据传输过程中,当 SCL 为高电平时,必须保证 SDA 上的数据稳定,也就是说, SDA 上的电平变换只能发生在 SCL 为低电平的时候,SDA 的信号在 SCL 为高电平时被采集。

OV7725005

图 58‑4 SCCB起始、终止信号

OV7725006

图 58‑5 SCCB 的数据有效性

SCCB 数据读写过程

在 SCCB 协议中定义的读写操作与 I2C 也是一样的,只是换了一种说法。它定义了两种写操作,即三步写操作和两步写操作。三步写操作可向从设备的一个目的寄存器中写入数据,见图 58‑6。在三步写操作中,第一阶段发送从设备的 ID 地址+W 标志(等于 I2C 的设备地址: 7 位设备地址+读写方向标志),第二阶段发送从设备目标寄存器的 8 位地址,第三阶段发送要写入寄存器的 8 位数据。图中的“X”数据位可写入 1 或 0,对通讯无影响。

OV7725007

图 58‑6 SCCB 的三步写操作

而两步写操作没有第三阶段,即只向从器件传输了设备 ID+W 标志和目的寄存器的地址,见图 58‑7。两步写操作是用来配合后面的读寄存器数据操作的,它与读操作一起使用,实现 I2C 的复合过程。

OV7725008

图 58‑7 SCCB 的两步写操作

两步读操作,它用于读取从设备目的寄存器中的数据,见图 58‑8。在第一阶段中发送从设备的设备 ID+R 标志(设备地址+读方向标志)和自由位,在第二阶段中读取寄存器中的8 位数据和写 NA 位(非应答信号)。 由于两步读操作没有确定目的寄存器的地址,所以在读操作前,必需有一个两步写操作,以提供读操作中的寄存器地址。

OV7725009

图 58‑8 SCCB 的两步读操作

可以看到,以上介绍的 SCCB 特性都与 I2C 无区别,完全可以使用 I2C驱动代替SCCB实现FPGA与 OV7725 的 通讯。

8.1.4. OV7725 的寄存器

想要驱动 OV7725 摄像头,需要对其内部诸多寄存器进行配置,对于相关寄存器的配置参数,读者可直接查询《OV7725datasheet》进行了解。通过这些寄存器的配置,可以控制摄像头输出图像的分辨率大小、图像格式、图像处理及图像方向等。见图 58‑9。

OV7725010

图 58‑9 OV7725寄存器配置说明(部分)

像素数据输出时序

FPGA驱动 OV7725 时采用 SCCB 协议对其寄存器进行配置,而它输出图像时则使用 VGA 或QVGA 时序, 其中 VGA 在输出图像分辨率为 640*480 时采用,QVGA 是 Quarter VGA,其输出分辨率为 320*240,这些时序跟控制液晶屏输出图像数据时十分类似。OV7725 传感器输出图像时,一帧帧地输出,在帧内的数据一般从左到右,从上到下,一个像素一个像素地输出(也可通过寄存器修改方向),与VGA扫描显示类似。

例如,见图 58‑10和图 58‑11,若我们使用 D2-D9 数据线,图像格式设置为 RGB565,进行数据输出时, D2-D9 数据线在 PCLK 在上升沿阶段维持稳定, 并且会在 1 个像素同步时钟 PCLK 的驱动下发送 1 字节的数据信号,所以 2 个 PCLK 时钟可发送 1 个 RGB565 格式的像素数据。当 HREF 为高电平时, 像素数据依次传输,每传输完一行数据时,行同步信号 HREF 会输出一个电平跳变信号间隔开当前行和下一行的数据;一帧的图像由 N 行数据组成, 当 VSYNC 为低电平时,各行的像素数据依次传输, 每传输完一帧图像时,VSYNC 会输出一个电平跳变信号。

OV7725011

图 58‑10 像素同步时序

OV7725012

图 58‑11 VGA 帧图像同步时序

8.2. 实战演练

8.2.1. 实验目标

使用SDRAM做图像数据缓存,将OV7725摄像头采集到的图像数据在VGA显示器上实时显示。分辨率为640*480。

8.2.2. 硬件资源

关于OV7725摄像头的硬件资源在前面部分已经做了详细介绍,这一部分说明一下开发板上的摄像头接口,征途开发板的摄像头接口位两排宽度为10 的直插式接口,摄像头接口实物图和原理图,具体见图 58‑12、图 58‑13。

OV7725013

图 58‑12 摄像头接口实物图

OV7725014

图 58‑13 摄像头接口原理图

8.3. 程序设计

8.3.1. 整体说明

在本小节我们分两部分讲解一下实验工程的整体框架,第一部分是OV7725的相关模块,OV7725部分的整体框图和介绍,具体见图 58‑14、表格 58‑2。

OV7725015

图 58‑14 OV7725整体框图

表格 58‑2 OV7725各模块功能介绍

模块名称

功能描述

ov7725_top

OV7725顶层模块

i2c_ctrl

i2c驱动模块

ov7725_cfg

OV7725寄存器配置模块

ov7725_data

OV7725图像采集模块

由上述图表可知,第一部分的OV7725相关模块包含4个子模块,首先是ov7725_top模块,这一模块作为ov7725部分的顶层模块,内部实例化3个子功能模块,连接各子模块对应信号,外部对摄像头进行相关配置并接收摄像头采集的数据信息;ov7725_cfg模块,是寄存器配置模块,内部包含对ov7725 摄像头的配置信息;i2c_ctrl模块,i2c协议与SCCB协议几乎无差别,使用i2c协议代替SCCB接口协议向ov7725摄像头写入ov7725_cfg模块内部包含的寄存器配置信息;ov7725_data模块,是ov7725摄像头的图像采集模块,将摄像头传入的图像数据处理后写入SDRAM。

第一部分介绍完毕后,我们来说一下实验工程的第二部分。在第二部分中将第一部分的ov7725摄像头部分视为一个子模块,与其他子功能模块构成整个实验工程。整体框图和子功能模块介绍,具体见图 58‑15。

OV7725016

图 58‑15 OV7725-VGA图像显示整体框图

表格 58‑3 OV7725-VGA图像显示各子模块功能描述

模块名称

功能描述

ov7725_vga_640x480

实验工程顶层模块

clk_gen

时钟生成模块

ov7725_top

OV7725相关部分

sdram_top

SDRAM读写控制器

vga_ctrl

VGA驱动模块

由图表可知,OV7725-VGA图像显示工程包含表格模块,ov7725_vga_640x480作为实验工程的顶层模块,内部实例化各子功能模块,连接对应信号,对外接收摄像头采集的图像数据,将处理后的数据存入SDRAM,在VGA显示器上显示出来;clk_gen模块,调用IP核生成,产生整个实验工程的工作 时钟;ov7725_top模块,是ov7725部分各子功能模块的集合,实现ov7725摄像头的配置、图像采集与处理;sdram_top模块为SDRAM读写控制器,存储处理后的图像数据;vga_ctrl模块实现VGA显示器的驱动控制,读取SDRAM存储的图像数据并在VGA显示屏上显示出来。

到了这里,实验工程的整体框架我们介绍完毕,对于内部的各个子功能模块的设计与实现,我们会在后面的小结进行详细讲解,但对于调用IP核生成的时钟生成模块clk_gen、SDRAM读写控制器sdram_top和VGA驱动模块vga_ctrl,在前面的章节已经做过 详细介绍,在这里不在过多叙述。对于SCCB通讯协议模块,可使用I2C驱动模块代替。

模块框图

图像采集模块的主要功能是接收并拼接OV7725摄像头传入的图像数据,模块框图和输入输出信号简介,具体见图 58‑16、表格 58‑4。

OV7725017

图 58‑16 图像采集模块框图

表格 58‑4 模块输入输出信号简介

信号

位宽

类型

功能描述

ov7725_pclk

1Bit

Input

摄像头传入工作时钟,频率24MHz

sys_rst_n

1Bit

Input

复位信号,低有效

ov7725_vsync

1Bit

Input

传入图像场同步信号

ov7725_href

1Bit

Input

传入图像行有效区域

ov7725_data

8Bit

Input

传入图像信息

ov7725_wr_en

1Bit

Output

图像信息写入SDRAM使能信号

ov7725_data_out

16Bit

Output

写入SDRAM图像信息

波形图绘制

模块波形图绘制如下,我们将会对各信号做详细讲解。通过各信号设计与实现的单独讲解,学习掌握模块功能实现过程和方法。

OV7725018

图 58‑17 图像采集模块波形图

第一部分:输入信号说明

模块输入信号有5路,输入时钟信号为ov7725_pclk,由ov7725摄像头自带晶振产生并传入,频率24MHz,作为模块工作时钟;复位信号sys_rst_n,低电平有效;ov7725_vsync为摄像头采集图像的场淘宝信号,可类比与VGA场同步信号,只在同步阶段为高电平,其他时刻保持低电平;ov7 725_href为行有效图像使能信号,信号只有采集图像行有效显示区域为高电平,其他时刻为低电平;最后的ov7725_data为摄像头采集到的图像数据,读者要注意的是,ov7725_data位宽为8bit,采集的图像数据分两次传入模块,先传入图像数据高字节,下个时钟周期传入低字节。输入信号波形如下。

OV7725019

图 58‑18 输入信号波形图

第二部分:系统上电后,摄像头刚采集的前几帧图像数据不太稳定,根据手册要求要先舍弃前10帧图像,之后的图像才能用于显示。为了舍弃前10帧图像,我们需要声明几个变量。首先要舍弃前10帧图像,需要一个计数器来计数,声明计数器cnt_pic对输入图像帧数进行计数;接下来就要考虑以什么为标志进行计数,这时我们 想到每帧图像的传入,帧同步信号必不可少,那么声明帧同步信号寄存信号ov7725_vsync_dly,此信号延后帧同步信号一个时钟周期,利用两信号产生帧同步信号下降沿pic_flag,作为帧计数器cnt_pic的计数标志信号,该信号每拉高一次计数器自加1;声明帧有效信号pic_valid,当计数器计数 到第10帧,pic_flag为高电平,将帧有效信号拉高并始终保持高电平。相关信号波形图如下。

OV7725020

图 58‑19 帧计数器及相关信号波形图

第三部分:图像数据拼接相关信号

前面说到,像素点图像信息并不是在一个时钟周期传入,而是在第一个时钟周期传入高8位,下一个时钟周期传入低8位,所以要正确显示图像就需要对传入图像数据进行拼接。实现数据拼接就需要声明若干变量。

我们的想法是需要先声明一个寄存器对图像数据的高字节进行数据缓存,待低字节数据传入时,将图像数据进行拼接。首先声明寄存器pic_data_reg对高字节数据进行缓存;声明标志信号data_flag控制数据缓存与拼接,在ov7725_href信号有效时,标志着输入图像数据有效,data_flag不断取反 ,当其为低电平时对高字节数据进行缓存,当其为高电平时对数据进行拼接。将拼接后的数据赋值给data_out_reg。各信号波形如下图。

OV7725021

图 58‑20 数据拼接相关信号波形图

第四部分:数据输出信号波形图

拼接后的数据做一下缓存输出写入做回数据缓存的SDRAM,有数据同时传出的还有同步的使能信号,波形图如下。

OV7725022

图 58‑21 输出信号波形图

代码编写

参照绘制的模块波形图,编写模块参考代码。图像数据采集模块参考代码,具体见代码清单 58‑1。

代码清单 58‑1 图像采集模块参考代码(ov7725_data.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
module ov7725_data
(
input wire sys_rst_n , //复位信号
// OV7725
input wire ov7725_pclk , //摄像头像素时钟
input wire ov7725_href , //摄像头行同步信号
input wire ov7725_vsync , //摄像头场同步信号
input wire [ 7:0] ov7725_data , //摄像头图像数据
// 写FIFO
output wire ov7725_wr_en , //图像数据有效使能信号
output wire [15:0] ov7725_data_out //图像数据
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter PIC_WAIT = 4'd10; //图像稳定前等待帧图像个数

//wire define
wire pic_flag ; //帧图像标志信号,每拉高一次,代表一帧完整图像

//reg define

reg ov7725_vsync_dly ; //摄像头输入场同步信号打拍
reg [3:0] cnt_pic ; //图像帧计数器
reg pic_valid ; //帧有效标志信号
reg [7:0] pic_data_reg ; //输入8位图像数据缓存
reg [15:0] data_out_reg ; //输出16位图像数据缓存
reg data_flag ; //输入8位图像数据缓存
reg data_flag_dly1 ; //图像数据拼接标志信号打拍

////
//\* Main Code \//
////

//ov7725_vsync_dly:摄像头输入场同步信号打拍
always@(posedge ov7725_pclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ov7725_vsync_dly <= 1'b0;
else
ov7725_vsync_dly <= ov7725_vsync;

//pic_flag:帧图像标志信号,每拉高一次,代表一帧完整图像
assign pic_flag = ((ov7725_vsync_dly == 1'b0)
&& (ov7725_vsync == 1'b1)) ? 1'b1 : 1'b0;

//cnt_pic:图像帧计数器
always@(posedge ov7725_pclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_pic <= 4'd0;
else if((cnt_pic < PIC_WAIT) && (pic_flag == 1'b1))
cnt_pic <= cnt_pic + 1'b1;
else
cnt_pic <= cnt_pic;

//pic_valid:帧有效标志
always@(posedge ov7725_pclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pic_valid <= 1'b0;
else if((cnt_pic == PIC_WAIT) && (pic_flag == 1'b1))
pic_valid <= 1'b1;
else
pic_valid <= pic_valid;

//data_out_reg,pic_data_reg,data_flag:输出16位图像数据缓冲,输入8位图像数据缓存输入8位,图像数据缓存
always@(posedge ov7725_pclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
data_out_reg <= 16'd0;
pic_data_reg <= 8'd0;
data_flag <= 1'b0;
end
else if(ov7725_href == 1'b1)
begin
data_flag <= ~data_flag;
pic_data_reg <= ov7725_data;
data_out_reg <= data_out_reg;
if(data_flag == 1'b1)
data_out_reg <= {pic_data_reg,ov7725_data};
else
data_out_reg <= data_out_reg;
end
else
begin
data_flag <= 1'b0;
pic_data_reg <= 8'd0;
data_out_reg <= data_out_reg;
end

//data_flag_dly1:图像数据缓存打拍
always@(posedge ov7725_pclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_flag_dly1 <= 1'b0;
else
data_flag_dly1 <= data_flag;

//ov7725_data_out:输出16位图像数据
assign ov7725_data_out = (pic_valid == 1'b1) ? data_out_reg : 16'b0;

//ov7725_wr_en:输出16位图像数据使能
assign ov7725_wr_en = (pic_valid == 1'b1) ? data_flag_dly1 : 1'b0;

endmodule

仿真代码编写

模块参考代码编写完成后,为检验代码正确性,编写仿真文件模拟模块输入信号对模块参考代码进行仿真验证。模块仿真文件参考代码,具体见代码清单 58‑2。

代码清单 58‑2 图像采集模块仿真文件参考代码(tb_ov7725_data.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
\`timescale 1ns/1ns
module tb_ov7725_data();

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter H_VALID = 10'd640 , //行有效数据
H_TOTAL = 10'd784 ; //行扫描周期
parameter V_SYNC = 10'd4 , //场同步
V_BACK = 10'd18 , //场时序后沿
V_VALID = 10'd480 , //场有效数据
V_FRONT = 10'd8 , //场时序前沿
V_TOTAL = 10'd510 ; //场扫描周期

//wire define
wire ov7725_wr_en ; //有效图像使能信号
wire [15:0] ov7725_data_out ; //有效图像数据
wire ov7725_href ; //行同步信号
wire ov7725_vsync ; //场同步信号

//reg define
reg sys_clk ; //模拟时钟信号
reg sys_rst_n ; //模拟复位信号
reg [7:0] ov7725_data ; //模拟摄像头采集图像数据
reg [11:0] cnt_h ; //行同步计数器
reg [9:0] cnt_v ; //场同步计数器

////
//\* Main Code \//
////
//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #20 sys_clk = ~sys_clk;

//cnt_h:行同步信号计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_h <= 12'd0 ;
else if(cnt_h == ((H_TOTAL \* 2) - 1'b1))
cnt_h <= 12'd0 ;
else
cnt_h <= cnt_h + 1'd1 ;

//ov7725_href:行同步信号
assign ov7725_href = (((cnt_h >= 0)
&& (cnt_h <= ((H_VALID \* 2) - 1'b1)))
&& ((cnt_v >= (V_SYNC + V_BACK))
&& (cnt_v <= (V_SYNC + V_BACK + V_VALID - 1'b1))))
? 1'b1 : 1'b0 ;

//cnt_v:场同步信号计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_v <= 10'd0 ;
else if((cnt_v == (V_TOTAL - 1'b1))
&& (cnt_h == ((H_TOTAL \* 2) - 1'b1)))
cnt_v <= 10'd0 ;
else if(cnt_h == ((H_TOTAL \* 2) - 1'b1))
cnt_v <= cnt_v + 1'd1 ;
else
cnt_v <= cnt_v ;

//vsync:场同步信号
assign ov7725_vsync = (cnt_v <= (V_SYNC - 1'b1)) ? 1'b1 : 1'b0 ;

//ov7725_data:模拟摄像头采集图像数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ov7725_data <= 8'd0;
else if(ov7725_href == 1'b1)
ov7725_data <= ov7725_data + 1'b1;
else
ov7725_data <= 8'd0;

////
//\* Instantiation \//
////
//------------- ov7725_data_inst -------------
ov7725_data ov7725_data_inst
(
.sys_rst_n (sys_rst_n ), //复位信号
.ov7725_pclk (sys_clk ), //摄像头像素时钟
.ov7725_href (ov7725_href ), //摄像头行同步信号
.ov7725_vsync (ov7725_vsync ), //摄像头场同步信号
.ov7725_data (ov7725_data ), //摄像头图像数据

.ov7725_wr_en (ov7725_wr_en ), //图像数据有效使能信号
.ov7725_data_out (ov7725_data_out) //图像数据
);

endmodule

仿真波形分析

使用Quartus和ModelSim进行联合仿真,仿真完成后,查看模块仿真波形,如图 58‑22至图 58‑27所示。由图可知,各信号仿真波形与绘制波形图吻合,且模块输出的相关信号正确,模块通过仿真验证。

OV7725023

图 58‑22 图像采集模块仿真波形图(一)

OV7725024

图 58‑23 图像采集模块仿真波形图(二)

OV7725025

图 58‑24 图像采集模块仿真波形图(三)

OV7725026

图 58‑25 图像采集模块仿真波形图(四)

OV7725027

图 58‑26 图像采集模块仿真波形图(五)

OV7725028

图 58‑27 图像采集模块仿真波形图(六)

要想OV7725摄像头正常工作,需要先对摄像头进行寄存器配置,即向摄像头寄存器写入对应指令。由于需要配置较多寄存器,对于OV7725摄像头具体配置信息,这里不再列举,读者可查阅数据手册或相关资料。对于寄存器的配置方法,我们采用与“WM8978音频回环”章节的“iic配置模块”相同的方法。这里只列出寄 存器配置模块的模块框图和参考代码,对于波形图的绘制,读者可回顾“WM8978音频回环”章节的“i2c配置模块”的相关部分。

模块框图

寄存器配置模块框图,具体见图 58‑28;模块输入输出信号简介,具体见表格 58‑5。

OV7725029

图 58‑28 寄存器配置模块框图

表格 58‑5 寄存器配置模块输入输出信号简介

信号

位宽

类型

功能描述

sys_rst_n

1bit

input

复位信号,低有效

cfg_end

1bit

input

一个寄存器配置完成

sys_clk

1bit

input

模块工作时钟

cfg_start

1bit

output

单个寄存器配置触发信号

cfg_data

16bit

output

寄存器地址+数据

cfg_done

1bit

output

寄存器配置完成信号

代码编写

寄存器配置模块参考代码,具体见代码清单 58‑3。

代码清单 58‑3 寄存器配置模块参考代码(ov7725_cfg.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
module ov7725_cfg
(
input wire sys_clk , //系统时钟,由iic模块传入
input wire sys_rst_n , //系统复位,低有效
input wire cfg_end , //单个寄存器配置完成

output reg cfg_start , //单个寄存器配置触发信号
output wire [15:0] cfg_data , //ID,REG_ADDR,REG_VAL
output reg cfg_done //寄存器配置完成
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter REG_NUM = 7'd69 ; //总共需要配置的寄存器个数
parameter CNT_WAIT_MAX = 10'd1023; //寄存器配置等待计数最大值

//wire define
wire [15:0] cfg_data_reg[REG_NUM-1:0] ; //寄存器配置数据暂存

//reg define
reg [9:0] cnt_wait ; //寄存器配置等待计数器
reg [6:0] reg_num ; //配置寄存器个数

////
//\* Main Code \//
////

//cnt_wait:寄存器配置等待计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 15'd0;
else if(cnt_wait < CNT_WAIT_MAX)
cnt_wait <= cnt_wait + 1'b1;

//reg_num:配置寄存器个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
reg_num <= 7'd0;
else if(cfg_end == 1'b1)
reg_num <= reg_num + 1'b1;

//cfg_start:单个寄存器配置触发信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cfg_start <= 1'b0;
else if(cnt_wait == (CNT_WAIT_MAX - 1'b1))
cfg_start <= 1'b1;
else if((cfg_end == 1'b1) && (reg_num < REG_NUM))
cfg_start <= 1'b1;
else
cfg_start <= 1'b0;

//cfg_done:寄存器配置完成
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cfg_done <= 1'b0;
else if((reg_num == REG_NUM) && (cfg_end == 1'b1))
cfg_done <= 1'b1;

//cfg_data:ID,REG_ADDR,REG_VAL
assign cfg_data = (cfg_done == 1'b1) ? 16'h0000 : cfg_data_reg[reg_num];

//----------------------------------------------------
//cfg_data_reg:寄存器配置数据暂存 ID REG_ADDR REG_VAL
//assign cfg_data_reg[00] = {8'h12, 8'h80};
assign cfg_data_reg[00] = {8'h3d, 8'h03};
assign cfg_data_reg[01] = {8'h15, 8'h00};
assign cfg_data_reg[02] = {8'h17, 8'h23};
assign cfg_data_reg[03] = {8'h18, 8'ha0};
assign cfg_data_reg[04] = {8'h19, 8'h07};
assign cfg_data_reg[05] = {8'h1a, 8'hf0};
assign cfg_data_reg[06] = {8'h32, 8'h00};
assign cfg_data_reg[07] = {8'h29, 8'ha0};
assign cfg_data_reg[08] = {8'h2a, 8'h00};
assign cfg_data_reg[09] = {8'h2b, 8'h00};
assign cfg_data_reg[10] = {8'h2c, 8'hf0};
assign cfg_data_reg[11] = {8'h0d, 8'h41};
assign cfg_data_reg[12] = {8'h11, 8'h00};
assign cfg_data_reg[13] = {8'h12, 8'h06};
assign cfg_data_reg[14] = {8'h0c, 8'hd0};
assign cfg_data_reg[15] = {8'h42, 8'h7f};
assign cfg_data_reg[16] = {8'h4d, 8'h09};
assign cfg_data_reg[17] = {8'h63, 8'hf0};
assign cfg_data_reg[18] = {8'h64, 8'hff};
assign cfg_data_reg[19] = {8'h65, 8'h00};
assign cfg_data_reg[20] = {8'h66, 8'h00};
assign cfg_data_reg[21] = {8'h67, 8'h00};
assign cfg_data_reg[22] = {8'h13, 8'hff};
assign cfg_data_reg[23] = {8'h0f, 8'hc5};
assign cfg_data_reg[24] = {8'h14, 8'h11};
assign cfg_data_reg[25] = {8'h22, 8'h98};
assign cfg_data_reg[26] = {8'h23, 8'h03};
assign cfg_data_reg[27] = {8'h24, 8'h40};
assign cfg_data_reg[28] = {8'h25, 8'h30};
assign cfg_data_reg[29] = {8'h26, 8'ha1};
assign cfg_data_reg[30] = {8'h6b, 8'haa};
 assign cfg_data_reg[31] = {8'h13, 8'hff};
 assign cfg_data_reg[32] = {8'h90, 8'h0a};
 assign cfg_data_reg[33] = {8'h91, 8'h01};
 assign cfg_data_reg[34] = {8'h92, 8'h01};
 assign cfg_data_reg[35] = {8'h93, 8'h01};
 assign cfg_data_reg[36] = {8'h94, 8'h5f};
 assign cfg_data_reg[37] = {8'h95, 8'h53};
 assign cfg_data_reg[38] = {8'h96, 8'h11};
 assign cfg_data_reg[39] = {8'h97, 8'h1a};
 assign cfg_data_reg[40] = {8'h98, 8'h3d};
 assign cfg_data_reg[41] = {8'h99, 8'h5a};
 assign cfg_data_reg[42] = {8'h9a, 8'h1e};
 assign cfg_data_reg[43] = {8'h9b, 8'h3f};
 assign cfg_data_reg[44] = {8'h9c, 8'h25};
 assign cfg_data_reg[45] = {8'h9e, 8'h81};
 assign cfg_data_reg[46] = {8'ha6, 8'h06};
 assign cfg_data_reg[47] = {8'ha7, 8'h65};
 assign cfg_data_reg[48] = {8'ha8, 8'h65};
 assign cfg_data_reg[49] = {8'ha9, 8'h80};
 assign cfg_data_reg[50] = {8'haa, 8'h80};
 assign cfg_data_reg[51] = {8'h7e, 8'h0c};
 assign cfg_data_reg[52] = {8'h7f, 8'h16};
 assign cfg_data_reg[53] = {8'h80, 8'h2a};
 assign cfg_data_reg[54] = {8'h81, 8'h4e};
 assign cfg_data_reg[55] = {8'h82, 8'h61};
 assign cfg_data_reg[56] = {8'h83, 8'h6f};
 assign cfg_data_reg[57] = {8'h84, 8'h7b};
 assign cfg_data_reg[58] = {8'h85, 8'h86};
 assign cfg_data_reg[59] = {8'h86, 8'h8e};
 assign cfg_data_reg[60] = {8'h87, 8'h97};
 assign cfg_data_reg[61] = {8'h88, 8'ha4};
 assign cfg_data_reg[62] = {8'h89, 8'haf};
 assign cfg_data_reg[63] = {8'h8a, 8'hc5};
 assign cfg_data_reg[64] = {8'h8b, 8'hd7};
 assign cfg_data_reg[65] = {8'h8c, 8'he8};
 assign cfg_data_reg[66] = {8'h8d, 8'h20};
 assign cfg_data_reg[67] = {8'h0e, 8'h65};
 assign cfg_data_reg[68] = {8'h09, 8'h00};

 //-------------------------------------------------------

 endmodule

模块参考代码编写完毕,对于模块的仿真验证,我们会对ov7725顶层模块进行整体仿真,到时再查看本模块的仿真波形。

模块框图

ov7725顶层模块内部实例化ov7725摄像头寄存器配置、I2C驱动模块和图像数据采集模块,实现摄像头的寄存器配置和图像采集,模块框图和输入输出端口简介,具体见图 58‑29、表格 58‑6。

OV7725030

图 58‑29 ov7725顶层模块框图

表格 58‑6 ov7725顶层模块输入输出信号简介

信号

位宽

类型

功能描述

sys_clk

1bit

input

系统时钟,频率50MHz

sys_rst_n

1bit

input

复位信号,低有效

ov7725_pclk

1bit

input

摄像头传入时钟,频率24MHz

ov7725_vsync

1bit

input

图像场同步信号

ov7725_href

1bit

input

图像行有效使能信号

ov7725_data

8bit

input

摄像头采集图像数据

sys_init_done

1bit

input

系统初始化完成信号

ov7725_wr_en

1bit

output

SDRAM写使能信号

ov7725_data_out

16bit

output

写入SDRAM的图像数据

cfg_done

1bit

output

摄像头寄存器配置完成

sccb_scl

1bit

output

寄存器配置串行时钟

sccb_sda

1bit

output

寄存器配置串行数据

代码编写

对于ov7725顶层模块,无需波形图的绘制,直接编写参考代码。模块参考代码,具体见代码清单 58‑4。

代码清单 58‑4 ov7725顶层模块参考代码(ov7725_top.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
module ov7725_top
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //复位信号
input wire sys_init_done , //系统初始化完成(SDRAM + 摄像头)

input wire ov7725_pclk , //摄像头像素时钟
input wire ov7725_href , //摄像头行同步信号
input wire ov7725_vsync , //摄像头场同步信号
input wire [ 7:0] ov7725_data , //摄像头图像数据

output wire cfg_done , //寄存器配置完成
output wire sccb_scl , //SCL
output wire sccb_sda , //SDA
output wire ov7725_wr_en , //图像数据有效使能信号
output wire [15:0] ov7725_data_out //图像数据
);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter SLAVE_ADDR = 7'h21 ; // 器件地址(SLAVE_ADDR)
parameter BIT_CTRL = 1'b0 ; // 字地址位控制参数(16b/8b)
parameter CLK_FREQ = 26'd50_000_000; // i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 ; // I2C的SCL时钟频率

//wire define
wire cfg_end ;
wire cfg_start ;
wire [23:0] cfg_data ;
wire cfg_clk ;

////
//\* Instantiation \//
////
//------------- i2c_ctrl_inst -------------
i2c_ctrl
#(
.DEVICE_ADDR (SLAVE_ADDR ), //i2c设备器件地址
.SYS_CLK_FREQ (CLK_FREQ ), //i2c_ctrl模块系统时钟频率
.SCL_FREQ (I2C_FREQ ) //i2c的SCL时钟频率
)
i2c_ctrl_inst
(
.sys_clk (sys_clk ), //输入系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
.wr_en (1'b1 ), //输入写使能信号
.rd_en ( ), //输入读使能信号
.i2c_start (cfg_start ), //输入i2c触发信号
.addr_num (BIT_CTRL ), //输入i2c字节地址字节数
.byte_addr (cfg_data[15:8]), //输入i2c字节地址
.wr_data (cfg_data[7:0] ), //输入i2c设备数据

.rd_data ( ), //输出i2c设备读取数据
.i2c_end (cfg_end ), //i2c一次读/写操作完成
.i2c_clk (cfg_clk ), //i2c驱动时钟
.i2c_scl (sccb_scl ), //输出至i2c设备的串行时钟信号scl
.i2c_sda (sccb_sda ) //输出至i2c设备的串行数据信号sda
);

//------------- ov7725_cfg_inst -------------
ov7725_cfg ov7725_cfg_inst(

.sys_clk (cfg_clk ), //系统时钟,由i2c模块传入
.sys_rst_n (sys_rst_n ), //系统复位,低有效
.cfg_end (cfg_end ), //单个寄存器配置完成

.cfg_start (cfg_start ), //单个寄存器配置触发信号
.cfg_data (cfg_data ), //ID,REG_ADDR,REG_VAL
.cfg_done (cfg_done ) //寄存器配置完成
);

//------------- ov7725_data_inst -------------
ov7725_data ov7725_data_inst(

.sys_rst_n (sys_rst_n & sys_init_done ), //复位信号
.ov7725_pclk (ov7725_pclk ), //摄像头像素时钟
.ov7725_href (ov7725_href ), //摄像头行同步信号
.ov7725_vsync (ov7725_vsync ), //摄像头场同步信号
.ov7725_data (ov7725_data ), //摄像头图像数据

.ov7725_wr_en (ov7725_wr_en ), //图像数据有效使能信号
.ov7725_data_out (ov7725_data_out) //图像数据

);

endmodule

仿真文件编写

OV7725摄像头的相关模块已经介绍完毕,开始对OV7725顶层模块进行仿真。OV7725顶层模块仿真文件参考代码,具体见。

代码清单 58‑5 OV7725顶层模块仿真文件参考代码(tb_ov725_top.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
\`timescale 1ns/1ns
module tb_ov7725_top();

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter H_VALID = 10'd640 , //行有效数据
H_TOTAL = 10'd784 ; //行扫描周期
parameter V_SYNC = 10'd4 , //场同步
V_BACK = 10'd18 , //场时序后沿
V_VALID = 10'd480 , //场有效数据
V_FRONT = 10'd8 , //场时序前沿
V_TOTAL = 10'd510 ; //场扫描周期

//wire define
wire ov7725_href ; //行同步信号
wire ov7725_vsync ; //场同步信号
wire cfg_done ; //寄存器配置完成
wire sccb_scl ; //SCL
wire sccb_sda ; //SDA
wire wr_en ; //图像数据有效使能信号
wire [15:0] wr_data ; //图像数据
wire ov7725_rst_n ; //模拟ov7725复位信号

//reg define
reg sys_clk ; //模拟时钟信号
reg sys_rst_n ; //模拟复位信号
reg ov7725_pclk ; //模拟摄像头时钟信号
reg [7:0] ov7725_data ; //模拟摄像头采集图像数据
reg [11:0] cnt_h ; //行同步计数器
reg [9:0] cnt_v ; //场同步计数器

////
//\* Main Code \//
////
//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
ov7725_pclk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #20 sys_clk = ~sys_clk;
always #20 ov7725_pclk = ~ov7725_pclk;

assign ov7725_rst_n = sys_rst_n && cfg_done;

//cnt_h:行同步信号计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_h <= 12'd0 ;
else if(cnt_h == ((H_TOTAL \* 2) - 1'b1))
cnt_h <= 12'd0 ;
else
cnt_h <= cnt_h + 1'd1 ;

//ov7725_href:行同步信号
assign ov7725_href = (((cnt_h >= 0)
&& (cnt_h <= ((H_VALID \* 2) - 1'b1)))
&& ((cnt_v >= (V_SYNC + V_BACK))
&& (cnt_v <= (V_SYNC + V_BACK + V_VALID - 1'b1))))
? 1'b1 : 1'b0 ;

//cnt_v:场同步信号计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_v <= 10'd0 ;
else if((cnt_v == (V_TOTAL - 1'b1))
&& (cnt_h == ((H_TOTAL \* 2) - 1'b1)))
cnt_v <= 10'd0 ;
else if(cnt_h == ((H_TOTAL \* 2) - 1'b1))
cnt_v <= cnt_v + 1'd1 ;
else
cnt_v <= cnt_v ;

//vsync:场同步信号
assign ov7725_vsync = (cnt_v <= (V_SYNC - 1'b1)) ? 1'b1 : 1'b0 ;

//ov7725_data:模拟摄像头采集图像数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ov7725_data <= 8'd0;
else if(ov7725_href == 1'b1)
ov7725_data <= ov7725_data + 1'b1;
else
ov7725_data <= 8'd0;

////
//\* Instantiation \//
////
//------------- ov7725_top_inst -------------
ov7725_top ov7725_top_inst(

.sys_clk (sys_clk ), //系统时钟
.sys_rst_n (sys_rst_n ), //复位信号
.sys_init_done (ov7725_rst_n ), //系统初始化完成(SDRAM + 摄像头)

.ov7725_pclk (ov7725_pclk ), //摄像头像素时钟
.ov7725_href (ov7725_href ), //摄像头行同步信号
.ov7725_vsync (ov7725_vsync ), //摄像头场同步信号
.ov7725_data (ov7725_data ), //摄像头图像数据

.cfg_done (cfg_done ), //寄存器配置完成
.sccb_scl (sccb_scl ), //SCL
.sccb_sda (sccb_sda ), //SDA
.ov7725_wr_en (wr_en ), //图像数据有效使能信号
.ov7725_data_out (wr_data ) //图像数据

);

endmodule

仿真波形分析

使用ModelSim对ov7725顶层模块进行仿真验证,在仿真波形中。我们只查看寄存器配置模块的仿真波形,寄存器模块仿真波形如图 58‑30、图 58‑31所示。

OV7725031

图 58‑30 寄存器模块仿真波形(一)

OV7725032

图 58‑31 寄存器模块仿真波形(二)

本实验工程中我们使用SDRAM对OV7725部分整合的图像信息进行数据缓存,SDRAM控制器使用我们前文编写的模块,但要做一些改动。其中改动最大的是FIFO控制模块fifo_ctrl,本模块在原有功能的基础上增加了乒乓操作的方式,乒乓操作的主要内容,读者和回顾前面的章节。下面我们对本模块做一下介绍。

FIFO控制模块框图

fifo_ctrl模块的原有功能是使用FIFO对传入的待写入SDRAM的数据和自SDRAM读出的数据进行进行缓存,实现跨时钟域处理;为数据读写模块提供SDRAM读写地址,产生读写请求,我们在此基础上对数据的写入加入乒乓操作的方式。

fifo_ctrl模块的框图设计,fifio_ctrl模块的模块框图和模块输入输出信号的功能描述,具体见图 58‑32、表格 58‑7。

OV7725033

图 58‑32 FIFO控制模块框图

表格 58‑7 FIFO控制模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率100MHz

sys_rst_n

1Bit

Input

复位信号,低有效

wr_fifo_wr_clk

1Bit

Input

写FIFO写时钟

wr_fifo_wr_req

1Bit

Input

写FIFO写请求

wr_fifo_wr_data

16Bit

Input

写FIFO写数据

sdram_wr_b_addr

24Bit

Input

SDRAM写数据首地址

sdram_wr_e_addr

24Bit

Input

SDRAM写数据末地址

wr_burst_len

10Bit

Input

写突发长度

wr_rst

1Bit

Input

写地址复位信号

rd_fifo_rd_clk

1Bit

Input

读FIFO读时钟

rd_fifo_rd_req

1Bit

Input

读FIFO读请求

sdram_rd_b_addr

24Bit

Input

SDRAM读数据首地址

sdram_rd_e_addr

24Bit

Input

SDRAM读数据末地址

rd_burst_len

10Bit

Input

读突发长度

rd_rst

1Bit

Input

读地址复位信号

read_valid

1Bit

Input

读有效信号

pingpang_en

1Bit

Input

乒乓操作使能信号

init_end

1Bit

Input

初始化完成信号

sdram_wr_ack

1Bit

Input

SDRAM数据写响应

sdram_rd_ack

1Bit

Input

SDRAM数据读响应

sdram_data_out

16Bit

Input

读出SDRAM数据

sdram_wr_req

1Bit

Output

SDRAM写操作请求

sdram_wr_addr

24Bit

Output

SDRAM写数据地址

sdram_data_in

16Bit

Output

SDRAM待写入数据

sdram_rd_req

1Bit

Output

SDRAM读操作请求

sdram_rd_addr

24Bit

Output

SDRAM读数据地址

rd_fifo_rd_data

16Bit

Output

读FIFO读数据

rd_fifo_num

10Bit

Output

读FIFO数据计数

代码编写

FIFO控制模块是在原基础上改动得到,增加的乒乓操作的相关内容也在前面章节做了详细介绍,这里不再进行波形图的绘制,参考代码在原有基础上加以改动。FIFO控制模块参考代码,具体见代码清单 58‑6。

代码清单 58‑6 FIFO控制模块参考代码(fifo_ctrl.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
module fifo_ctrl
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //复位信号
//写fifo信号
input wire wr_fifo_wr_clk , //写FIFO写时钟
input wire wr_fifo_wr_req , //写FIFO写请求
input wire [15:0] wr_fifo_wr_data , //写FIFO写数据
input wire [23:0] sdram_wr_b_addr , //写SDRAM首地址
input wire [23:0] sdram_wr_e_addr , //写SDRAM末地址
input wire [9:0] wr_burst_len , //写SDRAM数据突发长度
input wire wr_rst , //写复位信号
//读fifo信号
input wire rd_fifo_rd_clk , //读FIFO读时钟
input wire rd_fifo_rd_req , //读FIFO读请求
input wire [23:0] sdram_rd_b_addr , //读SDRAM首地址
input wire [23:0] sdram_rd_e_addr , //读SDRAM末地址
input wire [9:0] rd_burst_len , //读SDRAM数据突发长度
input wire rd_rst , //读复位信号
output wire [15:0] rd_fifo_rd_data , //读FIFO读数据
output wire [9:0] rd_fifo_num , //读fifo中的数据量

input wire read_valid , //SDRAM读使能
input wire init_end , //SDRAM初始化完成标志
input wire pingpang_en , //SDRAM乒乓操作使能
//SDRAM写信号
input wire sdram_wr_ack , //SDRAM写响应
output reg sdram_wr_req , //SDRAM写请求
output reg [23:0] sdram_wr_addr , //SDRAM写地址
output wire [15:0] sdram_data_in , //写入SDRAM的数据
//SDRAM读信号
input wire sdram_rd_ack , //SDRAM读相应
input wire [15:0] sdram_data_out , //读出SDRAM数据
output reg sdram_rd_req , //SDRAM读请求
output reg [23:0] sdram_rd_addr //SDRAM读地址
);

////
//\* Parameter and Internal Signal \//
////

//wire define
wire wr_ack_fall ; //写响应信号下降沿
wire rd_ack_fall ; //读相应信号下降沿
wire [9:0] wr_fifo_num ; //写fifo中的数据量

//reg define
reg wr_ack_dly ; //写响应打拍
reg rd_ack_dly ; //读响应打拍
reg bank_en ; //Bank切换使能信号
reg bank_flag ; //Bank的地址切换标志

////
//\* Main Code \//
////

//wr_ack_dly:写响应信号打拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_ack_dly <= 1'b0;
else
wr_ack_dly <= sdram_wr_ack;

//rd_ack_dly:读响应信号打拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_ack_dly <= 1'b0;
else
rd_ack_dly <= sdram_rd_ack;


//wr_ack_fall,rd_ack_fall:检测读写响应信号下降沿
assign wr_ack_fall = (wr_ack_dly & ~sdram_wr_ack);
assign rd_ack_fall = (rd_ack_dly & ~sdram_rd_ack);

//bank_en,bank_flag:BANK切换使能,读写Bank标志
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
bank_en <= 1'b0;
bank_flag <= 1'b0;
end
else if((wr_ack_fall == 1'b1) && (pingpang_en ==1'b1))
begin
if(sdram_wr_addr[21:0] < (sdram_wr_e_addr - wr_burst_len))
begin
bank_en <= bank_en;
bank_flag <= bank_flag;
end
else
begin
bank_flag <= ~bank_flag;
bank_en <= 1'b1;
end
end
else if(bank_en == 1'b1)
begin
bank_en <= 1'b0;
bank_flag <= bank_flag;
end

//sdram_wr_addr:sdram写地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sdram_wr_addr <= 24'd0;
else if(wr_rst == 1'b1)
sdram_wr_addr <= sdram_wr_b_addr;
else if(wr_ack_fall == 1'b1) //一次突发写结束,更改写地址
begin
if((pingpang_en==1'b1)&&(sdram_wr_addr[21:0]<(sdram_wr_e_addr-wr_burst_len)))
//使用乒乓操作,写地址未达到末地址,写地址累加
sdram_wr_addr <= sdram_wr_addr + wr_burst_len;
else if(sdram_wr_addr < (sdram_wr_e_addr - wr_burst_len))
//不使用乒乓操作,一次突发写结束,更改写地址,未达到末地址,写地址累加
sdram_wr_addr <= sdram_wr_addr + wr_burst_len;
else //不使用乒乓操作,到达末地址,回到写起始地址
sdram_wr_addr <= sdram_wr_b_addr;
end
else if(bank_en == 1'b1) //切换Bank使能信号有效
begin
if(bank_flag == 1'b0) //切换Bank地址
sdram_wr_addr <= {2'b00,sdram_wr_b_addr[21:0]};
else
sdram_wr_addr <= {2'b01,sdram_wr_b_addr[21:0]};
end

//sdram_rd_addr:sdram读地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sdram_rd_addr <= 24'd0;
else if(rd_rst == 1'b1)
sdram_rd_addr <= sdram_rd_b_addr;
else if(rd_ack_fall == 1'b1) //一次突发读结束,更改读地址
begin
if(pingpang_en == 1'b1)//使用乒乓操作,读地址未达到末地址,读地址累加
begin
if(sdram_rd_addr[21:0]<(sdram_rd_e_addr-rd_burst_len))
sdram_rd_addr <= sdram_rd_addr + rd_burst_len;
else
begin
if(bank_flag == 1'b0) //切换Bank地址
sdram_rd_addr <= {2'b01,sdram_rd_b_addr[21:0]};
else
sdram_rd_addr <= {2'b00,sdram_rd_b_addr[21:0]};
end
end
else if(sdram_rd_addr < (sdram_rd_e_addr - rd_burst_len))
//不使用乒乓操作,读地址未达到末地址,读地址累加
sdram_rd_addr <= sdram_rd_addr + rd_burst_len;
else //到达末地址,回到首地址
sdram_rd_addr <= sdram_rd_b_addr;
end

//sdram_wr_req,sdram_rd_req:读写请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end
else if(init_end == 1'b1) //初始化完成后响应读写请求
begin //优先执行写操作,防止写入SDRAM中的数据丢失
if(wr_fifo_num >= wr_burst_len)
begin //写FIFO中的数据量达到写突发长度
sdram_wr_req <= 1'b1; //写请求有效
sdram_rd_req <= 1'b0;
end
else if((rd_fifo_num < rd_burst_len) && (read_valid == 1'b1))
begin //读FIFO中的数据量小于读突发长度,且读使能信号有效
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b1; //读请求有效
end
else
begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end
end
else
begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end

////
//\* Instantiation \//
////

//------------- wr_fifo_data -------------
fifo_data wr_fifo_data(
//用户接口
.wrclk (wr_fifo_wr_clk ), //写时钟
.wrreq (wr_fifo_wr_req ), //写请求
.data (wr_fifo_wr_data), //写数据
//SDRAM接口
.rdclk (sys_clk ), //读时钟
.rdreq (sdram_wr_ack ), //读请求
.q (sdram_data_in ), //读数据

.rdusedw (wr_fifo_num ), //FIFO中的数据量
.wrusedw ( ),
.aclr (~sys_rst_n \|\| wr_rst) //清零信号
);

//------------- rd_fifo_data -------------
fifo_data rd_fifo_data(
//sdram接口
.wrclk (sys_clk ), //写时钟
.wrreq (sdram_rd_ack ), //写请求
.data (sdram_data_out ), //写数据
//用户接口
.rdclk (rd_fifo_rd_clk ), //读时钟
.rdreq (rd_fifo_rd_req ), //读请求
.q (rd_fifo_rd_data), //读数据

.rdusedw ( ),
.wrusedw (rd_fifo_num ), //FIFO中的数据量
.aclr (~sys_rst_n \|\| rd_rst) //清零信号
);

endmodule

仿真代码编写

fifo_ctrl模块是SDRAM控制器的子功能模块,在此我们直接对SDRAM控制器进行仿真,仿真参考代码,具体见代码清单 58‑7。

代码清单 58‑7 SDRAM控制器仿真参考代码(tb_sdram_top.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
\`timescale 1ns/1ns
module tb_sdram_top();

////
//\* Internal Signal and Defparam \//
////
//wire define
//clk_gen
wire clk_25m ; //PLL输出50M时钟
wire clk_100m ; //PLL输出100M时钟
wire clk_100m_shift ; //PLL输出100M时钟,相位偏移-30deg
wire locked ; //PLL时钟锁定信号
wire rst_n ; //复位信号,低有效
//sdram
wire sdram_clk ; //SDRAM时钟
wire sdram_cke ; //SDRAM时钟使能信号
wire sdram_cs_n ; //SDRAM片选信号
wire sdram_ras_n ; //SDRAM行选通信号
wire sdram_cas_n ; //SDRAM列选题信号
wire sdram_we_n ; //SDRAM写使能信号
wire [1:0] sdram_ba ; //SDRAM L-Bank地址
wire [12:0] sdram_addr ; //SDRAM地址总线
wire [15:0] sdram_dq ; //SDRAM数据总线
wire sdram_dqm ; //SDRAM数据总线
//uart_rx
wire rx_flag ;
wire [7:0] rx_data ;
//vga_ctrl
wire data_req ;
wire [15:0] data_in ;



wire [9:0] rd_fifo_num ; //fifo_ctrl模块中读fifo中的数据量
wire [15:0] rfifo_rd_data ; //fifo_ctrl模块中读fifo读数据

//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号
reg rx ; //串行数据接收信号
reg [7:0] data_mem [99:0] ; //data_mem是一个存储器,相当于一个ram

//defparam
//重定义仿真模型中的相关参数
defparam uart_rx_inst.BAUD_CNT_END = 26;
defparam uart_rx_inst.BAUD_CNT_END_HALF = 13;
defparam sdram_model_plus_inst.addr_bits = 13; //地址位宽
defparam sdram_model_plus_inst.data_bits = 16; //数据位宽
defparam sdram_model_plus_inst.col_bits = 9; //列地址位宽
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024; //L-Bank容量

//重定义自动刷新模块自动刷新间隔时间计数最大值
defparam sdram_top_inst.sdram_ctrl_inst.sdram_a_ref_inst.CNT_REF_MAX=40;

////
//\* Main Code \//
////
//加载测试数据
initial
readmemh("E:/GitLib/Altera/EP4CE10/code/46_ov7725_vga_640x480/sim/test_data.txt",data_mem);

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

//模拟串口数据接收信号
initial
begin
rx <= 1'b1;
#200
rx_byte();
end

task rx_byte();
integer j;
for(j=0;j<100;j=j+1)
rx_bit(data_mem[j]);
endtask

task rx_bit(input[7:0] data); //data是data_mem[j]的值。
integer i;
for(i=0;i<10;i=i+1)
begin
case(i)
0: rx <= 1'b0 ; //起始位
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7]; //上面8个发送的是数据位
9: rx <= 1'b1 ; //停止位
endcase
#1040; //一个波特时间=sys_clk周期*波特计数器
end
endtask

////
//\* Instantiation \//
////

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_100m ),
.c1 (clk_100m_shift ),
.c2 (clk_25m ),

.locked (locked )
);

//------------- uart_rx_inst -------------
uart_rx uart_rx_inst(
.sys_clk (clk_25m ), //input sys_clk
.sys_rst_n (rst_n ), //input sys_rst_n
.rx (rx ), //input rx

.po_data (rx_data ), //output [7:0] rx_data
.po_flag (rx_flag ) //output rx_flag
);

//------------- sdram_top_inst -------------
sdram_top sdram_top_inst(
.sys_clk (clk_100m ), //sdram 控制器参考时钟
.clk_out (clk_100m_shift ), //用于输出的相位偏移时钟
.sys_rst_n (rst_n ), //系统复位
//用户写端口
.wr_fifo_wr_clk (clk_25m ), //写端口FIFO: 写时钟
.wr_fifo_wr_req (rx_flag ), //写端口FIFO: 写使能
.wr_fifo_wr_data ({8'b0,rx_data} ), //写端口FIFO: 写数据
.sdram_wr_b_addr (24'd0 ), //写SDRAM的首地址
.sdram_wr_e_addr (24'd50 ), //写SDRAM的末地址
.wr_burst_len (10'd10 ), //写SDRAM时的数据突发长度
.wr_rst (~rst_n ), //写地址复位信号
//用户读端口
.rd_fifo_rd_clk (clk_25m ), //读端口FIFO: 读时钟
.rd_fifo_rd_req (data_req ), //读端口FIFO: 读使能
.rd_fifo_rd_data (data_in ), //读端口FIFO: 读数据
.sdram_rd_b_addr (24'd0 ), //读SDRAM的首地址
.sdram_rd_e_addr (24'd50 ), //读SDRAM的末地址
.rd_burst_len (10'd10 ), //从SDRAM中读数据时的突发长度
.rd_rst (~rst_n ), //读地址复位信号
.rd_fifo_num ( ), //读fifo中的数据量
//用户控制端口
.read_valid (1'b1 ), //SDRAM 读使能
.pingpang_en (1'b1 ), //SDRAM 乒乓操作使能
.init_end ( ), //SDRAM 初始化完成标志
//SDRAM 芯片接口
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码
);

//------------- vga_ctrl_inst -------------
vga_ctrl vga_ctrl_inst(
.vga_clk (clk_25m ), //输入工作时钟,频率25MHz
.sys_rst_n (rst_n ), //输入复位信号,低电平有效
.pix_data (data_in ),

.pix_data_req(data_req ),
.hsync (hsync ), //输出行同步信号
.vsync (vsync ), //输出场同步信号
.rgb (rgb_vga ) //输出像素信息

);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk ),
.Cke (sdram_cke ),
.Cs_n (sdram_cs_n ),
.Ras_n (sdram_ras_n ),
.Cas_n (sdram_cas_n ),
.We_n (sdram_we_n ),
.Dqm (sdram_dqm ),
.Debug (1'b1 )

);

endmodule

在仿真过程中,对SDRAM的数据读写使用了乒乓操作,我们通过仿真模块向串口接收模块发送了100字节数据,将SDRAM数据读写首地址设置为0,数据读写末地址设置为50,因为使用了乒乓操作的数据写入方式,100字节数据的前50个数据会被写到逻辑Bank0,后50 个数据会被写到逻辑Bank1。模块是否能实现预期效果,我们需要查看仿真结果。

仿真波形分析

使用Modelsim对代码进行仿真,仿真结果如下。

OV7725034

图 58‑33 仿真模型打印信息(一)

OV7725035

图 58‑34 仿真模型打印信息(二)

OV7725036

图 58‑35 仿真模型打印信息(三)

上述图片为SDRAM仿真模型打印信息,由图片可知,模块实现了SDRAM以乒乓操作的发送进行数据读写,将100字节的前50个字节写入SDRAM的逻辑Bank0并读出,将后50个数据写入SDRAM逻辑Bank1并读出。

OV7725037

图 58‑36 fifo_ctrl模块整体波形图

顶层模块是使用工程必不可少的一部分,内部实例化个子功能模块,外部进行工程输入输出信号的连接,作用巨大。但顶层模块代码写起来较为简单,无需波形图绘制,只需连接对应端口即可。顶层模块参考代码具体见代码清单 58‑8。

代码清单 58‑8 顶层模块参考代码(ov7725_vga_640x480.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
module ov7725_vga_640x480
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //系统复位,低电平有效
//摄像头接口
input wire ov7725_pclk , //摄像头数据像素时钟
input wire ov7725_vsync, //摄像头场同步信号
input wire ov7725_href , //摄像头行同步信号
input wire [7:0] ov7725_data , //摄像头数据
output wire ov7725_rst_n, //摄像头复位信号,低电平有效
output wire ov7725_pwdn , //摄像头时钟选择信号, 1:使用摄像头自带的晶振
output wire sccb_scl , //摄像头SCCB_SCL线
inout wire sccb_sda , //摄像头SCCB_SDA线
//SDRAM接口
output wire sdram_clk , //SDRAM 时钟
output wire sdram_cke , //SDRAM 时钟使能
output wire sdram_cs_n , //SDRAM 片选
output wire sdram_ras_n , //SDRAM 行有效
output wire sdram_cas_n , //SDRAM 列有效
output wire sdram_we_n , //SDRAM 写有效
output wire [1:0] sdram_ba , //SDRAM Bank地址
output wire [1:0] sdram_dqm , //SDRAM 数据掩码
output wire [12:0] sdram_addr , //SDRAM 地址
inout wire [15:0] sdram_dq , //SDRAM 数据
//VGA接口
output wire vga_hs , //行同步信号
output wire vga_vs , //场同步信号
output wire [15:0] vga_rgb //红绿蓝三原色输出
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter H_PIXEL = 24'd640 ;//水平方向像素个数,用于设置SDRAM缓存大小
parameter V_PIXEL = 24'd480 ;//垂直方向像素个数,用于设置SDRAM缓存大小

//wire define
wire clk_100m ; //100MHz时钟,SDRAM操作时钟
wire clk_100m_shift ; //100MHz时钟,SDRAM相位偏移时钟
wire clk_25m ; //25MHz时钟,提供给vga驱动时钟
wire locked ; //PLL锁定信号
wire rst_n ; //复位信号(sys_rst_n & locked)
wire cfg_done ; //摄像头初始化完成
wire wr_en ; //sdram写使能
wire [15:0] wr_data ; //sdram写数据
wire rd_en ; //sdram读使能
wire [15:0] rd_data ; //sdram读数据
wire sdram_init_done ; //SDRAM初始化完成
wire sys_init_done ; //系统初始化完成(SDRAM初始化+摄像头初始化)

////
//\* Main Code \//
////

//rst_n:复位信号(sys_rst_n & locked)
assign rst_n = sys_rst_n & locked;

//sys_init_done:系统初始化完成(SDRAM初始化+摄像头初始化)
assign sys_init_done = sdram_init_done & cfg_done;

//ov7725_rst_n:摄像头复位,固定高电平
assign ov7725_rst_n = 1'b1;

//ov7725_pwdn:摄像头时钟选择信号,0:使用引脚XCLK提供的时钟 1:使用摄像头自带的晶振
assign ov7725_pwdn = 1'b0;

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst(

.areset (~sys_rst_n ),
.inclk0 (sys_clk ),
.c0 (clk_100m ),
.c1 (clk_100m_shift ),
.c2 (clk_25m ),
.locked (locked )

);

//------------- ov7725_top_inst -------------
ov7725_top ov7725_top_inst(

.sys_clk (clk_25m ), //系统时钟
.sys_rst_n (rst_n ), //复位信号
.sys_init_done (sys_init_done ), //系统初始化完成(SDRAM + 摄像头)

.ov7725_pclk (ov7725_pclk ), //摄像头像素时钟
.ov7725_href (ov7725_href ), //摄像头行同步信号
.ov7725_vsync (ov7725_vsync ), //摄像头场同步信号
.ov7725_data (ov7725_data ), //摄像头图像数据

.cfg_done (cfg_done ), //寄存器配置完成
.sccb_scl (sccb_scl ), //SCL
.sccb_sda (sccb_sda ), //SDA
.ov7725_wr_en (wr_en ), //图像数据有效使能信号
.ov7725_data_out (wr_data ) //图像数据

);

//------------- sdram_top_inst -------------
sdram_top sdram_top_inst
(
.sys_clk (clk_100m ), //sdram 控制器参考时钟
.clk_out (clk_100m_shift ), //用于输出的相位偏移时钟
.sys_rst_n (rst_n ), //系统复位
//用户写端口
.wr_fifo_wr_clk (ov7725_pclk ), //写端口FIFO: 写时钟
.wr_fifo_wr_req (wr_en ), //写端口FIFO: 写使能
.wr_fifo_wr_data (wr_data ), //写端口FIFO: 写数据
.sdram_wr_b_addr (24'd0 ), //写SDRAM的起始地址
.sdram_wr_e_addr (H_PIXEL*V_PIXEL), //写SDRAM的结束地址
.wr_burst_len (10'd512 ), //写SDRAM时的数据突发长度
.wr_rst ( ),//写端口复位: 复位写地址,清空写FIFO
//用户读端口
.rd_fifo_rd_clk (clk_25m ), //读端口FIFO: 读时钟
.rd_fifo_rd_req (rd_en ), //读端口FIFO: 读使能
.rd_fifo_rd_data (rd_data ), //读端口FIFO: 读数据
.sdram_rd_b_addr (24'd0 ), //读SDRAM的起始地址
.sdram_rd_e_addr (H_PIXEL*V_PIXEL), //读SDRAM的结束地址
.rd_burst_len (10'd512 ), //从SDRAM中读数据时的突发长度
.rd_fifo_num ( ), //读fifo中的数据量
.rd_rst ( ),//读端口复位: 复位读地址,清空读FIFO
//用户控制端口
.read_valid (1'b1 ), //SDRAM 读使能
.pingpang_en (1'b1 ), //SDRAM 乒乓操作使能
.init_end (sdram_init_done), //SDRAM 初始化完成标志
//SDRAM 芯片接口
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码
);

//------------- vga_ctrl_inst -------------
vga_ctrl vga_ctrl_inst
(
.vga_clk (clk_25m ), //输入工作时钟,频率25MHz
.sys_rst_n (rst_n ), //输入复位信号,低电平有效
.pix_data (rd_data ), //待显示数据输入

.pix_data_req (rd_en ), //数据请求信号
.hsync (vga_hs ), //输出行同步信号
.vsync (vga_vs ), //输出场同步信号
.rgb (vga_rgb ) //输出像素信息
);

endmodule

本实验工程调用的模块大多为复用前面的模块,模块仿真在前面章节已经通过验证,此处不再进行仿真验证。

至此实验工程基本完成,在Quartus中对代码进行编译,编译若有错误,请读者根据错误提示信息作出更改,直至编译通过,编译通过后查看RTL视图,与顶层模块框图对比,两者一致,各信号连接正确。RTL视图,具体见图 58‑37。

OV7725038

图 58‑37 RTL视图

8.4. 上板调试

8.4.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 58‑8所示。

表格 58‑8 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

时钟

sys_rst_n

Input

M15

复位

ov7725_data[7]

Input

L13

OV7725摄像头采集图像数据

ov7725_ data[6]

Input

F13

OV7725摄像头采集图像数据

ov7725_ data[5]

Input

B16

OV7725摄像头采集图像数据

ov7725_ data[4]

Input

C15

OV7725摄像头采集图像数据

ov7725_ data[3]

Input

D16

OV7725摄像头采集图像数据

ov7725_ data[2]

Input

F14

OV7725摄像头采集图像数据

ov7725_ data[1]

Input

F15

OV7725摄像头采集图像数据

ov7725_ data[0]

Input

G15

OV7725摄像头采集图像数据

ov7725_href

Input

J13

摄像头行同步信号

ov7725_vsync

Input

C16

摄像头场同步信号

ov7725_pclk

Input

M16

摄像头数据像素时钟

ov7725_pwdn

Output

G11

摄像头时钟选择信号

ov7725_rst_n

Output

F16

摄像头复位信号

sccb_scl

Output

P15

摄像头SCCB_SCL线

sccb_sda

Inout

N14

摄像头SCCB_SDA线

sdram_clk

Output

R4

SDRAM芯片时钟

sdram_cke

Output

R9

SDRAM时钟有效信号

sdram_cs_n

Output

R12

SDRAM片选信号

sdram_cas_n

Output

R10

SDRAM列地址选通脉冲

sdram_ras_n

Output

R11

SDRAM行地址选通脉冲

sdram_we_n

Output

L9

SDRAM写允许位

sdram_ba[0]

Output

R13

SDRAM的L-Bank地址线

sdram_ba[1]

Output

R14

SDRAM的L-Bank地址线

sdram_addr[0]

Output

P11

SDRAM地址总线

sdram_addr[1]

Output

P14

SDRAM地址总线

sdram_addr[2]

Output

N9

SDRAM地址总线

sdram_addr[3]

Output

N11

SDRAM地址总线

sdram_addr[4]

Output

T14

SDRAM地址总线

sdram_addr[5]

Output

T13

SDRAM地址总线

sdram_addr[6]

Output

T12

SDRAM地址总线

sdram_addr[7]

Output

T11

SDRAM地址总线

sdram_addr[8]

Output

T10

SDRAM地址总线

sdram_addr[9]

Output

P9

SDRAM地址总线

sdram_addr[10]

Output

T15

SDRAM地址总线

sdram_addr[11]

Output

N12

SDRAM地址总线

sdram_addr[12]

Output

M11

SDRAM地址总线

sdram_dqm[0]

Output

M10

SDRAM数据掩码

sdram_dqm[1]

Output

M9

SDRAM数据掩码

sdram_dq[0]

Output

R3

SDRAM数据总线

sdram_dq[1]

Output

T9

SDRAM数据总线

sdram_dq[2]

Output

R5

SDRAM数据总线

sdram_dq[3]

Output

R6

SDRAM数据总线

sdram_dq[4]

Output

R7

SDRAM数据总线

sdram_dq[5]

Output

M8

SDRAM数据总线

sdram_dq[6]

Output

R8

SDRAM数据总线

sdram_dq[7]

Output

N8

SDRAM数据总线

sdram_dq[8]

Output

P8

SDRAM数据总线

sdram_dq[9]

Output

T8

SDRAM数据总线

sdram_dq[10]

Output

T7

SDRAM数据总线

sdram_dq[11]

Output

T6

SDRAM数据总线

sdram_dq[12]

Output

T5

SDRAM数据总线

sdram_dq[13]

Output

T4

SDRAM数据总线

sdram_dq[14]

Output

T3

SDRAM数据总线

sdram_dq[15]

Output

T2

SDRAM数据总线

vga_hs

Output

C2

行同步信号

vga_vs

Output

D1

场同步信号

vga_rgb[15]

Output

A5

RGB色彩信息(红)

vga_rgb[14]

Output

E6

RGB色彩信息(红)

vga_rgb[13]

Output

E7

RGB色彩信息(红)

vga_rgb[12]

Output

B8

RGB色彩信息(红)

vga_rgb[11]

Output

A8

RGB色彩信息(红)

vga_rgb[10]

Output

F8

RGB色彩信息(绿)

vga_rgb[9]

Output

E8

RGB色彩信息(绿)

vga_rgb[8]

Output

B7

RGB色彩信息(绿)

vga_rgb[7]

Output

A7

RGB色彩信息(绿)

vga_rgb[6]

Output

F7

RGB色彩信息(绿)

vga_rgb[5]

Output

F6

RGB色彩信息(绿)

vga_rgb[4]

Output

B6

RGB色彩信息(蓝)

vga_rgb[3]

Output

A6

RGB色彩信息(蓝)

vga_rgb[2]

Output

B5

RGB色彩信息(蓝)

vga_rgb[1]

Output

A2

RGB色彩信息(蓝)

vga_rgb[0]

Output

B4

RGB色彩信息(蓝)

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 58‑38、图 58‑39所示。

OV7725039

图 58‑38 管脚分配

OV7725040

图 58‑39 管脚分配

如图 58‑40所示,开发板连接12V直流电源、USB-Blaster下载器JTAG端口、VGA显示器和OV7725摄像头。线路正确连接后,打开开关为板卡上电。

OV7725041

图 58‑40 程序下载连线图

如图 58‑41所示,使用“Programmer”为开发板下载程序。

OV7725042

图 58‑41 程序下载图

程序下载完成后,VGA显示屏上会显示出OV7725摄像头采集到的图像,分辨率为640*480,如图 58‑42所示。

OV7725043

图 58‑42 OV7725+VGA图像显示

8.5. 章末总结

本章节详细介绍了OV7725摄像头的相关知识,并将OV7725摄像头与VGA相结合,实现了OV7725采集图像的实时显示,本章节对于OV7725的使用知识冰山一角,OV7725还有更多强大功能,读者查阅数据手册深入学习。

8.6. 拓展训练

查阅OV7725摄像头数据手册,了解相关寄存器在不同配置下的功能,更改寄存器配置,实现采集图像的上下翻转、左右镜像等功能,在VGA 显示器上实时显示。