700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【正点原子FPGA连载】第三十九章OV7725摄像头RGB-LCD显示实验 -摘自【正点原子】新起

【正点原子FPGA连载】第三十九章OV7725摄像头RGB-LCD显示实验 -摘自【正点原子】新起

时间:2018-09-03 21:27:12

相关推荐

【正点原子FPGA连载】第三十九章OV7725摄像头RGB-LCD显示实验 -摘自【正点原子】新起

1)实验平台:正点原子新起点V2开发板

2)平台购买地址:/item.htm?id=609758951113

2)全套实验源码+手册+视频下载地址:/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的上升沿作为一帧的开始,高电平同步脉冲的时间为4tLine,紧接着等待18tLine时间后,HREF开始拉高,此时输出有效数据;HREF由640tp个高电平和144tp个低电平构成;输出480行数据之后等待8tLine时间一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame =(4 + 18 + 480 + 8)tLine = 510tLine。

本次实验采用OV7725支持的最大分辨率640480,摄像头的输入时钟为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 outputcam_rst_n , //cmos 复位信号,低电平有效10outputcam_sgm_ctrl, //cmos 时钟选择信号, 1:使用摄像头自带的晶振11outputcam_scl, //cmos SCCB_SCL线12inout cam_sda, //cmos SCCB_SDA线13//SDRAM接口14outputsdram_clk , //SDRAM 时钟15outputsdram_cke , //SDRAM 时钟有效16outputsdram_cs_n , //SDRAM 片选17outputsdram_ras_n , //SDRAM 行有效18outputsdram_cas_n , //SDRAM 列有效19outputsdram_we_n , //SDRAM 写有效20output [1:0] sdram_ba , //SDRAM Bank地址21output [1:0] sdram_dqm , //SDRAM 数据掩码22output [12:0] sdram_addr , //SDRAM 地址23inout [15:0] sdram_data , //SDRAM 数据 24//lcd接口25outputlcd_hs, //LCD 行同步信号26outputlcd_vs, //LCD 场同步信号27outputlcd_de, //LCD 数据输入使能28inout [15:0] lcd_rgb, //LCD RGB565颜色数据29outputlcd_bl, //LCD 背光控制信号30outputlcd_rst, //LCD 复位信号31outputlcd_pclk //LCD 采样时钟32);33 34 //parameter define35 parameter SLAVE_ADDR = 7'h21 ; //OV7725的器件地址7'h2136 parameter BIT_CTRL = 1'b0; //OV7725的字节地址为8位 0:8位 1:16位37 parameter CLK_FREQ = 26'd50_000_000; //i2c_dri模块的驱动时钟频率 33.3MHz38 parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率,不超过400KHz39 40 //wire define41 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_complete60 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 code77 //*****************************************************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 interface119.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 interface129.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), //系统复位157158 //用户写端口 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), //写端口复位: 复位写地址,清空写FIFO166167 //用户读端口 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), //读端口复位: 复位读地址,清空读FIFO175176 //用户控制端口 177 .sdram_read_valid (1'b1), //SDRAM 读使能178 .sdram_pingpang_en (1'b1), //SDRAM 乒乓操作使能179 .sdram_init_done (sdram_init_done), //SDRAM 初始化完成标志180181 //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),199200//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 inputclk, //时钟信号3 inputrst_n , //复位信号,低电平有效4 5 inputi2c_done , //I2C寄存器配置完成信号6 output regi2c_exec , //I2C触发执行信号 7 output reg [15:0] i2c_data , //I2C要配置的地址与数据(高8位地址,低8位数据)8 output reginit_done //初始化完成信号9 );10 11 //parameter define12 parameter REG_NUM = 7'd70 ; //总共需要配置的寄存器个数13 14 //reg define15 reg [9:0] start_init_cnt; //等待延时计数器16 reg [6:0] init_reg_cnt ; //寄存器配置个数计数器17 18 //*****************************************************19 //**main code20 //*****************************************************21 22 //cam_scl配置成250khz,输入的clk为1Mhz,周期为1us,1023*1us = 1.023ms23 //寄存器延时配置24 always @(posedge clk or negedge rst_n) begin25if(!rst_n)26start_init_cnt <= 10'b0; 27else if((init_reg_cnt == 7'd1) && i2c_done)28start_init_cnt <= 10'b0;29else if(start_init_cnt < 10'd1023) begin30start_init_cnt <= start_init_cnt + 1'b1;31end32 end33 34 //寄存器配置个数计数 35 always @(posedge clk or negedge rst_n) begin36if(!rst_n)37init_reg_cnt <= 7'd0;38else if(i2c_exec) 39init_reg_cnt <= init_reg_cnt + 7'b1;40 end 41 42 //i2c触发执行信号 43 always @(posedge clk or negedge rst_n) begin44if(!rst_n)45i2c_exec <= 1'b0;46else if(start_init_cnt == 10'd1022)47i2c_exec <= 1'b1;48//只有刚上电和配置第一个寄存器增加延时49else if(i2c_done && (init_reg_cnt != 7'd1) && (init_reg_cnt < REG_NUM))50i2c_exec <= 1'b1;51else52i2c_exec <= 1'b0; 53 end 54 55 //初始化完成信号56 always @(posedge clk or negedge rst_n) begin57if(!rst_n)58init_done <= 1'b0;59else if((init_reg_cnt == REG_NUM) && i2c_done) 60init_done <= 1'b1; 61 end 62 63 //配置寄存器地址与数据64 always @(posedge clk or negedge rst_n) begin65if(!rst_n)66i2c_data <= 16'b0;67else begin68case(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 虚拟像素MSB81 7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB82 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 自动白平衡控制字节091 7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字节192 7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字节293 7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字节394 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}; //COM699 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 UV108 7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1109 //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 矩阵系数1114 7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩阵系数2115 7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩阵系数3116 7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩阵系数4117 7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩阵系数5118 7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩阵系数6119 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}; //COM5146 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 endcase150end151 end152 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 行同步信号10input [7:0]cam_data , 11//用户接口 12output[10:0] h_pixel, //存入SDRAM的水平分辨率13output[10:0] v_pixel, //存入SDRAM的屏垂直分辨率 14output[27:0] sdram_addr_max , //存入SDRAM的最大读写地址 15outputcmos_frame_vsync , //帧有效信号 16outputcmos_frame_href , //行有效信号17outputcmos_frame_valid , //数据有效使能信号18output[15:0] cmos_frame_data//有效数据 19);20 21 //wire define22 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 code28 //***************************************************** 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 ;3233 //摄像头数据裁剪模块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);6667 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 outputcmos_frame_vsync , //帧有效信号 10outputcmos_frame_href , //行有效信号11outputcmos_frame_valid , //数据有效使能信号12output [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 code35 //*****************************************************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; 5152 always @(posedge cam_pclk or negedge rst_n) begin53if(!rst_n) begin54cam_vsync_d0 <= 1'b0;55cam_vsync_d1 <= 1'b0;56cam_href_d0 <= 1'b0;57cam_href_d1 <= 1'b0;58end59else begin60cam_vsync_d0 <= cam_vsync;61cam_vsync_d1 <= cam_vsync_d0;62cam_href_d0 <= cam_href;63cam_href_d1 <= cam_href_d0;64end65 end66 67 //对帧数进行计数68 always @(posedge cam_pclk or negedge rst_n) begin69if(!rst_n)70cmos_ps_cnt <= 4'd0;71else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))72cmos_ps_cnt <= cmos_ps_cnt + 4'd1;73 end74 75 //帧有效标志76 always @(posedge cam_pclk or negedge rst_n) begin77if(!rst_n)78frame_val_flag <= 1'b0;79else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)80frame_val_flag <= 1'b1;81else; 82 end 83 84 //8位数据转16位RGB565数据 85 always @(posedge cam_pclk or negedge rst_n) begin86if(!rst_n) begin87cmos_data_t <= 16'd0;88cam_data_d0 <= 8'd0;89byte_flag <= 1'b0;90end91else if(cam_href) begin92byte_flag <= ~byte_flag;93cam_data_d0 <= cam_data;94if(byte_flag)95 cmos_data_t <= {cam_data_d0,cam_data};96else; 97end98else begin99byte_flag <= 1'b0;100 cam_data_d0 <= 8'b0;101end 102 end 103 104 //产生输出数据有效信号(cmos_frame_valid)105 always @(posedge cam_pclk or negedge rst_n) begin106if(!rst_n)107 byte_flag_d0 <= 1'b0;108else109 byte_flag_d0 <= byte_flag; 110 end 111112 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//摄像头接口 11input cam_pclk , //cmos 数据像素时钟12input cam_vsync , //cmos 场同步信号13input cam_href , //cmos 行同步信号14input [15:0] cam_data , 15input cam_data_valid , 16//用户接口17output reg cmos_frame_valid , //数据有效使能信号18output 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屏垂直分辨率 3233 //wire define34 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 code45 //*****************************************************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) begin71if(!rst_n) begin72cam_vsync_d0 <= 1'b0;73cam_vsync_d1 <= 1'b0;74cam_href_d0 <= 1'b0;75cam_href_d1 <= 1'b0;76lcd_id_a <= 0;77v_disp_a <= 0;78h_disp_a <= 0; 79end80else begin81cam_vsync_d0 <= cam_vsync;82cam_vsync_d1 <= cam_vsync_d0;83cam_href_d0 <= cam_href;84cam_href_d1 <= cam_href_d0;85lcd_id_a <= lcd_id;86v_disp_a <= v_disp;87h_disp_a <= h_disp;88end89 end90 91 //计算存入sdram的分辨率92 always @(posedge cam_pclk or negedge rst_n) begin93if(!rst_n) begin94h_pixel <= 11'b0;95v_pixel <= 11'b0;96end97else begin98if(lcd_id_a == 16'h4342)begin99 h_pixel <= h_disp_a;100 v_pixel <= v_disp_a;101end102else begin103 h_pixel <= cmos_h_pixel;104 v_pixel <= cmos_v_pixel; 105end 106end107 end108 109 //对行计数110 always @(posedge cam_pclk or negedge rst_n) begin111if(!rst_n) 112 h_cnt <= 11'b0;113else begin114 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; 122end123 end124 125 //对场计数126 always @(posedge cam_pclk or negedge rst_n) begin127if(!rst_n) 128 v_cnt <= 11'b0;129else begin130 if(pos_vsync)131 v_cnt <= 11'b0;132 else if(neg_hsync)133 v_cnt <= v_cnt + 1'b1; 134 else135 v_cnt <= v_cnt; 136end137 end138 139 //产生输出数据有效信号(cmos_frame_valid)140 always @(posedge cam_pclk or negedge rst_n) begin141if(!rst_n)142 cmos_frame_valid <= 1'b0;143else 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;146else147 cmos_frame_valid <= 1'b0;148 149 end 150 151 always @(posedge cam_pclk or negedge rst_n) begin152if(!rst_n)153 cmos_frame_data <= 1'b0;154else 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;157else158 cmos_frame_data <= 1'b0;159 160 end 161162 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) begin158if (!rst_n) begin159 sdram_wr_addr <= 24'd0;160 sw_bank_en <= 1'b0;161 rw_bank_flag <= 1'b0;162end163else if(wr_load_flag) begin //检测到写端口复位信号时,写地址复位164 sdram_wr_addr <= wr_min_addr; 165 sw_bank_en <= 1'b0;166 rw_bank_flag <= 1'b0;167end168else 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 //切换BANK174 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;183end184else if(sw_bank_en) begin//到达写SDRAM的结束地址,回到写起始地址185 sw_bank_en <= 1'b0;186 if(rw_bank_flag == 1'b0) //切换BANK187 sdram_wr_addr <= {1'b0,wr_min_addr[22:0]};188 else189 sdram_wr_addr <= {1'b1,wr_min_addr[22:0]};190end191 end192 193 //sdram读地址产生模块194 always @(posedge clk_ref or negedge rst_n) begin195if(!rst_n) begin196 sdram_rd_addr <= 24'd0;197end 198else if(rd_load_flag)//检测到读端口复位信号时,读地址复位199 sdram_rd_addr <= rd_min_addr;200else 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 else210 sdram_rd_addr <= {1'b0,rd_min_addr[22:0]}; 211 end 212 end213//若突发写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;218end219 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 outputlcd_clk, //LCD驱动时钟 7 outputlcd_hs, //LCD 行同步信号8 outputlcd_vs, //LCD 场同步信号9 outputlcd_de, //LCD 数据输入使能10inout [15:0] lcd_rgb, //LCD RGB颜色数据11outputlcd_bl, //LCD 背光控制信号12outputlcd_rst, //LCD 复位信号13outputlcd_pclk, //LCD 采样时钟14output [15:0] lcd_id, //LCD屏ID 15outputout_vsync,//lcd场信号 16output [10:0] pixel_xpos,//像素点横坐标17output [10:0] pixel_ypos,//像素点纵坐标 18output [10:0] h_disp, //LCD屏水平分辨率19output [10:0] v_disp, //LCD屏垂直分辨率 20input [15:0] data_in, //数据输入21outputdata_req //请求数据输入22);23 24 //wire define25 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 code36 //***************************************************** 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),8081.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), 9596.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屏ID5 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屏水平分辨率10input[10:0] v_disp, //LCD屏垂直分辨率 1112output[15:0] lcd_data, //LCD像素点数据13output 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 define26 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 code33 //*****************************************************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) begin59if(!sys_rst_n)60data_val <= 1'b0;61else62data_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屏ID5 input [15:0] pixel_data, //像素点数据6 outputdata_req , //请求像素点颜色数据输入 7 output [10:0] pixel_xpos, //像素点横坐标8 output [10:0] pixel_ypos, //像素点纵坐标9 output [10:0] h_disp, //LCD屏水平分辨率10output [10:0] v_disp, //LCD屏垂直分辨率 11outputout_vsync, //帧复位,高有效1213//RGB-LCD接口14outputlcd_hs, //LCD 行同步信号15outputlcd_vs, //LCD 场同步信号16outputlcd_de, //LCD 数据输入使能17output [15:0] lcd_rgb,//LCD RGB565颜色数据18outputlcd_bl, //LCD 背光控制信号19outputlcd_rst,//LCD 复位信号20outputlcd_pclk//LCD 采样时钟2122); 此处省略一段代码。 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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。