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

第三十九章OV7725摄像头RGB-LCD显示实验

OV7725是OmniVision(豪威科技)公司生产的一颗CMOS图像传感器,该传感器功耗低、可靠性高以及采集速率快,主要应用在玩具、安防监控、电脑多媒体等领域。本章我们将使用FPGA开发板实现对OV7725的数字图像采集并通过LCD实时显示。
本章包括以下几个部分:
3838.1简介
38.2实验任务
38.3硬件设计
38.4程序设计
38.5下载验证

39.1简介
OV7725是一款1/4英寸单芯片图像传感器,其感光阵列达到640*480,能实现最快60fps VGA分辨率的图像采集。传感器内部集成了图像处理的功能,包括自动曝光控制(AEC)、自动增益控制(AGC)和自动白平衡(AWB)等。同时传感器具有较高的感光灵敏度,适合低照度的应用,下图为OV7725的功能框图。

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

图 39.1.2 SCCB写传输协议
上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写 1:读),OV7725的器件地址为7’h21,所以在写传输协议中,ID Address(W) = 8’h42(器件地址左移1位,低位补0);Sub-address为8位寄存器地址,在OV7725的数据手册中定义了0x00~0xAC共173个寄存器,有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;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的读传输协议。

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

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

图 39.1.4 OV7725的特性
从上图可以看出,OV7725的输入时钟频率的范围是10Mhz~48Mhz,本次实验摄像头的输入时钟为12 Mhz;SCCB总线的SIO_C的时钟频率最大为400KHz;配置寄存器软件复位(寄存器地址0x12 Bit[7]位)和硬件复位(cam_rst_n引脚)后需要等待最大1ms才能配置其它寄存器;每次配置完寄存器后,需要最大300ms时间的延迟,也就是10帧图像输出的时间才能输出稳定的视频流。
OV7725支持多种不同分辨率图像的输出,包括VGA(640480)、QVGA(320240)以及CIF(一种常用的标准化图像格式,分辨率为352288)到4030等任意尺寸。可通过寄存器地址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上显示,且新起点开发板上的数据接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此我们将OV7725摄像头输出的图像像素数据配置成RGB565格式,然后转换为RGB888格式。下图为摄像头输出的VGA帧模式时序图。

图 39.1.5 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实际上是6402=1280个tPCLK;
由图 39.1.5可知,VSYNC的上升沿作为一帧的开始,高电平同步脉冲的时间为4
tLine,紧接着等待18tLine时间后,HREF开始拉高,此时输出有效数据;HREF由640tp个高电平和144tp个低电平构成;输出480行数据之后等待8tLine时间一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame =(4 + 18 + 480 + 8)tLine = 510tLine。
本次实验采用OV7725支持的最大分辨率640
480,摄像头的输入时钟为12 Mhz,摄像头的输出时钟为24 Mhz(详细公式请看表 39.1.1),由此我们可以计算出摄像头的输出帧率,以PCLK=24Mhz(周期为42ns)为例,计算出OV7725输出一帧图像所需的时间如下:
一帧图像输出时间:tFrame = 510tLine = 510784tp = 5107842tPCLK = 799680*42ns =33.5866ms;
摄像头输出帧率:1000ms/33.5866ms ≈ 30Hz。
如果把像素时钟频率提高到摄像头的最大时钟频率48Mhz,通过上述计算方法,摄像头的输出帧率约为60Hz。
下图为OV7725输出RGB565格式的时序图:

图 39.1.6 RGB565模式时序图
上图中的PCLK为OV7725输出的像素时钟,HREF为行同步信号,D[9:2]为8位像素数据。OV7725最大可以输出10位数据,在RGB565输出模式中,只有高8位是有效的。像素数据在HREF为高电平时有效,第一次输出的数据为RGB565数据的高8位,第二次输出的数据为RGB565数据的低8位,first byte和second byte组成一个16位RGB565数据。由上图可知,数据是在像素时钟的下降沿改变的,为了在数据最稳定的时刻采集图像数据,所以我们需要在像素时钟的上升沿采集数据。
39.2实验任务
本节实验任务是使用新起点开发板及OV7725摄像头实现图像采集,并通过RGB-LCD接口驱动RGB-LCD液晶屏(支持目前正点原子推出的所有RGB-LCD屏),并实时显示出图像。
39.3硬件设计
新起点FPGA开发板上有一个摄像头扩展接口,该接口可以用来连接OV7725/OV5640等摄像头模块,摄像头扩展接口原理图如图 39.3.1所示:

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

图 39.3.2 OV7725摄像头连接开发板图
前面说过,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接口和SDRAM引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配,如下表所示:
表 39.3.1 OV7725摄像头管脚分配

摄像头TCL约束文件如下:
set_location_assignment PIN_M2 -to sys_clk
set_location_assignment PIN_M1 -to sys_rst_n
set_location_assignment PIN_B14 -to sdram_clk
set_location_assignment PIN_G11 -to sdram_ba[0]
set_location_assignment PIN_F13 -to sdram_ba[1]
set_location_assignment PIN_J12 -to sdram_cas_n
set_location_assignment PIN_F16 -to sdram_cke
set_location_assignment PIN_K11 -to sdram_ras_n
set_location_assignment PIN_J13 -to sdram_we_n
set_location_assignment PIN_K10 -to sdram_cs_n
set_location_assignment PIN_J14 -to sdram_dqm[0]
set_location_assignment PIN_G15 -to sdram_dqm[1]
set_location_assignment PIN_F11 -to sdram_addr[0]
set_location_assignment PIN_E11 -to sdram_addr[1]
set_location_assignment PIN_D14 -to sdram_addr[2]
set_location_assignment PIN_C14 -to sdram_addr[3]
set_location_assignment PIN_A14 -to sdram_addr[4]
set_location_assignment PIN_A15 -to sdram_addr[5]
set_location_assignment PIN_B16 -to sdram_addr[6]
set_location_assignment PIN_C15 -to sdram_addr[7]
set_location_assignment PIN_C16 -to sdram_addr[8]
set_location_assignment PIN_D15 -to sdram_addr[9]
set_location_assignment PIN_F14 -to sdram_addr[10]
set_location_assignment PIN_D16 -to sdram_addr[11]
set_location_assignment PIN_F15 -to sdram_addr[12]
set_location_assignment PIN_P14 -to sdram_data[0]
set_location_assignment PIN_M12 -to sdram_data[1]
set_location_assignment PIN_N14 -to sdram_data[2]
set_location_assignment PIN_L12 -to sdram_data[3]
set_location_assignment PIN_L13 -to sdram_data[4]
set_location_assignment PIN_L14 -to sdram_data[5]
set_location_assignment PIN_L11 -to sdram_data[6]
set_location_assignment PIN_K12 -to sdram_data[7]
set_location_assignment PIN_G16 -to sdram_data[8]
set_location_assignment PIN_J11 -to sdram_data[9]
set_location_assignment PIN_J16 -to sdram_data[10]
set_location_assignment PIN_J15 -to sdram_data[11]
set_location_assignment PIN_K16 -to sdram_data[12]
set_location_assignment PIN_K15 -to sdram_data[13]
set_location_assignment PIN_L16 -to sdram_data[14]
set_location_assignment PIN_L15 -to sdram_data[15]
set_location_assignment PIN_R1 -to lcd_bl
set_location_assignment PIN_T2 -to lcd_de
set_location_assignment PIN_T3 -to lcd_hs
set_location_assignment PIN_P3 -to lcd_vs
set_location_assignment PIN_R3 -to lcd_pclk
set_location_assignment PIN_L1 -to lcd_rst
set_location_assignment PIN_T4 -to lcd_rgb[4]
set_location_assignment PIN_R4 -to lcd_rgb[3]
set_location_assignment PIN_T5 -to lcd_rgb[2]
set_location_assignment PIN_R5 -to lcd_rgb[1]
set_location_assignment PIN_T6 -to lcd_rgb[0]
set_location_assignment PIN_R6 -to lcd_rgb[10]
set_location_assignment PIN_T7 -to lcd_rgb[9]
set_location_assignment PIN_R7 -to lcd_rgb[8]
set_location_assignment PIN_T8 -to lcd_rgb[7]
set_location_assignment PIN_R8 -to lcd_rgb[6]
set_location_assignment PIN_T9 -to lcd_rgb[5]
set_location_assignment PIN_R9 -to lcd_rgb[15]
set_location_assignment PIN_T10 -to lcd_rgb[14]
set_location_assignment PIN_R10 -to lcd_rgb[13]
set_location_assignment PIN_T11 -to lcd_rgb[12]
set_location_assignment PIN_R11 -to lcd_rgb[11]
set_location_assignment PIN_T14 -to cam_data[7]
set_location_assignment PIN_R14 -to cam_data[6]
set_location_assignment PIN_N6 -to cam_data[5]
set_location_assignment PIN_P6 -to cam_data[4]
set_location_assignment PIN_M8 -to cam_data[3]
set_location_assignment PIN_N8 -to cam_data[2]
set_location_assignment PIN_P8 -to cam_data[1]
set_location_assignment PIN_K9 -to cam_data[0]
set_location_assignment PIN_M9 -to cam_href
set_location_assignment PIN_R13 -to cam_pclk
set_location_assignment PIN_L9 -to cam_rst_n
set_location_assignment PIN_N9 -to cam_scl
set_location_assignment PIN_L10 -to cam_sda
set_location_assignment PIN_P9 -to cam_vsync
set_location_assignment PIN_R12 -to cam_sgm_ctrl
39.4程序设计
OV7725在VGA帧模式下,以RGB565格式输出最高帧率可达60Hz,LCD屏的刷新频率也可以达到60Hz,那么是不是直接将采集到的图像数据连接到LCD屏的输入数据端口就行了呢?答案是不可以。我们在前面说过,OV7725帧率如果要达到60Hz,那么像素时钟频率必须为48Mhz,而LCD屏根据屏的分辨率不同时钟也不同,首先就有时钟不匹配的问题,其次是时序方面的不匹配,LCD屏驱动对时序有着严格的要求。我们在“RGB-LCD彩条显示实验”的章节中可以获知,LCD一行或一场分为四个部分:低电平同步脉冲、显示后沿、有效数据段以及显示前沿,各个部分的时序参数很显然跟OV7725并不是完全一致的。因此必须先把一帧图像缓存下来,然后再把图像数据按照LCD的时序发送到LCD屏上显示。OV7725在VGA帧模式输出下,一帧图像的数据量达到64048016bit = 4915200bit = 4800kbit = 4.6875Mbit,带宽为4.6875Mbit45Hz(帧率)=210.9375 Mbit/S,我们新起点FPGA开发板芯片型号为EP4CE10F17C8,器件手册可以发现,EP4CE10F17C8的片内存储资源为414Kbit,远不能达到存储要求。因此我们只能使用板载的外部存储器SDRAM来缓存图像数据,新起点板载的SDRAM容量为256Mbit,最大带宽为2.66Gbit/S(16bit166M),足以满足缓存图像数据的需求。
OV7725在正常工作之前必须通过配置寄存器进行初始化,而配置寄存器的SCCB协议和I2C协议在写操作时几乎一样,所以我们需要一个I2C驱动模块。为了使OV7725在期望的模式下运行并且提高图像显示效果,需要配置较多的寄存器,这么多寄存器的地址与参数需要单独放在一个模块,因此还需要一个寄存配置信息的I2C配置模块。在摄像头配置完成后,开始输出图像数据,因此需要一个摄像头图像采集模块来采集图像;外接SDRAM存储器当然离不开SDRAM控制器模块的支持,最后LCD顶层模块读取SDRAM缓存的数据以达到最终实时显示的效果。

