OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 643|回复: 3

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第三十九章 OV7725摄像头RGB-LCD显示实验

[复制链接]

1049

主题

1060

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4359
金钱
4359
注册时间
2019-5-8
在线时间
1193 小时
发表于 2023-12-19 17:56:55 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-12-19 17:56 编辑

第三十九章 OV7725摄像头RGB-LCD显示实验
1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

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

1.1 简介
OV7725是一款1/4英寸单芯片图像传感器,其感光阵列达到640*480,能实现最快60fps VGA分辨率的图像采集。传感器内部集成了图像处理的功能,包括自动曝光控制(AEC)、自动增益控制(AGC)和自动白平衡(AWB)等。同时传感器具有较高的感光灵敏度,适合低照度的应用,下图为OV7725的功能框图。                             
image001.png
图 39.1.1 OV7725功能框图
由上图可知,感光阵列(imagearray)在XCLK时钟的驱动下进行图像采样,输出640*480阵列的模拟数据;接着模拟信号处理器在时序发生器(video timinggenerator)的控制下对模拟数据进行算法处理(analog processing);模拟数据处理完成后分成G(绿色)和R/B(红色/蓝色)两路通道经过AD转换器后转换成数字信号,并且通过DSP进行相关图像处理,最终输出所配置格式的10位视频数据流。模拟信号处理以及DSP等都可以通过寄存器(registers)来配置,配置寄存器的接口就是SCCB接口,该接口协议兼容IIC协议。
SCCB(Serial Camera Control Bus,串行摄像头控制总线)是由OV(OmniVision的简称)公司定义和发展的三线式串行总线,该总线控制着摄像头大部分的功能,包括图像数据格式、分辨率以及图像处理参数等。OV公司为了减少传感器引脚的封装,现在SCCB总线大多采用两线式接口总线。
OV7725使用的是两线式接口总线,该接口总线包括SIO_C串行时钟输入线和SIO_D串行双向数据线,分别相当于IIC协议的SCL信号线和SDA信号线。我们在前面提到过SCCB协议兼容IIC协议,是因为SCCB协议和IIC协议非常相似,有关IIC协议的详细介绍请大家参考“EEPROM读写实验”章节。
SCCB的写传输协议如下图所示:
image004.png
图 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的读传输协议。
image006.png
图 39.1.3 SCCB读传输协议
由上图可知,SCCB读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据,注意ID Address(R)= 8’h43(器件地址左移1位,低位补1)。上图中的NA位由主机(这里指FPGA)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。
在OV7725正常工作前,必须先对传感器进行初始化,即通过配置寄存器使其工作在预期的工作模式,以及得到较好画质的图像。因为SCCB的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。OV公司提供了OV7725的软件使用手册(位于开发板所随附的资料“7_硬件资料/6_OV7725资料/OV7725软件使用手册.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。
QQ截图20231219173734.png
QQ截图20231219173745.png
QQ截图20231219173751.png
表 39.1.1 OV7725关键寄存器配置说明
OV7725的寄存器较多,对于其它寄存器的描述可以参考OV7725的数据手册(位于开发板所随附的资料“7_硬件资料/6_OV7725资料/OV7725数据手册.pdf”)。
下图为OV7725的一些特性。
image007.png
图 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上显示,且ATK-DFPGL22G开发板上的数据接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此我们将OV7725摄像头输出的图像像素数据配置成RGB565格式,然后转换为RGB888格式。下图为摄像头输出的VGA帧模式时序图。
image009.png
图 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格式的时序图:
image011.png
图 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数据。由上图可知,数据是在像素时钟的下降沿改变的,为了在数据最稳定的时刻采集图像数据,所以我们需要在像素时钟的上升沿采集数据。

1.2 实验任务
本节实验任务是使用ATK-DFPGL22G开发板及OV7725摄像头实现图像采集,并通过RGB-LCD接口驱动RGB-LCD液晶屏(支持目前正点原子推出的所有RGB-LCD屏),并实时显示出图像。

1.3 硬件设计
ATK-DFPGL22G开发板上有一个摄像头扩展接口,该接口可以用来连接OV7725/OV5640等摄像头模块,摄像头扩展接口原理图如下所示:
image013.png
图 39.3.1 摄像头扩展接口原理图
ATK-OV7725是正点原子推出的一款高性能30W像素高清摄像头模块。该模块通过2*9排针(2.54mm间距)同外部连接,我们将摄像头的排针直接插在开发板上的摄像头接口即可。
前面说过,OV7725在RGB565模式中只有高8位(即D[9:2])数据是有效的,而我们的摄像头排针上数据引脚的个数是8位。实际上,摄像头排针上的8位数据连接的就是OV7725传感器的D[9:2],所以我们直接使用摄像头排针上的8位数据引脚即可。
需要注意的是,由图 39.3.1可知,摄像头扩展口的第18个引脚定义为CMOS_PWDN,而我们的OV7725摄像头模块的PWDN引脚固定为低电平,也就是一直处于正常工作模式。OV7725摄像头模块的第18个引脚定义为SGM_CTRL(CMOS_PWDN),这个引脚是摄像头驱动时钟的选择引脚。OV7725摄像头模块内部自带晶振的,当SGM_CTRL引脚为低电平时,选择使用摄像头的外部时钟,也就是FPGA需要输出时钟给摄像头;当SGM_CTRL引脚为高电平时,选择使用摄像头的晶振提供时钟。本次实验将SGM_CTRL引脚驱动为高电平,这样就不用为摄像头提供驱动时钟,即不用在CMOS_XCLK引脚上输出时钟。
由于LCD接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配,如下表所示:
QQ截图20231219173802.png
表 39.3.1 OV7725摄像头RGB-LCD显示实验管脚分配
可以看出本章与之前例程相比,电平标准发生了改变,这里用到的电平标准是LVTTL33(该电平标准为配置引脚时的默认电平标准),用户也可根据需求将其改为LVCMOS33, LVCMOS33和LVTTL33可以直接相互驱动,所以这里没有进行修改,保持默认;LVCMOS和LVTTL的区别在于LVCMOS噪声容限更大,抗噪性更好,而LVTTL噪声容限小,但是速度快。
摄像头约束文件如下:
  1. define_attribute{n:cam_pclk} {PAP_CLOCK_DEDICATED_ROUTE} {FALSE}
  2. define_attribute{p:cam_scl} {PAP_IO_DIRECTION} {Output}
  3. define_attribute{p:cam_scl} {PAP_IO_LOC} {E6}
  4. define_attribute{p:cam_scl} {PAP_IO_VCCIO} {3.3}
  5. define_attribute{p:cam_scl} {PAP_IO_STANDARD} {LVTTL33}
  6. define_attribute{p:cam_scl} {PAP_IO_DRIVE} {16}
  7. define_attribute{p:cam_scl} {PAP_IO_PULLUP} {TRUE}
  8. define_attribute{p:cam_scl} {PAP_IO_SLEW} {SLOW}
  9. define_attribute{p:cam_sda} {PAP_IO_DIRECTION} {Inout}
  10. define_attribute{p:cam_sda} {PAP_IO_LOC} {B6}
  11. define_attribute{p:cam_sda} {PAP_IO_VCCIO} {3.3}
  12. define_attribute{p:cam_sda} {PAP_IO_STANDARD} {LVTTL33}
  13. define_attribute{p:cam_sda} {PAP_IO_DRIVE} {16}
  14. define_attribute{p:cam_sda} {PAP_IO_PULLUP} {TRUE}
  15. define_attribute{p:cam_sda} {PAP_IO_SLEW} {SLOW}
  16. define_attribute{p:cam_data[0]} {PAP_IO_DIRECTION} {Input}
  17. define_attribute{p:cam_data[0]} {PAP_IO_LOC} {A6}
  18. define_attribute{p:cam_data[0]} {PAP_IO_VCCIO} {3.3}
  19. define_attribute{p:cam_data[0]} {PAP_IO_STANDARD} {LVTTL33}
  20. define_attribute{p:cam_data[0]} {PAP_IO_PULLUP} {TRUE}
  21. define_attribute{p:cam_data[1]} {PAP_IO_DIRECTION} {Input}
  22. define_attribute{p:cam_data[1]} {PAP_IO_LOC} {B2}
  23. define_attribute{p:cam_data[1]} {PAP_IO_VCCIO} {3.3}
  24. define_attribute{p:cam_data[1]} {PAP_IO_STANDARD} {LVTTL33}
  25. define_attribute{p:cam_data[1]} {PAP_IO_PULLUP} {TRUE}
  26. define_attribute{p:cam_data[2]} {PAP_IO_DIRECTION} {Input}
  27. define_attribute{p:cam_data[2]} {PAP_IO_LOC} {A5}
  28. define_attribute{p:cam_data[2]} {PAP_IO_VCCIO} {3.3}
  29. define_attribute{p:cam_data[2]} {PAP_IO_STANDARD} {LVTTL33}
  30. define_attribute{p:cam_data[2]} {PAP_IO_PULLUP} {TRUE}
  31. define_attribute{p:cam_data[3]} {PAP_IO_DIRECTION} {Input}
  32. define_attribute{p:cam_data[3]} {PAP_IO_LOC} {A2}
  33. define_attribute{p:cam_data[3]} {PAP_IO_VCCIO} {3.3}
  34. define_attribute{p:cam_data[3]} {PAP_IO_STANDARD} {LVTTL33}
  35. define_attribute{p:cam_data[3]} {PAP_IO_PULLUP} {TRUE}
  36. define_attribute{p:cam_data[4]} {PAP_IO_DIRECTION} {Input}
  37. define_attribute{p:cam_data[4]} {PAP_IO_LOC} {F7}
  38. define_attribute{p:cam_data[4]} {PAP_IO_VCCIO} {3.3}
  39. define_attribute{p:cam_data[4]} {PAP_IO_STANDARD} {LVTTL33}
  40. define_attribute{p:cam_data[4]} {PAP_IO_PULLUP} {TRUE}
  41. define_attribute{p:cam_data[5]} {PAP_IO_DIRECTION} {Input}
  42. define_attribute{p:cam_data[5]} {PAP_IO_LOC} {B1}
  43. define_attribute{p:cam_data[5]} {PAP_IO_VCCIO} {3.3}
  44. define_attribute{p:cam_data[5]} {PAP_IO_STANDARD} {LVTTL33}
  45. define_attribute{p:cam_data[5]} {PAP_IO_PULLUP} {TRUE}
  46. define_attribute{p:cam_data[6]} {PAP_IO_DIRECTION} {Input}
  47. define_attribute{p:cam_data[6]} {PAP_IO_LOC} {F8}
  48. define_attribute{p:cam_data[6]} {PAP_IO_VCCIO} {3.3}
  49. define_attribute{p:cam_data[6]} {PAP_IO_STANDARD} {LVTTL33}
  50. define_attribute{p:cam_data[6]} {PAP_IO_PULLUP} {TRUE}
  51. define_attribute{p:cam_data[7]} {PAP_IO_DIRECTION} {Input}
  52. define_attribute{p:cam_data[7]} {PAP_IO_LOC} {A1}
  53. define_attribute{p:cam_data[7]} {PAP_IO_VCCIO} {3.3}
  54. define_attribute{p:cam_data[7]} {PAP_IO_STANDARD} {LVTTL33}
  55. define_attribute{p:cam_data[7]} {PAP_IO_PULLUP} {TRUE}
  56. define_attribute{p:cam_href} {PAP_IO_DIRECTION} {Input}
  57. define_attribute{p:cam_href} {PAP_IO_LOC} {F4}
  58. define_attribute{p:cam_href} {PAP_IO_VCCIO} {3.3}
  59. define_attribute{p:cam_href} {PAP_IO_STANDARD} {LVTTL33}
  60. define_attribute{p:cam_href} {PAP_IO_PULLUP} {TRUE}
  61. define_attribute{p:cam_pclk} {PAP_IO_DIRECTION} {Input}
  62. define_attribute{p:cam_pclk} {PAP_IO_LOC} {C1}
  63. define_attribute{p:cam_pclk} {PAP_IO_VCCIO} {3.3}
  64. define_attribute{p:cam_pclk} {PAP_IO_STANDARD} {LVTTL33}
  65. define_attribute{p:cam_pclk} {PAP_IO_PULLUP} {TRUE}
  66. define_attribute{p:cam_vsync} {PAP_IO_DIRECTION} {Input}
  67. define_attribute{p:cam_vsync} {PAP_IO_LOC} {G4}
  68. define_attribute{p:cam_vsync} {PAP_IO_VCCIO} {3.3}
  69. define_attribute{p:cam_vsync} {PAP_IO_STANDARD} {LVTTL33}
  70. define_attribute{p:cam_vsync} {PAP_IO_PULLUP} {TRUE}
  71. define_attribute{p:cam_rst_n} {PAP_IO_DIRECTION} {Output}
  72. define_attribute{p:cam_rst_n} {PAP_IO_LOC} {D1}
  73. define_attribute{p:cam_rst_n} {PAP_IO_VCCIO} {3.3}
  74. define_attribute{p:cam_rst_n} {PAP_IO_STANDARD} {LVTTL33}
  75. define_attribute{p:cam_rst_n} {PAP_IO_SLEW} {SLOW}
  76. define_attribute{p:cam_rst_n} {PAP_IO_DRIVE} {4}
  77. define_attribute{p:cam_rst_n} {PAP_IO_PULLUP} {TRUE}
  78. define_attribute{p:cam_sgm_ctrl} {PAP_IO_DIRECTION} {Output}
  79. define_attribute{p:cam_sgm_ctrl} {PAP_IO_LOC} {C2}
  80. define_attribute{p:cam_sgm_ctrl} {PAP_IO_VCCIO} {3.3}
  81. define_attribute{p:cam_sgm_ctrl} {PAP_IO_STANDARD} {LVTTL33}
  82. define_attribute{p:cam_sgm_ctrl} {PAP_IO_SLEW} {SLOW}
  83. define_attribute{p:cam_sgm_ctrl} {PAP_IO_DRIVE} {4}
  84. define_attribute{p:cam_sgm_ctrl} {PAP_IO_PULLUP} {TRUE}
复制代码
cam_pclk是摄像头时钟,因为配置在了普通io口,没有走时钟专用管脚且最终上了时钟网络,所以PDS流程运行到place&route会报错((报错编号为Elace-0084)),如果将该时钟相关的net或该net的驱动pin脚设置PAP_CLOCK_DEDICATED_ROUTE属性为FLASE,则PDS流程会在place&route中将报错降级为Critical Warning(C: Place-2028)。最终的表现形式为PDS流程在place&route阶段不会报错停止而是以报警告代替。cam_pclk约束语句如下:
define_attribute{n:cam_pclk} {PAP_CLOCK_DEDICATED_ROUTE} {FALSE}

1.4 程序设计
OV7725在VGA帧模式下,以RGB565格式输出最高帧率可达60Hz,LCD屏的刷新频率也可以达到60Hz,那么是不是直接将采集到的图像数据连接到LCD屏的输入数据端口就行了呢?答案是不可以。我们在前面说过,OV7725帧率如果要达到60Hz,那么像素时钟频率必须为48Mhz,而LCD屏根据屏的分辨率不同时钟也不同,首先就有时钟不匹配的问题,其次是时序方面的不匹配,LCD屏驱动对时序有着严格的要求。我们在“RGB-LCD彩条显示实验”的章节中可以获知,LCD一行或一场分为四个部分:低电平同步脉冲、显示后沿、有效数据段以及显示前沿,各个部分的时序参数很显然跟OV7725并不是完全一致的。因此必须先把一帧图像缓存下来,然后再把图像数据按照LCD的时序发送到LCD屏上显示。OV7725在VGA帧模式输出下,一帧图像的数据量达到640*480*16bit = 4915200bit = 4800kbit = 4.6875Mbit,带宽为4.6875Mbit*45Hz(帧率)=210.9375 Mbit/S,我们ATK-DFPGL22G开发板芯片型号为PGL22G-6MGB324,从器件手册可以发现,PGL22G-6MGB324的片内存储资源为864Kbit,远不能达到存储要求。因此我们只能使用板载的外部存储器DDR3来缓存图像数据,开发板板载的DDR3容量为4096Mbit(一块DDR3的容量),最大带宽为12.8Gbit/S,足以满足缓存图像数据的需求。
OV7725在正常工作之前必须通过配置寄存器进行初始化,而配置寄存器的SCCB协议和I2C协议在写操作时几乎一样,所以我们需要一个I2C驱动模块。为了使OV7725在期望的模式下运行并且提高图像显示效果,需要配置较多的寄存器,这么多寄存器的地址与参数需要单独放在一个模块,因此还需要一个寄存配置信息的I2C配置模块。在摄像头配置完成后,开始输出图像数据,因此需要一个摄像头图像采集模块来采集图像;外接DDR3存储器当然离不开DDR3控制器模块的支持,最后LCD顶层模块读取DDR3缓存的数据以达到最终实时显示的效果。
image015.png
图 39.4.1 程序结构框图
由上图可知,时钟模块为LCD顶层模块、DDR3控制模块以及I2C驱动模块提供驱动时钟。I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入DDR控制模块,LCD顶层模块从DDR3控制模块中读出数据,完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在DDR3和传感器都初始化完成之后才开始输出数据的,避免了在DDR3初始化过程中向里面写入数据。
顶层模块代码如下:
  1. 1   module ov7725_lcd(
  2. 2        input            sys_clk          ,
  3. 3        input            sys_rst_n        ,
  4. 4        //lcd接口                        
  5. 5        output           lcd_hs           ,  //LCD 行同步信号
  6. 6        output           lcd_vs           ,  //LCD 场同步信号
  7. 7        output            lcd_de           ,  //LCD 数据输入使能
  8. 8        inout  [23:0    lcd_rgb          ,  //LCD 颜色数据
  9. 9        output           lcd_bl           ,  //LCD 背光控制信号
  10. 10       output           lcd_rst          ,  //LCD 复位信号
  11. 11       output           lcd_pclk         ,  //LCD 采样时钟
  12. 12       //摄像头接口                       
  13. 13       input            cam_pclk         ,  //cmos 数据像素时钟
  14. 14       input            cam_vsync        ,  //cmos 场同步信号
  15. 15       input            cam_href         ,  //cmos 行同步信号
  16. 16       input  [7:0     cam_data         ,  //cmos 数据
  17. 17       output           cam_rst_n        ,  //cmos 复位信号,低电平有效
  18. 18       output           cam_sgm_ctrl     ,  //cmos 时钟选择信号, 1:使用摄像头自带的晶振
  19. 19       output           cam_scl          ,  //cmos SCCB_SCL线
  20. 20       inout            cam_sda          ,  //cmos SCCB_SDA线
  21. 21       //DDR3接口
  22. 22       input            pad_loop_in      ,  //低位温度补偿输入
  23. 23       input            pad_loop_in_h    ,  //高位温度补偿输入
  24. 24       output           pad_rstn_ch0     ,  //Memory复位
  25. 25       output           pad_ddr_clk_w    ,  //Memory差分时钟正
  26. 26       output           pad_ddr_clkn_w   ,  //Memory差分时钟负
  27. 27       output           pad_csn_ch0      ,  //Memory片选
  28. 28       output [15:0     pad_addr_ch0     ,  //Memory地址总线
  29. 29       inout  [16-1:0  pad_dq_ch0       ,  //数据总线
  30. 30       inout  [16/8-1:0 pad_dqs_ch0     ,  //数据时钟正端
  31. 31       inout  [16/8-1:0 pad_dqsn_ch0    ,  //数据时钟负端
  32. 32       output [16/8-1:0 pad_dm_rdqs_ch0 ,  //数据Mask
  33. 33       output           pad_cke_ch0      ,  //Memory差分时钟使
  34. 34       output           pad_odt_ch0      ,  //On Die Terminati
  35. 35       output           pad_rasn_ch0     ,  //行地址strobe
  36. 36       output           pad_casn_ch0     ,  //列地址strobe
  37. 37       output           pad_wen_ch0      ,  //写使能
  38. 38       output [2:0      pad_ba_ch0       ,  //Bank地址总线
  39. 39       output           pad_loop_out     ,  //低位温度补偿输出
  40. 40       output           pad_loop_out_h      //高位温度补偿输出
  41. 41      );
  42. 42  
  43. 43  //parameter define
  44. 44  parameter SLAVE_ADDR = 7'h21         ; //OV7725的器件地址7'h21
  45. 45  parameter BIT_CTRL   =1'b0          ; //OV7725的字节地址为8位  0:8位 1:16位
  46. 46  parameter CLK_FREQ   =27'd50_000_000; //i2c_dri模块的驱动时钟频率
  47. 47  parameter I2C_FREQ   =18'd250_000   ;//I2C的SCL时钟频率,不超过400KHz
  48. 48  parameter APP_ADDR_MIN = 28'd0       ; //ddr3读写起始地址,以一个16bit的数据为一个单位
  49. 49  parameter BURST_LENGTH = 8'd64       ; //ddr3读写突发长度,64个128bit的数据
  50. 50
复制代码
顶层模块中第46至第47行定义了两个变量:CLK_FREQ(i2c_dri模块的驱动时钟频率)和I2C_FREQ(I2C的SCL时钟频率),I2C_FREQ的时钟频率不能超过400KHz,否则有可能导致摄像头配置不成功。
  1. 51  //wire define
  2. 52  wire        sys_init_done   ;  //系统初始化完成(DDR3初始化+摄像头初始化)
  3. 53  wire        rst_n           ;  //全局复位
  4. 54  //PLL
  5. 55  wire        clk_50m         ;  //output 50M
  6. 56  wire        clk_100m        ;  //output 100M
  7. 57  wire        clk_locked      ;
  8. 58  //OV7725
  9. 59  wire        i2c_dri_clk     ;  //I2C操作时钟
  10. 60  wire        i2c_done        ;  //I2C寄存器配置完成信号
  11. 61  wire        i2c_exec        ;  //I2C触发执行信号
  12. 62  wire [15:0 i2c_data       ;  //I2C要配置的地址与数据(高8位地址,低8位数据)
  13. 63  wire        cam_init_done   ;  //摄像头初始化完成
  14. 64  wire        cmos_frame_vsync;  //帧有效信号
  15. 65  wire        cmos_frame_href ;  //行有效信号
  16. 66  wire        cmos_frame_valid;  //数据有效使能信号
  17. 67  wire [15:0 wr_data        ;  //OV7725写入DDR3控制器模块的数据
  18. 68  wire [27:0 ddr3_addr_max  ;  //存入DDR3的最大读写地址
  19. 69  //LCD                       
  20. 70  wire        lcd_clk         ;  //分频产生的LCD采样时钟
  21. 71  wire [10:0 h_disp         ;  //LCD屏水平分辨率
  22. 72  wire [10:0 v_disp         ;  //LCD屏垂直分辨率
  23. 73  wire [15:0 lcd_id         ;  //LCD屏的ID号
  24. 74  wire        out_vsync       ;  //LCD场信号
  25. 75  wire        rdata_req       ;  //读数据请求
  26. 76  //DDR3
  27. 77  wire [15:0 rd_data        ;  //DDR3控制器模块输出的数据
  28. 78  wire        fram_done       ;  //DDR中已经存入一帧画面标志
  29. 79  wire        ddr_init_done   ;  //ddr3初始化完成
  30. 80  
  31. 81  //*****************************************************
  32. 82  //**                    main code
  33. 83  //*****************************************************
  34. 84  
  35. 85  //待时钟锁定后产生结束复位信号
  36. 86  assign  rst_n = sys_rst_n & clk_locked;
  37. 87  
  38. 88  //系统初始化完成:DDR3和摄像头都初始化完成
  39. 89  //避免了在DDR3初始化过程中向里面写入数据
  40. 90  assign  sys_init_done = ddr_init_done & cam_init_done;
  41. 91  
  42. 92  //cmos 时钟选择信号, 1:使用摄像头自带的晶振
  43. 93  assign  cam_sgm_ctrl = 1'b1;
  44. 94  
  45. 95  //不对摄像头硬件复位,固定高电平
  46. 96  assign  cam_rst_n   = 1'b1;
  47. 97  
  48. 98  //例化PLL IP核
  49. 99  pll_clk  u_pll_clk(
  50. 100      .pll_rst        (~sys_rst_n  ),
  51. 101      .clkin1         (sys_clk     ),
  52. 102      .clkout0        (clk_50m     ),
  53. 103      .clkout1        (clk_100m    ),
  54. 104      .pll_lock       (clk_locked  )
  55. 105 );
  56. 106
  57. 107 //I2C配置模块   
  58. 108 i2c_ov7725_rgb565_cfg  u_i2c_cfg(
  59. 109      .clk            (i2c_dri_clk  ), //in
  60. 110      .rst_n          (rst_n        ), //in
  61. 111      .i2c_done       (i2c_done     ), //in
  62. 112      .i2c_exec       (i2c_exec     ), //out
  63. 113      .i2c_data       (i2c_data     ), //out
  64. 114      .init_done      (cam_init_done)  //out
  65. 115      );   
  66. 116
  67. 117 //I2C驱动模块
  68. 118 i2c_dri
  69. 119     #(
  70. 120      .SLAVE_ADDR     (SLAVE_ADDR),  //参数传递
  71. 121      .CLK_FREQ       (CLK_FREQ  ),
  72. 122      .I2C_FREQ       (I2C_FREQ  )
  73. 123      )               
  74. 124     u_i2c_dri(      
  75. 125      .clk            (clk_50m       ),
  76. 126      .rst_n          (rst_n         ),
  77. 127      //i2c interface
  78. 128      .i2c_exec       (i2c_exec      ),
  79. 129      .bit_ctrl       (BIT_CTRL      ),
  80. 130      .i2c_rh_wl      (1'b0          ), //固定为0,只用到了IIC驱动的写操作
  81. 131      .i2c_addr       (i2c_data[15:8]),
  82. 132      .i2c_data_w     (i2c_data[7:0 ),
  83. 133      .i2c_data_r     (              ), //out
  84. 134      .i2c_done       (i2c_done      ), //out
  85. 135      .i2c_ack        (              ), //out
  86. 136      .scl            (cam_scl       ), //out
  87. 137      .sda            (cam_sda       ), //inout
  88. 138      //user interface
  89. 139      .dri_clk        (i2c_dri_clk   )  //I2C操作时钟//out
  90. 140 );
  91. 141
  92. 142 //图像采集顶层模块
  93. 143 cmos_data_top u_cmos_data_top(
  94. 144      .rst_n                 (rst_n & sys_init_done), //系统初始化完成之后再开始采集数据
  95. 145      .cam_pclk              (cam_pclk            ),
  96. 146      .cam_vsync             (cam_vsync           ),
  97. 147      .cam_href              (cam_href            ),
  98. 148      .cam_data              (cam_data            ),
  99. 149      .lcd_id                (lcd_id              ), //in
  100. 150      .h_disp                (h_disp              ), //in
  101. 151      .v_disp                (v_disp              ), //in
  102. 152      .h_pixel               (                    ),
  103. 153      .v_pixel               (                    ),
  104. 154      .ddr3_addr_max         (ddr3_addr_max       ), //out
  105. 155      .cmos_frame_vsync      (cmos_frame_vsync    ),
  106. 156      .cmos_frame_href       (cmos_frame_href     ),
  107. 157      .cmos_frame_valid      (cmos_frame_valid    ), //数据有效使能信号
  108. 158      .cmos_frame_data       (wr_data             )  //有效数据
  109. 159      );
  110. 160
复制代码
在程序的第157和第158行,CMOS图像数据采集模块输出的cmos_frame_valid(数据有效使能信号)和wr_data(有效数据)连接到DDR3控制模块,实现了图像数据的缓存。
  1. 161 //ddr3
  2. 162 ddr3_top u_ddr3_top(
  3. 163      .refclk_in             (clk_50m        ),
  4. 164      .rst_n                 (rst_n          ),
  5. 165      .app_addr_rd_min       (APP_ADDR_MIN   ),
  6. 166      .app_addr_rd_max       (ddr3_addr_max  ),
  7. 167      .rd_bust_len           (BURST_LENGTH   ),
  8. 168      .app_addr_wr_min       (APP_ADDR_MIN   ),
  9. 169      .app_addr_wr_max       (ddr3_addr_max  ),
  10. 170      .wr_bust_len           (BURST_LENGTH   ),
  11. 171      .ddr3_read_valid       (1'b1            ),
  12. 172      .ddr3_pingpang_en      (1'b1            ),
  13. 173      .wr_clk                (cam_pclk       ),
  14. 174      .rd_clk                (lcd_clk        ),
  15. 175     .datain_valid          (cmos_frame_valid),
  16. 176      .datain                (wr_data        ),
  17. 177      .rdata_req             (rdata_req      ),
  18. 178      .rd_load               (out_vsync      ),
  19. 179      .wr_load               (cmos_frame_vsync),
  20. 180      .fram_done             (fram_done      ),
  21. 181      .dataout               (rd_data        ),
  22. 182      .pll_lock              (pll_lock       ),
  23. 183      .ddr_init_done         (ddr_init_done  ),
  24. 184      .ddrphy_rst_done       (                ),
  25. 185      .pad_loop_in           (pad_loop_in    ),
  26. 186      .pad_loop_in_h         (pad_loop_in_h  ),
  27. 187      .pad_rstn_ch0          (pad_rstn_ch0   ),
  28. 188      .pad_ddr_clk_w         (pad_ddr_clk_w  ),
  29. 189      .pad_ddr_clkn_w        (pad_ddr_clkn_w ),
  30. 190      .pad_csn_ch0           (pad_csn_ch0    ),
  31. 191      .pad_addr_ch0          (pad_addr_ch0   ),
  32. 192      .pad_dq_ch0            (pad_dq_ch0     ),
  33. 193      .pad_dqs_ch0           (pad_dqs_ch0    ),
  34. 194      .pad_dqsn_ch0          (pad_dqsn_ch0   ),
  35. 195      .pad_dm_rdqs_ch0       (pad_dm_rdqs_ch0 ),
  36. 196      .pad_cke_ch0           (pad_cke_ch0    ),
  37. 197      .pad_odt_ch0           (pad_odt_ch0    ),
  38. 198      .pad_rasn_ch0          (pad_rasn_ch0    ),
  39. 199      .pad_casn_ch0          (pad_casn_ch0   ),
  40. 200      .pad_wen_ch0           (pad_wen_ch0    ),
  41. 201      .pad_ba_ch0            (pad_ba_ch0     ),
  42. 202      .pad_loop_out          (pad_loop_out   ),
  43. 203      .pad_loop_out_h        (pad_loop_out_h )
  44. 204      
  45. 205      );  
  46. 206
复制代码
在程序的第167和第170行,信号(rd_bust_len)和信号(wr_bust_len)表示一次向DDR读或写的长度,这里长度我们设置为64(2的6次方),因为64*8可以被最大读写地址整除,能够匹配正点原子在售的所有RGB-LCD屏,这里建议突发长度设置为2的整数次方,其他突发长度可能会出现数据反卷,反卷现象的详细说明可以查看“DDR3读写测试实验”的程序设计部分。
  1. 207 //LCD驱动显示模块
  2. 208 lcd_rgb_top  u_lcd_rgb_top(
  3. 209      .sys_clk               (clk_50m            ),
  4. 210      .clk_100m              (clk_100m           ),
  5. 211      .sys_rst_n             (rst_n && clk_locked ),
  6. 212      .sys_init_done         (sys_init_done      ),
  7. 213      //lcd接口
  8. 214      .lcd_id                (lcd_id             ), //LCD屏的ID号
  9. 215      .lcd_hs                (lcd_hs             ), //LCD 行同步信号
  10. 216      .lcd_vs                (lcd_vs             ), //LCD 场同步信号
  11. 217      .lcd_de                (lcd_de             ), //LCD 数据输入使能
  12. 218      .lcd_rgb               (lcd_rgb            ), //LCD 颜色数据
  13. 219      .lcd_bl                (lcd_bl             ), //LCD 背光控制信号
  14. 220      .lcd_rst               (lcd_rst            ), //LCD 复位信号
  15. 221      .lcd_pclk              (lcd_pclk           ), //LCD 采样时钟
  16. 222      .lcd_clk               (lcd_clk            ), //LCD 驱动时钟
  17. 223      //用户接口                                 
  18. 224      .h_disp                (h_disp             ), //行分辨率
  19. 225      .v_disp                (v_disp             ), //场分辨率
  20. 226      .pixel_xpos            (                   ),
  21. 227      .pixel_ypos            (                   ),
  22. 228      .out_vsync             (out_vsync          ),
  23. 229      .data_in               (rd_data            ), //rfifo输出数据
  24. 230      .data_req              (rdata_req          )  //请求数据输入
  25. 231      );
  26. 232
  27. 233 endmodule
复制代码
在程序的第224和第225行,LCD顶层模块引出了h_disp和v_disp信号,并将其引入摄像头图像采集模块。因为LCD屏的分辨率是不一样的,而本次实验是驱动自适应不同分辨率的LCD屏,所以将这两个信号引入摄像头图像采集模块是为了与摄像头采集的分辨率进行比较。当摄像头的分辨率大时,就取LCD屏分辨率大小的数据存入DDR中,其余数据丢弃,当摄像头的分辨率小时,就将摄像头采集的数据存入DDR中,LCD屏其他显示区域填充黑色。
在程序的230行,LCD顶层模块输出rdata_req(请求像素点颜色数据输入)连接到DDR控制模块, 实现了图像数据的读取,将读出的数据连接到LCD顶层模块的data_in信号,从而实现了LCD实时显示的功能。
FPGA顶层模块(ov7725_lcd)例化了以下六个模块:时钟模块(pll_clk)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov7725_rgb565_cfg)、图像采集顶层模块(cmos_data_top)、ddr3控制模块(ddr3_top)和LCD顶层模块(lcd_rgb_top)。
时钟模块(pll_clk):时钟模块通过调用PLL IP核实现,共输出2个时钟,频率分别为50Mhz和100M时钟。100Mhz时钟给lcd的时钟分频模块使用,50Mhz时钟作为ddr3控制模块,I2C驱动模块和LCD顶层模块的驱动时钟。
I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV7725SCCB接口总线,用户根据该模块提供的用户接口可以很方便的对OV7725的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
I2C配置模块(i2c_ov7725_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV7725的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV7725传感器的初始化。
图像采集顶层模块(cmos_data_top):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成DDR控制模块的写使能信号和16位写数据信号,完成对OV7725传感器图像的采集。如果LCD屏的分辨率小于OV7725的分辨率,还要对OV7725采集的数据进行裁剪,以匹配LCD屏的分辨率。
ddr3控制模块(ddr3_top):ddr3控制模块负责驱动DDR3片外存储器,缓存图像传感器输出的图像数据。该模块将DDR3复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。在“DDR3读写测试实验”的程序中,读写操作地址都是DDR的同一存储空间,如果只使用一个存储空间缓存图像数据,那么同一存储空间中会出现两帧图像叠加的情况,为了避免这一情况,我们在DDR3的其它BANK中开辟了三个相同大小的存储空间,使用乒乓操作的方式来写入和读取数据,所以本次实验在“DDR3读写测试实验”的程序里做了一个小小的改动,有关该模块的详细介绍请大家参考“DDR3读写测试实验”章节,本章只对改动的地方作介绍。
LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。
I2C配置模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,代码如下所示:
  1. 1   module i2c_ov7725_rgb565_cfg(  
  2. 2        input               clk      ,  //时钟信号
  3. 3        input               rst_n    ,  //复位信号,低电平有效
  4. 4        
  5. 5        input               i2c_done ,  //I2C寄存器配置完成信号
  6. 6        output  reg         i2c_exec ,  //I2C触发执行信号
  7. 7        output  reg  [15:0  i2c_data ,  //I2C要配置的地址与数据(高8位地址,低8位数据)
  8. 8        output  reg         init_done   //初始化完成信号
  9. 9        );
  10. 10  
  11. 11  //parameter define
  12. 12  parameter  REG_NUM = 7'd70   ;       //总共需要配置的寄存器个数
  13. 13
复制代码
在代码的第12行定义了总共需要配置的寄存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。
  1. 14  //reg define
  2. 15  reg    [9:0  start_init_cnt;       //等待延时计数器
  3. 16  reg    [6:0  init_reg_cnt  ;       //寄存器配置个数计数器
  4. 17  
  5. 18  //*****************************************************
  6. 19  //**                    main code
  7. 20  //*****************************************************
  8. 21  
  9. 22  //cam_scl配置成250khz,输入的clk为1Mhz,周期为1us,1023*1us = 1.023ms
  10. 23  //寄存器延时配置
  11. 24  always @(posedge clk or negedge rst_n) begin
  12. 25       if(!rst_n)
  13. 26           start_init_cnt <= 10'b0;
  14. 27       else if((init_reg_cnt == 7'd1) && i2c_done)
  15. 28           start_init_cnt <= 10'b0;
  16. 29       else if(start_init_cnt < 10'd1023) begin
  17. 30           start_init_cnt <= start_init_cnt + 1'b1;
  18. 31       end
  19. 32  end
  20. 33
复制代码
图像传感器刚开始上电时电压有可能不够稳定,所以程序中的23行至32行定义了一个延时计数器(start_init_cnt)等待传感器工作在稳定的状态。当计数器计数到预设值之后,开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。从前面介绍的OV7725的特性可知,软件复位需要等待1ms的时间才能配置其它的寄存器,因此发送完软件复位命令后,延时计数器清零,并重新开始计数。当计数器计数到预设值之后,紧接着配置剩下的寄存器。只有软件复位命令需要1ms的等待时间,其它寄存器不需要等待时间,直接按照程序中定义的顺序发送即可。
  1. 34  //寄存器配置个数计数
  2. 35  always @(posedge clk or negedge rst_n) begin
  3. 36       if(!rst_n)
  4. 37           init_reg_cnt <= 7'd0;
  5. 38       else if(i2c_exec)
  6. 39           init_reg_cnt <= init_reg_cnt + 7'b1;
  7. 40  end
  8. 41  
  9. 42  //i2c触发执行信号
  10. 43  always @(posedge clk or negedge rst_n) begin
  11. 44       if(!rst_n)
  12. 45           i2c_exec <= 1'b0;
  13. 46       else if(start_init_cnt == 10'd1022)
  14. 47           i2c_exec <= 1'b1;
  15. 48       //只有刚上电和配置第一个寄存器增加延时
  16. 49       else if(i2c_done && (init_reg_cnt != 7'd1) && (init_reg_cnt < REG_NUM))
  17. 50           i2c_exec <= 1'b1;
  18. 51       else
  19. 52           i2c_exec <= 1'b0;
  20. 53  end
  21. 54  
  22. 55  //初始化完成信号
  23. 56  always @(posedge clk or negedge rst_n) begin
  24. 57       if(!rst_n)
  25. 58           init_done <= 1'b0;
  26. 59       else if((init_reg_cnt == REG_NUM) && i2c_done)
  27. 60           init_done <= 1'b1;
  28. 61  end
  29. 62  
  30. 63  //配置寄存器地址与数据
  31. 64  always @(posedge clk or negedge rst_n) begin
  32. 65       if(!rst_n)
  33. 66           i2c_data <= 16'b0;
  34. 67       else begin
  35. 68           case(init_reg_cnt)
  36. 69               //先对寄存器进行软件复位,使寄存器恢复初始值
  37. 70               //寄存器软件复位后,需要延时1ms才能配置其它寄存器
  38. 71               7'd0  : i2c_data <= {8'h12, 8'h80}; //COM7BIT[7]:复位所有的寄存器
  39. 72               7'd1  : i2c_data <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿
  40. 73               7'd2  : i2c_data <= {8'h15, 8'h00}; //COM10href/vsync/pclk/data信号控制
  41. 74               7'd3  : i2c_data <= {8'h17, 8'h23}; //HSTART水平起始位置
  42. 75               7'd4  : i2c_data <= {8'h18, 8'ha0}; //HSIZE 水平尺寸
  43. 76               7'd5  : i2c_data <= {8'h19, 8'h07}; //VSTRT 垂直起始位置
  44. 77               7'd6  : i2c_data <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸            
  45. 78               7'd7  : i2c_data <= {8'h32, 8'h00}; //HREF 图像开始和尺寸控制,控制低位
  46. 79               7'd8  : i2c_data <= {8'h29, 8'ha0}; //HOutSize水平输出尺寸
  47. 80               7'd9  : i2c_data <= {8'h2a, 8'h00}; //EXHCH 虚拟像素MSB
  48. 81               7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB
  49. 82               7'd11 : i2c_data <= {8'h2c, 8'hf0}; //VOutSize垂直输出尺寸
  50. 83               7'd12 : i2c_data <= {8'h0d, 8'h41}; //COM4PLL倍频设置(multiplier)
  51. 84                                                  //Bit[7:6]:  0:1x 1:4x 2:6x 3:8x
  52. 85               7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 内部时钟配置
  53. 86                                                  //Freq=multiplier/[(CLKRC[5:0]+1)*2]
  54. 87               7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式
  55. 88               7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3Bit[0]: 0:图像数据 1:彩条测试
  56. 89               //DSP 控制
  57. 90               7'd16 : i2c_data <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值
  58. 91               7'd17 : i2c_data <= {8'h4d, 8'h09}; //FixGain模拟增益放大器
  59. 92               7'd18 : i2c_data <= {8'h63, 8'hf0}; //AWB_Ctrl0自动白平衡控制字节0
  60. 93               7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1DSP控制字节1
  61. 94               7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2DSP控制字节2
  62. 95               7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3DSP控制字节3
  63. 96               7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4DSP控制字节4
  64. 97               //AGC AEC AWB
  65. 98               //COM8 Bit[2]:自动增益使能 Bit[1]:自动白平衡使能 Bit[0]:自动曝光功能
  66. 99               7'd23 : i2c_data <= {8'h13, 8'hff}; //COM8
  67. 100              7'd24 : i2c_data <= {8'h0f, 8'hc5}; //COM6
  68. 101              7'd25 : i2c_data <= {8'h14, 8'h11};
  69. 102              7'd26 : i2c_data <= {8'h22, 8'h98};
  70. 103              7'd27 : i2c_data <= {8'h23, 8'h03};
  71. 104              7'd28 : i2c_data <= {8'h24, 8'h40};
  72. 105              7'd29 : i2c_data <= {8'h25, 8'h30};
  73. 106              7'd30 : i2c_data <= {8'h26, 8'ha1};
  74. 107              7'd31 : i2c_data <= {8'h6b, 8'haa};
  75. 108              7'd32 : i2c_data <= {8'h13, 8'hff};
  76. 109              //matrix sharpness brightness contrast UV
  77. 110              7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1
  78. 111              //DNSOff 降噪阈值下限,仅在自动模式下有效
  79. 112              7'd34 : i2c_data <= {8'h91, 8'h01}; //DNSOff
  80. 113              7'd35 : i2c_data <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限
  81. 114              7'd36 : i2c_data <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限
  82. 115              7'd37 : i2c_data <= {8'h94, 8'h5f}; //MTX1 矩阵系数1
  83. 116              7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩阵系数2
  84. 117              7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩阵系数3
  85. 118              7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩阵系数4
  86. 119              7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩阵系数5
  87. 120              7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩阵系数6
  88. 121              7'd43 : i2c_data <= {8'h9a, 8'h1e}; //MTX_Ctrl矩阵控制
  89. 122              7'd44 : i2c_data <= {8'h9b, 8'h3f}; //BRIGHT亮度
  90. 123              7'd45 : i2c_data <= {8'h9c, 8'h25}; //CNST 对比度
  91. 124              7'd46 : i2c_data <= {8'h9e, 8'h81};
  92. 125              7'd47 : i2c_data <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制
  93. 126              7'd48 : i2c_data <= {8'ha7, 8'h65}; //USAT"U"饱和增益
  94. 127              7'd49 : i2c_data <= {8'ha8, 8'h65}; //VSAT "V"饱和增益
  95. 128              7'd50 : i2c_data <= {8'ha9, 8'h80}; //VSAT"V"饱和增益
  96. 129              7'd51 : i2c_data <= {8'haa, 8'h80}; //VSAT"V"饱和增益
  97. 130              //伽马控制
  98. 131              7'd52 : i2c_data <= {8'h7e, 8'h0c};
  99. 132              7'd53 : i2c_data <= {8'h7f, 8'h16};
  100. 133              7'd54 : i2c_data <= {8'h80, 8'h2a};
  101. 134              7'd55 : i2c_data <= {8'h81, 8'h4e};
  102. 135              7'd56 : i2c_data <= {8'h82, 8'h61};
  103. 136              7'd57 : i2c_data <= {8'h83, 8'h6f};
  104. 137              7'd58 : i2c_data <= {8'h84, 8'h7b};
  105. 138              7'd59 : i2c_data <= {8'h85, 8'h86};
  106. 139              7'd60 : i2c_data <= {8'h86, 8'h8e};
  107. 140              7'd61 : i2c_data <= {8'h87, 8'h97};
  108. 141              7'd62 : i2c_data <= {8'h88, 8'ha4};
  109. 142              7'd63 : i2c_data <= {8'h89, 8'haf};
  110. 143              7'd64 : i2c_data <= {8'h8a, 8'hc5};
  111. 144              7'd65 : i2c_data <= {8'h8b, 8'hd7};
  112. 145              7'd66 : i2c_data <= {8'h8c, 8'he8};
  113. 146              7'd67 : i2c_data <= {8'h8d, 8'h20};
  114. 147              
  115. 148              7'd68 : i2c_data <= {8'h0e, 8'h65}; //COM5
  116. 149              7'd69 : i2c_data <= {8'h09, 8'h00}; //COM2  Bit[1:0] 输出电流驱动能力
  117. 150              //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
  118. 151              default:i2c_data <=   {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位
  119. 152          endcase
  120. 153      end
  121. 154 end
  122. 155
  123. 156 endmodule
复制代码
在程序的83至86行,说明了关于摄像头输出时钟的寄存器配置,摄像头的寄存器地址0x0d配置成0x41,表示PLL倍频设置设为了4倍频,摄像头的寄存器地址0x11配置成0x00,而摄像头的输入时钟为12M,所以根据第86行的公式可得到摄像头的输出时钟为24M。
图像采集顶层模块的代码如下:
  1. 1  module cmos_data_top(
  2. 2       input                rst_n            ,  //复位信号
  3. 3       input       [15:0    lcd_id           ,  //LCD屏的ID号
  4. 4       input       [10:0    h_disp           ,  //LCD屏水平分辨率
  5. 5       input       [10:0    v_disp           ,  //LCD屏垂直分辨率
  6. 6       //摄像头接口
  7. 7       input                cam_pclk         ,  //cmos 数据像素时钟
  8. 8       input                cam_vsync        ,  //cmos 场同步信号
  9. 9       input                cam_href         ,  //cmos 行同步信号
  10. 10      input  [7:0         cam_data         ,  //cmos 数据
  11. 11      //用户接口
  12. 12      output      [10:0   h_pixel          ,  //存入ddr3的水平分辨率
  13. 13      output      [10:0   v_pixel          ,  //存入ddr3的屏垂直分辨率
  14. 14      output      [27:0   ddr3_addr_max    ,  //存入DDR3的最大读写地址
  15. 15      output               cmos_frame_vsync ,  //帧有效信号
  16. 16      output               cmos_frame_href  ,  //行有效信号
  17. 17      output               cmos_frame_valid ,  //数据有效使能信号
  18. 18      output       [15:0   cmos_frame_data     //有效数据
  19. 19      );
  20. 20
  21. 21 //wire define
  22. 22 wire  [10:0 h_pixel       ;  //存入ddr3的水平分辨率
  23. 23 wire  [10:0 v_pixel       ;  //存入ddr3的屏垂直分辨率
  24. 24 wire  [15:0 lcd_id_a      ;  //时钟同步后的LCD屏的ID号
  25. 25 wire  [27:0 ddr3_addr_max ;  //存入DDR3的最大读写地址
  26. 26 wire  [15:0 wr_data_tailor;  //经过裁剪的摄像头数据
  27. 27 wire  [15:0 wr_data       ;  //没有经过裁剪的摄像头数据
  28. 28
  29. 29 //*****************************************************
  30. 30 //**                    main code
  31. 31 //*****************************************************
  32. 32
  33. 33 assign cmos_frame_valid = (lcd_id_a == 16'h4342) ? data_valid_tailor : data_valid ;
  34. 34 assign cmos_frame_data = (lcd_id_a == 16'h4342) ? wr_data_tailor : wr_data ;
  35. 35
  36. 36 //摄像头数据裁剪模块
  37. 37 cmos_tailor  u_cmos_tailor(
  38. 38      .rst_n                 (rst_n           ),
  39. 39      .lcd_id                (lcd_id          ),
  40. 40      .lcd_id_a              (lcd_id_a        ),
  41. 41      .cam_pclk              (cam_pclk        ),
  42. 42      .cam_vsync             (cmos_frame_vsync ),
  43. 43      .cam_href              (cmos_frame_href ),
  44. 44      .cam_data              (wr_data         ),
  45. 45      .cam_data_valid        (data_valid      ),
  46. 46      .h_disp                (h_disp          ),
  47. 47     .v_disp                (v_disp          ),
  48. 48      .h_pixel               (h_pixel         ),
  49. 49      .v_pixel               (v_pixel         ),
  50. 50      .ddr3_addr_max         (ddr3_addr_max   ),
  51. 51      .cmos_frame_valid      (data_valid_tailor),
  52. 52      .cmos_frame_data       (wr_data_tailor  )
  53. 53
  54. 54 );
  55. 55
  56. 56 //摄像头数据采集模块
  57. 57 cmos_capture_data u_cmos_capture_data(
  58. 58      .rst_n                 (rst_n          ),
  59. 59      .cam_pclk              (cam_pclk       ),
  60. 60      .cam_vsync            (cam_vsync       ),
  61. 61      .cam_href              (cam_href       ),
  62. 62      .cam_data              (cam_data       ),
  63. 63      .cmos_frame_vsync      (cmos_frame_vsync),
  64. 64      .cmos_frame_href       (cmos_frame_href ),
  65. 65      .cmos_frame_valid      (data_valid     ),
  66. 66      .cmos_frame_data       (wr_data        )
  67. 67      );
  68. 68
  69. 69 endmodule
复制代码
图像采集顶层模块(cmos_data_ top)例化了以下二个模块:图像采集模块(cmos_capture_data)、图像裁剪模块(cmos_tailor)。
图像采集模块(cmos_capture_data)为其他模块提供摄像头8bit输入数据转化后的16bit数据和数据使能以及摄像头稳定后的行场信号。图像裁剪模块(cmos_tailor)只有在LCD的器件ID为16’h4342时起作用,即摄像头的分辨率大于LCD屏的分辨率,起到裁剪图像数据,使图像的有效数据达到匹配LCD屏的尺寸。
在程序中的33行至34行,定义了两个信号cmos_frame_valid和cmos_frame_data,这两个信号的输出是根据LCD的器件ID来选择的,当摄像头的分辨率大于LCD的分辨率时,将裁剪后的数据和数据使能赋给这两个信号,反之将没有裁剪的信号和使能赋给这两个信号。
摄像头接口输出8位像素数据,而本次实验配置摄像头的模式是RGB565,所以需要在图像采集模块实现8位数据转16位数据的功能。
CMOS图像数据采集模块的代码如下所示:
  1. 1   module cmos_capture_data(
  2. 2        input                rst_n            ,  //复位信号
  3. 3        //摄像头接口                           
  4. 4        input                cam_pclk         ,  //cmos 数据像素时钟
  5. 5        input                cam_vsync        ,  //cmos 场同步信号
  6. 6        input                cam_href         ,  //cmos 行同步信号
  7. 7        input  [7:0          cam_data         ,
  8. 8        //用户接口                              
  9. 9        output               cmos_frame_vsync ,  //帧有效信号
  10. 10       output               cmos_frame_href  ,  //行有效信号
  11. 11       output               cmos_frame_valid ,  //数据有效使能信号
  12. 12       output       [15:0  cmos_frame_data     //有效数据
  13. 13       );
  14. 14  
  15. 15  //寄存器全部配置完成后,先等待10帧数据
  16. 16  //待寄存器配置生效后再开始采集图像
  17. 17  //parameter define
  18. 18  parameter  WAIT_FRAME = 4'd10    ;  //寄存器数据稳定等待的帧个数
  19. 19  
  20. 20  //reg define
  21. 21  reg             cam_vsync_d0     ;
  22. 22  reg             cam_vsync_d1     ;
  23. 23  reg             cam_href_d0      ;
  24. 24  reg             cam_href_d1      ;
  25. 25  reg    [3:0   cmos_ps_cnt      ;  //等待帧数稳定计数器
  26. 26  reg    [7:0   cam_data_d0      ;
  27. 27  reg    [15:0  cmos_data_t      ;  //用于8位转16位的临时寄存器
  28. 28  reg             byte_flag        ;  //16位RGB数据转换完成的标志信号
  29. 29  reg             byte_flag_d0     ;
  30. 30  reg             frame_val_flag   ;  //帧有效的标志
  31. 31  
  32. 32  //wire define
  33. 33  wire            pos_vsync        ;  //采输入场同步信号的上升沿
  34. 34  
  35. 35  //*****************************************************
  36. 36  //**                    main code
  37. 37  //*****************************************************
  38. 38  
  39. 39  //采输入场同步信号的上升沿
  40. 40  assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
  41. 41  
  42. 42  //输出帧有效信号
  43. 43  assign  cmos_frame_vsync = frame_val_flag  ? cam_vsync_d1  :  1'b0;
  44. 44  
  45. 45  //输出行有效信号
  46. 46  assign  cmos_frame_href  = frame_val_flag  ?  cam_href_d1  :  1'b0;
  47. 47  
  48. 48  //输出数据使能有效信号
  49. 49  assign  cmos_frame_valid = frame_val_flag  ? byte_flag_d0  :  1'b0;
  50. 50  
  51. 51  //输出数据
  52. 52  assign  cmos_frame_data  = frame_val_flag  ?  cmos_data_t  :  1'b0;
  53. 53  
  54. 54  always @(posedge cam_pclk or negedge rst_n) begin
  55. 55       if(!rst_n) begin
  56. 56           cam_vsync_d0 <= 1'b0;
  57. 57           cam_vsync_d1 <= 1'b0;
  58. 58           cam_href_d0 <= 1'b0;
  59. 59           cam_href_d1 <= 1'b0;
  60. 60       end
  61. 61       else begin
  62. 62           cam_vsync_d0 <= cam_vsync;
  63. 63           cam_vsync_d1 <= cam_vsync_d0;
  64. 64           cam_href_d0 <= cam_href;
  65. 65           cam_href_d1 <= cam_href_d0;
  66. 66       end
  67. 67  end
  68. 68  
  69. 69  //对帧数进行计数
  70. 70  always @(posedge cam_pclk or negedge rst_n) begin
  71. 71       if(!rst_n)
  72. 72           cmos_ps_cnt <= 4'd0;
  73. 73       else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
  74. 74           cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
  75. 75  end
  76. 76  
  77. 77  //帧有效标志
  78. 78  always @(posedge cam_pclk or negedge rst_n) begin
  79. 79       if(!rst_n)
  80. 80           frame_val_flag <= 1'b0;
  81. 81       else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
  82. 82           frame_val_flag <= 1'b1;
  83. 83       else;
  84. 84  end
  85. 85  
  86. 86  //8位数据转16位RGB565数据
  87. 87  always @(posedge cam_pclk or negedge rst_n) begin
  88. 88       if(!rst_n) begin
  89. 89           cmos_data_t <= 16'd0;
  90. 90           cam_data_d0 <= 8'd0;
  91. 91           byte_flag <= 1'b0;
  92. 92       end
  93. 93       else if(cam_href) begin
  94. 94           byte_flag <= ~byte_flag;
  95. 95           cam_data_d0 <= cam_data;
  96. 96           if(byte_flag)
  97. 97               cmos_data_t <= {cam_data_d0,cam_data};
  98. 98           else;
  99. 99       end
  100. 100      else begin
  101. 101          byte_flag <= 1'b0;
  102. 102          cam_data_d0 <= 8'b0;
  103. 103      end
  104. 104 end
  105. 105
  106. 106 //产生输出数据有效信号(cmos_frame_valid)
  107. 107 always @(posedge cam_pclk or negedge rst_n) begin
  108. 108      if(!rst_n)
  109. 109          byte_flag_d0 <= 1'b0;
  110. 110      else
  111. 111          byte_flag_d0 <= byte_flag;
  112. 112 end
  113. 113
  114. 114 endmodule
复制代码
CMOS图像采集模块第18行定义了参数WAIT_FRAME(寄存器数据稳定等待的帧个数),我们在前面介绍寄存器时提到过配置寄存器生效的时间最长为300ms,约为摄像头输出10帧图像数据。所以这里采集场同步信号的上升沿来统计帧数,计数器计数超过10次之后产生数据有效的标志,开始采集图像。在程序的第87行开始的always块实现了8位数据转16位数据的功能。需要注意的是摄像头的图像数据是在像素时钟(cam_pclk)下输出的,因此摄像头的图像数据必须使用像素钟来采集,否则会造成数据采集错误。
当摄像头的分辨率大于LCD屏的分辨率时,需要对其进行裁剪,使存入DDR3的分辨率匹配LCD屏的尺寸。
图像裁剪模块的代码如下所示:
  1. 1   module cmos_tailor(
  2. 2        input                rst_n            ,  //复位信号
  3. 3        input       [15:0    lcd_id           ,  //LCD屏的ID号
  4. 4        input       [10:0    h_disp           ,  //LCD屏水平分辨率
  5. 5        input       [10:0    v_disp           ,  //LCD屏垂直分辨率
  6. 6        output      [10:0   h_pixel          ,  //存入ddr3的水平分辨率
  7. 7        output      [10:0   v_pixel          ,  //存入ddr3的屏垂直分辨率
  8. 8        output      [27:0   ddr3_addr_max    ,  //存入ddr3的最大读写地址
  9. 9        output      [15:0   lcd_id_a         ,  //时钟同步后的LCD屏的ID号
  10. 10       //摄像头接口
  11. 11       input                cam_pclk         ,  //cmos 数据像素时钟
  12. 12       input                cam_vsync        ,  //cmos 场同步信号
  13. 13       input                cam_href         ,  //cmos 行同步信号
  14. 14       input  [15:0        cam_data         ,
  15. 15       input                cam_data_valid   ,
  16. 16       //用户接口
  17. 17       output                cmos_frame_valid ,  //数据有效使能信号
  18. 18       output       [15:0  cmos_frame_data     //有效数据
  19. 19       );
  20. 20  
  21. 21  //reg define
  22. 22  reg             cam_vsync_d0     ;
  23. 23  reg             cam_vsync_d1     ;
  24. 24  reg             cam_href_d0      ;
  25. 25  reg             cam_href_d1      ;
  26. 26  reg    [10:0   h_pixel          ;  //存入ddr3的水平分辨率
  27. 27  reg    [10:0   v_pixel          ;  //存入ddr3的屏垂直分辨率
  28. 28  reg    [10:0   h_cnt            ;  //对行计数
  29. 29  reg    [10:0   v_cnt            ;  //对场计数
  30. 30  reg             cmos_frame_valid ;
  31. 31  reg    [15:0  cmos_frame_data  ;
  32. 32  reg    [15:0  lcd_id_a         ;  //LCD屏的ID号
  33. 33  reg    [10:0  h_disp_a         ;  //LCD屏水平分辨率
  34. 34  reg    [10:0  v_disp_a         ;  //LCD屏垂直分辨率
  35. 35  
  36. 36  //wire define
  37. 37  wire            pos_vsync        ;  //采输入场同步信号的上升沿
  38. 38  wire            neg_hsync        ;  //采输入行同步信号的下降沿
  39. 39  wire   [10:0  cmos_h_pixel     ;  //CMOS水平方向像素个数
  40. 40  wire   [10:0  cmos_v_pixel     ;  //CMOS垂直方向像素个数
  41. 41  wire   [10:0  cam_border_pos_l ;  //左侧边界的横坐标
  42. 42  wire   [10:0  cam_border_pos_r ;  //右侧边界的横坐标
  43. 43  wire   [10:0  cam_border_pos_t ;  //上端边界的纵坐标
  44. 44  wire   [10:0  cam_border_pos_b ;  //下端边界的纵坐标
  45. 45  
  46. 46  //*****************************************************
  47. 47  //**                    main code
  48. 48  //*****************************************************
  49. 49  
  50. 50  assign  ddr3_addr_max = h_pixel * v_pixel;  //存入ddr3的最大读写地址
  51. 51  assign  cmos_h_pixel = 11'd640 ;  //CMOS水平方向像素个数
  52. 52  assign  cmos_v_pixel = 11'd480 ;  //CMOS垂直方向像素个数
  53. 53
复制代码
在模块的第50行对信号ddr3_addr_max(存入DDR3的最大读写地址)进行了赋值,因为本次实验用了一片DDR3,故DDR3的数据位宽为16位,而摄像头的数据位宽为16位,所以DDR3的最大存储地址为行场分辨率的乘积。
在模块的第51和52行对两个信号cmos_h_pixel(CMOS水平方向像素个数)和cmos_v_pixel(CMOS垂直方向像素个数)进行定义,这两个信号代表了本次实验摄像头的分辨率,而这次实验的LCD的分辨率是不固定的,故可能出现LCD屏的分辨率比摄像头分辨率小的情况,而正点原子的LCD屏只有一种480x272的屏分辨率比摄像头的分辨率小,所以需要将摄像头的图像大小裁剪成和LCD屏的分辨率大小一样。
  1. 54  //采输入场同步信号的上升沿
  2. 55  assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
  3. 56  
  4. 57  //采输入行同步信号的下降沿
  5. 58  assign neg_hsync = (~cam_href_d0) & cam_href_d1;
  6. 59  
  7. 60  //左侧边界的横坐标计算
  8. 61  assign cam_border_pos_l  = (cmos_h_pixel - h_disp_a)/2-1;
  9. 62  
  10. 63  //右侧边界的横坐标计算
  11. 64  assign cam_border_pos_r = h_disp + (cmos_h_pixel - h_disp_a)/2-1;
  12. 65  
  13. 66  //上端边界的纵坐标计算
  14. 67  assign cam_border_pos_t  = (cmos_v_pixel - v_disp_a)/2;
  15. 68  
  16. 69  //下端边界的纵坐标计算
  17. 70  assign cam_border_pos_b = v_disp_a + (cmos_v_pixel - v_disp_a)/2;
  18. 71  
复制代码

image018.png
图 39.4.2 摄像头裁剪图
如上图所示,在代码的第60至70行,计算了摄像头存入DDR3的边界坐标,上边界坐标(cam_border_pos_t)是摄像头的场分辨率(480)减去LCD屏的场分辨率再除以2得到的,其他的边界也是以类似的计算方法得到。
  1. 72  always @(posedge cam_pclk or negedge rst_n) begin
  2. 73       if(!rst_n) begin
  3. 74           cam_vsync_d0 <= 1'b0;
  4. 75           cam_vsync_d1 <= 1'b0;
  5. 76           cam_href_d0  <= 1'b0;
  6. 77           cam_href_d1  <= 1'b0;
  7. 78           lcd_id_a     <= 0   ;
  8. 79           v_disp_a     <= 0   ;
  9. 80           h_disp_a     <= 0   ;
  10. 81       end
  11. 82       else begin
  12. 83           cam_vsync_d0 <= cam_vsync;
  13. 84           cam_vsync_d1 <= cam_vsync_d0;
  14. 85           cam_href_d0 <= cam_href;
  15. 86           cam_href_d1 <= cam_href_d0;
  16. 87           lcd_id_a <= lcd_id;
  17. 88           v_disp_a <= v_disp;
  18. 89           h_disp_a <= h_disp;
  19. 90       end
  20. 91  end
  21. 92
复制代码
在模块的第72至91行,对输入信号进行时钟同步,是为了减少信号的扇出和防止时序不满足。
  1. 93  //计算存入ddr3的分辨率
  2. 94  always @(posedge cam_pclk or negedge rst_n) begin
  3. 95       if(!rst_n) begin
  4. 96           h_pixel <= 11'b0;
  5. 97           v_pixel <= 11'b0;
  6. 98       end
  7. 99       else begin
  8. 100          if(lcd_id_a == 16'h4342)begin
  9. 101              h_pixel <= h_disp_a;
  10. 102              v_pixel <= v_disp_a;
  11. 103        end
  12. 104        else begin
  13. 105            h_pixel <= cmos_h_pixel;
  14. 106            v_pixel <= cmos_v_pixel;
  15. 107        end
  16. 108      end
  17. 109 end
  18. 110
复制代码
在模块的第94至109行,根据LCD屏的器件ID来判断存入DDR3的分辨率,若当LCD的ID为16'h4342时,说明LCD屏的分辨率比摄像头小,所以存入DDR3的分辨率就是LCD屏的分辨率。
  1. 111 //对行计数
  2. 112 always @(posedge cam_pclk or negedge rst_n) begin
  3. 113      if(!rst_n)
  4. 114          h_cnt <= 11'b0;
  5. 115      else begin
  6. 116          if(pos_vsync||neg_hsync)
  7. 117              h_cnt <= 11'b0;
  8. 118          else if(cam_data_valid)
  9. 119              h_cnt <= h_cnt + 1'b1;
  10. 120          else if (cam_href_d0)
  11. 121              h_cnt <= h_cnt;
  12. 122          else
  13. 123              h_cnt <= h_cnt;
  14. 124      end
  15. 125 end
  16. 126
  17. 127 //对场计数
  18. 128 always @(posedge cam_pclk or negedge rst_n) begin
  19. 129      if(!rst_n)
  20. 130          v_cnt <= 11'b0;
  21. 131      else begin
  22. 132          if(pos_vsync)
  23. 133              v_cnt <= 11'b0;
  24. 134          else if(neg_hsync)
  25. 135              v_cnt <= v_cnt + 1'b1;
  26. 136          else
  27. 137              v_cnt <= v_cnt;
  28. 138      end
  29. 139 end
  30. 140
  31. 141 //产生输出数据有效信号(cmos_frame_valid)
  32. 142 always @(posedge cam_pclk or negedge rst_n) begin
  33. 143      if(!rst_n)
  34. 144          cmos_frame_valid <= 1'b0;
  35. 145      else if(h_cnt[10:0]>=cam_border_pos_l && h_cnt[10:0]<cam_border_pos_r&&
  36. 146              v_cnt[10:0]>=cam_border_pos_t && v_cnt[10:0]<cam_border_pos_b)
  37. 147              cmos_frame_valid <= cam_data_valid;
  38. 148      else
  39. 149              cmos_frame_valid <= 1'b0;
  40. 150 end
  41. 151
  42. 152 always @(posedge cam_pclk or negedge rst_n) begin
  43. 153      if(!rst_n)
  44. 154          cmos_frame_data <= 1'b0;
  45. 155      else if(h_cnt[10:0]>=cam_border_pos_l && h_cnt[10:0]<cam_border_pos_r&&
  46. 156              v_cnt[10:0]>=cam_border_pos_t && v_cnt[10:0]<cam_border_pos_b)
  47. 157              cmos_frame_data <= cam_data;
  48. 158      else
  49. 159              cmos_frame_data <= 1'b0;
  50. 160 end
  51. 161
  52. 162 endmodule
复制代码
在模块的第142至150行,根据LCD屏的器件ID来判断输出的数据使能是否有效,当器件ID为16'h4342时,必须满足行场计数器在边界的范围内有效,其他时候是无效的,同理数据也是这么判断的。
本次实验的ddr3控制模块相较于“ddr3读写测试实验”只是在子模块(ddr3读写模块)中添加了一个乒乓缓存的功能,以及在ddr3控制器fifo控制模块添加了load信号来产生一段复位电平,以满足fifo复位时序。
ddr3读写模块添加的代码如下所示:
  1. 102  //读/写结束信号上升沿
  2. 103  assign wr_end_r=wr_end_d0&&(~wr_end_d1);
  3. 104  assign rd_end_r=rd_end_d0&&(~rd_end_d1);
  4. 105  
  5. 106  //乒乓操作
  6. 107  assign axi_araddr=ddr3_pingpang_en?
  7. {4'b0,raddr_page,axi_araddr_n[24:0],1'b0}:{6'b0,axi_araddr_n[24:0],1'b0};
  8. 108  assign axi_awaddr=ddr3_pingpang_en?
  9. {4'b0,waddr_page,axi_awaddr_n[24:0],1'b0}:{6'b0,axi_awaddr_n[24:0],1'b0};
复制代码
省略部分源代码……
  1. 279  //对信号进行打拍处理
  2. 280  always @(posedge clk or negedge rst_n)  begin
  3. 281      if(~rst_n)begin
  4. 282         rd_load_d0 <= 0;
  5. 283         rd_load_d1 <= 0;
  6. 284         wr_load_d0 <= 0;
  7. 285         wr_load_d1 <= 0;
  8. 286      end   
  9. 287      else begin
  10. 288         rd_load_d0 <= rd_load;
  11. 289         rd_load_d1 <= rd_load_d0;
  12. 290         wr_load_d0 <= wr_load;
  13. 291         wr_load_d1 <= wr_load_d0;
  14. 292      end   
  15. 293  end
  16. 294  
  17. 295  //对输入源做个帧复位标志
  18. 296  always @(posedge clk or negedge rst_n)  begin
  19. 297      if(~rst_n)
  20. 298         wr_rst <= 0;
  21. 299      else if(wr_load_d0&& !wr_load_d1)
  22. 300         wr_rst <= 1;
  23. 301      else
  24. 302         wr_rst <= 0;
  25. 303  end
  26. 304  
  27. 305  //对输出源做个帧复位标志
  28. 306  always @(posedge clk or negedge rst_n)  begin
  29. 307      if(~rst_n)
  30. 308         rd_rst <= 0;
  31. 309      else if(!rd_load_d0&& rd_load_d1)
  32. 310         rd_rst <= 1;
  33. 311      else
  34. 312         rd_rst <= 0;
  35. 313  end
  36. 314
复制代码
第296行至313行代码对输入/输出源做了帧复位标志,即每完成一帧读/写操作,就将对应的读/写复位标志拉高一个时钟,使读/写地址复位到读/写起始地址。
  1. 315  //对输出源的读地址做个帧复位脉冲
  2. 316  always @(posedge clk or negedge rst_n)  begin
  3. 317      if(~rst_n)
  4. 318         raddr_rst_h <= 1'b0;
  5. 319      else if(rd_load_d0&& !rd_load_d1)
  6. 320         raddr_rst_h <= 1'b1;
  7. 321      else if(axi_araddr_n== app_addr_rd_min)
  8. 322         raddr_rst_h <= 1'b0;
  9. 323      else
  10. 324         raddr_rst_h <= raddr_rst_h;
  11. 325  end
  12. 326  
  13. 327  //对输出源帧的读地址高位切换
  14. 328  always @(posedge clk or negedge rst_n)  begin
  15. 329      if(~rst_n)
  16. 330         raddr_page <= 2'b0;
  17. 331      else if(rd_end_r)
  18. 332          raddr_page <= waddr_page + 2;
  19. 333      else
  20. 334         raddr_page <= raddr_page;
  21. 335  end
  22. 336  
  23. 337  //对输入源帧的写地址高位切换
  24. 338  always @(posedge clk or negedge rst_n)  begin
  25. 339      if(~rst_n)begin
  26. 340         waddr_page <= 2'b1;
  27. 341         fram_done<= 1'b0;
  28. 342      end
  29. 343      else if(wr_end_r)begin
  30. 344         fram_done<= 1'b1;
  31. 345         waddr_page <= waddr_page + 1 ;
  32. 346      end
  33. 347      else
  34. 348         waddr_page <= waddr_page;
  35. 349  end
  36. 350
复制代码
第328行至349行代码对输入/输出源帧的读/写地址高位切换,即在ddr3中划分了四个大小相同的存储空间。读写模块修改后的帧切换原理如下所示:
image020.png
图 39.4.3 写比读快的示意图

image022.png
图 39.4.4 读比写快的示意图
在图 39.4.3中的图2是写操作完成了一帧的存储,换了一个存储空间继续写,而读操作还没有读完一帧数据,继续读原来的存储空间;因为读帧率和写帧率的帧率比小于2倍,所以写操作端的第二帧还没有写完,读操作端已经读完了第一帧,换了一个存储空间继续读,如图3所示;图4中写操作端完成了第二帧的存储,而读操作还没有读完第二帧数据;同理图5中写操作端完成了第三帧的存储,开始第四帧的写入,而读操作将要读完第二帧数据;图6表示读操作读完第二帧数据,而写操作端没有完成第四帧的存储,这里将存储空间跳转了2个,就是为了防止写操作追上读操作,出现画面撕裂的现象。
在图 39.4.4中图2是读操作读出了一帧的数据,而本次实验是根据写存储空间来判断读存储空间的,所以继续读之前的存储空间;图3是写操作端完成了第一帧的存储,而读操作还没有读完第二帧数据;图4是读操作端读完第二帧数据,而写操作还没有完成了第二帧的存储。
  1. 351  //DDR3读写逻辑实现
  2. 352  always @(posedge clk or negedge rst_n) begin
  3. 353      if(~rst_n)
  4. 354         state_cnt    <= IDLE;
  5. 355      else begin
  6. 356          case(state_cnt)
  7. 357             IDLE:begin
  8. 358                 if(init_start)
  9. 359                      state_cnt <= DDR3_DONE ;
  10. 360                 else
  11. 361                      state_cnt <= IDLE;
  12. 362             end
  13. 363             DDR3_DONE:begin
  14. 364                 //当帧复位到来时,对寄存器进行复位
  15. 365                 if(wr_rst)
  16. 366                      state_cnt <= DDR3_DONE;
  17. 367                 //当读到结束地址对寄存器复位
  18. 368                 else if(wfifo_rcount>= wr_bust_len)
  19. 369                      state_cnt <= WRITE_ADDR;   //跳到写操作
  20. 370                 //当帧复位到来时,对寄存器进行复位
  21. 371                 else if(raddr_rst_h)
  22. 372                      state_cnt <= DDR3_DONE;
  23. 373                 //当rfifo存储数据少于一次突发长度时,并且ddr已经写入了1帧数据
  24. 374                  else if(rfifo_wcount< rd_bust_len && ddr3_read_valid &&fram_done )
  25. 375                      state_cnt <= READ_ADDR;   //跳到读操作
  26. 376                 else
  27. 377                      state_cnt <= state_cnt;
  28. 378             end
  29. 379             WRITE_ADDR:begin
  30. 380                 if(axi_awvalid&& axi_awready)
  31. 381                      state_cnt <= WRITE_DATA;  //跳到写数据操作
  32. 382                 else
  33. 383                      state_cnt <= state_cnt;   //条件不满足,保持当前值
  34. 384             end
  35. 385             WRITE_DATA: begin
  36. 386                 //写到设定的长度跳到等待状态
  37. 387                 if(axi_wvalid&& axi_wready &&init_addr == wr_bust_len - 1)
  38. 388                      state_cnt <= DDR3_DONE;  //写到设定的长度跳到等待状态
  39. 389                 else
  40. 390                      state_cnt <= state_cnt;  //写条件不满足,保持当前值
  41. 391             end
  42. 392             READ_ADDR:begin
  43. 393                 if(axi_arvalid&& axi_arready)
  44. 394                      state_cnt <= READ_DATA;
  45. 395                  else
  46. 396                      state_cnt <= state_cnt;
  47. 397             end
  48. 398             READ_DATA:begin                   //读到设定的地址长度
  49. 399                 if(axi_rlast)
  50. 400                      state_cnt <= DDR3_DONE;   //则跳到空闲状态
  51. 401                 else                          //若MIG没准备好,则保持原值
  52. 402                      state_cnt   <= state_cnt;//则跳到空闲状态
  53. 403             end
  54. 404             default:begin
  55. 405                 state_cnt    <= IDLE;
  56. 406             end
  57. 407          endcase
  58. 408      end
  59. 409  end
  60. 410  
  61. 411  endmodule
复制代码
DDR3读写逻辑实现部分和“DDR3读写测试实验”一致,唯一的区别在于第374行代码对读操作的开启多了一个判定条件,即ddr已经写入了1帧图像数据后才会开启。
ddr3控制器fifo控制模块添加代码如下所示:
  1. 1   module ddr3_fifo_ctrl(
  2. 2        input          rst_n            ,  //复位信号
  3. 3        input          wr_clk           ,  //wfifo时钟
  4. 4        input          rd_clk           ,  //rfifo时钟
  5. 5        input          clk_100          ,  //用户时钟
  6. 6        input          datain_valid     ,  //数据有效使能信号
  7. 7        input  [15:0   datain           ,  //有效数据
  8. 8        input  [127:0 rfifo_din        ,  //用户读数据
  9. 9        input          rdata_req        ,  //请求像素点颜色数据输入
  10. 10       input          rfifo_wren       ,  //从ddr3读出数据的有效使能
  11. 11       input          wfifo_rden       ,  //wfifo读使能
  12. 12       input          rd_load          ,  //输出源场信号
  13. 13       input          wr_load          ,  //输入源场信号
  14. 14  
  15. 15       output [127:0 wfifo_dout       ,  //用户写数据
  16. 16       output [10:0  wfifo_rcount     ,  //rfifo剩余数据计数
  17. 17       output [10:0  rfifo_wcount     ,  //wfifo写进数据计数
  18. 18       output [15:0  pic_data            //有效数据
  19. 19       );
  20. 20  
  21. 21  //reg define
  22. 22  reg          rd_load_d0        ;
  23. 23  reg  [15:0 rd_load_d         ;  //由输出源场信号移位拼接得到
  24. 24  reg          rdfifo_rst_h      ;  //rfifo复位信号,高有效
  25. 25  reg          wr_load_d0        ;
  26. 26  reg  [15:0 wr_load_d         ;  //由输入源场信号移位拼接得到
  27. 27  reg          wfifo_rst_h       ;  //wfifo复位信号,高有效
  28. 28  
  29. 29  //*****************************************************
  30. 30  //**                    main code
  31. 31  //*****************************************************
  32. 32  
  33. 33  //对输出源场信号取反
  34. 34  always @(posedge clk_100 or negedge rst_n) begin
  35. 35       if(!rst_n)
  36. 36           rd_load_d0 <= 1'b0;
  37. 37       else
  38. 38           rd_load_d0 <= rd_load;
  39. 39  end
  40. 40  
  41. 41  //对输出源场信号进行移位寄存
  42. 42  always @(posedge clk_100 or negedge rst_n) begin
  43. 43       if(!rst_n)
  44. 44           rd_load_d <= 1'b0;
  45. 45       else
  46. 46           rd_load_d <= {rd_load_d[14:0],rd_load_d0};
  47. 47  end
  48. 48  
  49. 49  //产生一段复位电平,满足fifo复位时序
  50. 50  always @(posedge clk_100 or negedge rst_n) begin
  51. 51       if(!rst_n)
  52. 52           rdfifo_rst_h <= 1'b0;
  53. 53       else if(rd_load_d[0 && !rd_load_d[14])
  54. 54           rdfifo_rst_h <= 1'b1;
  55. 55       else
  56. 56           rdfifo_rst_h <= 1'b0;
  57. 57  end  
  58. 58  
  59. 59  //对输入源场信号进行移位寄存
  60. 60   always @(posedge wr_clk or negedge rst_n) begin
  61. 61       if(!rst_n)begin
  62. 62           wr_load_d0 <= 1'b0;
  63. 63           wr_load_d  <= 16'b0;
  64. 64       end     
  65. 65       else begin
  66. 66           wr_load_d0 <= wr_load;
  67. 67           wr_load_d <= {wr_load_d[14:0],wr_load_d0};
  68. 68       end
  69. 69  end
  70. 70  
  71. 71  //产生一段复位电平,满足fifo复位时序
  72. 72   always @(posedge wr_clk or negedge rst_n) begin
  73. 73       if(!rst_n)
  74. 74         wfifo_rst_h <= 1'b0;
  75. 75       else if(wr_load_d[0 && !wr_load_d[15])
  76. 76         wfifo_rst_h <= 1'b1;
  77. 77       else
  78. 78         wfifo_rst_h <= 1'b0;
  79. 79  end
  80. 80  
  81. 81  rd_fifo u_rd_fifo (
  82. 82     .wr_clk         (clk_100           ), // input
  83. 83     .wr_rst         (~rst_n|rdfifo_rst_h), // input
  84. 84     .wr_en          (rfifo_wren        ), // input
  85. 85     .wr_data        (rfifo_din         ), // input [127:0]
  86. 86     .wr_full        (                  ), // output
  87. 87     .wr_water_level (rfifo_wcount       ), // output [12:0]
  88. 88     .almost_full    (                  ), // output
  89. 89     .rd_clk         (rd_clk            ), // input
  90. 90     .rd_rst         (~rst_n|rdfifo_rst_h), // input
  91. 91     .rd_en          (rdata_req         ), // input
  92. 92     .rd_data        (pic_data          ), // output [15:0]
  93. 93     .rd_empty       (                  ), // output
  94. 94     .rd_water_level (                   ), // output [12:0]
  95. 95     .almost_empty   (                  )  //output
  96. 96  );
  97. 97  
  98. 98  wr_fifo u_wr_fifo (
  99. 99     .wr_clk         (wr_clk           ), // input
  100. 100   .wr_rst         (~rst_n|wfifo_rst_h), // input
  101. 101   .wr_en          (datain_valid      ), // input
  102. 102   .wr_data        (datain            ), // input[15:0]
  103. 103   .wr_full        (                  ), //output
  104. 104   .wr_water_level (                 ), // output [12:0]
  105. 105   .almost_full    (                  ), //output
  106. 106   .rd_clk         (clk_100           ), // input
  107. 107   .rd_rst         (~rst_n|wfifo_rst_h), // input
  108. 108   .rd_en          (wfifo_rden        ), // input
  109. 109   .rd_data        (wfifo_dout        ), //output [127:0]
  110. 110   .rd_empty       (                  ), //output
  111. 111   .rd_water_level (wfifo_rcount     ), // output [12:0]
  112. 112   .almost_empty   (                  )  // output
  113. 113 );
  114. 114
  115. 115 endmodule
复制代码
在“ddr3读写测试实验”的ddr3控制器fifo控制模块的基础上,该模块添加了load信号,通过load信号来产生一段复位电平,以此来满足fifo复位时序。
LCD顶层模块例化了四个模块,分别为时钟分频模块(clk_div)、读LCD ID模块(rd_id)、lcd驱动模块(lcd_driver)和lcd显示模块(lcd_display)。
lcd驱动模块负责驱动RGB-LCD显示屏,当摄像头的分辨率大于LCD屏的分辨率时,LCD驱动模块也负责通过发出内部信号data_req(数据请求信号)输出至端口来读取DDR3读写控制模块的输出像素数据,有关LCD驱动模块的详细介绍请大家参考“RGB-LCD彩条显示实验”章节。
当摄像头的分辨率小于LCD屏的分辨率时,lcd显示模块负责通过发出内部信号data_req(数据请求信号)输出至端口来读取DDR3读写控制模块的输出像素数据,以此完成液晶屏两侧填充黑色背景,中间区域显示图像数据的功能。
时钟分频模块负责分频出对应器件ID的采样时钟。读LCD ID模块负责采集外部LCD屏的设备型号。有关LCD ID模块和时钟分频模块的详细介绍请大家参考“RGB-LCD彩条显示实验”章节。
LCD顶层模块的代码如下:
  1. 1   module lcd_rgb_top(
  2. 2        input          sys_clk      ,  //系统时钟
  3. 3        input          clk_100m     ,
  4. 4        input          sys_rst_n    ,  //复位信号
  5. 5        input          sys_init_done,
  6. 6        //lcd接口
  7. 7        output         lcd_clk      ,  //LCD驱动时钟
  8. 8        output         lcd_hs       ,  //LCD 行同步信号
  9. 9        output         lcd_vs       ,  //LCD 场同步信号
  10. 10       output         lcd_de       ,  //LCD 数据输入使能
  11. 11       inout  [23:0   lcd_rgb      ,  //LCD RGB颜色数据
  12. 12      output         lcd_bl       ,  //LCD 背光控制信号
  13. 13       output         lcd_rst      ,  //LCD 复位信号
  14. 14       output         lcd_pclk     ,  //LCD 采样时钟
  15. 15       output  [15:0  lcd_id       ,  //LCD屏ID
  16. 16       output  [10:0 pixel_xpos   ,  //像素点横坐标
  17. 17       output  [10:0 pixel_ypos   ,  //像素点纵坐标
  18. 18       output         out_vsync    ,  //帧复位,高有效
  19. 19       output  [10:0  h_disp       ,  //LCD屏水平分辨率
  20. 20       output  [10:0  v_disp       ,  //LCD屏垂直分辨率
  21. 21       input   [15:0  data_in      ,  //数据输入
  22. 22       output         data_req        //请求数据输入
  23. 23      
  24. 24       );
  25. 25  
  26. 26  //wire define
  27. 27  wire [15:0 lcd_data_w    ;  //像素点数据
  28. 28  wire         data_req_w    ;  //请求像素点颜色数据输入
  29. 29  wire [10:0 pixel_xpos    ;  //像素点横坐标
  30. 30  wire [10:0 pixel_ypos    ;  //像素点纵坐标
  31. 31  wire         out_vsync     ;  //帧复位,高有效
  32. 32  wire [10:0  h_disp        ;  //LCD屏水平分辨率
  33. 33  wire [10:0  v_disp        ;  //LCD屏垂直分辨率
  34. 34  wire         data_req_big  ;  //大于640x480分辨率lcd屏的请求信号
  35. 35  wire         data_req_small;  //小于640x480分辨率lcd屏的请求信号
  36. 36  wire [15:0  lcd_data      ;  //选择屏后的数据
  37. 37  wire  [15:0 lcd_rgb_565   ;  //输出的16位lcd数据
  38. 38  wire  [23:0 lcd_rgb_o     ;  //LCD 输出颜色数据
  39. 39  wire  [23:0 lcd_rgb_i     ;  //LCD 输入颜色数据
  40. 40  
  41. 41  //*****************************************************
  42. 42  //**                    main code
  43. 43  //*****************************************************
  44. 44  
  45. 45  //区分大小屏的读请求
  46. 46  assign data_req = (lcd_id == 16'h4342) ? data_req_small : data_req_big;
  47. 47  
  48. 48  //区分大小屏的数据
  49. 49  assign lcd_data = (lcd_id == 16'h4342) ? data_in : lcd_data_w ;
  50. 50  
  51. 51  //将摄像头16bit数据转换为24bit的lcd数据
  52. 52  assign lcd_rgb_o = {lcd_rgb_565[15:11],3'b000,lcd_rgb_565[10:5],2'b00,
  53. 53                       lcd_rgb_565[4:0],3'b000};
  54. 54  
  55. 55  //像素数据方向切换
  56. 56  assign lcd_rgb = lcd_de ?  lcd_rgb_o :  {24{1'bz}};
  57. 57  assign lcd_rgb_i = lcd_rgb;
  58. 58  
  59. 59  //时钟分频模块
  60. 60  clk_div u_clk_div(
  61. 61       .clk                    (clk_100m ),
  62. 62       .rst_n                  (sys_rst_n),
  63. 63       .lcd_id                 (lcd_id   ),
  64. 64       .lcd_pclk               (lcd_clk  )
  65. 65       );
  66. 66  
  67. 67  //读LCD ID模块
  68. 68  rd_id u_rd_id(
  69. 69       .clk                    (sys_clk  ),
  70. 70       .rst_n                  (sys_rst_n),
  71. 71       .lcd_rgb                (lcd_rgb_i),
  72. 72       .lcd_id                 (lcd_id   )
  73. 73       );
  74. 74  
  75. 75  //lcd驱动模块
  76. 76  lcd_driver u_lcd_driver(
  77. 77       .lcd_clk        (lcd_clk                  ),
  78. 78       .sys_rst_n      (sys_rst_n & sys_init_done),
  79. 79       .lcd_id         (lcd_id                   ),
  80. 80       .lcd_hs         (lcd_hs                   ),
  81. 81       .lcd_vs         (lcd_vs                   ),
  82. 82       .lcd_de         (lcd_de                   ),
  83. 83       .lcd_rgb        (lcd_rgb_565              ),
  84. 84       .lcd_bl         (lcd_bl                   ),
  85. 85       .lcd_rst        (lcd_rst                  ),
  86. 86       .lcd_pclk       (lcd_pclk                 ),
  87. 87       .pixel_data     (lcd_data                 ),
  88. 88       .data_req       (data_req_small           ),
  89. 89       .out_vsync      (out_vsync                ),
  90. 90       .h_disp         (h_disp                   ),
  91. 91       .v_disp         (v_disp                   ),
  92. 92       .pixel_xpos     (pixel_xpos               ),
  93. 93       .pixel_ypos     (pixel_ypos               )
  94. 94       );
  95. 95  
  96. 96  //lcd显示模块
  97. 97  lcd_display u_lcd_display(
  98. 98       .lcd_clk        (lcd_clk                  ),
  99. 99       .sys_rst_n      (sys_rst_n & sys_init_done),
  100. 100      .lcd_id         (lcd_id                   ),
  101. 101      .pixel_xpos     (pixel_xpos               ),
  102. 102      .pixel_ypos     (pixel_ypos               ),
  103. 103      .h_disp         (h_disp                   ),
  104. 104      .v_disp         (v_disp                   ),
  105. 105      .cmos_data      (data_in                  ),
  106. 106      .lcd_data       (lcd_data_w               ),
  107. 107      .data_req       (data_req_big             )
  108. 108      );
  109. 109
  110. 110 endmodule
复制代码
在程序的第46行和第49行,根据器件ID来判断读请求信号的最后输出和输入数据的最后输入。当摄像头分辨率大于屏体分辨率时,读请求信号由LCD驱动模块输出,数据直接输入到LCD驱动模块,反之,读请求信号由LCD显示模块输出,数据进入LCD显示模块进行处理后再进入LCD驱动模块。
在程序的第52行,将摄像头输出的格式RGB565转换为LCD需要输出的格式RGB888,在本次实验中的做法是给各个颜色的低位补零,其中lcd_rgb_565[15:11]表示红色,lcd_rgb_565[10:5]表示绿色,lcd_rgb_565[4:0]表示蓝色。
在程序的第56行至57行,由于lcd_rgb是24位的双向引脚,所以这里对双向引脚的方向做一个切换。当lcd_de信号为高电平时,此时输出的像素数据有效,将lcd_rgb的引脚方向切换成输出,并将LCD驱动模块输出的lcd_rgb_o(像素数据)连接至lcd_rgb引脚;当lcd_de信号为低电平时,此时输出的像素数据无效,将lcd_rgb的引脚方向切换成输入。代码中将高阻状态“Z”赋值给lcd_rgb的引脚,表示此时lcd_rgb的引脚电平由外围电路决定,此时可以读取lcd_rgb的引脚电平,从而获取到LCD屏的ID。
LCD 显示模块的代码如下:
  1. 1  module lcd_display(
  2. 2       input         lcd_clk   ,  //lcd驱动时钟
  3. 3       input         sys_rst_n ,  //复位信号
  4. 4       input   [15:0 lcd_id    ,  //LCD屏ID
  5. 5       input   [10:0 pixel_xpos,  //像素点横坐标
  6. 6       input   [10:0 pixel_ypos,  //像素点纵坐标
  7. 7       input   [15:0 cmos_data ,  //CMOS传感器像素点数据
  8. 8       input   [10:0 h_disp    ,  //LCD屏水平分辨率
  9. 9       input   [10:0 v_disp    ,  //LCD屏垂直分辨率
  10. 10      output  [15:0 lcd_data  ,  //LCD像素点数据
  11. 11      output        data_req     //请求像素点颜色数据输入
  12. 12      );
  13. 13
  14. 14 //parameter define  
  15. 15 parameter  V_CMOS_DISP = 11'd480;            //CMOS分辨率——行
  16. 16 parameter  H_CMOS_DISP = 11'd640;            //CMOS分辨率——列
  17. 17
  18. 18 localparam BLACK  = 16'b00000_000000_00000;  //RGB565 黑色
  19. 19
  20. 20 //reg define
  21. 21 reg             data_val            ;       //数据有效信号
  22. 22
  23. 23 //wire define
  24. 24 wire    [10:0 display_border_pos_l;       //左侧边界的横坐标
  25. 25 wire    [10:0 display_border_pos_r;       //右侧边界的横坐标
  26. 26 wire    [10:0 display_border_pos_t;       //上端边界的纵坐标
  27. 27 wire    [10:0 display_border_pos_b;       //下端边界的纵坐标
  28. 28
  29. 29 //*****************************************************
  30. 30  //**                    main code
  31. 31 //*****************************************************
  32. 32
  33. 33 //左侧边界的横坐标计算
  34. 34 assigndisplay_border_pos_l  = (h_disp - H_CMOS_DISP)/2-1;
  35. 35
  36. 36 //右侧边界的横坐标计算
  37. 37 assign display_border_pos_r = H_CMOS_DISP + (h_disp - H_CMOS_DISP)/2-1;
  38. 38
  39. 39 //上端边界的纵坐标计算
  40. 40 assigndisplay_border_pos_t  = (v_disp - V_CMOS_DISP)/2;
  41. 41
  42. 42 //下端边界的纵坐标计算
  43. 43 assign display_border_pos_b = V_CMOS_DISP + (v_disp - V_CMOS_DISP)/2;
  44. 44
  45. 45 //请求像素点颜色数据输入 范围:79~718,共640个时钟周期
  46. 46 assign data_req = ((pixel_xpos >= display_border_pos_l) &&
  47. 47                    (pixel_xpos < display_border_pos_r) &&
  48. 48                    (pixel_ypos > display_border_pos_t) &&
  49. 49                    (pixel_ypos <= display_border_pos_b)
  50. 50                    ) ? 1'b1 : 1'b0;
  51. 51
  52. 52 //在数据有效范围内,将摄像头采集的数据赋值给LCD像素点数据
  53. 53 assign lcd_data = data_val ? cmos_data : BLACK;
  54. 54
  55. 55 //有效数据滞后于请求信号一个时钟周期,所以数据有效信号在此延时一拍
  56. 56 always @(posedge lcd_clk or negedge sys_rst_n) begin
  57. 57      if(!sys_rst_n)
  58. 58          data_val <= 1'b0;
  59. 59      else
  60. 60          data_val <= data_req;
  61. 61 end
  62. 62
  63. 63 endmodule
复制代码
当LCD屏的分辨率大于摄像头的分辨率时,将摄像头采集的图像放在LCD屏的中央,四周填充黑色,如下图所示。
image024.png
图 39.4.5 LCD屏裁剪图
在程序的第34行至50行,分别对摄像头数据的显示区域的横纵的起始坐标和结束坐标进行计算,上边界坐标(display_border_pos_t)是LCD屏的场分辨率摄像头的场分辨率减去摄像头的场分辨率(480)再除以2得到的,其他的边界也是以类似的计算方法得到。
在程序的第53行,对有效数据以外的区域填充黑色。这里还有一点需要提醒大家注意的是我们读数据请求发送给DDR3控制器后实际上是发送给读FIFO,而读FIFO接收到数据请求到数据出来是需要一个时钟周期的延迟的,也就是说有效数据实际上比读数据请求晚了一个时钟(如果FIFO用的是另外一种数据提前模式则有效数据和读数据请求是同步的,本节实验采用的是普通模式),因此代码的55~61行对读数据请求作了一个打拍处理,生成数据有效使能信号(data_val)。
本次实验的LCD驱动模块是在“RGB-LCD彩条显示实验”中添加了一个场信号输出,本次实验只对改动的地方进行讲解。
LCD驱动模块的代码如下:
  1. 1   module lcd_driver(
  2. 2        input          lcd_clk   ,  //lcd模块驱动时钟
  3. 3        input          sys_rst_n ,  //复位信号
  4. 4        input   [15:0  lcd_id    ,  //LCD屏ID
  5. 5        input   [15:0  pixel_data,  //像素点数据
  6. 6        output         data_req  ,  //请求像素点颜色数据输入
  7. 7        output  [10:0  pixel_xpos,  //像素点横坐标
  8. 8        output  [10:0  pixel_ypos,  //像素点纵坐标
  9. 9        output         out_vsync ,  //帧复位,高有效
  10. 10       output  [10:0  h_disp    ,  //LCD屏水平分辨率
  11. 11       output  [10:0  v_disp    ,  //LCD屏垂直分辨率
  12. 12       //RGB LCD接口
  13. 13       output         lcd_hs    ,  //LCD 行同步信号
  14. 14       output         lcd_vs    ,  //LCD 场同步信号
  15. 15       output         lcd_de    ,  //LCD 数据输入使能
  16. 16       output  [15:0  lcd_rgb   ,  //LCDRGB565颜色数据
  17. 17       output          lcd_bl    ,  //LCD 背光控制信号
  18. 18       output         lcd_rst   ,  //LCD 复位信号
  19. 19       output         lcd_pclk     //LCD 采样时钟
  20. 20       );
复制代码
此处省略一段代码。
  1. 118  //帧复位,高有效
  2. 119  assign out_vsync = ((h_cnt <= 100) && (v_cnt == 1)) ? 1'b1 : 1'b0;
复制代码
此处省略一段代码。
  1. 248  
  2. 249  endmodule
复制代码
在程序的第119行,在LCD屏场消隐的时候增加了一个模拟的场信号,提供给其他模块。到这里整个工程的程序设计就讲完了。

1.5 下载验证
首先将OV7725摄像头插入开发板上的摄像头扩展接口(注意摄像头镜头朝外);将FPC排线一端与正点原子的7寸RGB模块上的J1接口连接,另一端与ATK-DFPGL22G开发板上的RGB_LCD接口连接;如图 39.5.1和图 39.5.2所示。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线。
连接实物图如下图所示:
image025.png
图 39.5.1 ATK-7’ RGBLCD模块FPC连接器

image027.png
图 39.5.2 ATK-DFPGL22G开发板FPC连接器
最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证OV7725摄像头RGB-LCD实时显示功能。下载完成后观察显示器的显示图像如下图所示,说明OV7725摄像头LCD显示程序下载验证成功。
image029.png
图 39.5.3 RGB-LCD实时显示图像
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

86

主题

980

帖子

0

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1840
金钱
1840
注册时间
2013-4-15
在线时间
162 小时
发表于 2024-3-4 10:43:41 | 显示全部楼层
ov7725f配置的摄像头输出的图像是横着的吗?
合肥-文盲
回复 支持 反对

使用道具 举报

3

主题

1895

帖子

0

精华

资深版主

Rank: 8Rank: 8

积分
5285
金钱
5285
注册时间
2018-10-21
在线时间
1463 小时
发表于 2024-3-4 11:23:14 | 显示全部楼层
合肥-文盲 发表于 2024-3-4 10:43
ov7725f配置的摄像头输出的图像是横着的吗?

是的
回复 支持 反对

使用道具 举报

86

主题

980

帖子

0

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1840
金钱
1840
注册时间
2013-4-15
在线时间
162 小时
发表于 2024-3-5 15:28:08 | 显示全部楼层

嗯,我实测也是横着的,软件代码做的90度旋转
合肥-文盲
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-5-14 07:51

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表