超级版主
- 积分
- 4754
- 金钱
- 4754
- 注册时间
- 2019-5-8
- 在线时间
- 1239 小时
|
本帖最后由 正点原子运营 于 2021-10-30 10:18 编辑
1)实验平台:正点原子新起点V2FPGA开发板
2) 章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_xinqidian(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
第三十九章 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(640*480)、QVGA(320*240)以及CIF(一种常用的标准化图像格式,分辨率为352*288)到40*30等任意尺寸。可通过寄存器地址0x12(COM7)、0x17(HSTART)、0x18(HSIZE)、0x19(VSTRT)、0x1A(VSIZE)、0x32(HREF)、0x29(HoutSize)、0x2C(VOutSize)、0x2A(EXHCH)来配置输出图像的分辨率。
OV7725支持多种不同的数据像素格式,包括YUV(亮度参量和色度参量分开表示的像素格式)、RGB(其中RGB格式包含RGB565、RGB555等)以及8位的RAW(原始图像数据)和10位的RAW,通过寄存器地址0x12(COM7)配置不同的数据像素格式。
由于摄像头采集的图像最终要在LCD上显示,且新起点开发板上的数据接口为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实际上是640*2=1280个tPCLK;
由图 39.1.5可知,VSYNC的上升沿作为一帧的开始,高电平同步脉冲的时间为4*tLine,紧接着等待18*tLine时间后,HREF开始拉高,此时输出有效数据;HREF由640tp个高电平和144tp个低电平构成;输出480行数据之后等待8*tLine时间一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame =(4 + 18 + 480 + 8)*tLine = 510tLine。
本次实验采用OV7725支持的最大分辨率640*480,摄像头的输入时钟为12 Mhz,摄像头的输出时钟为24 Mhz(详细公式请看表 39.1.1),由此我们可以计算出摄像头的输出帧率,以PCLK=24Mhz(周期为42ns)为例,计算出OV7725输出一帧图像所需的时间如下:
一帧图像输出时间:tFrame = 510*tLine = 510*784tp = 510*784*2tPCLK = 799680*42ns =33.5866ms;
摄像头输出帧率:1000ms/33.5866ms ≈ 30Hz。
如果把像素时钟频率提高到摄像头的最大时钟频率48Mhz,通过上述计算方法,摄像头的输出帧率约为60Hz。
下图为OV7725输出RGB565格式的时序图:
图 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帧模式输出下,一帧图像的数据量达到640*480*16bit = 4915200bit = 4800kbit = 4.6875Mbit,带宽为4.6875Mbit*45Hz(帧率)=210.9375 Mbit/S,我们新起点FPGA开发板芯片型号为EP4CE10F17C8,器件手册可以发现,EP4CE10F17C8的片内存储资源为414Kbit,远不能达到存储要求。因此我们只能使用板载的外部存储器SDRAM来缓存图像数据,新起点板载的SDRAM容量为256Mbit,最大带宽为2.66Gbit/S(16bit*166M),足以满足缓存图像数据的需求。
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图像数据采集模块的代码如下所示:
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中开辟出一个存储空间(大小为640*480)用于缓存一帧图像。在摄像头初始化结束后输出的第一个数据对应图像的第一个像素点,将其写入存储空间的首地址中。通过在SDRAM读写控制模块中对输出的图像数据进行计数,从而将它们分别写入相应的地址空间。计数达640*480后,完成一帧图像的存储,然后回到存储空间的首地址继续下一帧图像的存储。在显示图像时,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实时显示图像 |
|