本帖最后由 正点原子运营 于 2023-12-19 17:56 编辑
第三十九章 OV7725摄像头RGB-LCD显示实验 1)实验平台:正点原子 ATK-DFPGL22G开发板
2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0
6)FPGA技术交流QQ群:435699340
OV7725是OmniVision(豪威科技)公司生产的一颗CMOS图像传感器,该传感器功耗低、可靠性高且采集速率快,主要应用在玩具、安防监控、电脑多媒体等领域。本章我们将使用FPGA开发板实现对OV7725的数字图像采集并通过LCD实时显示。 本章包括以下几个部分: 1.1 简介 1.2 实验任务 1.3 硬件设计 1.4 程序设计 1.5 下载验证
1.1 简介OV7725是一款1/4英寸单芯片图像传感器,其感光阵列达到640*480,能实现最快60fps VGA分辨率的图像采集。传感器内部集成了图像处理的功能,包括自动曝光控制(AEC)、自动增益控制(AGC)和自动白平衡(AWB)等。同时传感器具有较高的感光灵敏度,适合低照度的应用,下图为OV7725的功能框图。 由上图可知,感光阵列(imagearray)在XCLK时钟的驱动下进行图像采样,输出640*480阵列的模拟数据;接着模拟信号处理器在时序发生器(video timinggenerator)的控制下对模拟数据进行算法处理(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的写传输协议如下图所示: 上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写 1:读),OV7725的器件地址为7’h21,所以在写传输协议中,ID Address(W) = 8’h42(器件地址左移1位,低位补0);Sub-address为8位寄存器地址,在OV7725的数据手册中定义了0x00~0xAC共173个寄存器,有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;Write Data为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的读传输协议。 由上图可知,SCCB读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据,注意ID Address(R)= 8’h43(器件地址左移1位,低位补1)。上图中的NA位由主机(这里指FPGA)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。 在OV7725正常工作前,必须先对传感器进行初始化,即通过配置寄存器使其工作在预期的工作模式,以及得到较好画质的图像。因为SCCB的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。OV公司提供了OV7725的软件使用手册(位于开发板所随附的资料“7_硬件资料/6_OV7725资料/OV7725软件使用手册.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。 OV7725的寄存器较多,对于其它寄存器的描述可以参考OV7725的数据手册(位于开发板所随附的资料“7_硬件资料/6_OV7725资料/OV7725数据手册.pdf”)。 下图为OV7725的一些特性。 从上图可以看出,OV7725的输入时钟频率的范围是10Mhz~48Mhz,本次实验摄像头的输入时钟为12 Mhz;SCCB总线的SIO_C的时钟频率最大为400KHz;配置寄存器软件复位(寄存器地址0x12 Bit[7]位)和硬件复位(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)配置不同的数据像素格式。 由于摄像头采集的图像最终要在LCD上显示,且ATK-DFPGL22G开发板上的数据接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此我们将OV7725摄像头输出的图像像素数据配置成RGB565格式,然后转换为RGB888格式。下图为摄像头输出的VGA帧模式时序图。 在介绍时序图之前先了解几个基本的概念。 VSYNC:场同步信号,由摄像头输出,用于标志一帧数据的开始与结束。上图中VSYNC的高电平作为一帧的同步信号,在低电平时输出的数据有效。需要注意的是场同步信号是可以通过设置寄存器0x15 Bit[1]位进行取反的,即低电平同步高电平有效,本次实验使用的是和上图一致的默认设置; HREF/HSYNC:行同步信号,由摄像头输出,用于标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的,只是数据的同步方式不一样。本次实验使用的是HREF格式输出,当HREF为高电平时,图像输出有效,可以通过寄存器0x15 Bit[6]进行配置。本次实验使用的是HREF格式输出; D[9:0]:数据信号,由摄像头输出,在RGB格式输出中,只有高8位D[9:2]是有效的; tPCLK:一个像素时钟周期; tp:单个数据周期,这里需要注意的是上图中左下角红框标注的部分,在RGB模式中,tp代表两个tPCLK(像素时钟)。以RGB565数据格式为例,RGB565采用16bit数据表示一个像素点,而OV7725在一个像素周期(tPCLK)内只能传输8bit数据,因此需要两个时钟周期才能输出一个RGB565数据; tLine:摄像头输出一行数据的时间,共784个tp,包含640tp个高电平和144tp个低电平,其中640tp为有效像素数据输出的时间。以RGB565数据格式为例,640tp实际上是640*2=1280个tPCLK; 由图 39.1.5可知,VSYNC的上升沿作为一帧的开始,高电平同步脉冲的时间为4*tLine,紧接着等待18*tLine时间后,HREF开始拉高,此时输出有效数据;HREF由640tp个高电平和144tp个低电平构成;输出480行数据之后等待8*tLine时间一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame =(4 + 18 + 480 + 8)*tLine = 510tLine。 本次实验采用OV7725支持的最大分辨率640*480,摄像头的输入时钟为12 Mhz,摄像头的输出时钟为24 Mhz(详细公式请看表 39.1.1),由此我们可以计算出摄像头的输出帧率,以PCLK=24Mhz(周期为42ns)为例,计算出OV7725输出一帧图像所需的时间如下: 一帧图像输出时间:tFrame= 510*tLine = 510*784tp = 510*784*2tPCLK = 799680*42ns =33.5866ms; 摄像头输出帧率:1000ms/33.5866ms≈ 30Hz。 如果把像素时钟频率提高到摄像头的最大时钟频率48Mhz,通过上述计算方法,摄像头的输出帧率约为60Hz。 下图为OV7725输出RGB565格式的时序图: 上图中的PCLK为OV7725输出的像素时钟,HREF为行同步信号,D[9:2]为8位像素数据。OV7725最大可以输出10位数据,在RGB565输出模式中,只有高8位是有效的。像素数据在HREF为高电平时有效,第一次输出的数据为RGB565数据的高8位,第二次输出的数据为RGB565数据的低8位,first byte和second byte组成一个16位RGB565数据。由上图可知,数据是在像素时钟的下降沿改变的,为了在数据最稳定的时刻采集图像数据,所以我们需要在像素时钟的上升沿采集数据。
1.2 实验任务本节实验任务是使用ATK-DFPGL22G开发板及OV7725摄像头实现图像采集,并通过RGB-LCD接口驱动RGB-LCD液晶屏(支持目前正点原子推出的所有RGB-LCD屏),并实时显示出图像。
1.3 硬件设计ATK-DFPGL22G开发板上有一个摄像头扩展接口,该接口可以用来连接OV7725/OV5640等摄像头模块,摄像头扩展接口原理图如下所示: ATK-OV7725是正点原子推出的一款高性能30W像素高清摄像头模块。该模块通过2*9排针(2.54mm间距)同外部连接,我们将摄像头的排针直接插在开发板上的摄像头接口即可。 前面说过,OV7725在RGB565模式中只有高8位(即D[9:2])数据是有效的,而我们的摄像头排针上数据引脚的个数是8位。实际上,摄像头排针上的8位数据连接的就是OV7725传感器的D[9:2],所以我们直接使用摄像头排针上的8位数据引脚即可。 需要注意的是,由图 39.3.1可知,摄像头扩展口的第18个引脚定义为CMOS_PWDN,而我们的OV7725摄像头模块的PWDN引脚固定为低电平,也就是一直处于正常工作模式。OV7725摄像头模块的第18个引脚定义为SGM_CTRL(CMOS_PWDN),这个引脚是摄像头驱动时钟的选择引脚。OV7725摄像头模块内部自带晶振的,当SGM_CTRL引脚为低电平时,选择使用摄像头的外部时钟,也就是FPGA需要输出时钟给摄像头;当SGM_CTRL引脚为高电平时,选择使用摄像头的晶振提供时钟。本次实验将SGM_CTRL引脚驱动为高电平,这样就不用为摄像头提供驱动时钟,即不用在CMOS_XCLK引脚上输出时钟。 由于LCD接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配,如下表所示: 表 39.3.1 OV7725摄像头RGB-LCD显示实验管脚分配 可以看出本章与之前例程相比,电平标准发生了改变,这里用到的电平标准是LVTTL33(该电平标准为配置引脚时的默认电平标准),用户也可根据需求将其改为LVCMOS33, LVCMOS33和LVTTL33可以直接相互驱动,所以这里没有进行修改,保持默认;LVCMOS和LVTTL的区别在于LVCMOS噪声容限更大,抗噪性更好,而LVTTL噪声容限小,但是速度快。 摄像头约束文件如下: - define_attribute{n:cam_pclk} {PAP_CLOCK_DEDICATED_ROUTE} {FALSE}
- define_attribute{p:cam_scl} {PAP_IO_DIRECTION} {Output}
- define_attribute{p:cam_scl} {PAP_IO_LOC} {E6}
- define_attribute{p:cam_scl} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_scl} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_scl} {PAP_IO_DRIVE} {16}
- define_attribute{p:cam_scl} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_scl} {PAP_IO_SLEW} {SLOW}
- define_attribute{p:cam_sda} {PAP_IO_DIRECTION} {Inout}
- define_attribute{p:cam_sda} {PAP_IO_LOC} {B6}
- define_attribute{p:cam_sda} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_sda} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_sda} {PAP_IO_DRIVE} {16}
- define_attribute{p:cam_sda} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_sda} {PAP_IO_SLEW} {SLOW}
- define_attribute{p:cam_data[0]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[0]} {PAP_IO_LOC} {A6}
- define_attribute{p:cam_data[0]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[0]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[0]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_data[1]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[1]} {PAP_IO_LOC} {B2}
- define_attribute{p:cam_data[1]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[1]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[1]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_data[2]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[2]} {PAP_IO_LOC} {A5}
- define_attribute{p:cam_data[2]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[2]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[2]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_data[3]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[3]} {PAP_IO_LOC} {A2}
- define_attribute{p:cam_data[3]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[3]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[3]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_data[4]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[4]} {PAP_IO_LOC} {F7}
- define_attribute{p:cam_data[4]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[4]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[4]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_data[5]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[5]} {PAP_IO_LOC} {B1}
- define_attribute{p:cam_data[5]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[5]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[5]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_data[6]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[6]} {PAP_IO_LOC} {F8}
- define_attribute{p:cam_data[6]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[6]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[6]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_data[7]} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_data[7]} {PAP_IO_LOC} {A1}
- define_attribute{p:cam_data[7]} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_data[7]} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_data[7]} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_href} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_href} {PAP_IO_LOC} {F4}
- define_attribute{p:cam_href} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_href} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_href} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_pclk} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_pclk} {PAP_IO_LOC} {C1}
- define_attribute{p:cam_pclk} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_pclk} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_pclk} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_vsync} {PAP_IO_DIRECTION} {Input}
- define_attribute{p:cam_vsync} {PAP_IO_LOC} {G4}
- define_attribute{p:cam_vsync} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_vsync} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_vsync} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_rst_n} {PAP_IO_DIRECTION} {Output}
- define_attribute{p:cam_rst_n} {PAP_IO_LOC} {D1}
- define_attribute{p:cam_rst_n} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_rst_n} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_rst_n} {PAP_IO_SLEW} {SLOW}
- define_attribute{p:cam_rst_n} {PAP_IO_DRIVE} {4}
- define_attribute{p:cam_rst_n} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:cam_sgm_ctrl} {PAP_IO_DIRECTION} {Output}
- define_attribute{p:cam_sgm_ctrl} {PAP_IO_LOC} {C2}
- define_attribute{p:cam_sgm_ctrl} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:cam_sgm_ctrl} {PAP_IO_STANDARD} {LVTTL33}
- define_attribute{p:cam_sgm_ctrl} {PAP_IO_SLEW} {SLOW}
- define_attribute{p:cam_sgm_ctrl} {PAP_IO_DRIVE} {4}
- define_attribute{p:cam_sgm_ctrl} {PAP_IO_PULLUP} {TRUE}
复制代码cam_pclk是摄像头时钟,因为配置在了普通io口,没有走时钟专用管脚且最终上了时钟网络,所以PDS流程运行到place&route会报错((报错编号为Elace-0084)),如果将该时钟相关的net或该net的驱动pin脚设置PAP_CLOCK_DEDICATED_ROUTE属性为FLASE,则PDS流程会在place&route中将报错降级为Critical Warning(C: Place-2028)。最终的表现形式为PDS流程在place&route阶段不会报错停止而是以报警告代替。cam_pclk约束语句如下: define_attribute{n:cam_pclk} {PAP_CLOCK_DEDICATED_ROUTE} {FALSE}
1.4 程序设计OV7725在VGA帧模式下,以RGB565格式输出最高帧率可达60Hz,LCD屏的刷新频率也可以达到60Hz,那么是不是直接将采集到的图像数据连接到LCD屏的输入数据端口就行了呢?答案是不可以。我们在前面说过,OV7725帧率如果要达到60Hz,那么像素时钟频率必须为48Mhz,而LCD屏根据屏的分辨率不同时钟也不同,首先就有时钟不匹配的问题,其次是时序方面的不匹配,LCD屏驱动对时序有着严格的要求。我们在“RGB-LCD彩条显示实验”的章节中可以获知,LCD一行或一场分为四个部分:低电平同步脉冲、显示后沿、有效数据段以及显示前沿,各个部分的时序参数很显然跟OV7725并不是完全一致的。因此必须先把一帧图像缓存下来,然后再把图像数据按照LCD的时序发送到LCD屏上显示。OV7725在VGA帧模式输出下,一帧图像的数据量达到640*480*16bit = 4915200bit = 4800kbit = 4.6875Mbit,带宽为4.6875Mbit*45Hz(帧率)=210.9375 Mbit/S,我们ATK-DFPGL22G开发板芯片型号为PGL22G-6MGB324,从器件手册可以发现,PGL22G-6MGB324的片内存储资源为864Kbit,远不能达到存储要求。因此我们只能使用板载的外部存储器DDR3来缓存图像数据,开发板板载的DDR3容量为4096Mbit(一块DDR3的容量),最大带宽为12.8Gbit/S,足以满足缓存图像数据的需求。 OV7725在正常工作之前必须通过配置寄存器进行初始化,而配置寄存器的SCCB协议和I2C协议在写操作时几乎一样,所以我们需要一个I2C驱动模块。为了使OV7725在期望的模式下运行并且提高图像显示效果,需要配置较多的寄存器,这么多寄存器的地址与参数需要单独放在一个模块,因此还需要一个寄存配置信息的I2C配置模块。在摄像头配置完成后,开始输出图像数据,因此需要一个摄像头图像采集模块来采集图像;外接DDR3存储器当然离不开DDR3控制器模块的支持,最后LCD顶层模块读取DDR3缓存的数据以达到最终实时显示的效果。 由上图可知,时钟模块为LCD顶层模块、DDR3控制模块以及I2C驱动模块提供驱动时钟。I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入DDR控制模块,LCD顶层模块从DDR3控制模块中读出数据,完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在DDR3和传感器都初始化完成之后才开始输出数据的,避免了在DDR3初始化过程中向里面写入数据。 顶层模块代码如下: - 1 module ov7725_lcd(
- 2 input sys_clk ,
- 3 input sys_rst_n ,
- 4 //lcd接口
- 5 output lcd_hs , //LCD 行同步信号
- 6 output lcd_vs , //LCD 场同步信号
- 7 output lcd_de , //LCD 数据输入使能
- 8 inout [23:0 lcd_rgb , //LCD 颜色数据
- 9 output lcd_bl , //LCD 背光控制信号
- 10 output lcd_rst , //LCD 复位信号
- 11 output lcd_pclk , //LCD 采样时钟
- 12 //摄像头接口
- 13 input cam_pclk , //cmos 数据像素时钟
- 14 input cam_vsync , //cmos 场同步信号
- 15 input cam_href , //cmos 行同步信号
- 16 input [7:0 cam_data , //cmos 数据
- 17 output cam_rst_n , //cmos 复位信号,低电平有效
- 18 output cam_sgm_ctrl , //cmos 时钟选择信号, 1:使用摄像头自带的晶振
- 19 output cam_scl , //cmos SCCB_SCL线
- 20 inout cam_sda , //cmos SCCB_SDA线
- 21 //DDR3接口
- 22 input pad_loop_in , //低位温度补偿输入
- 23 input pad_loop_in_h , //高位温度补偿输入
- 24 output pad_rstn_ch0 , //Memory复位
- 25 output pad_ddr_clk_w , //Memory差分时钟正
- 26 output pad_ddr_clkn_w , //Memory差分时钟负
- 27 output pad_csn_ch0 , //Memory片选
- 28 output [15:0 pad_addr_ch0 , //Memory地址总线
- 29 inout [16-1:0 pad_dq_ch0 , //数据总线
- 30 inout [16/8-1:0 pad_dqs_ch0 , //数据时钟正端
- 31 inout [16/8-1:0 pad_dqsn_ch0 , //数据时钟负端
- 32 output [16/8-1:0 pad_dm_rdqs_ch0 , //数据Mask
- 33 output pad_cke_ch0 , //Memory差分时钟使
- 34 output pad_odt_ch0 , //On Die Terminati
- 35 output pad_rasn_ch0 , //行地址strobe
- 36 output pad_casn_ch0 , //列地址strobe
- 37 output pad_wen_ch0 , //写使能
- 38 output [2:0 pad_ba_ch0 , //Bank地址总线
- 39 output pad_loop_out , //低位温度补偿输出
- 40 output pad_loop_out_h //高位温度补偿输出
- 41 );
- 42
- 43 //parameter define
- 44 parameter SLAVE_ADDR = 7'h21 ; //OV7725的器件地址7'h21
- 45 parameter BIT_CTRL =1'b0 ; //OV7725的字节地址为8位 0:8位 1:16位
- 46 parameter CLK_FREQ =27'd50_000_000; //i2c_dri模块的驱动时钟频率
- 47 parameter I2C_FREQ =18'd250_000 ;//I2C的SCL时钟频率,不超过400KHz
- 48 parameter APP_ADDR_MIN = 28'd0 ; //ddr3读写起始地址,以一个16bit的数据为一个单位
- 49 parameter BURST_LENGTH = 8'd64 ; //ddr3读写突发长度,64个128bit的数据
- 50
复制代码顶层模块中第46至第47行定义了两个变量:CLK_FREQ(i2c_dri模块的驱动时钟频率)和I2C_FREQ(I2C的SCL时钟频率),I2C_FREQ的时钟频率不能超过400KHz,否则有可能导致摄像头配置不成功。 - 51 //wire define
- 52 wire sys_init_done ; //系统初始化完成(DDR3初始化+摄像头初始化)
- 53 wire rst_n ; //全局复位
- 54 //PLL
- 55 wire clk_50m ; //output 50M
- 56 wire clk_100m ; //output 100M
- 57 wire clk_locked ;
- 58 //OV7725
- 59 wire i2c_dri_clk ; //I2C操作时钟
- 60 wire i2c_done ; //I2C寄存器配置完成信号
- 61 wire i2c_exec ; //I2C触发执行信号
- 62 wire [15:0 i2c_data ; //I2C要配置的地址与数据(高8位地址,低8位数据)
- 63 wire cam_init_done ; //摄像头初始化完成
- 64 wire cmos_frame_vsync; //帧有效信号
- 65 wire cmos_frame_href ; //行有效信号
- 66 wire cmos_frame_valid; //数据有效使能信号
- 67 wire [15:0 wr_data ; //OV7725写入DDR3控制器模块的数据
- 68 wire [27:0 ddr3_addr_max ; //存入DDR3的最大读写地址
- 69 //LCD
- 70 wire lcd_clk ; //分频产生的LCD采样时钟
- 71 wire [10:0 h_disp ; //LCD屏水平分辨率
- 72 wire [10:0 v_disp ; //LCD屏垂直分辨率
- 73 wire [15:0 lcd_id ; //LCD屏的ID号
- 74 wire out_vsync ; //LCD场信号
- 75 wire rdata_req ; //读数据请求
- 76 //DDR3
- 77 wire [15:0 rd_data ; //DDR3控制器模块输出的数据
- 78 wire fram_done ; //DDR中已经存入一帧画面标志
- 79 wire ddr_init_done ; //ddr3初始化完成
- 80
- 81 //*****************************************************
- 82 //** main code
- 83 //*****************************************************
- 84
- 85 //待时钟锁定后产生结束复位信号
- 86 assign rst_n = sys_rst_n & clk_locked;
- 87
- 88 //系统初始化完成:DDR3和摄像头都初始化完成
- 89 //避免了在DDR3初始化过程中向里面写入数据
- 90 assign sys_init_done = ddr_init_done & cam_init_done;
- 91
- 92 //cmos 时钟选择信号, 1:使用摄像头自带的晶振
- 93 assign cam_sgm_ctrl = 1'b1;
- 94
- 95 //不对摄像头硬件复位,固定高电平
- 96 assign cam_rst_n = 1'b1;
- 97
- 98 //例化PLL IP核
- 99 pll_clk u_pll_clk(
- 100 .pll_rst (~sys_rst_n ),
- 101 .clkin1 (sys_clk ),
- 102 .clkout0 (clk_50m ),
- 103 .clkout1 (clk_100m ),
- 104 .pll_lock (clk_locked )
- 105 );
- 106
- 107 //I2C配置模块
- 108 i2c_ov7725_rgb565_cfg u_i2c_cfg(
- 109 .clk (i2c_dri_clk ), //in
- 110 .rst_n (rst_n ), //in
- 111 .i2c_done (i2c_done ), //in
- 112 .i2c_exec (i2c_exec ), //out
- 113 .i2c_data (i2c_data ), //out
- 114 .init_done (cam_init_done) //out
- 115 );
- 116
- 117 //I2C驱动模块
- 118 i2c_dri
- 119 #(
- 120 .SLAVE_ADDR (SLAVE_ADDR), //参数传递
- 121 .CLK_FREQ (CLK_FREQ ),
- 122 .I2C_FREQ (I2C_FREQ )
- 123 )
- 124 u_i2c_dri(
- 125 .clk (clk_50m ),
- 126 .rst_n (rst_n ),
- 127 //i2c interface
- 128 .i2c_exec (i2c_exec ),
- 129 .bit_ctrl (BIT_CTRL ),
- 130 .i2c_rh_wl (1'b0 ), //固定为0,只用到了IIC驱动的写操作
- 131 .i2c_addr (i2c_data[15:8]),
- 132 .i2c_data_w (i2c_data[7:0 ),
- 133 .i2c_data_r ( ), //out
- 134 .i2c_done (i2c_done ), //out
- 135 .i2c_ack ( ), //out
- 136 .scl (cam_scl ), //out
- 137 .sda (cam_sda ), //inout
- 138 //user interface
- 139 .dri_clk (i2c_dri_clk ) //I2C操作时钟//out
- 140 );
- 141
- 142 //图像采集顶层模块
- 143 cmos_data_top u_cmos_data_top(
- 144 .rst_n (rst_n & sys_init_done), //系统初始化完成之后再开始采集数据
- 145 .cam_pclk (cam_pclk ),
- 146 .cam_vsync (cam_vsync ),
- 147 .cam_href (cam_href ),
- 148 .cam_data (cam_data ),
- 149 .lcd_id (lcd_id ), //in
- 150 .h_disp (h_disp ), //in
- 151 .v_disp (v_disp ), //in
- 152 .h_pixel ( ),
- 153 .v_pixel ( ),
- 154 .ddr3_addr_max (ddr3_addr_max ), //out
- 155 .cmos_frame_vsync (cmos_frame_vsync ),
- 156 .cmos_frame_href (cmos_frame_href ),
- 157 .cmos_frame_valid (cmos_frame_valid ), //数据有效使能信号
- 158 .cmos_frame_data (wr_data ) //有效数据
- 159 );
- 160
复制代码在程序的第157和第158行,CMOS图像数据采集模块输出的cmos_frame_valid(数据有效使能信号)和wr_data(有效数据)连接到DDR3控制模块,实现了图像数据的缓存。 - 161 //ddr3
- 162 ddr3_top u_ddr3_top(
- 163 .refclk_in (clk_50m ),
- 164 .rst_n (rst_n ),
- 165 .app_addr_rd_min (APP_ADDR_MIN ),
- 166 .app_addr_rd_max (ddr3_addr_max ),
- 167 .rd_bust_len (BURST_LENGTH ),
- 168 .app_addr_wr_min (APP_ADDR_MIN ),
- 169 .app_addr_wr_max (ddr3_addr_max ),
- 170 .wr_bust_len (BURST_LENGTH ),
- 171 .ddr3_read_valid (1'b1 ),
- 172 .ddr3_pingpang_en (1'b1 ),
- 173 .wr_clk (cam_pclk ),
- 174 .rd_clk (lcd_clk ),
- 175 .datain_valid (cmos_frame_valid),
- 176 .datain (wr_data ),
- 177 .rdata_req (rdata_req ),
- 178 .rd_load (out_vsync ),
- 179 .wr_load (cmos_frame_vsync),
- 180 .fram_done (fram_done ),
- 181 .dataout (rd_data ),
- 182 .pll_lock (pll_lock ),
- 183 .ddr_init_done (ddr_init_done ),
- 184 .ddrphy_rst_done ( ),
- 185 .pad_loop_in (pad_loop_in ),
- 186 .pad_loop_in_h (pad_loop_in_h ),
- 187 .pad_rstn_ch0 (pad_rstn_ch0 ),
- 188 .pad_ddr_clk_w (pad_ddr_clk_w ),
- 189 .pad_ddr_clkn_w (pad_ddr_clkn_w ),
- 190 .pad_csn_ch0 (pad_csn_ch0 ),
- 191 .pad_addr_ch0 (pad_addr_ch0 ),
- 192 .pad_dq_ch0 (pad_dq_ch0 ),
- 193 .pad_dqs_ch0 (pad_dqs_ch0 ),
- 194 .pad_dqsn_ch0 (pad_dqsn_ch0 ),
- 195 .pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0 ),
- 196 .pad_cke_ch0 (pad_cke_ch0 ),
- 197 .pad_odt_ch0 (pad_odt_ch0 ),
- 198 .pad_rasn_ch0 (pad_rasn_ch0 ),
- 199 .pad_casn_ch0 (pad_casn_ch0 ),
- 200 .pad_wen_ch0 (pad_wen_ch0 ),
- 201 .pad_ba_ch0 (pad_ba_ch0 ),
- 202 .pad_loop_out (pad_loop_out ),
- 203 .pad_loop_out_h (pad_loop_out_h )
- 204
- 205 );
- 206
复制代码在程序的第167和第170行,信号(rd_bust_len)和信号(wr_bust_len)表示一次向DDR读或写的长度,这里长度我们设置为64(2的6次方),因为64*8可以被最大读写地址整除,能够匹配正点原子在售的所有RGB-LCD屏,这里建议突发长度设置为2的整数次方,其他突发长度可能会出现数据反卷,反卷现象的详细说明可以查看“DDR3读写测试实验”的程序设计部分。 - 207 //LCD驱动显示模块
- 208 lcd_rgb_top u_lcd_rgb_top(
- 209 .sys_clk (clk_50m ),
- 210 .clk_100m (clk_100m ),
- 211 .sys_rst_n (rst_n && clk_locked ),
- 212 .sys_init_done (sys_init_done ),
- 213 //lcd接口
- 214 .lcd_id (lcd_id ), //LCD屏的ID号
- 215 .lcd_hs (lcd_hs ), //LCD 行同步信号
- 216 .lcd_vs (lcd_vs ), //LCD 场同步信号
- 217 .lcd_de (lcd_de ), //LCD 数据输入使能
- 218 .lcd_rgb (lcd_rgb ), //LCD 颜色数据
- 219 .lcd_bl (lcd_bl ), //LCD 背光控制信号
- 220 .lcd_rst (lcd_rst ), //LCD 复位信号
- 221 .lcd_pclk (lcd_pclk ), //LCD 采样时钟
- 222 .lcd_clk (lcd_clk ), //LCD 驱动时钟
- 223 //用户接口
- 224 .h_disp (h_disp ), //行分辨率
- 225 .v_disp (v_disp ), //场分辨率
- 226 .pixel_xpos ( ),
- 227 .pixel_ypos ( ),
- 228 .out_vsync (out_vsync ),
- 229 .data_in (rd_data ), //rfifo输出数据
- 230 .data_req (rdata_req ) //请求数据输入
- 231 );
- 232
- 233 endmodule
复制代码在程序的第224和第225行,LCD顶层模块引出了h_disp和v_disp信号,并将其引入摄像头图像采集模块。因为LCD屏的分辨率是不一样的,而本次实验是驱动自适应不同分辨率的LCD屏,所以将这两个信号引入摄像头图像采集模块是为了与摄像头采集的分辨率进行比较。当摄像头的分辨率大时,就取LCD屏分辨率大小的数据存入DDR中,其余数据丢弃,当摄像头的分辨率小时,就将摄像头采集的数据存入DDR中,LCD屏其他显示区域填充黑色。 在程序的230行,LCD顶层模块输出rdata_req(请求像素点颜色数据输入)连接到DDR控制模块, 实现了图像数据的读取,将读出的数据连接到LCD顶层模块的data_in信号,从而实现了LCD实时显示的功能。 FPGA顶层模块(ov7725_lcd)例化了以下六个模块:时钟模块(pll_clk)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov7725_rgb565_cfg)、图像采集顶层模块(cmos_data_top)、ddr3控制模块(ddr3_top)和LCD顶层模块(lcd_rgb_top)。 时钟模块(pll_clk):时钟模块通过调用PLL IP核实现,共输出2个时钟,频率分别为50Mhz和100M时钟。100Mhz时钟给lcd的时钟分频模块使用,50Mhz时钟作为ddr3控制模块,I2C驱动模块和LCD顶层模块的驱动时钟。 I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV7725SCCB接口总线,用户根据该模块提供的用户接口可以很方便的对OV7725的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。 I2C配置模块(i2c_ov7725_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV7725的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV7725传感器的初始化。 图像采集顶层模块(cmos_data_top):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成DDR控制模块的写使能信号和16位写数据信号,完成对OV7725传感器图像的采集。如果LCD屏的分辨率小于OV7725的分辨率,还要对OV7725采集的数据进行裁剪,以匹配LCD屏的分辨率。 ddr3控制模块(ddr3_top):ddr3控制模块负责驱动DDR3片外存储器,缓存图像传感器输出的图像数据。该模块将DDR3复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。在“DDR3读写测试实验”的程序中,读写操作地址都是DDR的同一存储空间,如果只使用一个存储空间缓存图像数据,那么同一存储空间中会出现两帧图像叠加的情况,为了避免这一情况,我们在DDR3的其它BANK中开辟了三个相同大小的存储空间,使用乒乓操作的方式来写入和读取数据,所以本次实验在“DDR3读写测试实验”的程序里做了一个小小的改动,有关该模块的详细介绍请大家参考“DDR3读写测试实验”章节,本章只对改动的地方作介绍。 LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。 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 [15:0 i2c_data , //I2C要配置的地址与数据(高8位地址,低8位数据)
- 8 output reg init_done //初始化完成信号
- 9 );
- 10
- 11 //parameter define
- 12 parameter REG_NUM = 7'd70 ; //总共需要配置的寄存器个数
- 13
复制代码在代码的第12行定义了总共需要配置的寄存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。 - 14 //reg define
- 15 reg [9:0 start_init_cnt; //等待延时计数器
- 16 reg [6:0 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
复制代码图像传感器刚开始上电时电压有可能不够稳定,所以程序中的23行至32行定义了一个延时计数器(start_init_cnt)等待传感器工作在稳定的状态。当计数器计数到预设值之后,开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。从前面介绍的OV7725的特性可知,软件复位需要等待1ms的时间才能配置其它的寄存器,因此发送完软件复位命令后,延时计数器清零,并重新开始计数。当计数器计数到预设值之后,紧接着配置剩下的寄存器。只有软件复位命令需要1ms的等待时间,其它寄存器不需要等待时间,直接按照程序中定义的顺序发送即可。 - 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}; //COM7BIT[7]:复位所有的寄存器
- 72 7'd1 : i2c_data <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿
- 73 7'd2 : i2c_data <= {8'h15, 8'h00}; //COM10href/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}; //COM4PLL倍频设置(multiplier)
- 84 //Bit[7:6]: 0:1x 1:4x 2:6x 3:8x
- 85 7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 内部时钟配置
- 86 //Freq=multiplier/[(CLKRC[5:0]+1)*2]
- 87 7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式
- 88 7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3Bit[0]: 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_Ctrl1DSP控制字节1
- 94 7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2DSP控制字节2
- 95 7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3DSP控制字节3
- 96 7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4DSP控制字节4
- 97 //AGC AEC AWB
- 98 //COM8 Bit[2]:自动增益使能 Bit[1]:自动白平衡使能 Bit[0]:自动曝光功能
- 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[1:0] 输出电流驱动能力
- 150 //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
- 151 default:i2c_data <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位
- 152 endcase
- 153 end
- 154 end
- 155
- 156 endmodule
复制代码在程序的83至86行,说明了关于摄像头输出时钟的寄存器配置,摄像头的寄存器地址0x0d配置成0x41,表示PLL倍频设置设为了4倍频,摄像头的寄存器地址0x11配置成0x00,而摄像头的输入时钟为12M,所以根据第86行的公式可得到摄像头的输出时钟为24M。 图像采集顶层模块的代码如下: - 1 module cmos_data_top(
- 2 input rst_n , //复位信号
- 3 input [15:0 lcd_id , //LCD屏的ID号
- 4 input [10:0 h_disp , //LCD屏水平分辨率
- 5 input [10:0 v_disp , //LCD屏垂直分辨率
- 6 //摄像头接口
- 7 input cam_pclk , //cmos 数据像素时钟
- 8 input cam_vsync , //cmos 场同步信号
- 9 input cam_href , //cmos 行同步信号
- 10 input [7:0 cam_data , //cmos 数据
- 11 //用户接口
- 12 output [10:0 h_pixel , //存入ddr3的水平分辨率
- 13 output [10:0 v_pixel , //存入ddr3的屏垂直分辨率
- 14 output [27:0 ddr3_addr_max , //存入DDR3的最大读写地址
- 15 output cmos_frame_vsync , //帧有效信号
- 16 output cmos_frame_href , //行有效信号
- 17 output cmos_frame_valid , //数据有效使能信号
- 18 output [15:0 cmos_frame_data //有效数据
- 19 );
- 20
- 21 //wire define
- 22 wire [10:0 h_pixel ; //存入ddr3的水平分辨率
- 23 wire [10:0 v_pixel ; //存入ddr3的屏垂直分辨率
- 24 wire [15:0 lcd_id_a ; //时钟同步后的LCD屏的ID号
- 25 wire [27:0 ddr3_addr_max ; //存入DDR3的最大读写地址
- 26 wire [15:0 wr_data_tailor; //经过裁剪的摄像头数据
- 27 wire [15:0 wr_data ; //没有经过裁剪的摄像头数据
- 28
- 29 //*****************************************************
- 30 //** main code
- 31 //*****************************************************
- 32
- 33 assign cmos_frame_valid = (lcd_id_a == 16'h4342) ? data_valid_tailor : data_valid ;
- 34 assign cmos_frame_data = (lcd_id_a == 16'h4342) ? wr_data_tailor : wr_data ;
- 35
- 36 //摄像头数据裁剪模块
- 37 cmos_tailor u_cmos_tailor(
- 38 .rst_n (rst_n ),
- 39 .lcd_id (lcd_id ),
- 40 .lcd_id_a (lcd_id_a ),
- 41 .cam_pclk (cam_pclk ),
- 42 .cam_vsync (cmos_frame_vsync ),
- 43 .cam_href (cmos_frame_href ),
- 44 .cam_data (wr_data ),
- 45 .cam_data_valid (data_valid ),
- 46 .h_disp (h_disp ),
- 47 .v_disp (v_disp ),
- 48 .h_pixel (h_pixel ),
- 49 .v_pixel (v_pixel ),
- 50 .ddr3_addr_max (ddr3_addr_max ),
- 51 .cmos_frame_valid (data_valid_tailor),
- 52 .cmos_frame_data (wr_data_tailor )
- 53
- 54 );
- 55
- 56 //摄像头数据采集模块
- 57 cmos_capture_data u_cmos_capture_data(
- 58 .rst_n (rst_n ),
- 59 .cam_pclk (cam_pclk ),
- 60 .cam_vsync (cam_vsync ),
- 61 .cam_href (cam_href ),
- 62 .cam_data (cam_data ),
- 63 .cmos_frame_vsync (cmos_frame_vsync),
- 64 .cmos_frame_href (cmos_frame_href ),
- 65 .cmos_frame_valid (data_valid ),
- 66 .cmos_frame_data (wr_data )
- 67 );
- 68
- 69 endmodule
复制代码图像采集顶层模块(cmos_data_ top)例化了以下二个模块:图像采集模块(cmos_capture_data)、图像裁剪模块(cmos_tailor)。 图像采集模块(cmos_capture_data)为其他模块提供摄像头8bit输入数据转化后的16bit数据和数据使能以及摄像头稳定后的行场信号。图像裁剪模块(cmos_tailor)只有在LCD的器件ID为16’h4342时起作用,即摄像头的分辨率大于LCD屏的分辨率,起到裁剪图像数据,使图像的有效数据达到匹配LCD屏的尺寸。 在程序中的33行至34行,定义了两个信号cmos_frame_valid和cmos_frame_data,这两个信号的输出是根据LCD的器件ID来选择的,当摄像头的分辨率大于LCD的分辨率时,将裁剪后的数据和数据使能赋给这两个信号,反之将没有裁剪的信号和使能赋给这两个信号。 摄像头接口输出8位像素数据,而本次实验配置摄像头的模式是RGB565,所以需要在图像采集模块实现8位数据转16位数据的功能。 CMOS图像数据采集模块的代码如下所示: CMOS图像采集模块第18行定义了参数WAIT_FRAME(寄存器数据稳定等待的帧个数),我们在前面介绍寄存器时提到过配置寄存器生效的时间最长为300ms,约为摄像头输出10帧图像数据。所以这里采集场同步信号的上升沿来统计帧数,计数器计数超过10次之后产生数据有效的标志,开始采集图像。在程序的第87行开始的always块实现了8位数据转16位数据的功能。需要注意的是摄像头的图像数据是在像素时钟(cam_pclk)下输出的,因此摄像头的图像数据必须使用像素钟来采集,否则会造成数据采集错误。 当摄像头的分辨率大于LCD屏的分辨率时,需要对其进行裁剪,使存入DDR3的分辨率匹配LCD屏的尺寸。 图像裁剪模块的代码如下所示: - 1 module cmos_tailor(
- 2 input rst_n , //复位信号
- 3 input [15:0 lcd_id , //LCD屏的ID号
- 4 input [10:0 h_disp , //LCD屏水平分辨率
- 5 input [10:0 v_disp , //LCD屏垂直分辨率
- 6 output [10:0 h_pixel , //存入ddr3的水平分辨率
- 7 output [10:0 v_pixel , //存入ddr3的屏垂直分辨率
- 8 output [27:0 ddr3_addr_max , //存入ddr3的最大读写地址
- 9 output [15:0 lcd_id_a , //时钟同步后的LCD屏的ID号
- 10 //摄像头接口
- 11 input cam_pclk , //cmos 数据像素时钟
- 12 input cam_vsync , //cmos 场同步信号
- 13 input cam_href , //cmos 行同步信号
- 14 input [15:0 cam_data ,
- 15 input cam_data_valid ,
- 16 //用户接口
- 17 output cmos_frame_valid , //数据有效使能信号
- 18 output [15:0 cmos_frame_data //有效数据
- 19 );
- 20
- 21 //reg define
- 22 reg cam_vsync_d0 ;
- 23 reg cam_vsync_d1 ;
- 24 reg cam_href_d0 ;
- 25 reg cam_href_d1 ;
- 26 reg [10:0 h_pixel ; //存入ddr3的水平分辨率
- 27 reg [10:0 v_pixel ; //存入ddr3的屏垂直分辨率
- 28 reg [10:0 h_cnt ; //对行计数
- 29 reg [10:0 v_cnt ; //对场计数
- 30 reg cmos_frame_valid ;
- 31 reg [15:0 cmos_frame_data ;
- 32 reg [15:0 lcd_id_a ; //LCD屏的ID号
- 33 reg [10:0 h_disp_a ; //LCD屏水平分辨率
- 34 reg [10:0 v_disp_a ; //LCD屏垂直分辨率
- 35
- 36 //wire define
- 37 wire pos_vsync ; //采输入场同步信号的上升沿
- 38 wire neg_hsync ; //采输入行同步信号的下降沿
- 39 wire [10:0 cmos_h_pixel ; //CMOS水平方向像素个数
- 40 wire [10:0 cmos_v_pixel ; //CMOS垂直方向像素个数
- 41 wire [10:0 cam_border_pos_l ; //左侧边界的横坐标
- 42 wire [10:0 cam_border_pos_r ; //右侧边界的横坐标
- 43 wire [10:0 cam_border_pos_t ; //上端边界的纵坐标
- 44 wire [10:0 cam_border_pos_b ; //下端边界的纵坐标
- 45
- 46 //*****************************************************
- 47 //** main code
- 48 //*****************************************************
- 49
- 50 assign ddr3_addr_max = h_pixel * v_pixel; //存入ddr3的最大读写地址
- 51 assign cmos_h_pixel = 11'd640 ; //CMOS水平方向像素个数
- 52 assign cmos_v_pixel = 11'd480 ; //CMOS垂直方向像素个数
- 53
复制代码在模块的第50行对信号ddr3_addr_max(存入DDR3的最大读写地址)进行了赋值,因为本次实验用了一片DDR3,故DDR3的数据位宽为16位,而摄像头的数据位宽为16位,所以DDR3的最大存储地址为行场分辨率的乘积。 在模块的第51和52行对两个信号cmos_h_pixel(CMOS水平方向像素个数)和cmos_v_pixel(CMOS垂直方向像素个数)进行定义,这两个信号代表了本次实验摄像头的分辨率,而这次实验的LCD的分辨率是不固定的,故可能出现LCD屏的分辨率比摄像头分辨率小的情况,而正点原子的LCD屏只有一种480x272的屏分辨率比摄像头的分辨率小,所以需要将摄像头的图像大小裁剪成和LCD屏的分辨率大小一样。 - 54 //采输入场同步信号的上升沿
- 55 assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
- 56
- 57 //采输入行同步信号的下降沿
- 58 assign neg_hsync = (~cam_href_d0) & cam_href_d1;
- 59
- 60 //左侧边界的横坐标计算
- 61 assign cam_border_pos_l = (cmos_h_pixel - h_disp_a)/2-1;
- 62
- 63 //右侧边界的横坐标计算
- 64 assign cam_border_pos_r = h_disp + (cmos_h_pixel - h_disp_a)/2-1;
- 65
- 66 //上端边界的纵坐标计算
- 67 assign cam_border_pos_t = (cmos_v_pixel - v_disp_a)/2;
- 68
- 69 //下端边界的纵坐标计算
- 70 assign cam_border_pos_b = v_disp_a + (cmos_v_pixel - v_disp_a)/2;
- 71
复制代码
如上图所示,在代码的第60至70行,计算了摄像头存入DDR3的边界坐标,上边界坐标(cam_border_pos_t)是摄像头的场分辨率(480)减去LCD屏的场分辨率再除以2得到的,其他的边界也是以类似的计算方法得到。 - 72 always @(posedge cam_pclk or negedge rst_n) begin
- 73 if(!rst_n) begin
- 74 cam_vsync_d0 <= 1'b0;
- 75 cam_vsync_d1 <= 1'b0;
- 76 cam_href_d0 <= 1'b0;
- 77 cam_href_d1 <= 1'b0;
- 78 lcd_id_a <= 0 ;
- 79 v_disp_a <= 0 ;
- 80 h_disp_a <= 0 ;
- 81 end
- 82 else begin
- 83 cam_vsync_d0 <= cam_vsync;
- 84 cam_vsync_d1 <= cam_vsync_d0;
- 85 cam_href_d0 <= cam_href;
- 86 cam_href_d1 <= cam_href_d0;
- 87 lcd_id_a <= lcd_id;
- 88 v_disp_a <= v_disp;
- 89 h_disp_a <= h_disp;
- 90 end
- 91 end
- 92
复制代码在模块的第72至91行,对输入信号进行时钟同步,是为了减少信号的扇出和防止时序不满足。 - 93 //计算存入ddr3的分辨率
- 94 always @(posedge cam_pclk or negedge rst_n) begin
- 95 if(!rst_n) begin
- 96 h_pixel <= 11'b0;
- 97 v_pixel <= 11'b0;
- 98 end
- 99 else begin
- 100 if(lcd_id_a == 16'h4342)begin
- 101 h_pixel <= h_disp_a;
- 102 v_pixel <= v_disp_a;
- 103 end
- 104 else begin
- 105 h_pixel <= cmos_h_pixel;
- 106 v_pixel <= cmos_v_pixel;
- 107 end
- 108 end
- 109 end
- 110
复制代码在模块的第94至109行,根据LCD屏的器件ID来判断存入DDR3的分辨率,若当LCD的ID为16'h4342时,说明LCD屏的分辨率比摄像头小,所以存入DDR3的分辨率就是LCD屏的分辨率。 - 111 //对行计数
- 112 always @(posedge cam_pclk or negedge rst_n) begin
- 113 if(!rst_n)
- 114 h_cnt <= 11'b0;
- 115 else begin
- 116 if(pos_vsync||neg_hsync)
- 117 h_cnt <= 11'b0;
- 118 else if(cam_data_valid)
- 119 h_cnt <= h_cnt + 1'b1;
- 120 else if (cam_href_d0)
- 121 h_cnt <= h_cnt;
- 122 else
- 123 h_cnt <= h_cnt;
- 124 end
- 125 end
- 126
- 127 //对场计数
- 128 always @(posedge cam_pclk or negedge rst_n) begin
- 129 if(!rst_n)
- 130 v_cnt <= 11'b0;
- 131 else begin
- 132 if(pos_vsync)
- 133 v_cnt <= 11'b0;
- 134 else if(neg_hsync)
- 135 v_cnt <= v_cnt + 1'b1;
- 136 else
- 137 v_cnt <= v_cnt;
- 138 end
- 139 end
- 140
- 141 //产生输出数据有效信号(cmos_frame_valid)
- 142 always @(posedge cam_pclk or negedge rst_n) begin
- 143 if(!rst_n)
- 144 cmos_frame_valid <= 1'b0;
- 145 else if(h_cnt[10:0]>=cam_border_pos_l && h_cnt[10:0]<cam_border_pos_r&&
- 146 v_cnt[10:0]>=cam_border_pos_t && v_cnt[10:0]<cam_border_pos_b)
- 147 cmos_frame_valid <= cam_data_valid;
- 148 else
- 149 cmos_frame_valid <= 1'b0;
- 150 end
- 151
- 152 always @(posedge cam_pclk or negedge rst_n) begin
- 153 if(!rst_n)
- 154 cmos_frame_data <= 1'b0;
- 155 else if(h_cnt[10:0]>=cam_border_pos_l && h_cnt[10:0]<cam_border_pos_r&&
- 156 v_cnt[10:0]>=cam_border_pos_t && v_cnt[10:0]<cam_border_pos_b)
- 157 cmos_frame_data <= cam_data;
- 158 else
- 159 cmos_frame_data <= 1'b0;
- 160 end
- 161
- 162 endmodule
复制代码在模块的第142至150行,根据LCD屏的器件ID来判断输出的数据使能是否有效,当器件ID为16'h4342时,必须满足行场计数器在边界的范围内有效,其他时候是无效的,同理数据也是这么判断的。 本次实验的ddr3控制模块相较于“ddr3读写测试实验”只是在子模块(ddr3读写模块)中添加了一个乒乓缓存的功能,以及在ddr3控制器fifo控制模块添加了load信号来产生一段复位电平,以满足fifo复位时序。 ddr3读写模块添加的代码如下所示: - 102 //读/写结束信号上升沿
- 103 assign wr_end_r=wr_end_d0&&(~wr_end_d1);
- 104 assign rd_end_r=rd_end_d0&&(~rd_end_d1);
- 105
- 106 //乒乓操作
- 107 assign axi_araddr=ddr3_pingpang_en?
- {4'b0,raddr_page,axi_araddr_n[24:0],1'b0}:{6'b0,axi_araddr_n[24:0],1'b0};
- 108 assign axi_awaddr=ddr3_pingpang_en?
- {4'b0,waddr_page,axi_awaddr_n[24:0],1'b0}:{6'b0,axi_awaddr_n[24:0],1'b0};
复制代码省略部分源代码…… - 279 //对信号进行打拍处理
- 280 always @(posedge clk or negedge rst_n) begin
- 281 if(~rst_n)begin
- 282 rd_load_d0 <= 0;
- 283 rd_load_d1 <= 0;
- 284 wr_load_d0 <= 0;
- 285 wr_load_d1 <= 0;
- 286 end
- 287 else begin
- 288 rd_load_d0 <= rd_load;
- 289 rd_load_d1 <= rd_load_d0;
- 290 wr_load_d0 <= wr_load;
- 291 wr_load_d1 <= wr_load_d0;
- 292 end
- 293 end
- 294
- 295 //对输入源做个帧复位标志
- 296 always @(posedge clk or negedge rst_n) begin
- 297 if(~rst_n)
- 298 wr_rst <= 0;
- 299 else if(wr_load_d0&& !wr_load_d1)
- 300 wr_rst <= 1;
- 301 else
- 302 wr_rst <= 0;
- 303 end
- 304
- 305 //对输出源做个帧复位标志
- 306 always @(posedge clk or negedge rst_n) begin
- 307 if(~rst_n)
- 308 rd_rst <= 0;
- 309 else if(!rd_load_d0&& rd_load_d1)
- 310 rd_rst <= 1;
- 311 else
- 312 rd_rst <= 0;
- 313 end
- 314
复制代码第296行至313行代码对输入/输出源做了帧复位标志,即每完成一帧读/写操作,就将对应的读/写复位标志拉高一个时钟,使读/写地址复位到读/写起始地址。 - 315 //对输出源的读地址做个帧复位脉冲
- 316 always @(posedge clk or negedge rst_n) begin
- 317 if(~rst_n)
- 318 raddr_rst_h <= 1'b0;
- 319 else if(rd_load_d0&& !rd_load_d1)
- 320 raddr_rst_h <= 1'b1;
- 321 else if(axi_araddr_n== app_addr_rd_min)
- 322 raddr_rst_h <= 1'b0;
- 323 else
- 324 raddr_rst_h <= raddr_rst_h;
- 325 end
- 326
- 327 //对输出源帧的读地址高位切换
- 328 always @(posedge clk or negedge rst_n) begin
- 329 if(~rst_n)
- 330 raddr_page <= 2'b0;
- 331 else if(rd_end_r)
- 332 raddr_page <= waddr_page + 2;
- 333 else
- 334 raddr_page <= raddr_page;
- 335 end
- 336
- 337 //对输入源帧的写地址高位切换
- 338 always @(posedge clk or negedge rst_n) begin
- 339 if(~rst_n)begin
- 340 waddr_page <= 2'b1;
- 341 fram_done<= 1'b0;
- 342 end
- 343 else if(wr_end_r)begin
- 344 fram_done<= 1'b1;
- 345 waddr_page <= waddr_page + 1 ;
- 346 end
- 347 else
- 348 waddr_page <= waddr_page;
- 349 end
- 350
复制代码第328行至349行代码对输入/输出源帧的读/写地址高位切换,即在ddr3中划分了四个大小相同的存储空间。读写模块修改后的帧切换原理如下所示: 在图 39.4.3中的图2是写操作完成了一帧的存储,换了一个存储空间继续写,而读操作还没有读完一帧数据,继续读原来的存储空间;因为读帧率和写帧率的帧率比小于2倍,所以写操作端的第二帧还没有写完,读操作端已经读完了第一帧,换了一个存储空间继续读,如图3所示;图4中写操作端完成了第二帧的存储,而读操作还没有读完第二帧数据;同理图5中写操作端完成了第三帧的存储,开始第四帧的写入,而读操作将要读完第二帧数据;图6表示读操作读完第二帧数据,而写操作端没有完成第四帧的存储,这里将存储空间跳转了2个,就是为了防止写操作追上读操作,出现画面撕裂的现象。 在图 39.4.4中图2是读操作读出了一帧的数据,而本次实验是根据写存储空间来判断读存储空间的,所以继续读之前的存储空间;图3是写操作端完成了第一帧的存储,而读操作还没有读完第二帧数据;图4是读操作端读完第二帧数据,而写操作还没有完成了第二帧的存储。 - 351 //DDR3读写逻辑实现
- 352 always @(posedge clk or negedge rst_n) begin
- 353 if(~rst_n)
- 354 state_cnt <= IDLE;
- 355 else begin
- 356 case(state_cnt)
- 357 IDLE:begin
- 358 if(init_start)
- 359 state_cnt <= DDR3_DONE ;
- 360 else
- 361 state_cnt <= IDLE;
- 362 end
- 363 DDR3_DONE:begin
- 364 //当帧复位到来时,对寄存器进行复位
- 365 if(wr_rst)
- 366 state_cnt <= DDR3_DONE;
- 367 //当读到结束地址对寄存器复位
- 368 else if(wfifo_rcount>= wr_bust_len)
- 369 state_cnt <= WRITE_ADDR; //跳到写操作
- 370 //当帧复位到来时,对寄存器进行复位
- 371 else if(raddr_rst_h)
- 372 state_cnt <= DDR3_DONE;
- 373 //当rfifo存储数据少于一次突发长度时,并且ddr已经写入了1帧数据
- 374 else if(rfifo_wcount< rd_bust_len && ddr3_read_valid &&fram_done )
- 375 state_cnt <= READ_ADDR; //跳到读操作
- 376 else
- 377 state_cnt <= state_cnt;
- 378 end
- 379 WRITE_ADDR:begin
- 380 if(axi_awvalid&& axi_awready)
- 381 state_cnt <= WRITE_DATA; //跳到写数据操作
- 382 else
- 383 state_cnt <= state_cnt; //条件不满足,保持当前值
- 384 end
- 385 WRITE_DATA: begin
- 386 //写到设定的长度跳到等待状态
- 387 if(axi_wvalid&& axi_wready &&init_addr == wr_bust_len - 1)
- 388 state_cnt <= DDR3_DONE; //写到设定的长度跳到等待状态
- 389 else
- 390 state_cnt <= state_cnt; //写条件不满足,保持当前值
- 391 end
- 392 READ_ADDR:begin
- 393 if(axi_arvalid&& axi_arready)
- 394 state_cnt <= READ_DATA;
- 395 else
- 396 state_cnt <= state_cnt;
- 397 end
- 398 READ_DATA:begin //读到设定的地址长度
- 399 if(axi_rlast)
- 400 state_cnt <= DDR3_DONE; //则跳到空闲状态
- 401 else //若MIG没准备好,则保持原值
- 402 state_cnt <= state_cnt;//则跳到空闲状态
- 403 end
- 404 default:begin
- 405 state_cnt <= IDLE;
- 406 end
- 407 endcase
- 408 end
- 409 end
- 410
- 411 endmodule
复制代码DDR3读写逻辑实现部分和“DDR3读写测试实验”一致,唯一的区别在于第374行代码对读操作的开启多了一个判定条件,即ddr已经写入了1帧图像数据后才会开启。 ddr3控制器fifo控制模块添加代码如下所示: - 1 module ddr3_fifo_ctrl(
- 2 input rst_n , //复位信号
- 3 input wr_clk , //wfifo时钟
- 4 input rd_clk , //rfifo时钟
- 5 input clk_100 , //用户时钟
- 6 input datain_valid , //数据有效使能信号
- 7 input [15:0 datain , //有效数据
- 8 input [127:0 rfifo_din , //用户读数据
- 9 input rdata_req , //请求像素点颜色数据输入
- 10 input rfifo_wren , //从ddr3读出数据的有效使能
- 11 input wfifo_rden , //wfifo读使能
- 12 input rd_load , //输出源场信号
- 13 input wr_load , //输入源场信号
- 14
- 15 output [127:0 wfifo_dout , //用户写数据
- 16 output [10:0 wfifo_rcount , //rfifo剩余数据计数
- 17 output [10:0 rfifo_wcount , //wfifo写进数据计数
- 18 output [15:0 pic_data //有效数据
- 19 );
- 20
- 21 //reg define
- 22 reg rd_load_d0 ;
- 23 reg [15:0 rd_load_d ; //由输出源场信号移位拼接得到
- 24 reg rdfifo_rst_h ; //rfifo复位信号,高有效
- 25 reg wr_load_d0 ;
- 26 reg [15:0 wr_load_d ; //由输入源场信号移位拼接得到
- 27 reg wfifo_rst_h ; //wfifo复位信号,高有效
- 28
- 29 //*****************************************************
- 30 //** main code
- 31 //*****************************************************
- 32
- 33 //对输出源场信号取反
- 34 always @(posedge clk_100 or negedge rst_n) begin
- 35 if(!rst_n)
- 36 rd_load_d0 <= 1'b0;
- 37 else
- 38 rd_load_d0 <= rd_load;
- 39 end
- 40
- 41 //对输出源场信号进行移位寄存
- 42 always @(posedge clk_100 or negedge rst_n) begin
- 43 if(!rst_n)
- 44 rd_load_d <= 1'b0;
- 45 else
- 46 rd_load_d <= {rd_load_d[14:0],rd_load_d0};
- 47 end
- 48
- 49 //产生一段复位电平,满足fifo复位时序
- 50 always @(posedge clk_100 or negedge rst_n) begin
- 51 if(!rst_n)
- 52 rdfifo_rst_h <= 1'b0;
- 53 else if(rd_load_d[0 && !rd_load_d[14])
- 54 rdfifo_rst_h <= 1'b1;
- 55 else
- 56 rdfifo_rst_h <= 1'b0;
- 57 end
- 58
- 59 //对输入源场信号进行移位寄存
- 60 always @(posedge wr_clk or negedge rst_n) begin
- 61 if(!rst_n)begin
- 62 wr_load_d0 <= 1'b0;
- 63 wr_load_d <= 16'b0;
- 64 end
- 65 else begin
- 66 wr_load_d0 <= wr_load;
- 67 wr_load_d <= {wr_load_d[14:0],wr_load_d0};
- 68 end
- 69 end
- 70
- 71 //产生一段复位电平,满足fifo复位时序
- 72 always @(posedge wr_clk or negedge rst_n) begin
- 73 if(!rst_n)
- 74 wfifo_rst_h <= 1'b0;
- 75 else if(wr_load_d[0 && !wr_load_d[15])
- 76 wfifo_rst_h <= 1'b1;
- 77 else
- 78 wfifo_rst_h <= 1'b0;
- 79 end
- 80
- 81 rd_fifo u_rd_fifo (
- 82 .wr_clk (clk_100 ), // input
- 83 .wr_rst (~rst_n|rdfifo_rst_h), // input
- 84 .wr_en (rfifo_wren ), // input
- 85 .wr_data (rfifo_din ), // input [127:0]
- 86 .wr_full ( ), // output
- 87 .wr_water_level (rfifo_wcount ), // output [12:0]
- 88 .almost_full ( ), // output
- 89 .rd_clk (rd_clk ), // input
- 90 .rd_rst (~rst_n|rdfifo_rst_h), // input
- 91 .rd_en (rdata_req ), // input
- 92 .rd_data (pic_data ), // output [15:0]
- 93 .rd_empty ( ), // output
- 94 .rd_water_level ( ), // output [12:0]
- 95 .almost_empty ( ) //output
- 96 );
- 97
- 98 wr_fifo u_wr_fifo (
- 99 .wr_clk (wr_clk ), // input
- 100 .wr_rst (~rst_n|wfifo_rst_h), // input
- 101 .wr_en (datain_valid ), // input
- 102 .wr_data (datain ), // input[15:0]
- 103 .wr_full ( ), //output
- 104 .wr_water_level ( ), // output [12:0]
- 105 .almost_full ( ), //output
- 106 .rd_clk (clk_100 ), // input
- 107 .rd_rst (~rst_n|wfifo_rst_h), // input
- 108 .rd_en (wfifo_rden ), // input
- 109 .rd_data (wfifo_dout ), //output [127:0]
- 110 .rd_empty ( ), //output
- 111 .rd_water_level (wfifo_rcount ), // output [12:0]
- 112 .almost_empty ( ) // output
- 113 );
- 114
- 115 endmodule
复制代码在“ddr3读写测试实验”的ddr3控制器fifo控制模块的基础上,该模块添加了load信号,通过load信号来产生一段复位电平,以此来满足fifo复位时序。 LCD顶层模块例化了四个模块,分别为时钟分频模块(clk_div)、读LCD ID模块(rd_id)、lcd驱动模块(lcd_driver)和lcd显示模块(lcd_display)。 lcd驱动模块负责驱动RGB-LCD显示屏,当摄像头的分辨率大于LCD屏的分辨率时,LCD驱动模块也负责通过发出内部信号data_req(数据请求信号)输出至端口来读取DDR3读写控制模块的输出像素数据,有关LCD驱动模块的详细介绍请大家参考“RGB-LCD彩条显示实验”章节。 当摄像头的分辨率小于LCD屏的分辨率时,lcd显示模块负责通过发出内部信号data_req(数据请求信号)输出至端口来读取DDR3读写控制模块的输出像素数据,以此完成液晶屏两侧填充黑色背景,中间区域显示图像数据的功能。 时钟分频模块负责分频出对应器件ID的采样时钟。读LCD ID模块负责采集外部LCD屏的设备型号。有关LCD ID模块和时钟分频模块的详细介绍请大家参考“RGB-LCD彩条显示实验”章节。 LCD顶层模块的代码如下: - 1 module lcd_rgb_top(
- 2 input sys_clk , //系统时钟
- 3 input clk_100m ,
- 4 input sys_rst_n , //复位信号
- 5 input sys_init_done,
- 6 //lcd接口
- 7 output lcd_clk , //LCD驱动时钟
- 8 output lcd_hs , //LCD 行同步信号
- 9 output lcd_vs , //LCD 场同步信号
- 10 output lcd_de , //LCD 数据输入使能
- 11 inout [23:0 lcd_rgb , //LCD RGB颜色数据
- 12 output lcd_bl , //LCD 背光控制信号
- 13 output lcd_rst , //LCD 复位信号
- 14 output lcd_pclk , //LCD 采样时钟
- 15 output [15:0 lcd_id , //LCD屏ID
- 16 output [10:0 pixel_xpos , //像素点横坐标
- 17 output [10:0 pixel_ypos , //像素点纵坐标
- 18 output out_vsync , //帧复位,高有效
- 19 output [10:0 h_disp , //LCD屏水平分辨率
- 20 output [10:0 v_disp , //LCD屏垂直分辨率
- 21 input [15:0 data_in , //数据输入
- 22 output data_req //请求数据输入
- 23
- 24 );
- 25
- 26 //wire define
- 27 wire [15:0 lcd_data_w ; //像素点数据
- 28 wire data_req_w ; //请求像素点颜色数据输入
- 29 wire [10:0 pixel_xpos ; //像素点横坐标
- 30 wire [10:0 pixel_ypos ; //像素点纵坐标
- 31 wire out_vsync ; //帧复位,高有效
- 32 wire [10:0 h_disp ; //LCD屏水平分辨率
- 33 wire [10:0 v_disp ; //LCD屏垂直分辨率
- 34 wire data_req_big ; //大于640x480分辨率lcd屏的请求信号
- 35 wire data_req_small; //小于640x480分辨率lcd屏的请求信号
- 36 wire [15:0 lcd_data ; //选择屏后的数据
- 37 wire [15:0 lcd_rgb_565 ; //输出的16位lcd数据
- 38 wire [23:0 lcd_rgb_o ; //LCD 输出颜色数据
- 39 wire [23:0 lcd_rgb_i ; //LCD 输入颜色数据
- 40
- 41 //*****************************************************
- 42 //** main code
- 43 //*****************************************************
- 44
- 45 //区分大小屏的读请求
- 46 assign data_req = (lcd_id == 16'h4342) ? data_req_small : data_req_big;
- 47
- 48 //区分大小屏的数据
- 49 assign lcd_data = (lcd_id == 16'h4342) ? data_in : lcd_data_w ;
- 50
- 51 //将摄像头16bit数据转换为24bit的lcd数据
- 52 assign lcd_rgb_o = {lcd_rgb_565[15:11],3'b000,lcd_rgb_565[10:5],2'b00,
- 53 lcd_rgb_565[4:0],3'b000};
- 54
- 55 //像素数据方向切换
- 56 assign lcd_rgb = lcd_de ? lcd_rgb_o : {24{1'bz}};
- 57 assign lcd_rgb_i = lcd_rgb;
- 58
- 59 //时钟分频模块
- 60 clk_div u_clk_div(
- 61 .clk (clk_100m ),
- 62 .rst_n (sys_rst_n),
- 63 .lcd_id (lcd_id ),
- 64 .lcd_pclk (lcd_clk )
- 65 );
- 66
- 67 //读LCD ID模块
- 68 rd_id u_rd_id(
- 69 .clk (sys_clk ),
- 70 .rst_n (sys_rst_n),
- 71 .lcd_rgb (lcd_rgb_i),
- 72 .lcd_id (lcd_id )
- 73 );
- 74
- 75 //lcd驱动模块
- 76 lcd_driver u_lcd_driver(
- 77 .lcd_clk (lcd_clk ),
- 78 .sys_rst_n (sys_rst_n & sys_init_done),
- 79 .lcd_id (lcd_id ),
- 80 .lcd_hs (lcd_hs ),
- 81 .lcd_vs (lcd_vs ),
- 82 .lcd_de (lcd_de ),
- 83 .lcd_rgb (lcd_rgb_565 ),
- 84 .lcd_bl (lcd_bl ),
- 85 .lcd_rst (lcd_rst ),
- 86 .lcd_pclk (lcd_pclk ),
- 87 .pixel_data (lcd_data ),
- 88 .data_req (data_req_small ),
- 89 .out_vsync (out_vsync ),
- 90 .h_disp (h_disp ),
- 91 .v_disp (v_disp ),
- 92 .pixel_xpos (pixel_xpos ),
- 93 .pixel_ypos (pixel_ypos )
- 94 );
- 95
- 96 //lcd显示模块
- 97 lcd_display u_lcd_display(
- 98 .lcd_clk (lcd_clk ),
- 99 .sys_rst_n (sys_rst_n & sys_init_done),
- 100 .lcd_id (lcd_id ),
- 101 .pixel_xpos (pixel_xpos ),
- 102 .pixel_ypos (pixel_ypos ),
- 103 .h_disp (h_disp ),
- 104 .v_disp (v_disp ),
- 105 .cmos_data (data_in ),
- 106 .lcd_data (lcd_data_w ),
- 107 .data_req (data_req_big )
- 108 );
- 109
- 110 endmodule
复制代码在程序的第46行和第49行,根据器件ID来判断读请求信号的最后输出和输入数据的最后输入。当摄像头分辨率大于屏体分辨率时,读请求信号由LCD驱动模块输出,数据直接输入到LCD驱动模块,反之,读请求信号由LCD显示模块输出,数据进入LCD显示模块进行处理后再进入LCD驱动模块。 在程序的第52行,将摄像头输出的格式RGB565转换为LCD需要输出的格式RGB888,在本次实验中的做法是给各个颜色的低位补零,其中lcd_rgb_565[15:11]表示红色,lcd_rgb_565[10:5]表示绿色,lcd_rgb_565[4:0]表示蓝色。 在程序的第56行至57行,由于lcd_rgb是24位的双向引脚,所以这里对双向引脚的方向做一个切换。当lcd_de信号为高电平时,此时输出的像素数据有效,将lcd_rgb的引脚方向切换成输出,并将LCD驱动模块输出的lcd_rgb_o(像素数据)连接至lcd_rgb引脚;当lcd_de信号为低电平时,此时输出的像素数据无效,将lcd_rgb的引脚方向切换成输入。代码中将高阻状态“Z”赋值给lcd_rgb的引脚,表示此时lcd_rgb的引脚电平由外围电路决定,此时可以读取lcd_rgb的引脚电平,从而获取到LCD屏的ID。 LCD 显示模块的代码如下: - 1 module lcd_display(
- 2 input lcd_clk , //lcd驱动时钟
- 3 input sys_rst_n , //复位信号
- 4 input [15:0 lcd_id , //LCD屏ID
- 5 input [10:0 pixel_xpos, //像素点横坐标
- 6 input [10:0 pixel_ypos, //像素点纵坐标
- 7 input [15:0 cmos_data , //CMOS传感器像素点数据
- 8 input [10:0 h_disp , //LCD屏水平分辨率
- 9 input [10:0 v_disp , //LCD屏垂直分辨率
- 10 output [15:0 lcd_data , //LCD像素点数据
- 11 output data_req //请求像素点颜色数据输入
- 12 );
- 13
- 14 //parameter define
- 15 parameter V_CMOS_DISP = 11'd480; //CMOS分辨率——行
- 16 parameter H_CMOS_DISP = 11'd640; //CMOS分辨率——列
- 17
- 18 localparam BLACK = 16'b00000_000000_00000; //RGB565 黑色
- 19
- 20 //reg define
- 21 reg data_val ; //数据有效信号
- 22
- 23 //wire define
- 24 wire [10:0 display_border_pos_l; //左侧边界的横坐标
- 25 wire [10:0 display_border_pos_r; //右侧边界的横坐标
- 26 wire [10:0 display_border_pos_t; //上端边界的纵坐标
- 27 wire [10:0 display_border_pos_b; //下端边界的纵坐标
- 28
- 29 //*****************************************************
- 30 //** main code
- 31 //*****************************************************
- 32
- 33 //左侧边界的横坐标计算
- 34 assigndisplay_border_pos_l = (h_disp - H_CMOS_DISP)/2-1;
- 35
- 36 //右侧边界的横坐标计算
- 37 assign display_border_pos_r = H_CMOS_DISP + (h_disp - H_CMOS_DISP)/2-1;
- 38
- 39 //上端边界的纵坐标计算
- 40 assigndisplay_border_pos_t = (v_disp - V_CMOS_DISP)/2;
- 41
- 42 //下端边界的纵坐标计算
- 43 assign display_border_pos_b = V_CMOS_DISP + (v_disp - V_CMOS_DISP)/2;
- 44
- 45 //请求像素点颜色数据输入 范围:79~718,共640个时钟周期
- 46 assign data_req = ((pixel_xpos >= display_border_pos_l) &&
- 47 (pixel_xpos < display_border_pos_r) &&
- 48 (pixel_ypos > display_border_pos_t) &&
- 49 (pixel_ypos <= display_border_pos_b)
- 50 ) ? 1'b1 : 1'b0;
- 51
- 52 //在数据有效范围内,将摄像头采集的数据赋值给LCD像素点数据
- 53 assign lcd_data = data_val ? cmos_data : BLACK;
- 54
- 55 //有效数据滞后于请求信号一个时钟周期,所以数据有效信号在此延时一拍
- 56 always @(posedge lcd_clk or negedge sys_rst_n) begin
- 57 if(!sys_rst_n)
- 58 data_val <= 1'b0;
- 59 else
- 60 data_val <= data_req;
- 61 end
- 62
- 63 endmodule
复制代码当LCD屏的分辨率大于摄像头的分辨率时,将摄像头采集的图像放在LCD屏的中央,四周填充黑色,如下图所示。 在程序的第34行至50行,分别对摄像头数据的显示区域的横纵的起始坐标和结束坐标进行计算,上边界坐标(display_border_pos_t)是LCD屏的场分辨率摄像头的场分辨率减去摄像头的场分辨率(480)再除以2得到的,其他的边界也是以类似的计算方法得到。 在程序的第53行,对有效数据以外的区域填充黑色。这里还有一点需要提醒大家注意的是我们读数据请求发送给DDR3控制器后实际上是发送给读FIFO,而读FIFO接收到数据请求到数据出来是需要一个时钟周期的延迟的,也就是说有效数据实际上比读数据请求晚了一个时钟(如果FIFO用的是另外一种数据提前模式则有效数据和读数据请求是同步的,本节实验采用的是普通模式),因此代码的55~61行对读数据请求作了一个打拍处理,生成数据有效使能信号(data_val)。 本次实验的LCD驱动模块是在“RGB-LCD彩条显示实验”中添加了一个场信号输出,本次实验只对改动的地方进行讲解。 LCD驱动模块的代码如下: - 1 module lcd_driver(
- 2 input lcd_clk , //lcd模块驱动时钟
- 3 input sys_rst_n , //复位信号
- 4 input [15:0 lcd_id , //LCD屏ID
- 5 input [15:0 pixel_data, //像素点数据
- 6 output data_req , //请求像素点颜色数据输入
- 7 output [10:0 pixel_xpos, //像素点横坐标
- 8 output [10:0 pixel_ypos, //像素点纵坐标
- 9 output out_vsync , //帧复位,高有效
- 10 output [10:0 h_disp , //LCD屏水平分辨率
- 11 output [10:0 v_disp , //LCD屏垂直分辨率
- 12 //RGB LCD接口
- 13 output lcd_hs , //LCD 行同步信号
- 14 output lcd_vs , //LCD 场同步信号
- 15 output lcd_de , //LCD 数据输入使能
- 16 output [15:0 lcd_rgb , //LCDRGB565颜色数据
- 17 output lcd_bl , //LCD 背光控制信号
- 18 output lcd_rst , //LCD 复位信号
- 19 output lcd_pclk //LCD 采样时钟
- 20 );
复制代码此处省略一段代码。 - 118 //帧复位,高有效
- 119 assign out_vsync = ((h_cnt <= 100) && (v_cnt == 1)) ? 1'b1 : 1'b0;
复制代码此处省略一段代码。 在程序的第119行,在LCD屏场消隐的时候增加了一个模拟的场信号,提供给其他模块。到这里整个工程的程序设计就讲完了。
1.5 下载验证 首先将OV7725摄像头插入开发板上的摄像头扩展接口(注意摄像头镜头朝外);将FPC排线一端与正点原子的7寸RGB模块上的J1接口连接,另一端与ATK-DFPGL22G开发板上的RGB_LCD接口连接;如图 39.5.1和图 39.5.2所示。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线。 连接实物图如下图所示: 图 39.5.1 ATK-7’ RGBLCD模块FPC连接器 图 39.5.2 ATK-DFPGL22G开发板FPC连接器 最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证OV7725摄像头RGB-LCD实时显示功能。下载完成后观察显示器的显示图像如下图所示,说明OV7725摄像头LCD显示程序下载验证成功。 |