搜索
bottom↓
回复: 1

《新起点V2之FPGA开发指南》第三十九章 OV7725摄像头RGB-LCD显示

[复制链接]

出0入234汤圆

发表于 2021-10-14 17:08:12 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2021-10-30 10:45 编辑

1)实验平台:正点原子新起点V2FPGA开发板
2)  章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-328002-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
1.png


2.jpg


3.png


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

         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的功能框图。
第三十九章 OV7725摄像头RGB348.png

图 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的写传输协议如下图所示:
1.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的读传输协议。
2.png

图 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”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。
3 1.png

3 2.png

表 39.1.1 OV7725关键寄存器配置说明


       OV7725的寄存器较多,对于其它寄存器的描述可以参OV7725的数据手册。
       下图为OV7725的一些特性。
第三十九章 OV7725摄像头RGB3469.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上显示,且新起点开发板上的数据接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此我们将OV7725摄像头输出的图像像素数据配置成RGB565格式,然后转换为RGB888格式。下图为摄像头输出的VGA帧模式时序图。
第三十九章 OV7725摄像头RGB4220.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格式的时序图:
第三十九章 OV7725摄像头RGB5540.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实验任务
      本节实验任务是使用新起点开发板及OV7725摄像头实现图像采集,并通过RGB-LCD接口驱动RGB-LCD液晶屏(支持目前正点原子推出的所有RGB-LCD屏),并实时显示出图像。
1.3硬件设计
      新起点FPGA开发板上有一个摄像头扩展接口,该接口可以用来连接OV7725/OV5640等摄像头模块,摄像头扩展接口原理图如图 39.3.1所示:
第三十九章 OV7725摄像头RGB6069.png

图 39.3.1 摄像头扩展接口原理图

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

图 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引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配,如下表所示:
4.png


表 39.3.1 OV7725摄像头管脚分配

       摄像头TCL约束文件如下:
  1. set_location_assignment PIN_M2 -to sys_clk
  2. set_location_assignment PIN_M1 -to sys_rst_n
  3. set_location_assignment PIN_B14 -to sdram_clk
  4. set_location_assignment PIN_G11 -to sdram_ba[0]
  5. set_location_assignment PIN_F13 -to sdram_ba[1]
  6. set_location_assignment PIN_J12 -to sdram_cas_n
  7. set_location_assignment PIN_F16 -to sdram_cke
  8. set_location_assignment PIN_K11 -to sdram_ras_n
  9. set_location_assignment PIN_J13 -to sdram_we_n
  10. set_location_assignment PIN_K10 -to sdram_cs_n
  11. set_location_assignment PIN_J14 -to sdram_dqm[0]
  12. set_location_assignment PIN_G15 -to sdram_dqm[1]
  13. set_location_assignment PIN_F11 -to sdram_addr[0]
  14. set_location_assignment PIN_E11 -to sdram_addr[1]
  15. set_location_assignment PIN_D14 -to sdram_addr[2]
  16. set_location_assignment PIN_C14 -to sdram_addr[3]
  17. set_location_assignment PIN_A14 -to sdram_addr[4]
  18. set_location_assignment PIN_A15 -to sdram_addr[5]
  19. set_location_assignment PIN_B16 -to sdram_addr[6]
  20. set_location_assignment PIN_C15 -to sdram_addr[7]
  21. set_location_assignment PIN_C16 -to sdram_addr[8]
  22. set_location_assignment PIN_D15 -to sdram_addr[9]
  23. set_location_assignment PIN_F14 -to sdram_addr[10]
  24. set_location_assignment PIN_D16 -to sdram_addr[11]
  25. set_location_assignment PIN_F15 -to sdram_addr[12]
  26. set_location_assignment PIN_P14 -to sdram_data[0]
  27. set_location_assignment PIN_M12 -to sdram_data[1]
  28. set_location_assignment PIN_N14 -to sdram_data[2]
  29. set_location_assignment PIN_L12 -to sdram_data[3]
  30. set_location_assignment PIN_L13 -to sdram_data[4]
  31. set_location_assignment PIN_L14 -to sdram_data[5]
  32. set_location_assignment PIN_L11 -to sdram_data[6]
  33. set_location_assignment PIN_K12 -to sdram_data[7]
  34. set_location_assignment PIN_G16 -to sdram_data[8]
  35. set_location_assignment PIN_J11 -to sdram_data[9]
  36. set_location_assignment PIN_J16 -to sdram_data[10]
  37. set_location_assignment PIN_J15 -to sdram_data[11]
  38. set_location_assignment PIN_K16 -to sdram_data[12]
  39. set_location_assignment PIN_K15 -to sdram_data[13]
  40. set_location_assignment PIN_L16 -to sdram_data[14]
  41. set_location_assignment PIN_L15 -to sdram_data[15]
  42. set_location_assignment PIN_R1 -to lcd_bl
  43. set_location_assignment PIN_T2 -to lcd_de
  44. set_location_assignment PIN_T3 -to lcd_hs
  45. set_location_assignment PIN_P3 -to lcd_vs
  46. set_location_assignment PIN_R3 -to lcd_pclk
  47. set_location_assignment PIN_L1 -to lcd_rst
  48. set_location_assignment PIN_T4 -to lcd_rgb[4]
  49. set_location_assignment PIN_R4 -to lcd_rgb[3]
  50. set_location_assignment PIN_T5 -to lcd_rgb[2]
  51. set_location_assignment PIN_R5 -to lcd_rgb[1]
  52. set_location_assignment PIN_T6 -to lcd_rgb[0]
  53. set_location_assignment PIN_R6 -to lcd_rgb[10]
  54. set_location_assignment PIN_T7 -to lcd_rgb[9]
  55. set_location_assignment PIN_R7 -to lcd_rgb[8]
  56. set_location_assignment PIN_T8 -to lcd_rgb[7]
  57. set_location_assignment PIN_R8 -to lcd_rgb[6]
  58. set_location_assignment PIN_T9 -to lcd_rgb[5]
  59. set_location_assignment PIN_R9 -to lcd_rgb[15]
  60. set_location_assignment PIN_T10 -to lcd_rgb[14]
  61. set_location_assignment PIN_R10 -to lcd_rgb[13]
  62. set_location_assignment PIN_T11 -to lcd_rgb[12]
  63. set_location_assignment PIN_R11 -to lcd_rgb[11]
  64. set_location_assignment PIN_T14 -to cam_data[7]
  65. set_location_assignment PIN_R14 -to cam_data[6]
  66. set_location_assignment PIN_N6 -to cam_data[5]
  67. set_location_assignment PIN_P6 -to cam_data[4]
  68. set_location_assignment PIN_M8 -to cam_data[3]
  69. set_location_assignment PIN_N8 -to cam_data[2]
  70. set_location_assignment PIN_P8 -to cam_data[1]
  71. set_location_assignment PIN_K9 -to cam_data[0]
  72. set_location_assignment PIN_M9 -to cam_href
  73. set_location_assignment PIN_R13 -to cam_pclk
  74. set_location_assignment PIN_L9 -to cam_rst_n
  75. set_location_assignment PIN_N9 -to cam_scl
  76. set_location_assignment PIN_L10 -to cam_sda
  77. set_location_assignment PIN_P9 -to cam_vsync
  78. set_location_assignment PIN_R12 -to cam_sgm_ctrl