图 39.4.1 程序结构框图
由上图可知,时钟模块(pll_clk)为LCD顶层模块、SDRAM控制模块以及I2C驱动模块提供驱动时钟。I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入SDRAM控制模块,LCD顶层模块从SDRAM控制模块中读出数据,完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在SDRAM和传感器都初始化完成之后才开始输出数据的,避免了在SDRAM初始化过程中向里面写入数据。
顶层模块的原理图如下图所示:

图 39.4.2 RTL视图
FPGA顶层模块(ov7725_rgb565_lcd)例化了以下六个模块:时钟模块(pll_clk)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov7725_rgb565_cfg)、图像采集顶层模块(cmos_data_top)、SDRAM控制模块(sdram_top)和LCD顶层模块(lcd_rgb_top)。
时钟模块(pll_clk):时钟模块通过调用PLL IP核实现,共输出3个时钟,频率分别为100Mhz(SDRAM参考时钟)、100M偏移-75度时钟(SDRAM芯片输入时钟)和50Mhz时钟。100Mhz时钟作为SDRAM控制模块的参考时钟,100M偏移-75度时钟用来输出给外部SDRAM芯片使用,50Mhz时钟作为I2C驱动模块和LCD顶层模块的驱动时钟。
I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV7725 SCCB接口总线,用户可根据该模块提供的用户接口可以很方便的对OV7725的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
I2C配置模块(i2c_ov7725_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV7725的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV7725传感器的初始化。
图像采集顶层模块(cmos_data_top):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成SDRAM控制模块的写使能信号和16位写数据信号,完成对OV7725传感器图像的采集。如果LCD屏的分辨率小于OV7725的分辨率,还要对OV7725采集的数据进行裁剪,以匹配LCD屏的分辨率。
SDRAM读写控制模块(sdram_top):SDRAM读写控制器模块负责驱动SDRAM片外存储器,缓存图像传感器输出的图像数据。该模块将SDRAM复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。在“SDRAM读写测试实验”的程序中,读写操作地址都是SDRAM的同一存储空间,如果只使用一个存储空间缓存图像数据,那么同一存储空间中会出现两帧图像叠加的情况,为了避免这一情况,我们在SDRAM的其它BANK中开辟一个相同大小的存储空间,使用乒乓操作的方式来写入和读取数据,所以本次实验在“SDRAM读写测试实验”的程序里做了一个小小的改动,有关该模块的详细介绍请大家参考“SDRAM读写测试实验”章节,本章只对改动的地方作介绍。
LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。
顶层模块的代码如下:

1   module ov7725_rgb565_lcd(
2       input                 sys_clk     ,  //系统时钟
3       input                 sys_rst_n   ,  //系统复位,低电平有效
4       //摄像头接口
5       input                 cam_pclk    ,  //cmos 数据像素时钟
6       input                 cam_vsync   ,  //cmos 场同步信号
7       input                 cam_href    ,  //cmos 行同步信号
8       input        [7:0]    cam_data    ,  //cmos 数据
9       output                cam_rst_n   ,  //cmos 复位信号,低电平有效
10      output                cam_sgm_ctrl,  //cmos 时钟选择信号, 1:使用摄像头自带的晶振
11      output                cam_scl     ,  //cmos SCCB_SCL线
12      inout                 cam_sda     ,  //cmos SCCB_SDA线
13      //SDRAM接口
14      output                sdram_clk   ,  //SDRAM 时钟
15      output                sdram_cke   ,  //SDRAM 时钟有效
16      output                sdram_cs_n  ,  //SDRAM 片选
17      output                sdram_ras_n ,  //SDRAM 行有效
18      output                sdram_cas_n ,  //SDRAM 列有效
19      output                sdram_we_n  ,  //SDRAM 写有效
20      output       [1:0]    sdram_ba    ,  //SDRAM Bank地址
21      output       [1:0]    sdram_dqm   ,  //SDRAM 数据掩码
22      output       [12:0]   sdram_addr  ,  //SDRAM 地址
23      inout        [15:0]   sdram_data  ,  //SDRAM 数据
24      //lcd接口
25      output                lcd_hs      ,  //LCD 行同步信号
26      output                lcd_vs      ,  //LCD 场同步信号
27      output                lcd_de      ,  //LCD 数据输入使能
28      inout        [15:0]   lcd_rgb     ,  //LCD RGB565颜色数据
29      output                lcd_bl      ,  //LCD 背光控制信号
30      output                lcd_rst     ,  //LCD 复位信号
31      output                lcd_pclk       //LCD 采样时钟
32      );
33
34  //parameter define
35  parameter  SLAVE_ADDR = 7'h21         ;  //OV7725的器件地址7'h21
36  parameter  BIT_CTRL   = 1'b0          ;  //OV7725的字节地址为8位  0:8位 1:16位
37  parameter  CLK_FREQ   = 26'd50_000_000;  //i2c_dri模块的驱动时钟频率 33.3MHz
38  parameter  I2C_FREQ   = 18'd250_000   ;  //I2C的SCL时钟频率,不超过400KHz
39
40  //wire define
41  wire                  clk_100m        ;  //100mhz时钟,SDRAM操作时钟
42  wire                  clk_100m_shift  ;  //100mhz时钟,SDRAM相位偏移时钟
43  wire                  clk_50m         ;  //50mhz时钟,提供给lcd驱动时钟
44  wire                  locked          ;
45  wire                  rst_n           ;
46
47  wire                  i2c_exec        ;  //I2C触发执行信号
48  wire   [15:0]         i2c_data        ;  //I2C要配置的地址与数据(高8位地址,低8位数据)
49  wire                  cam_init_done   ;  //摄像头初始化完成
50  wire                  i2c_done        ;  //I2C寄存器配置完成信号
51  wire                  i2c_dri_clk     ;  //I2C操作时钟
52  wire                  wr_en           ;  //sdram_ctrl模块写使能
53  wire   [15:0]         wr_data         ;  //sdram_ctrl模块写数据
54  wire                  rd_en           ;  //sdram_ctrl模块读使能
55  wire                  sdram_init_done ;  //SDRAM初始化完成
56  wire                  rdata_req       ;  //SDRAM控制器模块读使能
57  wire   [15:0]         rd_data             ;  //SDRAM控制器模块读数据
58  wire                  cmos_frame_valid    ;  //数据有效使能信号
59  wire                  init_calib_complete ;  //SDRAM初始化完成init_calib_complete
60  wire                  sys_init_done       ;  //系统初始化完成(SDRAM初始化+摄像头初始化)
61  wire                  clk_200m            ;  //SDRAM参考时钟
62  wire                  cmos_frame_vsync    ;  //输出帧有效场同步信号
63  wire                  cmos_frame_href     ;  //输出帧有效行同步信号
64  wire    [7:0]         wr_bust_len         ;  //从SDRAM中读数据时的突发长度
65  wire    [9:0]         pixel_xpos_w        ;  //像素点横坐标
66  wire    [9:0]         pixel_ypos_w        ;  //像素点纵坐标
67  wire                  lcd_clk             ;  //分频产生的LCD 采样时钟
68  wire    [10:0]        h_disp              ;  //LCD屏水平分辨率
69  wire    [10:0]        v_disp              ;  //LCD屏垂直分辨率
70  wire    [10:0]        h_pixel             ;  //存入SDRAM的水平分辨率
71  wire    [10:0]        v_pixel             ;  //存入SDRAM的屏垂直分辨率
72  wire    [15:0]        lcd_id              ;  //LCD屏的ID号
73  wire    [27:0]        sdram_addr_max      ;  //存入SDRAM的最大读写地址
74
75  //*****************************************************
76  //**                    main code
77  //*****************************************************
78
79  assign  rst_n = sys_rst_n & locked;
80  //系统初始化完成:SDRAM和摄像头都初始化完成
81  //避免了在SDRAM初始化过程中向里面写入数据
82  assign  sys_init_done = sdram_init_done & cam_init_done;
83  //不对摄像头硬件复位,固定高电平
84  assign  cam_rst_n = 1'b1;
85  //cmos 时钟选择信号, 1:使用摄像头自带的晶振
86  assign  cam_sgm_ctrl = 1'b1;
87
88  //锁相环
89  pll_clk u_pll_clk(
90      .areset       (~sys_rst_n),
91      .inclk0       (sys_clk),
92      .c0           (clk_100m),
93      .c1           (clk_100m_shift),
94      .c2           (clk_50m),
95      .locked       (locked)
96      );
97
98  //I2C配置模块
99  i2c_ov7725_rgb565_cfg u_i2c_cfg(
100     .clk           (i2c_dri_clk),
101     .rst_n         (rst_n),
102     .i2c_done      (i2c_done),
103     .i2c_exec      (i2c_exec),
104     .i2c_data      (i2c_data),
105     .init_done     (cam_init_done)
106     );
107
108 //I2C驱动模块
109 i2c_dri
110 #(
111     .SLAVE_ADDR  (SLAVE_ADDR),               //参数传递
112     .CLK_FREQ    (CLK_FREQ  ),
113     .I2C_FREQ    (I2C_FREQ  )
114     )
115 u_i2c_dri(
116     .clk         (clk_50m   ),
117     .rst_n       (rst_n     ),
118     //i2c interface
119     .i2c_exec    (i2c_exec  ),
120     .bit_ctrl    (BIT_CTRL  ),
121     .i2c_rh_wl   (1'b0),                     //固定为0,只用到了IIC驱动的写操作
122     .i2c_addr    (i2c_data[15:8]),
123     .i2c_data_w  (i2c_data[7:0]),
124     .i2c_data_r  (),
125     .i2c_done    (i2c_done  ),
126     .scl         (cam_scl   ),
127     .sda         (cam_sda   ),
128     //user interface
129     .dri_clk     (i2c_dri_clk)               //I2C操作时钟
130 );
131
132 //CMOS图像数据采集模块
133 cmos_data_top u_cmos_data_top(
134     .rst_n                 (rst_n & sys_init_done), //系统初始化完成之后再开始采集数据
135     .cam_pclk              (cam_pclk),
136     .cam_vsync             (cam_vsync),
137     .cam_href              (cam_href),
138     .cam_data              (cam_data),
139     .lcd_id                (lcd_id),
140     .h_disp                (h_disp),
141     .v_disp                (v_disp),
142     .h_pixel               (h_pixel),
143     .v_pixel               (v_pixel),
144     .sdram_addr_max        (sdram_addr_max),
145     .cmos_frame_vsync      (cmos_frame_vsync),
146     .cmos_frame_href       (cmos_frame_href),
147     .cmos_frame_valid      (cmos_frame_valid),      //数据有效使能信号
148     .cmos_frame_data       (wr_data)                //有效数据
149     );
150
151 //SDRAM 控制器顶层模块,封装成FIFO接口
152 //SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
153 sdram_top u_sdram_top(
154 .ref_clk      (clk_100m),                   //sdram 控制器参考时钟
155 .out_clk      (clk_100m_shift),             //用于输出的相位偏移时钟
156 .rst_n        (rst_n),                      //系统复位
157
158 //用户写端口
159 .wr_clk       (cam_pclk),                   //写端口FIFO: 写时钟
160 .wr_en        (cmos_frame_valid),           //写端口FIFO: 写使能
161 .wr_data      (wr_data),                    //写端口FIFO: 写数据
162 .wr_min_addr  (24'd0),                      //写SDRAM的起始地址
163 .wr_max_addr  (sdram_addr_max),             //写SDRAM的结束地址
164 .wr_len       (10'd512),                    //写SDRAM时的数据突发长度
165 .wr_load      (~rst_n),                     //写端口复位: 复位写地址,清空写FIFO
166
167 //用户读端口
168 .rd_clk       (lcd_clk),                    //读端口FIFO: 读时钟
169 .rd_en        (rdata_req),                  //读端口FIFO: 读使能
170 .rd_data      (rd_data),                    //读端口FIFO: 读数据
171 .rd_min_addr  (24'd0),                      //读SDRAM的起始地址
172 .rd_max_addr  (sdram_addr_max),             //读SDRAM的结束地址
173 .rd_len       (10'd512),                    //从SDRAM中读数据时的突发长度
174 .rd_load      (~rst_n),                     //读端口复位: 复位读地址,清空读FIFO
175
176 //用户控制端口
177 .sdram_read_valid  (1'b1),                  //SDRAM 读使能
178 .sdram_pingpang_en (1'b1),                  //SDRAM 乒乓操作使能
179 .sdram_init_done (sdram_init_done),         //SDRAM 初始化完成标志
180
181 //SDRAM 芯片接口
182 .sdram_clk    (sdram_clk),                  //SDRAM 芯片时钟
183 .sdram_cke    (sdram_cke),                  //SDRAM 时钟有效
184 .sdram_cs_n   (sdram_cs_n),                 //SDRAM 片选
185 .sdram_ras_n  (sdram_ras_n),                //SDRAM 行有效
186 .sdram_cas_n  (sdram_cas_n),                //SDRAM 列有效
187 .sdram_we_n   (sdram_we_n),                 //SDRAM 写有效
188 .sdram_ba     (sdram_ba),                   //SDRAM Bank地址
189 .sdram_addr   (sdram_addr),                 //SDRAM 行/列地址
190 .sdram_data   (sdram_data),                 //SDRAM 数据
191 .sdram_dqm    (sdram_dqm)                   //SDRAM 数据掩码
192     );
193
194 //LCD驱动显示模块
195 lcd_rgb_top  u_lcd_rgb_top(
196     .sys_clk               (clk_50m  ),
197     .sys_rst_n             (rst_n ),
198     .sys_init_done         (sys_init_done),
199
200     //lcd接口
201     .lcd_id                (lcd_id),                //LCD屏的ID号
202     .lcd_hs                (lcd_hs),                //LCD 行同步信号
203     .lcd_vs                (lcd_vs),                //LCD 场同步信号
204     .lcd_de                (lcd_de),                //LCD 数据输入使能
205     .lcd_rgb               (lcd_rgb),               //LCD 颜色数据
206     .lcd_bl                (lcd_bl),                //LCD 背光控制信号
207     .lcd_rst               (lcd_rst),               //LCD 复位信号
208     .lcd_pclk              (lcd_pclk),              //LCD 采样时钟
209     .lcd_clk               (lcd_clk),               //LCD 驱动时钟
210     //用户接口
211     .out_vsync             (rd_vsync),              //lcd场信号
212     .h_disp                (h_disp),                //行分辨率
213     .v_disp                (v_disp),                //场分辨率
214     .pixel_xpos            (),
215     .pixel_ypos            (),
216     .data_in               (rd_data),               //rfifo输出数据
217     .data_req              (rdata_req)              //请求数据输入
218     );
219 endmodule

顶层模块中第37至第38行定义了两个变量:CLK_FREQ(i2c_dri模块的驱动时钟频率)和I2C_FREQ(I2C的SCL时钟频率),I2C_FREQ的时钟频率不能超过400KHz,否则有可能导致摄像头配置不成功。
在程序的第164和第173行,信号(wr_len)和信号(rd_len)表示一次向SDRAM读或写的长度,这里长度我们设置的是512代表我们使用的是SDRAM页突发模式,一次读写512个数据。
在程序的第147和第148行,CMOS图像数据采集模块输出的cmos_frame_valid(数据有效使能信号)和wr_data(有效数据)连接到SDRAM控制模块,实现了图像数据的缓存。
在程序的第212和第213行,LCD顶层模块引出了h_disp和v_disp信号,并将其引入摄像头图像采集模块。因为LCD屏的分辨率是不一样的,而本次实验是驱动自适应分辨率的LCD屏,所以将这两个信号引入摄像头图像采集模块是为了与摄像头采集的分辨率进行比较。当摄像头的分辨率大时,就取LCD屏分辨率大小的数据存入SDRAM中,其余数据丢弃,当摄像头的分辨率小时,就将摄像头采集的数据存入SDRAM中,LCD屏其他显示区域填充黑色。
在程序的217行,LCD顶层模块输出rdata_req(请求像素点颜色数据输入)连接到SDRAM控制模块, 实现了图像数据的读取,将读出的数据连接到LCD顶层模块的rd_data信号,从而实现了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
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
34  //寄存器配置个数计数
35  always @(posedge clk or negedge rst_n) begin
36      if(!rst_n)
37          init_reg_cnt <= 7'd0;
38      else if(i2c_exec)
39          init_reg_cnt <= init_reg_cnt + 7'b1;
40  end
41
42  //i2c触发执行信号
43  always @(posedge clk or negedge rst_n) begin
44      if(!rst_n)
45          i2c_exec <= 1'b0;
46      else if(start_init_cnt == 10'd1022)
47          i2c_exec <= 1'b1;
48      //只有刚上电和配置第一个寄存器增加延时
49      else if(i2c_done && (init_reg_cnt != 7'd1) && (init_reg_cnt < REG_NUM))
50          i2c_exec <= 1'b1;
51      else
52          i2c_exec <= 1'b0;
53  end
54
55  //初始化完成信号
56  always @(posedge clk or negedge rst_n) begin
57      if(!rst_n)
58          init_done <= 1'b0;
59      else if((init_reg_cnt == REG_NUM) && i2c_done)
60          init_done <= 1'b1;
61  end
62
63  //配置寄存器地址与数据
64  always @(posedge clk or negedge rst_n) begin
65      if(!rst_n)
66          i2c_data <= 16'b0;
67      else begin
68          case(init_reg_cnt)
69              //先对寄存器进行软件复位,使寄存器恢复初始值
70              //寄存器软件复位后,需要延时1ms才能配置其它寄存器
71              7'd0  : i2c_data <= {8'h12, 8'h80}; //COM7 BIT[7]:复位所有的寄存器
72              7'd1  : i2c_data <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿
73              7'd2  : i2c_data <= {8'h15, 8'h00}; //COM10 href/vsync/pclk/data信号控制
74              7'd3  : i2c_data <= {8'h17, 8'h23}; //HSTART 水平起始位置
75              7'd4  : i2c_data <= {8'h18, 8'ha0}; //HSIZE 水平尺寸
76              7'd5  : i2c_data <= {8'h19, 8'h07}; //VSTRT 垂直起始位置
77              7'd6  : i2c_data <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸
78              7'd7  : i2c_data <= {8'h32, 8'h00}; //HREF 图像开始和尺寸控制,控制低位
79              7'd8  : i2c_data <= {8'h29, 8'ha0}; //HOutSize 水平输出尺寸
80              7'd9  : i2c_data <= {8'h2a, 8'h00}; //EXHCH 虚拟像素MSB
81              7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB
82              7'd11 : i2c_data <= {8'h2c, 8'hf0}; //VOutSize 垂直输出尺寸
83              7'd12 : i2c_data <= {8'h0d, 8'h41}; //COM4 PLL倍频设置(multiplier)
84              7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 内部时钟配置
85              7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式
86              7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3 Bit[0]: 0:图像数据 1:彩条测试
87              //DSP 控制
88              7'd16 : i2c_data <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值
89              7'd17 : i2c_data <= {8'h4d, 8'h09}; //FixGain 模拟增益放大器
90              7'd18 : i2c_data <= {8'h63, 8'hf0}; //AWB_Ctrl0 自动白平衡控制字节0
91              7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字节1
92              7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字节2
93              7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字节3
94              7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4 DSP控制字节4
95              //AGC AEC AWB
96              //COM8 Bit[2]:自动增益使能 Bit[1]:自动白平衡使能 Bit[0]:自动曝光功能
97              7'd23 : i2c_data <= {8'h13, 8'hff}; //COM8
98              7'd24 : i2c_data <= {8'h0f, 8'hc5}; //COM6
99              7'd25 : i2c_data <= {8'h14, 8'h11};
100             7'd26 : i2c_data <= {8'h22, 8'h98};
101             7'd27 : i2c_data <= {8'h23, 8'h03};
102             7'd28 : i2c_data <= {8'h24, 8'h40};
103             7'd29 : i2c_data <= {8'h25, 8'h30};
104             7'd30: i2c_data <= {8'h26, 8'ha1};
105             7'd31: i2c_data <= {8'h6b, 8'haa};
106             7'd32: i2c_data <= {8'h13, 8'hff};
107             //matrix sharpness brightness contrast UV
108             7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1
109             //DNSOff 降噪阈值下限,仅在自动模式下有效
110             7'd34 : i2c_data <= {8'h91, 8'h01}; //DNSOff
111             7'd35 : i2c_data <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限
112             7'd36 : i2c_data <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限
113             7'd37 : i2c_data <= {8'h94, 8'h5f}; //MTX1 矩阵系数1
114             7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩阵系数2
115             7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩阵系数3
116             7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩阵系数4
117             7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩阵系数5
118             7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩阵系数6
119             7'd43 : i2c_data <= {8'h9a, 8'h1e}; //MTX_Ctrl 矩阵控制
120             7'd44 : i2c_data <= {8'h9b, 8'h3f}; //BRIGHT 亮度
121             7'd45 : i2c_data <= {8'h9c, 8'h25}; //CNST 对比度
122             7'd46 : i2c_data <= {8'h9e, 8'h81};
123             7'd47 : i2c_data <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制
124             7'd48 : i2c_data <= {8'ha7, 8'h65}; //USAT "U"饱和增益
125             7'd49 : i2c_data <= {8'ha8, 8'h65}; //VSAT "V"饱和增益
126             7'd50 : i2c_data <= {8'ha9, 8'h80}; //VSAT "V"饱和增益
127             7'd51 : i2c_data <= {8'haa, 8'h80}; //VSAT "V"饱和增益
128             //伽马控制
129             7'd52 : i2c_data <= {8'h7e, 8'h0c};
130             7'd53 : i2c_data <= {8'h7f, 8'h16};
131             7'd54 : i2c_data <= {8'h80, 8'h2a};
132             7'd55 : i2c_data <= {8'h81, 8'h4e};
133             7'd56 : i2c_data <= {8'h82, 8'h61};
134             7'd57 : i2c_data <= {8'h83, 8'h6f};
135             7'd58 : i2c_data <= {8'h84, 8'h7b};
136             7'd59 : i2c_data <= {8'h85, 8'h86};
137             7'd60 : i2c_data <= {8'h86, 8'h8e};
138             7'd61 : i2c_data <= {8'h87, 8'h97};
139             7'd62 : i2c_data <= {8'h88, 8'ha4};
140             7'd63 : i2c_data <= {8'h89, 8'haf};
141             7'd64 : i2c_data <= {8'h8a, 8'hc5};
142             7'd65 : i2c_data <= {8'h8b, 8'hd7};
143             7'd66 : i2c_data <= {8'h8c, 8'he8};
144             7'd67 : i2c_data <= {8'h8d, 8'h20};
145             7'd68 : i2c_data <= {8'h0e, 8'h65}; //COM5
146             7'd69 : i2c_data <= {8'h09, 8'h00}; //COM2  Bit[1:0] 输出电流驱动能力
147             //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
148             default:i2c_data <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位
149         endcase
150     end
151 end
152
153 endmodule

在代码的第12行定义了总共需要配置的寄存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。
图像传感器刚开始上电时电压有可能不够稳定,所以程序中的23行至32行定义了一个延时计数器(start_init_cnt)等待传感器工作在稳定的状态。当计数器计数到预设值之后,开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。从前面介绍的OV7725的特性可知,软件复位需要等待1ms的时间才能配置其它的寄存器,因此发送完软件复位命令后,延时计数器清零,并重新开始计数。当计数器计数到预设值之后,紧接着配置剩下的寄存器。只有软件复位命令需要1ms的等待时间,其它寄存器不需要等待时间,直接按照程序中定义的顺序发送即可。
在程序的83至86行,说明了关于摄像头输出时钟的寄存器配置,摄像头的地址0x0d配置成0x41,表示PLL倍频设置设为了4倍频,摄像头的地址0x11配置成0x00,而摄像头的输入时钟为12M,所以根据第86行的公式可得到摄像头的输出时钟为24M。
图像采集顶层模块的原理图如下图所示:

图 39.4.3 图像采集顶层模块原理图(局部)
图像采集顶层模块(top_cmos_data)例化了以下二个模块:图像采集模块(cmos_capture_data)、图像裁剪模块(cmos_tailor)。
图像采集模块(cmos_capture_data)为其他模块提供摄像头8bit输入数据转化后的16bit数据和数据使能以及摄像头稳定后的行场信号。图像裁剪模块(cmos_tailor)只有在LCD的器件ID为16’h4342时起作用,即摄像头的分辨率大于LCD屏的分辨率,起到裁剪图像数据,使图像的有效数据达到匹配LCD屏的尺寸。
图像采集顶层模块的代码如下:

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         ,
11      //用户接口
12      output      [10:0]    h_pixel          ,  //存入SDRAM的水平分辨率
13      output      [10:0]    v_pixel          ,  //存入SDRAM的屏垂直分辨率
14      output      [27:0]    sdram_addr_max   ,  //存入SDRAM的最大读写地址
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  [15:0] lcd_id_a;           //时钟同步后的LCD屏的ID号
23  wire  [15:0] wr_data_tailor;     //经过裁剪的摄像头数据
24  wire  [15:0] wr_data;            //没有经过裁剪的摄像头数据
25
26  //*****************************************************
27  //**                    main code
28  //*****************************************************
29
30  assign cmos_frame_valid = (lcd_id_a == 16'h4342) ? data_valid_tailor : data_valid ;
31  assign cmos_frame_data = (lcd_id_a == 16'h4342) ? wr_data_tailor : wr_data ;
32
33  //摄像头数据裁剪模块
34  cmos_tailor  u_cmos_tailor(
35      .rst_n                 (rst_n),
36      .lcd_id                (lcd_id),
37      .lcd_id_a              (lcd_id_a),
38      .cam_pclk              (cam_pclk),
39      .cam_vsync             (cmos_frame_vsync),
40      .cam_href              (cmos_frame_href),
41      .cam_data              (wr_data),
42      .cam_data_valid        (data_valid),
43      .h_disp                (h_disp),
44      .v_disp                (v_disp),
45      .h_pixel               (h_pixel),
46      .v_pixel               (v_pixel),
47      .sdram_addr_max        (sdram_addr_max),
48      .cmos_frame_valid      (data_valid_tailor),
49      .cmos_frame_data       (wr_data_tailor)
50
51  );
52
53  //摄像头数据采集模块
54  cmos_capture_data u_cmos_capture_data(
55
56      .rst_n                 (rst_n),
57      .cam_pclk              (cam_pclk),
58      .cam_vsync             (cam_vsync),
59      .cam_href              (cam_href),
60      .cam_data              (cam_data),
61      .cmos_frame_vsync      (cmos_frame_vsync),
62      .cmos_frame_href       (cmos_frame_href),
63      .cmos_frame_valid      (data_valid),
64      .cmos_frame_data       (wr_data)
65      );
66
67  endmodule

在程序中的30行至31行,定义了两个信号cmos_frame_valid和cmos_frame_data,这两个信号的输出是根据LCD的器件ID来选择的,当摄像头的分辨率大于LCD的分辨率时,将裁剪后的数据和数据使能赋给这两个信号,反之将没有裁剪的信号和使能赋给这两个信号。
摄像头接口输出8位像素数据,而本次实验配置摄像头的模式是RGB565,所以需要在图像采集模块实现8位数据转16位数据的功能。
CMOS图像数据采集模块的代码如下所示:

1   module cmos_capture_data(
2       input                 rst_n            ,  //复位信号
3       //摄像头接口
4       input                 cam_pclk         ,  //cmos 数据像素时钟
5       input                 cam_vsync        ,  //cmos 场同步信号
6       input                 cam_href         ,  //cmos 行同步信号
7       input  [7:0]          cam_data         ,
8       //用户接口
9       output                cmos_frame_vsync ,  //帧有效信号
10      output                cmos_frame_href  ,  //行有效信号
11      output                cmos_frame_valid ,  //数据有效使能信号
12      output       [15:0]   cmos_frame_data     //有效数据
13      );
14
15  //寄存器全部配置完成后,先等待10帧数据
16  //待寄存器配置生效后再开始采集图像
17  parameter  WAIT_FRAME = 4'd10    ;            //寄存器数据稳定等待的帧个数
18
19  //reg define
20  reg             cam_vsync_d0     ;
21  reg             cam_vsync_d1     ;
22  reg             cam_href_d0      ;
23  reg             cam_href_d1      ;
24  reg    [3:0]    cmos_ps_cnt      ;            //等待帧数稳定计数器
25  reg    [7:0]    cam_data_d0      ;
26  reg    [15:0]   cmos_data_t      ;            //用于8位转16位的临时寄存器
27  reg             byte_flag        ;            //16位RGB数据转换完成的标志信号
28  reg             byte_flag_d0     ;
29  reg             frame_val_flag   ;            //帧有效的标志
30
31  wire            pos_vsync        ;            //采输入场同步信号的上升沿
32
33  //*****************************************************
34  //**                    main code
35  //*****************************************************
36
37  //采输入场同步信号的上升沿
38  assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
39
40  //输出帧有效信号
41  assign  cmos_frame_vsync = frame_val_flag  ?  cam_vsync_d1  :  1'b0;
42
43  //输出行有效信号
44  assign  cmos_frame_href  = frame_val_flag  ?  cam_href_d1   :  1'b0;
45
46  //输出数据使能有效信号
47  assign  cmos_frame_valid = frame_val_flag  ?  byte_flag_d0  :  1'b0;
48
49  //输出数据
50  assign  cmos_frame_data  = frame_val_flag  ?  cmos_data_t   :  1'b0;
51
52  always @(posedge cam_pclk or negedge rst_n) begin
53      if(!rst_n) begin
54          cam_vsync_d0 <= 1'b0;
55          cam_vsync_d1 <= 1'b0;
56          cam_href_d0 <= 1'b0;
57          cam_href_d1 <= 1'b0;
58      end
59      else begin
60          cam_vsync_d0 <= cam_vsync;
61          cam_vsync_d1 <= cam_vsync_d0;
62          cam_href_d0 <= cam_href;
63          cam_href_d1 <= cam_href_d0;
64      end
65  end
66
67  //对帧数进行计数
68  always @(posedge cam_pclk or negedge rst_n) begin
69      if(!rst_n)
70          cmos_ps_cnt <= 4'd0;
71      else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
72          cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
73  end
74
75  //帧有效标志
76  always @(posedge cam_pclk or negedge rst_n) begin
77      if(!rst_n)
78          frame_val_flag <= 1'b0;
79      else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
80          frame_val_flag <= 1'b1;
81      else;
82  end
83
84  //8位数据转16位RGB565数据
85  always @(posedge cam_pclk or negedge rst_n) begin
86      if(!rst_n) begin
87          cmos_data_t <= 16'd0;
88          cam_data_d0 <= 8'd0;
89          byte_flag <= 1'b0;
90      end
91      else if(cam_href) begin
92          byte_flag <= ~byte_flag;
93          cam_data_d0 <= cam_data;
94          if(byte_flag)
95              cmos_data_t <= {cam_data_d0,cam_data};
96          else;
97      end
98      else begin
99          byte_flag <= 1'b0;
100         cam_data_d0 <= 8'b0;
101     end
102 end
103
104 //产生输出数据有效信号(cmos_frame_valid)
105 always @(posedge cam_pclk or negedge rst_n) begin
106     if(!rst_n)
107         byte_flag_d0 <= 1'b0;
108     else
109         byte_flag_d0 <= byte_flag;
110 end
111
112 endmodule

CMOS图像采集模块第17行定义了参数WAIT_FRAME(寄存器数据稳定等待的帧个数),我们在前面介绍寄存器时提到过配置寄存器生效的时间最长为300ms,约为摄像头输出10帧图像数据。所以这里采集场同步信号的上升沿来统计帧数,计数器计数超过10次之后产生数据有效的标志,开始采集图像。在程序的第84行开始的always块实现了8位数据转16位数据的功能。需要注意的是摄像头的图像数据是在像素时钟(cam_pclk)下输出的,因此摄像头的图像数据必须使用像素钟来采集,否则会造成数据采集错误。
当摄像头的分辨率大于LCD屏的分辨率时,需要对其进行裁剪,使存入SDRAM的分辨率匹配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 reg  [10:0]    h_pixel          ,  //存入sdram的水平分辨率
7       output reg  [10:0]    v_pixel          ,  //存入sdram的屏垂直分辨率
8       output      [27:0]    sdram_addr_max   ,  //存入sdram的最大读写地址
9       output reg  [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 reg            cmos_frame_valid ,  //数据有效使能信号
18      output reg  [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
27  reg    [10:0]   h_cnt            ;            //对行计数
28  reg    [10:0]   v_cnt            ;            //对场计数
29
30  reg    [10:0]   h_disp_a         ;            //LCD屏水平分辨率
31  reg    [10:0]   v_disp_a         ;            //LCD屏垂直分辨率
32
33  //wire define
34  wire            pos_vsync        ;            //采输入场同步信号的上升沿
35  wire            neg_hsync        ;            //采输入行同步信号的下降沿
36  wire   [10:0]   cmos_h_pixel     ;            //CMOS水平方向像素个数
37  wire   [10:0]   cmos_v_pixel     ;            //CMOS垂直方向像素个数
38  wire   [10:0]   cam_border_pos_l ;            //左侧边界的横坐标
39  wire   [10:0]   cam_border_pos_r ;            //右侧边界的横坐标
40  wire   [10:0]   cam_border_pos_t ;            //上端边界的纵坐标
41  wire   [10:0]   cam_border_pos_b ;            //下端边界的纵坐标
42
43  //*****************************************************
44  //**                    main code
45  //*****************************************************
46
47  assign  sdram_addr_max = h_pixel * v_pixel;   //存入sdram的最大读写地址
48
49  assign  cmos_h_pixel = 11'd640 ;  //CMOS水平方向像素个数
50  assign  cmos_v_pixel = 11'd480 ;  //CMOS垂直方向像素个数
51
52  //采输入场同步信号的上升沿
53  assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
54
55  //采输入行同步信号的下降沿
56  assign neg_hsync = (~cam_href_d0) & cam_href_d1;
57
58  //左侧边界的横坐标计算
59  assign cam_border_pos_l  = (cmos_h_pixel - h_disp_a)/2-1;
60
61  //右侧边界的横坐标计算
62  assign cam_border_pos_r = h_disp + (cmos_h_pixel - h_disp_a)/2-1;
63
64  //上端边界的纵坐标计算
65  assign cam_border_pos_t  = (cmos_v_pixel - v_disp_a)/2;
66
67  //下端边界的纵坐标计算
68  assign cam_border_pos_b = v_disp_a + (cmos_v_pixel - v_disp_a)/2;
69
70  always @(posedge cam_pclk or negedge rst_n) begin
71      if(!rst_n) begin
72          cam_vsync_d0 <= 1'b0;
73          cam_vsync_d1 <= 1'b0;
74          cam_href_d0 <= 1'b0;
75          cam_href_d1 <= 1'b0;
76          lcd_id_a <= 0;
77          v_disp_a <= 0;
78          h_disp_a <= 0;
79      end
80      else begin
81          cam_vsync_d0 <= cam_vsync;
82          cam_vsync_d1 <= cam_vsync_d0;
83          cam_href_d0 <= cam_href;
84          cam_href_d1 <= cam_href_d0;
85          lcd_id_a <= lcd_id;
86          v_disp_a <= v_disp;
87          h_disp_a <= h_disp;
88      end
89  end
90
91  //计算存入sdram的分辨率
92  always @(posedge cam_pclk or negedge rst_n) begin
93      if(!rst_n) begin
94          h_pixel <= 11'b0;
95          v_pixel <= 11'b0;
96      end
97      else begin
98          if(lcd_id_a == 16'h4342)begin
99              h_pixel <= h_disp_a;
100             v_pixel <= v_disp_a;
101     end
102     else begin
103         h_pixel <= cmos_h_pixel;
104         v_pixel <= cmos_v_pixel;
105     end
106     end
107 end
108
109 //对行计数
110 always @(posedge cam_pclk or negedge rst_n) begin
111     if(!rst_n)
112         h_cnt <= 11'b0;
113     else begin
114         if(pos_vsync||neg_hsync)
115             h_cnt <= 11'b0;
116         else if(cam_data_valid)
117             h_cnt <= h_cnt + 1'b1;
118         else if (cam_href_d0)
119             h_cnt <= h_cnt;
120         else
121             h_cnt <= h_cnt;
122     end
123 end
124
125 //对场计数
126 always @(posedge cam_pclk or negedge rst_n) begin
127     if(!rst_n)
128         v_cnt <= 11'b0;
129     else begin
130         if(pos_vsync)
131             v_cnt <= 11'b0;
132         else if(neg_hsync)
133             v_cnt <= v_cnt + 1'b1;
134         else
135             v_cnt <= v_cnt;
136     end
137 end
138
139 //产生输出数据有效信号(cmos_frame_valid)
140 always @(posedge cam_pclk or negedge rst_n) begin
141     if(!rst_n)
142         cmos_frame_valid <= 1'b0;
143     else if(h_cnt[10:0]>=cam_border_pos_l && h_cnt[10:0]<cam_border_pos_r&&
144             v_cnt[10:0]>=cam_border_pos_t && v_cnt[10:0]<cam_border_pos_b)
145             cmos_frame_valid <= cam_data_valid;
146     else
147             cmos_frame_valid <= 1'b0;
148
149 end
150
151 always @(posedge cam_pclk or negedge rst_n) begin
152     if(!rst_n)
153         cmos_frame_data <= 1'b0;
154     else if(h_cnt[10:0]>=cam_border_pos_l && h_cnt[10:0]<cam_border_pos_r&&
155             v_cnt[10:0]>=cam_border_pos_t && v_cnt[10:0]<cam_border_pos_b)
156             cmos_frame_data <= cam_data;
157     else
158             cmos_frame_data <= 1'b0;
159
160 end
161
162 endmodule

在模块的第47行对信号sdram_addr_max(存入SDRAM的最大读写地址)进行了赋值,因为本次实验用了一片SDRAM,故SDRAM的数据位宽为16位,而摄像头的数据位宽为16位,所以SDRAM的最大存储地址为行场分辨率的乘积。
在模块的第49和50行对两个信号cmos_h_pixel(CMOS水平方向像素个数)和cmos_v_pixel(CMOS垂直方向像素个数)进行定义,这两个信号代表了本次实验摄像头的分辨率,而这次实验的LCD的分辨率是不固定的,故可能出现LCD屏的分辨率比摄像头分辨率小的情况,而正点原子的LCD屏只有一种480x272的屏分辨率比摄像头的分辨率小,所以需要将摄像头的图像大小裁剪成和LCD屏的分辨率大小一样。

图 39.4.4 摄像头裁剪图
如上图所示,在代码的第56至68行,计算了摄像头存入SDRAM的边界坐标,上边界坐标(cam_border_pos_t)是摄像头的场分辨率(480)减去LCD屏的场分辨率再除以2得到的,其他的边界也是以类似的计算方法得到。
在模块的第70至89行,对输入信号进行时钟同步,是为了减少信号的扇出和防止时序不满足。
在模块的第92至107行,根据LCD屏的器件ID来判断存入SDRAM的分辨率,当LCD的ID为16’h4342时,说明LCD屏的分辨率比摄像头小,所以存入SDRAM的分辨率就是LCD屏的分辨率。
在模块的第140至149行,根据LCD屏的器件ID来判断输出的数据使能是否有效,当器件ID为16’h4342时,必须满足行场计数器在边界的范围内有效,其他时候是无效的,同理数据也是这么判断的。
接下来再来看看SDRAM控制模块的代码,因为本节实验的SDRAM控制器是建立在SDRAM读写实验的基础上的,所以这里就不再贴出完整代码了。我们在SDRAM中开辟出一个存储空间(大小为640480)用于缓存一帧图像。在摄像头初始化结束后输出的第一个数据对应图像的第一个像素点,将其写入存储空间的首地址中。通过在SDRAM读写控制模块中对输出的图像数据进行计数,从而将它们分别写入相应的地址空间。计数达640480后,完成一帧图像的存储,然后回到存储空间的首地址继续下一帧图像的存储。在显示图像时,LCD驱动模块从SDRAM存储空间的首地址开始读数据,同样对读过程进行计数,并将读取的图像数据分别显示到显示器相应的像素点位置。
上述的操作保证了在没有行场同步信号下数据不会出现错乱的问题,但是会导致当前读取的图像与上一次存入的图像存在交错,如下图所示:

图 39.4.5 SDRAM单个BANK缓存图像机制
由上图的t2时刻可知,SDRAM存储空间中会出现缓存两帧图像交错的情况。为了解决这一问题,在顶层模块代码的第178行,使能了SDRAM读写控制器的乒乓操作(sdram_pingpang_en)。SDRAM乒乓操作使能之后,内部使用了两个存储空间(大小为640*480)分别缓存两帧图像。图像数据总是在两个存储空间之间不断切换写入,而读请求信号在读完当前存储空间后判断哪个存储空间没有被写入,然后去读取没有被写入的存储空间。对于本次程序设计来说,数据写入较慢而读出较快,因此会出现同一存储空间被读取多次的情况,但保证了读出的数据一定是一帧完整的图像而不是两帧数据拼接的图像。当正在读取其中一个缓存空间,另一个缓存空间已经写完,并开始切换写入下一个缓存空间时,由于图像数据读出的速度总是大于写入的速度,因此,读出的数据仍然是一帧完整的图像。
本次实验的SDRAM控制器模块在“SDRAM读写测试实验”程序的基础上增加了sdram_pingpang_en信号,用于控制是否增加乒乓存储操作,高电平有效。主要修改了SDRAM控制器的sdram_fifo_ctrl模块,修改后的核心源代码如下。
73 reg sw_bank_en; //切换BANK使能信号
74 reg rw_bank_flag; //读写bank的标志
省略部分源代码……

156 //sdram写地址产生模块
157 always @(posedge clk_ref or negedge rst_n) begin
158     if (!rst_n) begin
159         sdram_wr_addr <= 24'd0;
160         sw_bank_en <= 1'b0;
161         rw_bank_flag <= 1'b0;
162     end
163     else if(wr_load_flag) begin              //检测到写端口复位信号时,写地址复位
164         sdram_wr_addr <= wr_min_addr;
165         sw_bank_en <= 1'b0;
166         rw_bank_flag <= 1'b0;
167     end
168     else if(write_done_flag) begin           //若突发写SDRAM结束,更改写地址
169                                              //若未到达写SDRAM的结束地址,则写地址累加
170         if(sdram_pingpang_en) begin          //SDRAM 读写乒乓使能
171             if(sdram_wr_addr[22:0] < wr_max_addr - wr_length)
172                 sdram_wr_addr <= sdram_wr_addr + wr_length;
173             else begin                       //切换BANK
174                 rw_bank_flag <= ~rw_bank_flag;
175                 sw_bank_en <= 1'b1;          //拉高切换BANK使能信号
176             end
177         end
178                                              //若突发写SDRAM结束,更改写地址
179         else if(sdram_wr_addr < wr_max_addr - wr_length)
180             sdram_wr_addr <= sdram_wr_addr + wr_length;
181         else                                 //到达写SDRAM的结束地址,回到写起始地址
182             sdram_wr_addr <= wr_min_addr;
183     end
184     else if(sw_bank_en) begin                //到达写SDRAM的结束地址,回到写起始地址
185         sw_bank_en <= 1'b0;
186         if(rw_bank_flag == 1'b0)             //切换BANK
187             sdram_wr_addr <= {1'b0,wr_min_addr[22:0]};
188         else
189             sdram_wr_addr <= {1'b1,wr_min_addr[22:0]};
190     end
191 end
192
193 //sdram读地址产生模块
194 always @(posedge clk_ref or negedge rst_n) begin
195     if(!rst_n) begin
196         sdram_rd_addr <= 24'd0;
197     end
198     else if(rd_load_flag)                    //检测到读端口复位信号时,读地址复位
199         sdram_rd_addr <= rd_min_addr;
200     else if(read_done_flag) begin            //突发读SDRAM结束,更改读地址
201                                              //若未到达读SDRAM的结束地址,则读地址累加
202         if(sdram_pingpang_en) begin          //SDRAM 读写乒乓使能
203             if(sdram_rd_addr[22:0] < rd_max_addr - rd_length)
204                 sdram_rd_addr <= sdram_rd_addr + rd_length;
205             else begin                       //到达读SDRAM的结束地址,回到读起始地址
206                                              //读取没有在写数据的bank地址
207                 if(rw_bank_flag == 1'b0)     //根据rw_bank_flag的值切换读BANK地址
208                     sdram_rd_addr <= {1'b1,rd_min_addr[22:0]};
209                 else
210                     sdram_rd_addr <= {1'b0,rd_min_addr[22:0]};
211             end
212         end
213                                              //若突发写SDRAM结束,更改写地址
214         else if(sdram_rd_addr < rd_max_addr - rd_length)
215             sdram_rd_addr <= sdram_rd_addr + rd_length;
216         else                                 //到达写SDRAM的结束地址,回到写起始地址
217             sdram_rd_addr <= rd_min_addr;
218     end
219 end

程序中定义了两个用于切换BANK的寄存器(sw_bank_en信号和rw_bank_flag信号)。sdram_wr_addr和sdram_rd_addr分别代表SDRAM的写入地址和读出地址,其最高两位表示BANK的地址,切换BANK时改变sdram_wr_addr和sdram_rd_addr的最高位,相当于数据在BANK0(2’b00)和BANK2(2’b10)之间切换。当rw_bank_sw=0时,数据写入BANK0,从BANK2中读出数据;当rw_bank_sw=1时,数据写入BANK2,从BANK0中读出数据。
本次实验的LCD顶层模块包含以下四个模块,原理图如下:

图 39.4.6 LCD顶层模块原理图(局部)
由上图可知,LCD驱动模块负责驱动RGB-LCD显示屏,当摄像头的分辨率大于LCD屏的分辨率时,LCD驱动模块也负责通过发出内部信号data_req(数据请求信号)输出至端口来读取SDRAM读写控制模块的输出像素数据,有关LCD驱动模块的详细介绍请大家参考“RGB-LCD彩条显示实验”章节。当摄像头的分辨率小于LCD屏的分辨率时,LCD显示模块负责通过发出内部信号data_req(数据请求信号)输出至端口来读取DDR读写控制模块的输出像素数据,以此完成液晶屏两侧填充黑色背景,中间区域显示图像数据的功能。时钟分频模块负责分频出对应器件ID的采样时钟。LCD ID模块负责采集外部LCD屏的设备型号。有关LCD ID模块和时钟分频模块的详细介绍请大家参考“RGB-LCD彩条显示实验”章节。
LCD顶层模块的代码如下:

1   module lcd_rgb_top(
2       input           sys_clk      ,  //系统时钟
3       input           sys_rst_n,      //复位信号
4       input           sys_init_done,
5       //lcd接口
6       output          lcd_clk,        //LCD驱动时钟
7       output          lcd_hs,         //LCD 行同步信号
8       output          lcd_vs,         //LCD 场同步信号
9       output          lcd_de,         //LCD 数据输入使能
10      inout  [15:0]   lcd_rgb,        //LCD RGB颜色数据
11      output          lcd_bl,         //LCD 背光控制信号
12      output          lcd_rst,        //LCD 复位信号
13      output          lcd_pclk,       //LCD 采样时钟
14      output  [15:0]  lcd_id,         //LCD屏ID
15      output          out_vsync,      //lcd场信号
16      output  [10:0]  pixel_xpos,     //像素点横坐标
17      output  [10:0]  pixel_ypos,     //像素点纵坐标
18      output  [10:0]  h_disp,         //LCD屏水平分辨率
19      output  [10:0]  v_disp,         //LCD屏垂直分辨率
20      input   [15:0]  data_in,        //数据输入
21      output          data_req        //请求数据输入
22      );
23
24  //wire define
25  wire [15:0]  lcd_data_w  ;          //像素点数据
26  wire         data_req_w  ;          //请求像素点颜色数据输入
27  wire         data_req_big;          //大于640x480分辨率lcd屏的请求信号
28  wire         data_req_small;        //小于640x480分辨率lcd屏的请求信号
29  wire [15:0]  lcd_data;              //选择屏后的数据
30  wire  [15:0] lcd_rgb_565;           //输出的16位lcd数据
31  wire  [15:0] lcd_rgb_o ;            //LCD 输出颜色数据
32  wire  [15:0] lcd_rgb_i ;            //LCD 输入颜色数据
33
34  //*****************************************************
35  //**                    main code
36  //*****************************************************
37
38  //区分大小屏的读请求
39  assign data_req = (lcd_id == 16'h4342) ? data_req_small : data_req_big;
40
41  //区分大小屏的数据
42  assign lcd_data = (lcd_id == 16'h4342) ? data_in : lcd_data_w ;
43
44  //将摄像头16bit数据输出
45  assign lcd_rgb_o = lcd_rgb_565;
46
47  //像素数据方向切换
48  assign lcd_rgb = lcd_de ?  lcd_rgb_o :  {16{1'bz}};
49  assign lcd_rgb_i = lcd_rgb;
50
51  //时钟分频模块
52  clk_div u_clk_div(
53      .clk                    (sys_clk  ),
54      .rst_n                  (sys_rst_n),
55      .lcd_id                 (lcd_id   ),
56      .lcd_pclk               (lcd_clk  )
57      );
58
59  //读LCD ID模块
60  rd_id u_rd_id(
61      .clk                    (sys_clk  ),
62      .rst_n                  (sys_rst_n),
63      .lcd_rgb                (lcd_rgb_i),
64      .lcd_id                 (lcd_id   )
65      );
66
67  //lcd驱动模块
68  lcd_driver u_lcd_driver(
69      .lcd_clk        (lcd_clk),
70      .sys_rst_n      (sys_rst_n & sys_init_done),
71      .lcd_id         (lcd_id),
72
73      .lcd_hs         (lcd_hs),
74      .lcd_vs         (lcd_vs),
75      .lcd_de         (lcd_de),
76      .lcd_rgb        (lcd_rgb_565),
77      .lcd_bl         (lcd_bl),
78      .lcd_rst        (lcd_rst),
79      .lcd_pclk       (lcd_pclk),
80
81      .pixel_data     (lcd_data),
82      .data_req       (data_req_small),
83      .out_vsync      (out_vsync),
84      .h_disp         (h_disp),
85      .v_disp         (v_disp),
86      .pixel_xpos     (pixel_xpos),
87      .pixel_ypos     (pixel_ypos)
88      );
89
90  //lcd显示模块
91  lcd_display u_lcd_display(
92      .lcd_clk        (lcd_clk),
93      .sys_rst_n      (sys_rst_n & sys_init_done),
94      .lcd_id         (lcd_id),
95
96      .pixel_xpos     (pixel_xpos),
97      .pixel_ypos     (pixel_ypos),
98      .h_disp         (h_disp),
99      .v_disp         (v_disp),
100     .cmos_data      (data_in),
101     .lcd_data       (lcd_data_w),
102     .data_req       (data_req_big)
103     );
104
105 endmodule

在程序的第39行和第42行,根据器件ID来判断读请求信号的最后输出和输入数据的最后输入。当摄像头分辨率大于屏体分辨率时,读请求信号由LCD驱动模块输出,数据直接输入到LCD驱动模块,反之,读请求信号由LCD显示模块输出,数据进入LCD显示模块进行处理后再进入LCD驱动模块。
在程序的第48行至49行,由于lcd_rgb是16位的双向引脚,所以这里对双向引脚的方向做一个切换。当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
6       input      [10:0] pixel_xpos,               //像素点横坐标
7       input      [10:0] pixel_ypos,               //像素点纵坐标
8       input      [15:0] cmos_data,                //CMOS传感器像素点数据
9       input      [10:0] h_disp,                   //LCD屏水平分辨率
10      input      [10:0] v_disp,                   //LCD屏垂直分辨率
11
12      output     [15:0] lcd_data,                 //LCD像素点数据
13      output            data_req                  //请求像素点颜色数据输入
14      );
15
16  //parameter define
17  parameter  V_CMOS_DISP = 11'd480;                //CMOS分辨率——行
18  parameter  H_CMOS_DISP = 11'd640;                //CMOS分辨率——列
19
20  localparam BLACK  = 16'b00000_000000_00000;      //RGB565 黑色
21
22  //reg define
23  reg             data_val            ;            //数据有效信号
24
25  //wire define
26  wire    [10:0]  display_border_pos_l;            //左侧边界的横坐标
27  wire    [10:0]  display_border_pos_r;            //右侧边界的横坐标
28  wire    [10:0]  display_border_pos_t;            //上端边界的纵坐标
29  wire    [10:0]  display_border_pos_b;            //下端边界的纵坐标
30
31  //*****************************************************
32  //**                    main code
33  //*****************************************************
34
35  //左侧边界的横坐标计算
36  assign display_border_pos_l  = (h_disp - H_CMOS_DISP)/2-1;
37
38  //右侧边界的横坐标计算
39  assign display_border_pos_r = H_CMOS_DISP + (h_disp - H_CMOS_DISP)/2-1;
40
41  //上端边界的纵坐标计算
42  assign display_border_pos_t  = (v_disp - V_CMOS_DISP)/2;
43
44  //下端边界的纵坐标计算
45  assign display_border_pos_b = V_CMOS_DISP + (v_disp - V_CMOS_DISP)/2;
46
47  //请求像素点颜色数据输入 范围:79~718,共640个时钟周期
48  assign data_req = ((pixel_xpos >= display_border_pos_l) &&
49                  (pixel_xpos < display_border_pos_r) &&
50                  (pixel_ypos > display_border_pos_t) &&
51                  (pixel_ypos <= display_border_pos_b)
52                  ) ? 1'b1 : 1'b0;
53
54  //在数据有效范围内,将摄像头采集的数据赋值给LCD像素点数据
55  assign lcd_data = data_val ? cmos_data : BLACK;
56
57  //有效数据滞后于请求信号一个时钟周期,所以数据有效信号在此延时一拍
58  always @(posedge lcd_clk or negedge sys_rst_n) begin
59      if(!sys_rst_n)
60          data_val <= 1'b0;
61      else
62          data_val <= data_req;
63  end
64
65  endmodule

当LCD屏的分辨率大于摄像头的分辨率时,将摄像头采集的图像放在LCD屏的中央,四周填充黑色,如下图所示。

图 39.4.7 LCD屏裁剪图
在程序的第36行至52行,分别对摄像头数据的显示区域的横纵的起始坐标和结束坐标进行计算,上边界坐标(display_border_pos_t)是LCD屏的场分辨率摄像头的场分辨率(480)减去摄像头的场分辨率(480)再除以2得到的,其他的边界也是以类似的计算方法得到。在程序的第55行,对有效数据以外的区域填充黑色。在程序的第58行至63行,因为有效数据滞后于请求信号一个时钟周期,所以数据有效信号在此延时一拍。
本次实验的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  [10:0]  h_disp,       //LCD屏水平分辨率
10      output  [10:0]  v_disp,       //LCD屏垂直分辨率
11      output          out_vsync,    //帧复位,高有效
12
13      //RGB-LCD接口
14      output          lcd_hs,       //LCD 行同步信号
15      output          lcd_vs,       //LCD 场同步信号
16      output          lcd_de,       //LCD 数据输入使能
17      output  [15:0]  lcd_rgb,      //LCD RGB565颜色数据
18      output          lcd_bl,       //LCD 背光控制信号
19      output          lcd_rst,      //LCD 复位信号
20      output          lcd_pclk      //LCD 采样时钟
21
22      );  此处省略一段代码。
125
126 //帧复位,高有效
127 assign out_vsync = ((h_cnt <= 100) && (v_cnt == 1)) ? 1'b1 : 1'b0;
此处省略一段代码。
253
254 endmodule

在程序的第126行至127行,在LCD屏场消隐的时候增加了一个模拟的场信号,提供给其他模块。到这里整个工程的程序设计就讲完了。
39.5下载验证
首先将OV7725摄像头插入开发板上的摄像头扩展接口(注意摄像头镜头朝外);将FPC 排线一端与正点原子的7寸RGB接口模块上的J1接口连接,另一端与新起点开发板上的J1接口连接;如图 39.5.1、图 39.5.2所示。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线。
连接实物图如下图所示:

图 39.5.1 ATK-7’ RGBLCD 模块 FPC 连接器

图 39.5.2 新起点开发板 FPC 连接器
最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证OV7725摄像头RGB TFT-LCD实时显示功能。下载完成后观察显示器的显示图像如图 39.5.3所示,说明OV7725摄像头LCD显示程序下载验证成功。

图 39.5.3 RGB TFT-LCD实时显示图像

【正点原子FPGA连载】第三十九章OV7725摄像头RGB-LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1相关推荐

  1. 【正点原子FPGA连载】 第二十四章 RTC实时时钟LCD显示实验-摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

  2. 【正点原子FPGA连载】第四十九章OV5640摄像头HDMI灰度显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  3. 【正点原子FPGA连载】第三十二章RTC实时时钟LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  4. 【正点原子FPGA连载】第十九章IP核之双端口RAM实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  5. zynqsd的读写数据_【正点原子FPGA连载】 第十二章SD卡读写TXT文本实验-摘自【正点原子】领航者 ZYNQ 之嵌入式开发指南 (amobbs.com 阿莫电子论坛)...

    本帖最后由 正点原子 于 2020-10-24 10:25 编辑 QQ群头像.png (1.78 KB) 2020-10-24 10:25 上传5)关注正点原子公众号,获取最新资料 100846rel ...

  6. 【正点原子FPGA连载】第十四章SD卡读写TXT文本实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

    1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: h ...

  7. 【正点原子STM32连载】 第二十五章 TFTLCD(MCU屏)实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  8. 【正点原子STM32连载】 第二十五章 TFT-LCD(MCU屏)实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

    1)实验平台:正点原子stm32f103战舰开发板V4 2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420 3)全套实验源码+手册+视 ...

  9. bmp文件头_「正点原子FPGA连载」第十九章SD卡读BMP图片LCD显示

    1)摘自[正点原子]领航者 ZYNQ 之嵌入式开发指南 2)实验平台:正点原子领航者ZYNQ开发板 3)平台购买地址:https://item.taobao.com/item.htm?&id= ...

最新文章

  1. 2021年大数据Flink(四十五):​​​​​​扩展阅读 双流Join
  2. Python循环中的变量作用域的灵异现象
  3. 0218互联网新闻 | 《流浪地球》IMAX中国票房破3亿;苹果发力智能家居
  4. 计算机英语阅读理解,2017年12月英语四级阅读理解50篇:学习计算机
  5. linux svn配置教程,linux svn搭建及配置
  6. dsp怪胎_2012年6月最佳怪胎文章
  7. 在运行时修补Java
  8. 诗与远方:无题(三十四)- 曾经写给妹子的一首诗
  9. Remove Duplicates from Sorted Array II
  10. 【kafka】记一次线上kafka一直rebalance故障 消费慢 数据积压
  11. python判断素数的函数_使用Python判断质数(素数)的简单方法讲解
  12. C#基础-获得当前程序的 空间名.类名.方法名
  13. 算法速学速用大辞典 pdf_随机梯度蒙特卡洛算法-重要性采样
  14. Xcap使用教程--创建、发送和分析报文
  15. VMware centos7镜像安装详细步骤
  16. Error response from daemon: Container XXX is restarting, wait until the container is running
  17. cout与printf区别
  18. imagemagick安装问题
  19. 培养出最多亿万富翁的美国大学TOP10榜单
  20. 三棱锥之刻(求三棱锥中心球与表面覆盖面积之和)

热门文章

  1. Fil真的要归零了吗?
  2. cache stm32h7_【STM32H7教程】第24章 STM32H7的Cache解读(非常重要)
  3. [Leetcode][分治法]相关题目汇总/分析/总结
  4. 教师基本功训练计算机,教师教学基本功培训方案
  5. 计算机音乐专业考研,武汉音乐学院2021年硕士研究生招生考试《计算机音乐作曲》考试大纲...
  6. Eclipse修改项目编码方式
  7. Android基于讯飞AIUI的聊天Demo
  8. 增量式编码器和绝对式编码器区别
  9. 前端基础 HTML
  10. 神雕侠侣手游服务器维护,《神雕侠侣》3月30日更新维护新服开启公告