复制代码

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,我们新起点FPGA开发板芯片型号为EP4CE10F17C8,器件手册可以发现,EP4CE10F17C8的片内存储资源为414Kbit,远不能达到存储要求。因此我们只能使用板载的外部存储器SDRAM来缓存图像数据,新起点板载的SDRAM容量为256Mbit,最大带宽为2.66Gbit/S(16bit*166M),足以满足缓存图像数据的需求。
       OV7725在正常工作之前必须通过配置寄存器进行初始化,而配置寄存器的SCCB协议和I2C协议在写操作时几乎一样,所以我们需要一个I2C驱动模块。为了使OV7725在期望的模式下运行并且提高图像显示效果,需要配置较多的寄存器,这么多寄存器的地址与参数需要单独放在一个模块,因此还需要一个寄存配置信息的I2C配置模块。在摄像头配置完成后,开始输出图像数据,因此需要一个摄像头图像采集模块来采集图像;外接SDRAM存储器当然离不开SDRAM控制器模块的支持,最后LCD顶层模块读取SDRAM缓存的数据以达到最终实时显示的效果。
5.png

图 39.4.1 程序结构框图

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

图 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. 1   module ov7725_rgb565_lcd(   
  2. 2       input                 sys_clk     ,  //系统时钟
  3. 3       input                 sys_rst_n   ,  //系统复位,低电平有效
  4. 4       //摄像头接口
  5. 5       input                 cam_pclk    ,  //cmos 数据像素时钟
  6. 6       input                 cam_vsync   ,  //cmos 场同步信号
  7. 7       input                 cam_href    ,  //cmos 行同步信号
  8. 8       input        [7:0]    cam_data    ,  //cmos 数据
  9. 9       output                cam_rst_n   ,  //cmos 复位信号,低电平有效
  10. 10      output                cam_sgm_ctrl,  //cmos 时钟选择信号, 1:使用摄像头自带的晶振
  11. 11      output                cam_scl     ,  //cmos SCCB_SCL线
  12. 12      inout                 cam_sda     ,  //cmos SCCB_SDA线
  13. 13      //SDRAM接口
  14. 14      output                sdram_clk   ,  //SDRAM 时钟
  15. 15      output                sdram_cke   ,  //SDRAM 时钟有效
  16. 16      output                sdram_cs_n  ,  //SDRAM 片选
  17. 17      output                sdram_ras_n ,  //SDRAM 行有效
  18. 18      output                sdram_cas_n ,  //SDRAM 列有效
  19. 19      output                sdram_we_n  ,  //SDRAM 写有效
  20. 20      output       [1:0]    sdram_ba    ,  //SDRAM Bank地址
  21. 21      output       [1:0]    sdram_dqm   ,  //SDRAM 数据掩码
  22. 22      output       [12:0]   sdram_addr  ,  //SDRAM 地址
  23. 23      inout        [15:0]   sdram_data  ,  //SDRAM 数据   
  24. 24      //lcd接口                          
  25. 25      output                lcd_hs      ,  //LCD 行同步信号
  26. 26      output                lcd_vs      ,  //LCD 场同步信号
  27. 27      output                lcd_de      ,  //LCD 数据输入使能
  28. 28      inout        [15:0]   lcd_rgb     ,  //LCD RGB565颜色数据
  29. 29      output                lcd_bl      ,  //LCD 背光控制信号
  30. 30      output                lcd_rst     ,  //LCD 复位信号
  31. 31      output                lcd_pclk       //LCD 采样时钟
  32. 32      );
  33. 33  
  34. 34  //parameter define
  35. 35  parameter  SLAVE_ADDR = 7'h21         ;  //OV7725的器件地址7'h21
  36. 36  parameter  BIT_CTRL   = 1'b0          ;  //OV7725的字节地址为8位  0:8位 1:16位
  37. 37  parameter  CLK_FREQ   = 26'd50_000_000;  //i2c_dri模块的驱动时钟频率 33.3MHz
  38. 38  parameter  I2C_FREQ   = 18'd250_000   ;  //I2C的SCL时钟频率,不超过400KHz
  39. 39  
  40. 40  //wire define
  41. 41  wire                  clk_100m        ;  //100mhz时钟,SDRAM操作时钟
  42. 42  wire                  clk_100m_shift  ;  //100mhz时钟,SDRAM相位偏移时钟
  43. 43  wire                  clk_50m         ;  //50mhz时钟,提供给lcd驱动时钟
  44. 44  wire                  locked          ;
  45. 45  wire                  rst_n           ;
  46. 46                                      
  47. 47  wire                  i2c_exec        ;  //I2C触发执行信号
  48. 48  wire   [15:0]         i2c_data        ;  //I2C要配置的地址与数据(高8位地址,低8位数据)         
  49. 49  wire                  cam_init_done   ;  //摄像头初始化完成
  50. 50  wire                  i2c_done        ;  //I2C寄存器配置完成信号
  51. 51  wire                  i2c_dri_clk     ;  //I2C操作时钟                                 
  52. 52  wire                  wr_en           ;  //sdram_ctrl模块写使能
  53. 53  wire   [15:0]         wr_data         ;  //sdram_ctrl模块写数据
  54. 54  wire                  rd_en           ;  //sdram_ctrl模块读使能
  55. 55  wire                  sdram_init_done ;  //SDRAM初始化完成
  56. 56  wire                  rdata_req       ;  //SDRAM控制器模块读使能
  57. 57  wire   [15:0]         rd_data             ;  //SDRAM控制器模块读数据
  58. 58  wire                  cmos_frame_valid    ;  //数据有效使能信号
  59. 59  wire                  init_calib_complete ;  //SDRAM初始化完成init_calib_complete
  60. 60  wire                  sys_init_done       ;  //系统初始化完成(SDRAM初始化+摄像头初始化)
  61. 61  wire                  clk_200m            ;  //SDRAM参考时钟
  62. 62  wire                  cmos_frame_vsync    ;  //输出帧有效场同步信号   
  63. 63  wire                  cmos_frame_href     ;  //输出帧有效行同步信号
  64. 64  wire    [7:0]         wr_bust_len         ;  //从SDRAM中读数据时的突发长度
  65. 65  wire    [9:0]         pixel_xpos_w        ;  //像素点横坐标
  66. 66  wire    [9:0]         pixel_ypos_w        ;  //像素点纵坐标   
  67. 67  wire                  lcd_clk             ;  //分频产生的LCD 采样时钟
  68. 68  wire    [10:0]        h_disp              ;  //LCD屏水平分辨率
  69. 69  wire    [10:0]        v_disp              ;  //LCD屏垂直分辨率     
  70. 70  wire    [10:0]        h_pixel             ;  //存入SDRAM的水平分辨率        
  71. 71  wire    [10:0]        v_pixel             ;  //存入SDRAM的屏垂直分辨率
  72. 72  wire    [15:0]        lcd_id              ;  //LCD屏的ID号
  73. 73  wire    [27:0]        sdram_addr_max      ;  //存入SDRAM的最大读写地址
  74. 74  
  75. 75  //*****************************************************
  76. 76  //**                    main code
  77. 77  //*****************************************************
  78. 78  
  79. 79  assign  rst_n = sys_rst_n & locked;
  80. 80  //系统初始化完成:SDRAM和摄像头都初始化完成
  81. 81  //避免了在SDRAM初始化过程中向里面写入数据
  82. 82  assign  sys_init_done = sdram_init_done & cam_init_done;
  83. 83  //不对摄像头硬件复位,固定高电平
  84. 84  assign  cam_rst_n = 1'b1;
  85. 85  //cmos 时钟选择信号, 1:使用摄像头自带的晶振
  86. 86  assign  cam_sgm_ctrl = 1'b1;
  87. 87  
  88. 88  //锁相环
  89. 89  pll_clk u_pll_clk(
  90. 90      .areset       (~sys_rst_n),
  91. 91      .inclk0       (sys_clk),
  92. 92      .c0           (clk_100m),
  93. 93      .c1           (clk_100m_shift),
  94. 94      .c2           (clk_50m),
  95. 95      .locked       (locked)
  96. 96      );
  97. 97  
  98. 98  //I2C配置模块   
  99. 99  i2c_ov7725_rgb565_cfg u_i2c_cfg(
  100. 100     .clk           (i2c_dri_clk),
  101. 101     .rst_n         (rst_n),
  102. 102     .i2c_done      (i2c_done),
  103. 103     .i2c_exec      (i2c_exec),
  104. 104     .i2c_data      (i2c_data),
  105. 105     .init_done     (cam_init_done)
  106. 106     );   
  107. 107
  108. 108 //I2C驱动模块
  109. 109 i2c_dri
  110. 110 #(
  111. 111     .SLAVE_ADDR  (SLAVE_ADDR),               //参数传递
  112. 112     .CLK_FREQ    (CLK_FREQ  ),              
  113. 113     .I2C_FREQ    (I2C_FREQ  )               
  114. 114     )
  115. 115 u_i2c_dri(
  116. 116     .clk         (clk_50m   ),   
  117. 117     .rst_n       (rst_n     ),   
  118. 118     //i2c interface
  119. 119     .i2c_exec    (i2c_exec  ),   
  120. 120     .bit_ctrl    (BIT_CTRL  ),   
  121. 121     .i2c_rh_wl   (1'b0),                     //固定为0,只用到了IIC驱动的写操作   
  122. 122     .i2c_addr    (i2c_data[15:8]),   
  123. 123     .i2c_data_w  (i2c_data[7:0]),   
  124. 124     .i2c_data_r  (),   
  125. 125     .i2c_done    (i2c_done  ),   
  126. 126     .scl         (cam_scl   ),   
  127. 127     .sda         (cam_sda   ),   
  128. 128     //user interface
  129. 129     .dri_clk     (i2c_dri_clk)               //I2C操作时钟
  130. 130 );
  131. 131
  132. 132 //CMOS图像数据采集模块
  133. 133 cmos_data_top u_cmos_data_top(
  134. 134     .rst_n                 (rst_n & sys_init_done), //系统初始化完成之后再开始采集数据
  135. 135     .cam_pclk              (cam_pclk),
  136. 136     .cam_vsync             (cam_vsync),
  137. 137     .cam_href              (cam_href),
  138. 138     .cam_data              (cam_data),
  139. 139     .lcd_id                (lcd_id),  
  140. 140     .h_disp                (h_disp),
  141. 141     .v_disp                (v_disp),  
  142. 142     .h_pixel               (h_pixel),
  143. 143     .v_pixel               (v_pixel),
  144. 144     .sdram_addr_max        (sdram_addr_max),   
  145. 145     .cmos_frame_vsync      (cmos_frame_vsync),
  146. 146     .cmos_frame_href       (cmos_frame_href),
  147. 147     .cmos_frame_valid      (cmos_frame_valid),      //数据有效使能信号
  148. 148     .cmos_frame_data       (wr_data)                //有效数据
  149. 149     );
  150. 150
  151. 151 //SDRAM 控制器顶层模块,封装成FIFO接口
  152. 152 //SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
  153. 153 sdram_top u_sdram_top(
  154. 154 .ref_clk      (clk_100m),                   //sdram 控制器参考时钟
  155. 155 .out_clk      (clk_100m_shift),             //用于输出的相位偏移时钟
  156. 156 .rst_n        (rst_n),                      //系统复位
  157. 157                                             
  158. 158 //用户写端口                                 
  159. 159 .wr_clk       (cam_pclk),                   //写端口FIFO: 写时钟
  160. 160 .wr_en        (cmos_frame_valid),           //写端口FIFO: 写使能
  161. 161 .wr_data      (wr_data),                    //写端口FIFO: 写数据
  162. 162 .wr_min_addr  (24'd0),                      //写SDRAM的起始地址
  163. 163 .wr_max_addr  (sdram_addr_max),             //写SDRAM的结束地址
  164. 164 .wr_len       (10'd512),                    //写SDRAM时的数据突发长度
  165. 165 .wr_load      (~rst_n),                     //写端口复位: 复位写地址,清空写FIFO
  166. 166                                             
  167. 167 //用户读端口                                 
  168. 168 .rd_clk       (lcd_clk),                    //读端口FIFO: 读时钟
  169. 169 .rd_en        (rdata_req),                  //读端口FIFO: 读使能
  170. 170 .rd_data      (rd_data),                    //读端口FIFO: 读数据
  171. 171 .rd_min_addr  (24'd0),                      //读SDRAM的起始地址
  172. 172 .rd_max_addr  (sdram_addr_max),             //读SDRAM的结束地址
  173. 173 .rd_len       (10'd512),                    //从SDRAM中读数据时的突发长度
  174. 174 .rd_load      (~rst_n),                     //读端口复位: 复位读地址,清空读FIFO
  175. 175                                             
  176. 176 //用户控制端口                                
  177. 177 .sdram_read_valid  (1'b1),                  //SDRAM 读使能
  178. 178 .sdram_pingpang_en (1'b1),                  //SDRAM 乒乓操作使能
  179. 179 .sdram_init_done (sdram_init_done),         //SDRAM 初始化完成标志
  180. 180                                             
  181. 181 //SDRAM 芯片接口                                
  182. 182 .sdram_clk    (sdram_clk),                  //SDRAM 芯片时钟
  183. 183 .sdram_cke    (sdram_cke),                  //SDRAM 时钟有效
  184. 184 .sdram_cs_n   (sdram_cs_n),                 //SDRAM 片选
  185. 185 .sdram_ras_n  (sdram_ras_n),                //SDRAM 行有效
  186. 186 .sdram_cas_n  (sdram_cas_n),                //SDRAM 列有效
  187. 187 .sdram_we_n   (sdram_we_n),                 //SDRAM 写有效
  188. 188 .sdram_ba     (sdram_ba),                   //SDRAM Bank地址
  189. 189 .sdram_addr   (sdram_addr),                 //SDRAM 行/列地址
  190. 190 .sdram_data   (sdram_data),                 //SDRAM 数据
  191. 191 .sdram_dqm    (sdram_dqm)                   //SDRAM 数据掩码
  192. 192     );
  193. 193
  194. 194 //LCD驱动显示模块
  195. 195 lcd_rgb_top  u_lcd_rgb_top(
  196. 196     .sys_clk               (clk_50m  ),
  197. 197     .sys_rst_n             (rst_n ),
  198. 198     .sys_init_done         (sys_init_done),     
  199. 199                        
  200. 200     //lcd接口                           
  201. 201     .lcd_id                (lcd_id),                //LCD屏的ID号
  202. 202     .lcd_hs                (lcd_hs),                //LCD 行同步信号
  203. 203     .lcd_vs                (lcd_vs),                //LCD 场同步信号
  204. 204     .lcd_de                (lcd_de),                //LCD 数据输入使能
  205. 205     .lcd_rgb               (lcd_rgb),               //LCD 颜色数据
  206. 206     .lcd_bl                (lcd_bl),                //LCD 背光控制信号
  207. 207     .lcd_rst               (lcd_rst),               //LCD 复位信号
  208. 208     .lcd_pclk              (lcd_pclk),              //LCD 采样时钟
  209. 209     .lcd_clk               (lcd_clk),               //LCD 驱动时钟
  210. 210     //用户接口                     
  211. 211     .out_vsync             (rd_vsync),              //lcd场信号
  212. 212     .h_disp                (h_disp),                //行分辨率  
  213. 213     .v_disp                (v_disp),                //场分辨率
  214. 214     .pixel_xpos            (),
  215. 215     .pixel_ypos            (),   
  216. 216     .data_in               (rd_data),               //rfifo输出数据
  217. 217     .data_req              (rdata_req)              //请求数据输入
  218. 218     );
  219. 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. 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  
  14. 14  //reg define
  15. 15  reg    [9:0]   start_init_cnt;       //等待延时计数器
  16. 16  reg    [6:0]   init_reg_cnt  ;       //寄存器配置个数计数器
  17. 17  
  18. 18  //*****************************************************
  19. 19  //**                    main code
  20. 20  //*****************************************************
  21. 21  
  22. 22  //cam_scl配置成250khz,输入的clk为1Mhz,周期为1us,1023*1us = 1.023ms
  23. 23  //寄存器延时配置
  24. 24  always @(posedge clk or negedge rst_n) begin
  25. 25      if(!rst_n)
  26. 26          start_init_cnt <= 10'b0;   
  27. 27      else if((init_reg_cnt == 7'd1) && i2c_done)
  28. 28          start_init_cnt <= 10'b0;
  29. 29      else if(start_init_cnt < 10'd1023) begin
  30. 30          start_init_cnt <= start_init_cnt + 1'b1;                    
  31. 31      end
  32. 32  end
  33. 33  
  34. 34  //寄存器配置个数计数   
  35. 35  always @(posedge clk or negedge rst_n) begin
  36. 36      if(!rst_n)
  37. 37          init_reg_cnt <= 7'd0;
  38. 38      else if(i2c_exec)   
  39. 39          init_reg_cnt <= init_reg_cnt + 7'b1;
  40. 40  end         
  41. 41  
  42. 42  //i2c触发执行信号   
  43. 43  always @(posedge clk or negedge rst_n) begin
  44. 44      if(!rst_n)
  45. 45          i2c_exec <= 1'b0;
  46. 46      else if(start_init_cnt == 10'd1022)
  47. 47          i2c_exec <= 1'b1;
  48. 48      //只有刚上电和配置第一个寄存器增加延时
  49. 49      else if(i2c_done && (init_reg_cnt != 7'd1) && (init_reg_cnt < REG_NUM))
  50. 50          i2c_exec <= 1'b1;
  51. 51      else
  52. 52          i2c_exec <= 1'b0;   
  53. 53  end
  54. 54  
  55. 55  //初始化完成信号
  56. 56  always @(posedge clk or negedge rst_n) begin
  57. 57      if(!rst_n)
  58. 58          init_done <= 1'b0;
  59. 59      else if((init_reg_cnt == REG_NUM) && i2c_done)  
  60. 60          init_done <= 1'b1;  
  61. 61  end        
  62. 62  
  63. 63  //配置寄存器地址与数据
  64. 64  always @(posedge clk or negedge rst_n) begin
  65. 65      if(!rst_n)
  66. 66          i2c_data <= 16'b0;
  67. 67      else begin
  68. 68          case(init_reg_cnt)
  69. 69              //先对寄存器进行软件复位,使寄存器恢复初始值
  70. 70              //寄存器软件复位后,需要延时1ms才能配置其它寄存器
  71. 71              7'd0  : i2c_data <= {8'h12, 8'h80}; //COM7 BIT[7]:复位所有的寄存器
  72. 72              7'd1  : i2c_data <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿
  73. 73              7'd2  : i2c_data <= {8'h15, 8'h00}; //COM10 href/vsync/pclk/data信号控制
  74. 74              7'd3  : i2c_data <= {8'h17, 8'h23}; //HSTART 水平起始位置
  75. 75              7'd4  : i2c_data <= {8'h18, 8'ha0}; //HSIZE 水平尺寸
  76. 76              7'd5  : i2c_data <= {8'h19, 8'h07}; //VSTRT 垂直起始位置
  77. 77              7'd6  : i2c_data <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸            
  78. 78              7'd7  : i2c_data <= {8'h32, 8'h00}; //HREF 图像开始和尺寸控制,控制低位
  79. 79              7'd8  : i2c_data <= {8'h29, 8'ha0}; //HOutSize 水平输出尺寸
  80. 80              7'd9  : i2c_data <= {8'h2a, 8'h00}; //EXHCH 虚拟像素MSB
  81. 81              7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB
  82. 82              7'd11 : i2c_data <= {8'h2c, 8'hf0}; //VOutSize 垂直输出尺寸
  83. 83              7'd12 : i2c_data <= {8'h0d, 8'h41}; //COM4 PLL倍频设置(multiplier)                                             
  84. 84              7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 内部时钟配置                                                
  85. 85              7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式                                    
  86. 86              7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3 Bit[0]: 0:图像数据 1:彩条测试
  87. 87              //DSP 控制
  88. 88              7'd16 : i2c_data <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值
  89. 89              7'd17 : i2c_data <= {8'h4d, 8'h09}; //FixGain 模拟增益放大器
  90. 90              7'd18 : i2c_data <= {8'h63, 8'hf0}; //AWB_Ctrl0 自动白平衡控制字节0
  91. 91              7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字节1
  92. 92              7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字节2
  93. 93              7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字节3
  94. 94              7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4 DSP控制字节4   
  95. 95              //AGC AEC AWB        
  96. 96              //COM8 Bit[2]:自动增益使能 Bit[1]:自动白平衡使能 Bit[0]:自动曝光功能
  97. 97              7'd23 : i2c_data <= {8'h13, 8'hff}; //COM8
  98. 98              7'd24 : i2c_data <= {8'h0f, 8'hc5}; //COM6
  99. 99              7'd25 : i2c_data <= {8'h14, 8'h11};  
  100. 100             7'd26 : i2c_data <= {8'h22, 8'h98};
  101. 101             7'd27 : i2c_data <= {8'h23, 8'h03};  
  102. 102             7'd28 : i2c_data <= {8'h24, 8'h40};
  103. 103             7'd29 : i2c_data <= {8'h25, 8'h30};  
  104. 104             7'd30: i2c_data <= {8'h26, 8'ha1};      
  105. 105             7'd31: i2c_data <= {8'h6b, 8'haa};
  106. 106             7'd32: i2c_data <= {8'h13, 8'hff};  
  107. 107             //matrix sharpness brightness contrast UV
  108. 108             7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1
  109. 109             //DNSOff 降噪阈值下限,仅在自动模式下有效
  110. 110             7'd34 : i2c_data <= {8'h91, 8'h01}; //DNSOff
  111. 111             7'd35 : i2c_data <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限
  112. 112             7'd36 : i2c_data <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限
  113. 113             7'd37 : i2c_data <= {8'h94, 8'h5f}; //MTX1 矩阵系数1
  114. 114             7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩阵系数2
  115. 115             7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩阵系数3
  116. 116             7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩阵系数4
  117. 117             7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩阵系数5
  118. 118             7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩阵系数6
  119. 119             7'd43 : i2c_data <= {8'h9a, 8'h1e}; //MTX_Ctrl 矩阵控制
  120. 120             7'd44 : i2c_data <= {8'h9b, 8'h3f}; //BRIGHT 亮度
  121. 121             7'd45 : i2c_data <= {8'h9c, 8'h25}; //CNST 对比度            
  122. 122             7'd46 : i2c_data <= {8'h9e, 8'h81};
  123. 123             7'd47 : i2c_data <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制
  124. 124             7'd48 : i2c_data <= {8'ha7, 8'h65}; //USAT "U"饱和增益
  125. 125             7'd49 : i2c_data <= {8'ha8, 8'h65}; //VSAT "V"饱和增益            
  126. 126             7'd50 : i2c_data <= {8'ha9, 8'h80}; //VSAT "V"饱和增益   
  127. 127             7'd51 : i2c_data <= {8'haa, 8'h80}; //VSAT "V"饱和增益
  128. 128             //伽马控制
  129. 129             7'd52 : i2c_data <= {8'h7e, 8'h0c};
  130. 130             7'd53 : i2c_data <= {8'h7f, 8'h16};
  131. 131             7'd54 : i2c_data <= {8'h80, 8'h2a};
  132. 132             7'd55 : i2c_data <= {8'h81, 8'h4e};
  133. 133             7'd56 : i2c_data <= {8'h82, 8'h61};
  134. 134             7'd57 : i2c_data <= {8'h83, 8'h6f};
  135. 135             7'd58 : i2c_data <= {8'h84, 8'h7b};
  136. 136             7'd59 : i2c_data <= {8'h85, 8'h86};   
  137. 137             7'd60 : i2c_data <= {8'h86, 8'h8e};
  138. 138             7'd61 : i2c_data <= {8'h87, 8'h97};
  139. 139             7'd62 : i2c_data <= {8'h88, 8'ha4};
  140. 140             7'd63 : i2c_data <= {8'h89, 8'haf};
  141. 141             7'd64 : i2c_data <= {8'h8a, 8'hc5};
  142. 142             7'd65 : i2c_data <= {8'h8b, 8'hd7};
  143. 143             7'd66 : i2c_data <= {8'h8c, 8'he8};
  144. 144             7'd67 : i2c_data <= {8'h8d, 8'h20};            
  145. 145             7'd68 : i2c_data <= {8'h0e, 8'h65}; //COM5
  146. 146             7'd69 : i2c_data <= {8'h09, 8'h00}; //COM2  Bit[1:0] 输出电流驱动能力
  147. 147             //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
  148. 148             default:i2c_data <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位
  149. 149         endcase
  150. 150     end
  151. 151 end
  152. 152
  153. 153 endmodule
复制代码

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

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

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

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

图 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存储空间的首地址开始读数据,同样对读过程进行计数,并将读取的图像数据分别显示到显示器相应的像素点位置。
       上述的操作保证了在没有行场同步信号下数据不会出现错乱的问题,但是会导致当前读取的图像与上一次存入的图像存在交错,如下图所示:
第三十九章 OV7725摄像头RGB46995.png

图 39.4.5 SDRAM单个BANK缓存图像机制

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

        省略部分源代码……
  1. 156 //sdram写地址产生模块
  2. 157 always @(posedge clk_ref or negedge rst_n) begin
  3. 158     if (!rst_n) begin
  4. 159         sdram_wr_addr <= 24'd0;
  5. 160         sw_bank_en <= 1'b0;
  6. 161         rw_bank_flag <= 1'b0;
  7. 162     end
  8. 163     else if(wr_load_flag) begin              //检测到写端口复位信号时,写地址复位
  9. 164         sdram_wr_addr <= wr_min_addr;   
  10. 165         sw_bank_en <= 1'b0;
  11. 166         rw_bank_flag <= 1'b0;
  12. 167     end
  13. 168     else if(write_done_flag) begin           //若突发写SDRAM结束,更改写地址
  14. 169                                              //若未到达写SDRAM的结束地址,则写地址累加
  15. 170         if(sdram_pingpang_en) begin          //SDRAM 读写乒乓使能
  16. 171             if(sdram_wr_addr[22:0] < wr_max_addr - wr_length)
  17. 172                 sdram_wr_addr <= sdram_wr_addr + wr_length;
  18. 173             else begin                       //切换BANK
  19. 174                 rw_bank_flag <= ~rw_bank_flag;   
  20. 175                 sw_bank_en <= 1'b1;          //拉高切换BANK使能信号
  21. 176             end            
  22. 177         end      
  23. 178                                              //若突发写SDRAM结束,更改写地址
  24. 179         else if(sdram_wr_addr < wr_max_addr - wr_length)
  25. 180             sdram_wr_addr <= sdram_wr_addr + wr_length;
  26. 181         else                                 //到达写SDRAM的结束地址,回到写起始地址
  27. 182             sdram_wr_addr <= wr_min_addr;
  28. 183     end
  29. 184     else if(sw_bank_en) begin                //到达写SDRAM的结束地址,回到写起始地址
  30. 185         sw_bank_en <= 1'b0;
  31. 186         if(rw_bank_flag == 1'b0)             //切换BANK
  32. 187             sdram_wr_addr <= {1'b0,wr_min_addr[22:0]};
  33. 188         else
  34. 189             sdram_wr_addr <= {1'b1,wr_min_addr[22:0]};     
  35. 190     end
  36. 191 end
  37. 192
  38. 193 //sdram读地址产生模块
  39. 194 always @(posedge clk_ref or negedge rst_n) begin
  40. 195     if(!rst_n) begin
  41. 196         sdram_rd_addr <= 24'd0;
  42. 197     end
  43. 198     else if(rd_load_flag)                    //检测到读端口复位信号时,读地址复位
  44. 199         sdram_rd_addr <= rd_min_addr;
  45. 200     else if(read_done_flag) begin            //突发读SDRAM结束,更改读地址
  46. 201                                              //若未到达读SDRAM的结束地址,则读地址累加                 
  47. 202         if(sdram_pingpang_en) begin          //SDRAM 读写乒乓使能  
  48. 203             if(sdram_rd_addr[22:0] < rd_max_addr - rd_length)
  49. 204                 sdram_rd_addr <= sdram_rd_addr + rd_length;
  50. 205             else begin                       //到达读SDRAM的结束地址,回到读起始地址
  51. 206                                              //读取没有在写数据的bank地址
  52. 207                 if(rw_bank_flag == 1'b0)     //根据rw_bank_flag的值切换读BANK地址
  53. 208                     sdram_rd_addr <= {1'b1,rd_min_addr[22:0]};
  54. 209                 else
  55. 210                     sdram_rd_addr <= {1'b0,rd_min_addr[22:0]};   
  56. 211             end   
  57. 212         end
  58. 213                                              //若突发写SDRAM结束,更改写地址
  59. 214         else if(sdram_rd_addr < rd_max_addr - rd_length)  
  60. 215             sdram_rd_addr <= sdram_rd_addr + rd_length;
  61. 216         else                                 //到达写SDRAM的结束地址,回到写起始地址
  62. 217             sdram_rd_addr <= rd_min_addr;
  63. 218     end
  64. 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顶层模块包含以下四个模块,原理图如下:
第三十九章 OV7725摄像头RGB50961.png

图 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. 1   module lcd_rgb_top(
  2. 2       input           sys_clk      ,  //系统时钟
  3. 3       input           sys_rst_n,      //复位信号  
  4. 4       input           sys_init_done,
  5. 5       //lcd接口  
  6. 6       output          lcd_clk,        //LCD驱动时钟   
  7. 7       output          lcd_hs,         //LCD 行同步信号
  8. 8       output          lcd_vs,         //LCD 场同步信号
  9. 9       output          lcd_de,         //LCD 数据输入使能
  10. 10      inout  [15:0]   lcd_rgb,        //LCD RGB颜色数据
  11. 11      output          lcd_bl,         //LCD 背光控制信号
  12. 12      output          lcd_rst,        //LCD 复位信号
  13. 13      output          lcd_pclk,       //LCD 采样时钟
  14. 14      output  [15:0]  lcd_id,         //LCD屏ID  
  15. 15      output          out_vsync,      //lcd场信号
  16. 16      output  [10:0]  pixel_xpos,     //像素点横坐标
  17. 17      output  [10:0]  pixel_ypos,     //像素点纵坐标        
  18. 18      output  [10:0]  h_disp,         //LCD屏水平分辨率
  19. 19      output  [10:0]  v_disp,         //LCD屏垂直分辨率         
  20. 20      input   [15:0]  data_in,        //数据输入
  21. 21      output          data_req        //请求数据输入
  22. 22      );
  23. 23  
  24. 24  //wire define
  25. 25  wire [15:0]  lcd_data_w  ;          //像素点数据
  26. 26  wire         data_req_w  ;          //请求像素点颜色数据输入
  27. 27  wire         data_req_big;          //大于640x480分辨率lcd屏的请求信号
  28. 28  wire         data_req_small;        //小于640x480分辨率lcd屏的请求信号
  29. 29  wire [15:0]  lcd_data;              //选择屏后的数据
  30. 30  wire  [15:0] lcd_rgb_565;           //输出的16位lcd数据
  31. 31  wire  [15:0] lcd_rgb_o ;            //LCD 输出颜色数据
  32. 32  wire  [15:0] lcd_rgb_i ;            //LCD 输入颜色数据
  33. 33  
  34. 34  //*****************************************************
  35. 35  //**                    main code
  36. 36  //*****************************************************
  37. 37  
  38. 38  //区分大小屏的读请求
  39. 39  assign data_req = (lcd_id == 16'h4342) ? data_req_small : data_req_big;   
  40. 40  
  41. 41  //区分大小屏的数据
  42. 42  assign lcd_data = (lcd_id == 16'h4342) ? data_in : lcd_data_w ;  
  43. 43  
  44. 44  //将摄像头16bit数据输出
  45. 45  assign lcd_rgb_o = lcd_rgb_565;         
  46. 46  
  47. 47  //像素数据方向切换
  48. 48  assign lcd_rgb = lcd_de ?  lcd_rgb_o :  {16{1'bz}};
  49. 49  assign lcd_rgb_i = lcd_rgb;  
  50. 50  
  51. 51  //时钟分频模块   
  52. 52  clk_div u_clk_div(
  53. 53      .clk                    (sys_clk  ),
  54. 54      .rst_n                  (sys_rst_n),
  55. 55      .lcd_id                 (lcd_id   ),
  56. 56      .lcd_pclk               (lcd_clk  )
  57. 57      );  
  58. 58  
  59. 59  //读LCD ID模块
  60. 60  rd_id u_rd_id(
  61. 61      .clk                    (sys_clk  ),
  62. 62      .rst_n                  (sys_rst_n),
  63. 63      .lcd_rgb                (lcd_rgb_i),
  64. 64      .lcd_id                 (lcd_id   )
  65. 65      );  
  66. 66  
  67. 67  //lcd驱动模块
  68. 68  lcd_driver u_lcd_driver(           
  69. 69      .lcd_clk        (lcd_clk),   
  70. 70      .sys_rst_n      (sys_rst_n & sys_init_done),
  71. 71      .lcd_id         (lcd_id),   
  72. 72  
  73. 73      .lcd_hs         (lcd_hs),      
  74. 74      .lcd_vs         (lcd_vs),      
  75. 75      .lcd_de         (lcd_de),      
  76. 76      .lcd_rgb        (lcd_rgb_565),
  77. 77      .lcd_bl         (lcd_bl),
  78. 78      .lcd_rst        (lcd_rst),
  79. 79      .lcd_pclk       (lcd_pclk),
  80. 80      
  81. 81      .pixel_data     (lcd_data),
  82. 82      .data_req       (data_req_small),
  83. 83      .out_vsync      (out_vsync),
  84. 84      .h_disp         (h_disp),
  85. 85      .v_disp         (v_disp),
  86. 86      .pixel_xpos     (pixel_xpos),
  87. 87      .pixel_ypos     (pixel_ypos)
  88. 88      );
  89. 89  
  90. 90  //lcd显示模块
  91. 91  lcd_display u_lcd_display(         
  92. 92      .lcd_clk        (lcd_clk),   
  93. 93      .sys_rst_n      (sys_rst_n & sys_init_done),
  94. 94      .lcd_id         (lcd_id),  
  95. 95      
  96. 96      .pixel_xpos     (pixel_xpos),
  97. 97      .pixel_ypos     (pixel_ypos),
  98. 98      .h_disp         (h_disp),
  99. 99      .v_disp         (v_disp),   
  100. 100     .cmos_data      (data_in),
  101. 101     .lcd_data       (lcd_data_w),   
  102. 102     .data_req       (data_req_big)
  103. 103     );   
  104. 104            
  105. 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. 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      
  6. 6       input      [10:0] pixel_xpos,               //像素点横坐标
  7. 7       input      [10:0] pixel_ypos,               //像素点纵坐标   
  8. 8       input      [15:0] cmos_data,                //CMOS传感器像素点数据
  9. 9       input      [10:0] h_disp,                   //LCD屏水平分辨率
  10. 10      input      [10:0] v_disp,                   //LCD屏垂直分辨率   
  11. 11      
  12. 12      output     [15:0] lcd_data,                 //LCD像素点数据
  13. 13      output            data_req                  //请求像素点颜色数据输入
  14. 14      );   
  15. 15  
  16. 16  //parameter define  
  17. 17  parameter  V_CMOS_DISP = 11'd480;                //CMOS分辨率——行
  18. 18  parameter  H_CMOS_DISP = 11'd640;                //CMOS分辨率——列
  19. 19  
  20. 20  localparam BLACK  = 16'b00000_000000_00000;      //RGB565 黑色
  21. 21  
  22. 22  //reg define   
  23. 23  reg             data_val            ;            //数据有效信号
  24. 24  
  25. 25  //wire define
  26. 26  wire    [10:0]  display_border_pos_l;            //左侧边界的横坐标
  27. 27  wire    [10:0]  display_border_pos_r;            //右侧边界的横坐标
  28. 28  wire    [10:0]  display_border_pos_t;            //上端边界的纵坐标
  29. 29  wire    [10:0]  display_border_pos_b;            //下端边界的纵坐标
  30. 30  
  31. 31  //*****************************************************
  32. 32  //**                    main code
  33. 33  //*****************************************************
  34. 34  
  35. 35  //左侧边界的横坐标计算
  36. 36  assign display_border_pos_l  = (h_disp - H_CMOS_DISP)/2-1;
  37. 37  
  38. 38  //右侧边界的横坐标计算
  39. 39  assign display_border_pos_r = H_CMOS_DISP + (h_disp - H_CMOS_DISP)/2-1;
  40. 40  
  41. 41  //上端边界的纵坐标计算
  42. 42  assign display_border_pos_t  = (v_disp - V_CMOS_DISP)/2;
  43. 43  
  44. 44  //下端边界的纵坐标计算
  45. 45  assign display_border_pos_b = V_CMOS_DISP + (v_disp - V_CMOS_DISP)/2;
  46. 46  
  47. 47  //请求像素点颜色数据输入 范围:79~718,共640个时钟周期
  48. 48  assign data_req = ((pixel_xpos >= display_border_pos_l) &&
  49. 49                  (pixel_xpos < display_border_pos_r) &&
  50. 50                  (pixel_ypos > display_border_pos_t) &&
  51. 51                  (pixel_ypos <= display_border_pos_b)
  52. 52                  ) ? 1'b1 : 1'b0;
  53. 53  
  54. 54  //在数据有效范围内,将摄像头采集的数据赋值给LCD像素点数据
  55. 55  assign lcd_data = data_val ? cmos_data : BLACK;
  56. 56  
  57. 57  //有效数据滞后于请求信号一个时钟周期,所以数据有效信号在此延时一拍
  58. 58  always @(posedge lcd_clk or negedge sys_rst_n) begin
  59. 59      if(!sys_rst_n)
  60. 60          data_val <= 1'b0;
  61. 61      else
  62. 62          data_val <= data_req;   
  63. 63  end   
  64. 64  
  65. 65  endmodule
复制代码

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

图 39.4.7 LCD屏裁剪图

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

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

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

第三十九章 OV7725摄像头RGB59964.png

图 39.5.2 新起点开发板 FPC 连接器

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

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



阿莫论坛20周年了!感谢大家的支持与爱护!!

如果想吃一顿饺子,就得从冰箱里取出肉,剁馅儿,倒面粉、揉面、醒面,擀成皮儿,下锅……
一整个繁琐流程,就是为了出锅时那一嘴滚烫流油的热饺子。

如果这个过程,禁不住饿,零食下肚了,饺子出锅时也就不香了……《非诚勿扰3》

出15入70汤圆

发表于 2022-10-21 15:10:44 | 显示全部楼层
来贴一个OV7725产品介绍
https://www.omnivision-group.com ... %AD%90/OV7725-1.pdf
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-28 16:26

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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