OpenEdv-开源电子网

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

[XILINX] 《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十二章 OV5640摄像头RGB-LCD显示实验

[复制链接]

1107

主题

1118

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4615
金钱
4615
注册时间
2019-5-8
在线时间
1218 小时
发表于 2023-4-26 09:41:09 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-4-25 14:49 编辑

第三十二章 OV5640摄像头RGB-LCD显示实验

1)实验平台:正点原子 DFZU2EG/4EV MPSoC开发板

2) 章节摘自【正点原子】DFZU2EG/4EV MPSoC之FPGA开发指南 V1.0


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

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

6)FPGA技术交流QQ群:994244016

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

OV5640是OmniVision(豪威科技)公司生产的CMOS图像传感器,该传感器分辨率高、采集速率快,图像处理性能强,主要应用在手机、数码相机、电脑多媒体等领域。本章将使用FPGA开发板实现对OV5640的数字图像采集并通过LCD实时显示。
本章包括以下几个部分:
32.1简介
32.2实验任务
32.3硬件设计
32.4程序设计
32.5下载验证

32.1 简介
OV5640是一款1/4英寸单芯片图像传感器,其感光阵列达到2592*1944(即500W像素),能实现最快15fps QSXVGA(2592*1944)或者90fps VGA(640*480)分辨率的图像采集。传感器采用OmniVision推出的OmniBSI(背面照度)技术,使传感器达到更高的性能,如高灵敏度、低串扰和低噪声。传感器内部集成了图像处理的功能,包括自动曝光控制(AEC)、自动白平衡(AWB)等。同时该传感器支持LED补光、MIPI(移动产业处理器接口)输出接口和DVP(数字视频并行)输出接口选择、ISP(图像信号处理)以及AFC(自动聚焦控制)等功能。

OV5640的功能框图如下图所示:
image001.png
图 32.1.1 OV5640功能框图

由上图可知,时序发生器(timing generator)控制着感光阵列(image array)、放大器(AMP)、AD转换以及输出外部时序信号(VSYNC、HREF和PCLK),外部时钟XVCLK经过PLL锁相环后输出的时钟作为系统的控制时钟;感光阵列将光信号转化成模拟信号,经过增益放大器之后进入10位AD转换器;AD转换器将模拟信号转化成数字信号,并且经过ISP进行相关图像处理,最终输出所配置格式的10位视频数据流。增益放大器控制以及ISP等都可以通过寄存器(registers)来配置,配置寄存器的接口就是SCCB接口,该接口协议兼容IIC协议。

SCCB(Serial CameraControl Bus,串行摄像头控制总线)是由OV(OmniVision的简称)公司定义和发展的三线式串行总线,该总线控制着摄像头大部分的功能,包括图像数据格式、分辨率以及图像处理参数等。OV公司为了减少传感器引脚的封装,现在SCCB总线大多采用两线式接口总线。

OV5640使用的是两线式接口总线,该接口总线包括SIO_C串行时钟输入线和SIO_D串行双向数据线,分别相当于IIC协议的SCL信号线和SDA信号线。我们在前面提到过SCCB协议兼容IIC协议,是因为SCCB协议和IIC协议非常相似,有关IIC协议的详细介绍请大家参考“EEPROM读写实验”章节。

SCCB的写传输协议如下图所示:
image004.png
图 32.1.2 SCCB写传输协议

上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写 1:读);Sub-address为8位寄存器地址,一般有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;Write Data为8位写数据,每一个寄存器地址对应8位的配置数据。上图中的第9位X表示Don’t Care(不必关心位),该位是由从机(此处指摄像头)发出应答信号来响应主机表示当前ID Address、Sub-address和Write Data是否传输完成,但是从机有可能不发出应答信号,因此主机(此处指FPGA)可不用判断此处是否有应答,直接默认当前传输完成即可。

我们可以发现,SCCB和IIC写传输协议是极为相似的,只是在SCCB写传输协议中,第9位为不必关心位,而IIC写传输协议为应答位。SCCB的读传输协议和IIC有些差异,在IIC读传输协议中,写完寄存器地址后会有restart即重复开始的操作;而SCCB读传输协议中没有重复开始的概念,在写完寄存器地址后,发起总线停止信号,下图为SCCB的读传输协议。
image006.png
图 32.1.3 SCCB读传输协议

由上图可知,SCCB读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据。上图中的NA位由主机(这里指FPGA)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。

需要注意的是,对于OV5640摄像头来说,由于其可配置的寄存器非常多,所以OV5640摄像头的寄存器地址位数是16位(两个字节),OV5640 SCCB的写传输协议如下图所示:
image008.png
图 32.1.4 OV5640 SCCB写传输协议

上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写 1:读),OV5640的器件地址为7’h3c,所以在写传输协议中,ID Address(W)= 8’h78(器件地址左移1位,低位补0);Sub-address(H)为高8位寄存器地址,Sub-address(L)为低8位寄存器地址,在OV5640众多寄存器中,有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;Write Data为8位写数据,每一个寄存器地址对应8位的配置数据。

在OV5640正常工作之前,必须先对传感器进行初始化,即通过配置寄存器使其工作在预期的工作模式,以及得到较好画质的图像。因为SCCB的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。OV公司提供了OV5640的软件应用手册(OV5640 Software Application Note,位于开发板所随附的资料“7_硬件资料/4_OV5640资料/OV5640_camera_module_software_application_notes.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。
QQ截图20230425144055.png
QQ截图20230425144108.png
QQ截图20230425144123.png
QQ截图20230425144148.png
表 32.1.1 OV5640关键寄存器配置说明

OV5640的寄存器较多,对于其它寄存器的描述可以参考OV5640的数据手册。需要注意的是,OV5640的数据手册并没有提供全部的寄存器描述,而大多数必要的寄存器配置在ov5640的软件应用手册中可以找到,可以结合这两个手册学习如何对OV5640进行配置。

输出图像参数设置
接下来,我们介绍一下OV5640的ISP输入窗口设置、预缩放窗口设置和输出大小窗口设置,这几个设置与我们的正常使用密切相关,有必要了解一下,它们的设置关系如下图所示:
image009.png
图 32.1.5 图像窗口设置

ISP输入窗口设置(ISP Input Size)允许用户设置整个传感器显示区域(physical pixel size,2632*1951,其中2592*1944像素是有效的),开窗范围从0*0~2632*1951都可以任意设置。也就是上图中的X_ADDR_ST(寄存器地址0x3800、0x3801)、Y_ADDR_ST(寄存器地址0x3802、0x3803)、X_ADDR_END(寄存器地址0x3804、0x3805)和Y_ADDR_END(寄存器地址0x3806、0x3807)寄存器。该窗口设置范围中的像素数据将进入ISP进行图像处理。

预缩放窗口设置(pre-scaling size)允许用户在ISP输入窗口的基础上进行裁剪,用于设置将进行缩放的窗口大小,该设置仅在ISP输入窗口内进行X/Y方向的偏移。可以通过X_OFFSET(寄存器地址0x3810、0x3811)和Y_OFFSET(寄存器地址0x3812、0x3813)进行配置。

输出大小窗口设置(data output size)是在预缩放窗口的基础上,经过内部DSP进行缩放处理,并将处理后的数据输出给外部的图像窗口,图像窗口控制着最终的图像输出尺寸。可以通过X_OUTPUT_SIZE(寄存器地址0x3808、0x3809)和Y_OUTPUT_SIZE(寄存器地址0x380A、0x380B)进行配置。注意:当输出大小窗口与预缩放窗口比例不一致时,图像将进行缩放处理(图像变形),仅当两者比例一致时,输出比例才是1:1(正常图像)。

图 32.1.5中,右侧data output size区域,才是OV5640输出给外部的图像尺寸,也就是显示在显示器或者液晶屏上面的图像大小。输出大小窗口与预缩放窗口比例不一致时,会进行缩放处理,在显示器上面看到的图像将会变形。

输出像素格式
OV5640支持多种不同的数据像素格式,包括YUV(亮度参量和色度参量分开表示的像素格式)、RGB(其中RGB格式包含RGB565、RGB555等)以及RAW(原始图像数据),通过寄存器地址0x4300配置成不同的数据像素格式。

由于数据像素格式常用RGB565,我们这里也将ov5640配置为RGB565格式。由上表(表 32.1.1)可知,将寄存器0x4300寄存器的Bit[7:4]设置成0x6即可。OV5640支持调节RGB565输出格式中各颜色变量的顺序,对于我们常见的应用来说,一般是使用RGB或BGR序列。其中RGB序列最为常用,因此将寄存器0x4300寄存器的Bit[3:0]设置成0x1。

由于摄像头采集的图像最终要通过RGB LCD接口显示在LCD液晶屏上,且DFZU2EG/4EV MPSoC开发板上的LCD接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此我们将OV5640摄像头输出的图像像素数据配置成RGB565格式,然后通过颜色分量低位补零的方式将RGB565格式转换为RGB888格式。下图为摄像头输出的时序图。
image011.png
图 32.1.6 OV5640输出时序图

在介绍时序图之前先了解几个基本的概念。

VSYNC:场同步信号,由摄像头输出,用于标志一帧数据的开始与结束。上图中VSYNC的高电平作为一帧的同步信号,在低电平时输出的数据有效。需要注意的是场同步信号是可以通过设置寄存器0x4740 Bit[0]位进行取反的,即低电平同步高电平有效,本次实验使用的是和上图一致的默认设置;

HREF/HSYNC:行同步信号,由摄像头输出,用于标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的,只是数据的同步方式不一样。本次实验使用的是HREF格式输出,当HREF为高电平时,图像输出有效,可以通过寄存器0x4740 Bit[1]进行配置。本次实验使用的是HREF格式输出;

D[9:0]:数据信号,由摄像头输出,在RGB格式输出中,只有高8位D[9:2]是有效的;

tPCLK:一个像素时钟周期;

下图为OV5640输出RGB565格式的时序图:
image013.png
图 32.1.7 RGB565模式时序图

上图中的PCLK为OV5640输出的像素时钟,HREF为行同步信号,D[9:2]为8位像素数据。OV5640最大可以输出10位数据,在RGB565输出模式中,只有高8位是有效的。像素数据在HREF为高电平时有效,第一次输出的数据为RGB565数据的高8位,第二次输出的数据为RGB565数据的低8位,first byte和second byte组成一个16位RGB565数据。由上图可知,数据是在像素时钟的下降沿改变的,为了在数据最稳定的时刻采集图像数据,所以我们需要在像素时钟的上升沿采集数据。

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

32.3 硬件设计
我们的DFZU2EG/4EV MPSOC开发板上有一个扩展接口(J19),该接口可以用来连接一些扩展模块,如双目OV5640摄像头、高速ADDA模块、IO扩展板模块等。本次实验就是通过连接双目OV5640摄像头,实现单个OV5640摄像头图像的采集和显示。原理图如下图所示:
image015.png
图 32.3.1扩展接口原理图

ATK-Dual-OV5640是正点原子推出的一款高性能双目OV5640 500W像素高清摄像头模块。该模块通过2*20排母(2.54mm间距)同外部连接,我们将摄像头的排母直接插在开发板上的扩展接口即可,模块外观如图 32.3.2所示:
image017.png
图 32.3.2 ATK-OV5640摄像头模块实物图

我们在前面说过,OV5640在RGB565模式中只有高8位数据是有效的即D[9:2],而我们的摄像头排母上数据引脚的个数是8位。实际上,摄像头排母上的8位数据连接的就是OV5640传感器的D[9:2],所以我们直接使用摄像头排母上的8位数据引脚即可。

由于RGB LCD屏的引脚数目较多,且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配, 如下表所示:
QQ截图20230425144359.png
表 32.3.1 OV5640摄像头管脚分配

摄像头XDC约束文件如下:
  1. set_propertyCLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_IBUF_inst/O]

  2. set_property-dict {PACKAGE_PIN C13 IOSTANDARD LVCMOS33} [get_ports cam_pclk]
  3. set_property-dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports cam_rst_n]
  4. set_property-dict {PACKAGE_PIN B15 IOSTANDARD LVCMOS33} [get_ports cam_pwdn]
  5. set_property-dict {PACKAGE_PIN E15 IOSTANDARD LVCMOS33} [get_ports {cam_data[0]}]
  6. set_property-dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33} [get_ports {cam_data[1]}]
  7. set_property-dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33} [get_ports {cam_data[2]}]
  8. set_property-dict {PACKAGE_PIN D14 IOSTANDARD LVCMOS33} [get_ports {cam_data[3]}]
  9. set_property-dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33} [get_ports {cam_data[4]}]
  10. set_property-dict {PACKAGE_PIN B13 IOSTANDARD LVCMOS33} [get_ports {cam_data[5]}]
  11. set_property-dict {PACKAGE_PIN C14 IOSTANDARD LVCMOS33} [get_ports {cam_data[6]}]
  12. set_property-dict {PACKAGE_PIN A13 IOSTANDARD LVCMOS33} [get_ports {cam_data[7]}]
  13. set_property-dict {PACKAGE_PIN G14 IOSTANDARD LVCMOS33} [get_ports cam_vsync]
  14. set_property-dict {PACKAGE_PIN G13 IOSTANDARD LVCMOS33} [get_ports cam_href]
  15. set_property-dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33} [get_ports cam_scl]
  16. set_property-dict {PACKAGE_PIN F15 IOSTANDARD LVCMOS33} [get_ports cam_sda]
  17. set_propertyPULLUP true [get_ports cam_scl]
  18. set_propertyPULLUP true [get_ports cam_sda]]
复制代码
32.4 程序设计
图 32.4.1是根据本章实验任务画出的系统框图。
image019.png
图 32.4.1 顶层系统框图

由上图可知,本节实验的系统输入时钟是一对差分时钟(100Mhz),系统时钟进入DDR4顶层模块内部的MIG IP核后,由MIG IP内部的MMCM输出一路50Mhz的时钟给OV5640顶层模块、LCD顶层模块以及图像分辨率模块使用。其中LCD顶层模块会读取外部LCD显示屏的ID,并将这个ID传递给图像分辨率模块,而图像分辨率模块会根据不同的显示屏ID确定显示图像的不同分辨率,并生成对应的摄像头配置参数和DDR4的最大读写地址参数,其中摄像头配置参数传递给OV5640顶层模块,DDR4的最大读写地址参数传递给MIG IP核驱动模块。之后OV5640顶层模块会根据图像分辨率模块传递过来的参数去配置OV5640摄像头,等待摄像头配置完成后会接收摄像头采集到的数据,并将这个数据传递给MIG IP核模块。MIG IP模块会根据图像分辨率模块传递来的参数去配置DDR4最大读写地址,并将OV5640顶层模块传递过来的数据写入到外部DDR4芯片中去。最后LCD顶层模块再通过MIG IP核把外部DDR4芯片中存储的数据读出来传递给LCD显示屏,从而让LCD显示屏可以正常的显示摄像头捕捉的图像数据。

顶层模块的原理图如下图所示:
image021.png
图 32.4.2 顶层模块原理图

FPGA顶层模块(ov5640_lcd)例化了以下四个模块: OV5640驱动模块(ov5640_dri)、摄像头图像分辨率设置模块(picture_size)、DDR控制模块(ddr4_top)和LCD顶层模块(lcd_rgb_top)。

OV5640驱动模块(ov5640_dri):OV5640驱动模块负责驱动OV5640 SCCB接口总线,将像素时钟驱动下的传感器输出的场同步信号、行同步信号以及8位数据转换成DDR读写控制模块的写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。

图像分辨率设置模块(picture_size):图像尺寸配置模块用于配置摄像头输出图像尺寸的大小,此外还完成了DDR4的读写结束地址设置。

DDR控制模块(ddr4_top):DDR读写控制器模块负责驱动DDR片外存储器,缓存图像传感器输出的图像数据。该模块将MIG IP核复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。

LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。

接下来我们来详细分析一下这几个模块的代码,其中顶层代码仅仅是例化四个子模块,实现各模块的数据交互,此处不再贴出代码。

我们先来看看摄像头驱动代码,如下所示:
  1. 1  module ov5640_dri (
  2. 2       input          clk             ,  //时钟
  3. 3       input          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_pwdn        ,  //cmos 电源休眠模式选择信号
  11. 11      output         cam_scl         ,  //cmos SCCB_SCL线
  12. 12      input          cam_sda_i       ,  //cmos SCCB_SDA输入
  13. 13      output         cam_sda_o       ,  //cmos SCCB_SDA输出
  14. 14      output         cam_sda_t       ,  //cmos SCCB_SDA使能        
  15. 15      
  16. 16      //摄像头分辨率配置接口
  17. 17      input    [12:0 cmos_h_pixel   ,  //水平方向分辨率
  18. 18      input    [12:0 cmos_v_pixel   ,  //垂直方向分辨率
  19. 19      input    [12:0 total_h_pixel  ,  //水平总像素大小
  20. 20      input    [12:0 total_v_pixel  ,  //垂直总像素大小
  21. 21      input          capture_start   ,  //图像采集开始信号
  22. 22      output         cam_init_done   ,  //摄像头初始化完成
  23. 23      
  24. 24      //用户接口
  25. 25      output         cmos_frame_vsync,  //帧有效信号   
  26. 26      output         cmos_frame_href ,  //行有效信号
  27. 27      output         cmos_frame_valid,  //数据有效使能信号
  28. 28      output  [15:0 cmos_frame_data    //有效数据  
  29. 29 );
  30. 30
  31. 31 //parameter define
  32. 32 parameter SLAVE_ADDR = 7'h3c   ;//OV5640的器件地址7'h3c
  33. 33 parameter BIT_CTRL   =1'b1   ; //OV5640的字节地址为16位 0:8位 1:16位
  34. 34 parameter CLK_FREQ   =27'd50_000_000; //i2c_dri模块的驱动时钟频率
  35. 35 parameter I2C_FREQ   =18'd250_000    ; //I2C的SCL时钟频率,不超过400KHz
  36. 36
  37. 37 //wire difine
  38. 38 wire        i2c_exec       ;  //I2C触发执行信号
  39. 39 wire [23:0 i2c_data      ;  //I2C要配置的地址与数据(高8位地址,低8位数据)         
  40. 40 wire        i2c_done       ;  //I2C寄存器配置完成信号
  41. 41 wire        i2c_dri_clk    ;  //I2C操作时钟
  42. 42 wire [ 7:0 i2c_data_r    ;  //I2C读出的数据
  43. 43 wire        i2c_rh_wl      ;  //I2C读写控制信号
  44. 44
  45. 45 //*****************************************************
  46. 46 //**                    main code                     
  47. 47 //*****************************************************
  48. 48
  49. 49 //电源休眠模式选择 0:正常模式 1:电源休眠模式
  50. 50 assign  cam_pwdn = 1'b0;
  51. 51 assign  cam_rst_n = 1'b1;
  52. 52      
  53. 53 //I2C配置模块
  54. 54 i2c_ov5640_rgb565_cfg u_i2c_cfg(
  55. 55      .clk                (i2c_dri_clk),
  56. 56      .rst_n              (rst_n),
  57. 57              
  58. 58      .i2c_exec           (i2c_exec),
  59. 59      .i2c_data           (i2c_data),
  60. 60      .i2c_rh_wl          (i2c_rh_wl),        //I2C读写控制信号
  61. 61      .i2c_done           (i2c_done),
  62. 62      .i2c_data_r         (i2c_data_r),   
  63. 63                  
  64. 64      .cmos_h_pixel       (cmos_h_pixel),     //CMOS水平方向像素个数
  65. 65      .cmos_v_pixel       (cmos_v_pixel) ,    //CMOS垂直方向像素个数
  66. 66      .total_h_pixel      (total_h_pixel),    //水平总像素大小
  67. 67      .total_v_pixel      (total_v_pixel),    //垂直总像素大小
  68. 68         
  69. 69      .init_done          (cam_init_done)
  70. 70      );   
  71. 71
  72. 72 //I2C驱动模块
  73. 73 i2c_dri #(
  74. 74      .SLAVE_ADDR         (SLAVE_ADDR),       //参数传递
  75. 75      .CLK_FREQ           (CLK_FREQ  ),              
  76. 76      .I2C_FREQ           (I2C_FREQ  )
  77. 77      )
  78. 78 u_i2c_dr(
  79. 79      .clk                (clk),
  80. 80      .rst_n              (rst_n     ),
  81. 81
  82. 82      .i2c_exec           (i2c_exec  ),   
  83. 83      .bit_ctrl           (BIT_CTRL  ),   
  84. 84      .i2c_rh_wl          (i2c_rh_wl),        //固定为0,只用到了IIC驱动的写操作  
  85. 85      .i2c_addr           (i2c_data[23:8]),   
  86. 86      .i2c_data_w         (i2c_data[7:0]),   
  87. 87      .i2c_data_r         (i2c_data_r),   
  88. 88      .i2c_done           (i2c_done  ),
  89. 89      
  90. 90      .scl                (cam_scl     ),   
  91. 91      .sda_i              (cam_sda_i   ),  
  92. 92      .sda_o              (cam_sda_o   ),   
  93. 93      .sda_t              (cam_sda_t   ),        
  94. 94      .dri_clk            (i2c_dri_clk)       //I2C操作时钟
  95. 95      );
  96. 96
  97. 97 //CMOS图像数据采集模块
  98. 98 cmos_capture_data u_cmos_capture_data(      //系统初始化完成之后再开始采集数据
  99. 99      .rst_n              (rst_n & capture_start),
  100. 100     
  101. 101     .cam_pclk           (cam_pclk),
  102. 102     .cam_vsync          (cam_vsync),
  103. 103     .cam_href           (cam_href),
  104. 104     .cam_data           (cam_data),         
  105. 105     
  106. 106     .cmos_frame_vsync   (cmos_frame_vsync),
  107. 107     .cmos_frame_href    (cmos_frame_href ),
  108. 108     .cmos_frame_valid   (cmos_frame_valid), //数据有效使能信号
  109. 109     .cmos_frame_data    (cmos_frame_data )  //有效数据
  110. 110     );
  111. 111
  112. 112 endmodule
复制代码
摄像头驱动模块的顶层同样例化了三个模块,分别是摄像头配置模块(i2c_ov5640_rgb565_cfg)、IIC驱动模块(i2c_dri)以及数据采集模块(cmos_capture_data)。其中摄像头配置模块主要是包含了OV5640摄像头的寄存器信息,而IIC驱动模块就是把这些寄存器信息配置到OV5640摄像头中去;数据采集模块的作用就是接收摄像头采集到的数据并将数据传递给DDR4驱动模块。

下面我们先来看看摄像头配置模块(i2c_ov5640_rgb565_cfg)的代码,如下所示:
  1. 1   modulei2c_ov5640_rgb565_cfg
  2. 2      (  
  3. 3       input                clk      ,     //时钟信号
  4. 4       input                rst_n    ,     //复位信号,低电平有效
  5. 5      
  6. 6       input        [7:0]   i2c_data_r,    //I2C读出的数据
  7. 7       input                i2c_done ,     //I2C寄存器配置完成信号
  8. 8       input        [12:0]  cmos_h_pixel ,
  9. 9       input        [12:0]  cmos_v_pixel ,
  10. 10      input        [12:0]  total_h_pixel, //水平总像素大小
  11. 11      input        [12:0]  total_v_pixel, //垂直总像素大小
  12. 12      output  reg          i2c_exec ,     //I2C触发执行信号   
  13. 13      output  reg  [23:0]  i2c_data ,     //I2C要配置的地址与数据(高16位地址,低8位数据)
  14. 14      output  reg          i2c_rh_wl,     //I2C读写控制信号
  15. 15      output  reg          init_done      //初始化完成信号
  16. 16      );
  17. 17  
  18. 18  //parameter define
  19. 19  localparam  REG_NUM = 8'd250  ;       //总共需要配置的寄存器个数
  20. 20  
  21. 21  //reg define
  22. 22  reg   [12:0]   start_init_cnt;        //等待延时计数器
  23. 23  reg    [7:0]  init_reg_cnt  ;        //寄存器配置个数计数器
  24. 24  
  25. 25  //*****************************************************
  26. 26  //**                    main code
  27. 27  //*****************************************************
  28. 28  
  29. 29  //clk时钟配置成1Mhz,周期为1000ns 20000*1000ns = 20ms
  30. 30  //OV5640上电到开始配置IIC至少等待20ms
  31. 31  always @(posedge clk or negedge rst_n) begin
  32. 32      if(!rst_n)
  33. 33          start_init_cnt <= 13'b0;
  34. 34      else if(start_init_cnt < 13'd20000) begin
  35. 35          start_init_cnt <= start_init_cnt + 1'b1;                  
  36. 36      end
  37. 37  end
  38. 38  
  39. 39  //寄存器配置个数计数   
  40. 40  always @(posedge clk or negedge rst_n) begin
  41. 41      if(!rst_n)
  42. 42          init_reg_cnt <= 8'd0;
  43. 43      else if(i2c_exec)   
  44. 44          init_reg_cnt <= init_reg_cnt + 8'b1;
  45. 45  end
  46. 46  
  47. 47  //i2c触发执行信号   
  48. 48  always @(posedge clk or negedge rst_n) begin
  49. 49      if(!rst_n)
  50. 50          i2c_exec <= 1'b0;
  51. 51      else if(start_init_cnt == 13'd19999)
  52. 52          i2c_exec <= 1'b1;
  53. 53      else if(i2c_done && (init_reg_cnt < REG_NUM))
  54. 54          i2c_exec <= 1'b1;
  55. 55      else
  56. 56          i2c_exec <= 1'b0;
  57. 57  end
  58. 58  
  59. 59  //配置I2C读写控制信号
  60. 60  always @(posedge clk or negedge rst_n) begin
  61. 61      if(!rst_n)
  62. 62          i2c_rh_wl <= 1'b1;
  63. 63      else if(init_reg_cnt == 8'd2)  
  64. 64          i2c_rh_wl <= 1'b0;  
  65. 65  end
  66. 66  
  67. 67  //初始化完成信号
  68. 68  always @(posedge clk or negedge rst_n) begin
  69. 69      if(!rst_n)
  70. 70          init_done <= 1'b0;
  71. 71      else if((init_reg_cnt == REG_NUM) && i2c_done)  
  72. 72          init_done <= 1'b1;  
  73. 73  end
  74. 74  
  75. 75  //配置寄存器地址与数据
  76. 76  always @(posedge clk or negedge rst_n) begin
  77. 77      if(!rst_n)
  78. 78          i2c_data <= 24'b0;
  79. 79      else begin
  80. 80          case(init_reg_cnt)
  81. 81              //先对寄存器进行软件复位,使寄存器恢复初始值
  82. 82              //寄存器软件复位后,需要延时1ms才能配置其它寄存器
  83. 83              8'd0  : i2c_data <= {16'h300a,8'h0}; //
  84. 84              8'd1  : i2c_data <= {16'h300b,8'h0}; //
复制代码
配置代码较长,省略部分源代码……
  1. 295             8'd204: i2c_data <= {16'h5025,8'h00};
  2. 296             //系统时钟分频 Bit[7:4]:系统时钟分频 inputclock =24Mhz, PCLK = 48Mhz
  3. 297             8'd205: i2c_data <= {16'h3035,8'h11};
  4. 298             8'd206: i2c_data <= {16'h3036,8'h3c}; //PLL倍频
  5. 299             8'd207: i2c_data <= {16'h3c07,8'h08};
  6. 300             //时序控制 16'h3800~16'h3821
  7. 301             8'd208: i2c_data <= {16'h3820,8'h46};
  8. 302             8'd209: i2c_data <= {16'h3821,8'h01};
  9. 303             8'd210: i2c_data <= {16'h3814,8'h31};
  10. 304             8'd211: i2c_data <= {16'h3815,8'h31};
  11. 305             8'd212: i2c_data <= {16'h3800,8'h00};
  12. 306             8'd213: i2c_data <= {16'h3801,8'h00};
  13. 307             8'd214: i2c_data <= {16'h3802,8'h00};
  14. 308             8'd215: i2c_data <= {16'h3803,8'h04};
  15. 309             8'd216: i2c_data <= {16'h3804,8'h0a};
  16. 310             8'd217: i2c_data <= {16'h3805,8'h3f};
  17. 311             8'd218: i2c_data <= {16'h3806,8'h07};
  18. 312             8'd219: i2c_data <= {16'h3807,8'h9b};
  19. 313             //设置输出像素个数
  20. 314             //DVP 输出水平像素点数高4位
  21. 315             8'd220: i2c_data <= {16'h3808,{4'd0,cmos_h_pixel[11:8]}};
  22. 316             //DVP 输出水平像素点数低8位
  23. 317             8'd221: i2c_data <= {16'h3809,cmos_h_pixel[7:0]};
  24. 318             //DVP 输出垂直像素点数高3位
  25. 319             8'd222: i2c_data <= {16'h380a,{5'd0,cmos_v_pixel[10:8]}};
  26. 320             //DVP 输出垂直像素点数低8位
  27. 321             8'd223: i2c_data <= {16'h380b,cmos_v_pixel[7:0]};
  28. 322             //水平总像素大小高5位
  29. 323             8'd224: i2c_data <= {16'h380c,{3'd0,total_h_pixel[12:8]}};
  30. 324             //水平总像素大小低8位
  31. 325             8'd225: i2c_data <= {16'h380d,total_h_pixel[7:0]};
  32. 326             //垂直总像素大小高5位
  33. 327             8'd226: i2c_data <= {16'h380e,{3'd0,total_v_pixel[12:8]}};
  34. 328             //垂直总像素大小低8位     
  35. 329             8'd227: i2c_data <= {16'h380f,total_v_pixel[7:0]};
复制代码
配置代码较长,省略部分源代码……
  1. 350             8'd246: i2c_data <= {16'h3016,8'h02};
  2. 351             8'd247: i2c_data <= {16'h301c,8'h02};
  3. 352             8'd248: i2c_data <= {16'h3019,8'h02}; //打开闪光灯
  4. 353             8'd249: i2c_data <= {16'h3019,8'h00}; //关闭闪光灯
  5. 354             //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
  6. 355             default : i2c_data <= {16'h300a,8'h00}; //器件ID高8位
  7. 356         endcase
  8. 357     end
  9. 358 end
  10. 359
  11. 360 endmodule
复制代码
I2C配置模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束。需要注意的是,由OV5640的数据手册可知,图像传感器上电后到开始配置寄存器需要延时20ms,所以程序中定义了一个延时计数器(start_init_cnt),用于延时20ms。当计数器计数到预设值之后,开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。在代码的第19行定义了总共需要配置的寄存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。代码第40~45行是用来计数已经配置了多少个寄存器。代码第48~57行是用来生成IIC启动信号(i2c_exec)。代码第68~73行是当配置寄存器计数器(init_reg_cnt)数到了需要配置寄存器个数的最大值时(REG_NUM)认为寄存器配置完成。再往后面的代码就是寄存器具体的地址和需要写入的值了,这部分内容需要参考数据手册,几个比较重要的寄存器在简介部分也给大家着重讲解过了,这里不再重复赘述。

在程序的第313行至第329行,是对摄像头需要输出的行场分辨率和行场总像素进行设置的寄存器配置,根据不同的LCD屏幕的ID,这几个寄存器配置的参数也是不相同的。

关于IIC驱动模块的代码在前面“IIC读写测试实验”的例程中有详细的讲解,所以本节实验不再讲解了。最后我们来看看数据采集模块的代码,如下所示:
  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         
  97. 97          else;   
  98. 98      end
  99. 99      else begin
  100. 100         byte_flag <= 1'b0;
  101. 101         cam_data_d0 <= 8'b0;
  102. 102         cmos_data_t <= 16'd0;
  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
复制代码
摄像头在配置完寄存器后就会输出图像数据,但是此时输出的数据有可能不是特别稳定,所以我们的数据采集模块并没有立刻把摄像头输出的数据接收过来,而是在代码的67~82行做了一个帧计数器,当数到摄像头已经传输完10帧数据后(此时我们认为摄像头数据已经处于稳定状态)才开始接收数据并且拉高帧有效标志(frame_val_flag)。代码第85~104行是将8位的摄像头原始数据拼接成16位rgb565格式的像素数据。代码第106~114行是每当一个数据拼接完成就输出一个数据有效信号。

接下来我们来看看DDR控制模块,代码如下所示:
  1. 1  module ddr4_top(
  2. 2       input             sys_rst_n       ,   //复位,低有效
  3. 3       input             sys_init_done   ,   //系统初始化完成               
  4. 4       //DDR4接口信号                       
  5. 5       input   [27:0    app_addr_rd_min ,   //读DDR4的起始地址
  6. 6       input   [27:0    app_addr_rd_max ,   //读DDR4的结束地址
  7. 7       input   [7:0     rd_bust_len     ,   //从DDR4中读数据时的突发长度
  8. 8       input   [27:0    app_addr_wr_min ,   //读DDR4的起始地址
  9. 9       input   [27:0    app_addr_wr_max ,   //读DDR4的结束地址
  10. 10      input   [7:0     wr_bust_len     ,   //从DDR4中读数据时的突发长度
  11. 11      // DDR4 IO接口   
  12. 12      input             c0_sys_clk_p    ,
  13. 13      input             c0_sys_clk_n    ,
  14. 14      output            c0_ddr4_act_n   ,
  15. 15      output [16:0     c0_ddr4_adr     ,
  16. 16      output [1:0       c0_ddr4_ba      ,
  17. 17      output [0:0       c0_ddr4_bg      ,
  18. 18      output [0:0       c0_ddr4_cke     ,
  19. 19      output [0:0       c0_ddr4_odt     ,
  20. 20      output [0:0       c0_ddr4_cs_n    ,
  21. 21      output [0:0       c0_ddr4_ck_t    ,
  22. 22      output [0:0       c0_ddr4_ck_c    ,
  23. 23      output            c0_ddr4_reset_n ,
  24. 24      inout  [1:0      c0_ddr4_dm_dbi_n,
  25. 25      inout  [15:0     c0_ddr4_dq      ,
  26. 26      inout  [1:0      c0_ddr4_dqs_c   ,
  27. 27      inout  [1:0      c0_ddr4_dqs_t   ,
  28. 28
  29. 29      //用户
  30. 30      input             ddr4_read_valid     ,   //DDR4 读使能   
  31. 31      input             ddr4_pingpang_en    ,   //DDR4 乒乓操作使能      
  32. 32      input             wr_clk              ,   //wfifo时钟   
  33. 33      input             rd_clk              ,   //rfifo的读时钟      
  34. 34      input             datain_valid        ,   //数据有效使能信号
  35. 35      input   [15:0    datain              ,   //有效数据
  36. 36      input             rdata_req           ,   //请求像素点颜色数据输入  
  37. 37      input             rd_load             ,   //输出源更新信号
  38. 38      input             wr_load             ,   //输入源更新信号
  39. 39      output  [15:0     dataout             ,   //rfifo输出数据
  40. 40      output            clk_50m             ,
  41. 41      output            init_calib_complete     //ddr4初始化完成信号
  42. 42      );               
  43. 43                     
  44. 44 //wire define  
  45. 45 wire                  ui_clk               ;   //用户时钟
  46. 46 wire [27:0          app_addr             ;   //ddr4 地址
  47. 47 wire [2:0           app_cmd              ;   //用户读写命令
  48. 48 wire                  app_en               ;   //MIG IP核使能
  49. 49 wire                  app_rdy              ;   //MIG IP核空闲
  50. 50 wire [127:0         app_rd_data          ;   //用户读数据
  51. 51 wire                  app_rd_data_end      ;   //突发读当前时钟最后一个数据
  52. 52 wire                  app_rd_data_valid    ;   //读数据有效
  53. 53 wire [127:0         app_wdf_data         ;   //用户写数据
  54. 54 wire                  app_wdf_end          ;   //突发写当前时钟最后一个数据
  55. 55 wire [15:0          app_wdf_mask         ;   //写数据屏蔽                           
  56. 56 wire                  app_wdf_rdy          ;   //写空闲                              
  57. 57 wire                  app_sr_active        ;   //保留                                 
  58. 58 wire                  app_ref_ack          ;   //刷新请求                             
  59. 59 wire                  app_zq_ack           ;   //ZQ 校准请求                          
  60. 60 wire                  app_wdf_wren         ;   //ddr4 写使能                          
  61. 61 wire                  clk_ref_i            ;   //ddr4参考时钟                        
  62. 62 wire                  sys_clk_i            ;   //MIG IP核输入时钟                     
  63. 63 wire                  ui_clk_sync_rst      ;   //用户复位信号                        
  64. 64 wire [20:0          rd_cnt               ;   //实际读地址计数                       
  65. 65 wire [3 :0          state_cnt            ;   //状态计数器                           
  66. 66 wire [23:0          rd_addr_cnt          ;   //用户读地址计数器                     
  67. 67 wire [23:0           wr_addr_cnt          ;   //用户写地址计数器                     
  68. 68 wire                  rfifo_wren           ;   //从ddr4读出数据的有效使能            
  69. 69 wire [10:0          wfifo_rcount         ;   //rfifo剩余数据计数                    
  70. 70 wire [10:0          rfifo_wcount         ;   //wfifo写进数据计数  
  71. 71
  72. 72                                                                                                                                                                     
  73. 73 //*****************************************************                              
  74. 74 //**                    main code                                                   
  75. 75 //*****************************************************                              
  76. 76                                                                                    
  77. 77 //读写模块                                                                           
  78. 78 ddr4_rw u_ddr4_rw(                                                                  
  79. 79      .ui_clk               (ui_clk)              ,                                    
  80. 80      .ui_clk_sync_rst      (ui_clk_sync_rst)     ,                                      
  81. 81      //MIG 接口                                                                       
  82. 82      .init_calib_complete  (init_calib_complete) ,   //ddr4初始化完成信号                                   
  83. 83      .app_rdy              (app_rdy)             ,   //MIG IP核空闲                                 
  84. 84      .app_wdf_rdy          (app_wdf_rdy)         ,   //写空闲                                 
  85. 85      .app_rd_data_valid    (app_rd_data_valid)   ,   //读数据有效                                 
  86. 86      .app_addr             (app_addr)            ,   //ddr4 地址                                   
  87. 87      .app_en               (app_en)              ,   //MIG IP核使能                                 
  88. 88      .app_wdf_wren         (app_wdf_wren)        ,   //ddr4 写使能                                   
  89. 89      .app_wdf_end          (app_wdf_end)         ,   //突发写当前时钟最后一个数据                                 
  90. 90      .app_cmd              (app_cmd)             ,   //用户读写命令                                                                                                                        
  91. 91      //ddr4 地址参数                                                                  
  92. 92      .app_addr_rd_min      (app_addr_rd_min)     ,   //读ddr4的起始地址                                 
  93. 93      .app_addr_rd_max      (app_addr_rd_max)     ,   //读ddr4的结束地址                                 
  94. 94      .rd_bust_len          (rd_bust_len)         ,   //从ddr4中读数据时的突发长度                                 
  95. 95      .app_addr_wr_min      (app_addr_wr_min)     ,   //写ddr4的起始地址                                 
  96. 96      .app_addr_wr_max      (app_addr_wr_max)     ,   //写ddr4的结束地址                                 
  97. 97      .wr_bust_len          (wr_bust_len)         ,   //从ddr4中写数据时的突发长度                                 
  98. 98      //用户接口                                                                       
  99. 99      .rfifo_wren           (rfifo_wren)          ,   //从ddr4读出数据的有效使能
  100. 100     .rd_load              (rd_load)             ,   //输出源更新信号
  101. 101     .wr_load              (wr_load)             ,   //输入源更新信号
  102. 102     .ddr4_read_valid      (ddr4_read_valid)     ,   //ddr4 读使能
  103. 103     .ddr4_pingpang_en     (ddr4_pingpang_en)    ,   //ddr4 乒乓操作使能   
  104. 104     .wfifo_rcount         (wfifo_rcount)        ,   //rfifo剩余数据计数                  
  105. 105     .rfifo_wcount         (rfifo_wcount)            //wfifo写进数据计数
  106. 106     );                  
  107. 107
  108. 108 ddr4_0 u_ddr4_0 (
  109. 109 .c0_init_calib_complete(init_calib_complete), //初始化完成        
  110. 110 .dbg_clk(),                             
  111. 111 .c0_sys_clk_p(c0_sys_clk_p),            // 系统时钟p
  112. 112 .c0_sys_clk_n(c0_sys_clk_n),            // 系统时钟n
  113. 113 .dbg_bus(),                             //output wire [511 : 0] dbg_bus
  114. 114 .c0_ddr4_adr(c0_ddr4_adr),              // 行列地址
  115. 115 .c0_ddr4_ba(c0_ddr4_ba),               // bank地址
  116. 116 .c0_ddr4_cke(c0_ddr4_cke),              // 时钟使能
  117. 117 .c0_ddr4_cs_n(c0_ddr4_cs_n),            //片选信号
  118. 118 .c0_ddr4_dm_dbi_n(c0_ddr4_dm_dbi_n),    //数据掩码
  119. 119 .c0_ddr4_dq(c0_ddr4_dq),                //数据线
  120. 120 .c0_ddr4_dqs_c(c0_ddr4_dqs_c),         // 数据选通信号
  121. 121 .c0_ddr4_dqs_t(c0_ddr4_dqs_t),         //数据选通信号
  122. 122 .c0_ddr4_odt(c0_ddr4_odt),             //终端电阻使能
  123. 123 .c0_ddr4_bg(c0_ddr4_bg),               //bank组地址
  124. 124 .c0_ddr4_reset_n(c0_ddr4_reset_n),     // 复位信号
  125. 125 .c0_ddr4_act_n(c0_ddr4_act_n),         // 指令激活
  126. 126 .c0_ddr4_ck_c(c0_ddr4_ck_c),           //ddr时钟
  127. 127 .c0_ddr4_ck_t(c0_ddr4_ck_t),           //ddr时钟
  128. 128 //user interface
  129. 129 .c0_ddr4_ui_clk(ui_clk),               // 用户时钟   
  130. 130 .c0_ddr4_ui_clk_sync_rst(ui_clk_sync_rst), //用户复位
  131. 131 .c0_ddr4_app_en(app_en),              // 指令使能
  132. 132 .c0_ddr4_app_hi_pri(1'b0),           
  133. 133 .c0_ddr4_app_wdf_end(app_wdf_end),    // 写数据最后一个数据
  134. 134 .c0_ddr4_app_wdf_wren(app_wdf_wren),  // 写数据使能
  135. 135 .c0_ddr4_app_rd_data_end(app_rd_data_end),   // 读数据最后一个数据
  136. 136 .c0_ddr4_app_rd_data_valid(app_rd_data_valid), // 读数据有效
  137. 137 .c0_ddr4_app_rdy(app_rdy),                    // 指令接收准备完成,可以接收指令
  138. 138 .c0_ddr4_app_wdf_rdy(app_wdf_rdy),             // 数据接收准备完成,可以接收数据
  139. 139 .c0_ddr4_app_addr(app_addr),                   // 用户地址
  140. 140 .c0_ddr4_app_cmd(app_cmd),                     // 读写指令
  141. 141 .c0_ddr4_app_wdf_data(app_wdf_data),       //写数据
  142. 142 .c0_ddr4_app_wdf_mask(16'b0),              // 写数据掩码
  143. 143 .c0_ddr4_app_rd_data(app_rd_data),         //读数据
  144. 144 .addn_ui_clkout1(clk_50m),                 // 锁相环时钟输出
  145. 145 .sys_rst(~sys_rst_n)                      // 系统复位
  146. 146 );
  147. 147                                                   
  148. 148 ddr4_fifo_ctrl u_ddr4_fifo_ctrl (
  149. 149
  150. 150     .rst_n               (sys_rst_n &&sys_init_done )  ,  
  151. 151     //输入源接口
  152. 152     .wr_clk              (wr_clk)         ,
  153. 153     .rd_clk              (rd_clk)         ,
  154. 154     .clk_100             (ui_clk)         ,    //用户时钟
  155. 155     .datain_valid        (datain_valid)   ,    //数据有效使能信号
  156. 156     .datain              (datain)         ,    //有效数据
  157. 157     .rfifo_din           (app_rd_data)    ,    //用户读数据
  158. 158     .rdata_req           (rdata_req)      ,    //请求像素点颜色数据输入
  159. 159     .rfifo_wren          (rfifo_wren)     ,    //ddr4读出数据的有效使能
  160. 160     .wfifo_rden          (app_wdf_wren)   ,    //ddr4 写使能         
  161. 161     //用户接口
  162. 162     .wfifo_rcount        (wfifo_rcount)   ,    //rfifo剩余数据计数                 
  163. 163     .rfifo_wcount        (rfifo_wcount)   ,    //wfifo写进数据计数               
  164. 164     .wfifo_dout          (app_wdf_data)   ,    //用户写数据
  165. 165     .rd_load             (rd_load)        ,    //输出源更新信号
  166. 166     .wr_load             (wr_load)        ,    //输入源更新信号
  167. 167     .pic_data            (dataout)             //rfifo输出数据         
  168. 168     );
  169. 169
  170. 170 endmodule
复制代码
DDR4控制模块例化了三个子模块,分别是DDR4读写模块、MIG IP核以及FIFO控制模块。大家学习了前面的“DDR4读写实验例程”后,再来看我们本节实验的DDR4控制模块就比较轻松了,因为DDR4控制模块是在“DDR4读写实验例程”的基础上修改过来的。相比较“DDR4读写实验例程”,本节实验DDR4控制模块最大的区别就是添加了BANK切换和FIFO的调用。首先我们先来看一下我们为什么要添加BANK切换。

在“DDR4读写测试实验”的程序中,读写操作地址都是DDR的同一存储空间,如果只使用一个存储空间缓存图像数据,那么这么做虽然保证了数据不会出现错乱的问题,但是会导致当前读取的图像与上一次存入的图像存在交错,如下图所示:
image023.png
图 32.4.3 DDR4单个BANK缓存图像机制

为了避免这一情况,我们在DDR的其它BANK中开辟一个相同大小的存储空间,使用乒乓操作的方式来写入和读取数据,所以本次实验在“DDR4读写测试实验”的程序里做了改动。

我们在DDR中开辟出2个存储空间进行乒乓操作用于缓存帧图像。在摄像头初始化结束后输出的第一个数据对应图像的第一个像素点,将其写入存储空间的首地址中。通过在DDR控制模块中对输出的图像数据进行计数,从而将它们分别写入相应的地址空间。计数达存入DDR的最大写地址后,完成一帧图像的存储,然后当帧复位到来时来切换BANK以达到乒乓操作的目的,并同时回到存储空间的首地址继续下一帧图像的存储。在显示图像时,LCD顶层模块从DDR存储空间的首地址开始读数据,同样对读过程进行计数,并将读取的图像数据分别显示到显示器相应的像素点位置。

图像数据总是在两个存储空间之间不断切换写入,而读请求信号在读完当前存储空间后判断哪个存储空间没有被写入,然后去读取没有被写入的存储空间。对于本次程序设计来说,数据写入较慢而读出较快,因此会出现同一存储空间被读取多次的情况,但保证了读出的数据一定是一帧完整的图像而不是两帧数据拼接的图像。当正在读取其中一个缓存空间,另一个缓存空间已经写完,并开始切换写入下一个缓存空间时,由于图像数据读出的速度总是大于写入的速度,因此,读出的数据仍然是一帧完整的图像。下面给出ddr4_rw模块的代码,一起来看看BANK是如何切换的。
  1. 1   module ddr4_rw(         
  2. 2       input          ui_clk               ,  //用户时钟
  3. 3       input          ui_clk_sync_rst      ,  //复位,高有效
  4. 4       input          init_calib_complete  ,  //ddr4初始化完成
  5. 5       input          app_rdy              ,  //MIG IP核空闲
  6. 6       input          app_wdf_rdy          ,  //MIG写FIFO空闲
  7. 7       input          app_rd_data_valid    ,  //读数据有效
  8. 8       input   [10:0 wfifo_rcount         ,  //写端口FIFO中的数据量
  9. 9       input   [10:0  rfifo_wcount         ,  //读端口FIFO中的数据量
  10. 10      input          rd_load              ,  //输出源更新信号
  11. 11      input          wr_load              ,  //输入源更新信号
  12. 12      input   [27:0 app_addr_rd_min      ,  //读ddr4的起始地址
  13. 13      input   [27:0 app_addr_rd_max      ,  //读ddr4的结束地址
  14. 14      input   [7:0  rd_bust_len          ,  //从ddr4中读数据时的突发长度
  15. 15      input   [27:0 app_addr_wr_min      ,  //写ddr4的起始地址
  16. 16      input   [27:0 app_addr_wr_max      ,  //写ddr4的结束地址
  17. 17      input   [7:0   wr_bust_len          ,  //从ddr4中写数据时的突发长度
  18. 18
  19. 19      input          ddr4_read_valid      ,  //ddr4 读使能   
  20. 20      input          ddr4_pingpang_en     ,  //ddr4 乒乓操作使能         
  21. 21      output         rfifo_wren           ,  //从ddr4读出数据的有效使能
  22. 22      output  [27:0  app_addr             ,  //ddr4地址                 
  23. 23      output         app_en               ,  //MIG IP核操作使能
  24. 24      output         app_wdf_wren         ,  //用户写使能   
  25. 25      output         app_wdf_end          ,  //突发写当前时钟最后一个数据
  26. 26      output  [2:0   app_cmd                 //MIG IP核操作命令,读或者写      
  27. 27      );
  28. 28      
  29. 29 //localparam
  30. 30 localparam IDLE        = 4'b0001;   //空闲状态
  31. 31 localparam ddr4_DONE   =4'b0010;   //ddr4初始化完成状态
  32. 32 localparam WRITE       = 4'b0100;   //读FIFO保持状态
  33. 33 localparam READ        = 4'b1000;   //写FIFO保持状态
  34. 34
  35. 35 //reg define
  36. 36 reg    [27:0 app_addr;             //ddr4地址
  37. 37 reg    [27:0 app_addr_rd;          //ddr4读地址
  38. 38 reg    [27:0 app_addr_wr;          //ddr4写地址
  39. 39 reg    [3:0  state_cnt;            //状态计数器
  40. 40 reg    [23:0 rd_addr_cnt;          //用户读地址计数
  41. 41 reg    [23:0 wr_addr_cnt;          //用户写地址计数
  42. 42 reg    [8:0  burst_rd_cnt;         //突发读次数计数器
  43. 43 reg    [8:0  burst_wr_cnt;         //突发写次数计数器   
  44. 44 reg    [10:0 raddr_rst_h_cnt;      //输出源的帧复位脉冲进行计数
  45. 45 reg    [27:0 app_addr_rd_min_a;    //读ddr4的起始地址
  46. 46 reg    [27:0 app_addr_rd_max_a;    //读ddr4的结束地址
  47. 47 reg    [7:0  rd_bust_len_a;        //从ddr4中读数据时的突发长度
  48. 48 reg    [27:0 app_addr_wr_min_a;    //写ddr4的起始地址
  49. 49 reg    [27:0 app_addr_wr_max_a;    //写ddr4的结束地址
  50. 50 reg    [7:0  wr_bust_len_a;        //从ddr4中写数据时的突发长度
  51. 51 reg           star_rd_flag;         //复位后写入2帧的标志信号
  52. 52 reg           rd_load_d0;
  53. 53 reg           rd_load_d1;
  54. 54 reg           raddr_rst_h;          //输出源的帧复位脉冲
  55. 55 reg           wr_load_d0;
  56. 56 reg           wr_load_d1;
  57. 57 reg           wr_rst;               //输入源帧复位标志
  58. 58 reg           rd_rst;               //输出源帧复位标志
  59. 59 reg           raddr_page;           //ddr4读地址切换信号
  60. 60 reg           waddr_page;           //ddr4写地址切换信号
  61. 61 reg           burst_done_wr;        //一次突发写结束信号
  62. 62 reg           burst_done_rd;        //一次读发写结束信号
  63. 63 reg           wr_end;               //一次突发写结束信号
  64. 64 reg           rd_end;               //一次读发写结束信号   
  65. 65
  66. 66 wire          rst_n;
  67. 67
  68. 68 //*****************************************************
  69. 69 //**                    main code
  70. 70 //*****************************************************
  71. 71
  72. 72 //将数据有效信号赋给wfifo写使能
  73. 73 assign rfifo_wren = app_rd_data_valid;
  74. 74
  75. 75 assign rst_n = ~ui_clk_sync_rst;
  76. 76
  77. 77 //在写状态MIG空闲且写有效,或者在读状态MIG空闲,此时使能信号为高,其他情况为低
  78. 78 assign app_en = ((state_cnt == WRITE && (app_rdy && app_wdf_rdy))
  79. 79                  ||(state_cnt == READ && app_rdy)) ? 1'b1:1'b0;
  80. 80                  
  81. 81 //在写状态,MIG空闲且写有效,此时拉高写使能
  82. 82 assign app_wdf_wren = (state_cnt == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
  83. 83
  84. 84 //由于我们ddr4芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
  85. 85 assign app_wdf_end = app_wdf_wren;
  86. 86
  87. 87 //处于读的时候命令值为1,其他时候命令值为0
  88. 88 assign app_cmd = (state_cnt == READ) ? 3'd1 :3'd0;
  89. 89
  90. 90 //将数据读写地址赋给ddr地址
  91. 91 always @(*)  begin
  92. 92      if(~rst_n)
  93. 93          app_addr <= 0;
  94. 94      else if(state_cnt == READ )
  95. 95          if(ddr4_pingpang_en)
  96. 96              app_addr <= {2'b0,raddr_page,app_addr_rd[24:0]};
  97. 97          else
  98. 98              app_addr <= {3'b0,app_addr_rd[24:0]};            
  99. 99      else if(ddr4_pingpang_en)
  100. 100         app_addr <= {2'b0,waddr_page,app_addr_wr[24:0]};
  101. 101     else
  102. 102         app_addr <= {3'b0,app_addr_wr[24:0]};        
  103. 103 end  
  104. 104
复制代码
在程序的第73行至88行,这些代码在“DDR4读写测试实验”程序中已有解释,此处不再详述。

在程序的第91行至103行,设计的代码的意义是根据状态计数器(state_cnt)的状态来判断写入ddr4的地址是写地址还是读地址,在第95行和99行,表示的意义是当乒乓使能为高时,对读写存储空间进行乒乓操作,保证读写的存储不会在同一个空间,反之,就不进行乒乓操作,使读写的存储在同一个空间。
  1. 105 //对信号进行打拍处理
  2. 106 always @(posedge ui_clk or negedge rst_n)  begin
  3. 107     if(~rst_n)begin
  4. 108         rd_load_d0 <= 0;
  5. 109         rd_load_d1 <= 0;
  6. 110         wr_load_d0 <= 0;
  7. 111         wr_load_d1 <= 0;                  
  8. 112     end   
  9. 113     else begin
  10. 114         rd_load_d0 <= rd_load;
  11. 115         rd_load_d1 <= rd_load_d0;  
  12. 116         wr_load_d0 <= wr_load;
  13. 117         wr_load_d1 <= wr_load_d0;               
  14. 118     end   
  15. 119 end
  16. 120
  17. 121 //对异步信号进行打拍处理
  18. 122 always @(posedge ui_clk or negedge rst_n)  begin
  19. 123     if(~rst_n)begin
  20. 124         app_addr_rd_min_a <= 0;
  21. 125         app_addr_rd_max_a <= 0;
  22. 126         rd_bust_len_a <= 0;
  23. 127         app_addr_wr_min_a <= 0;  
  24. 128         app_addr_wr_max_a <= 0;
  25. 129         wr_bust_len_a <= 0;                           
  26. 130     end   
  27. 131     else begin
  28. 132         app_addr_rd_min_a <= app_addr_rd_min;
  29. 133         app_addr_rd_max_a <= app_addr_rd_max;
  30. 134         rd_bust_len_a <= rd_bust_len;
  31. 135         app_addr_wr_min_a <= app_addr_wr_min;  
  32. 136         app_addr_wr_max_a <= app_addr_wr_max;
  33. 137         wr_bust_len_a <= wr_bust_len;                  
  34. 138     end   
  35. 139 end
  36. 140
  37. 141 //对输入源做个帧复位标志
  38. 142 always @(posedge ui_clk or negedge rst_n)  begin
  39. 143     if(~rst_n)
  40. 144         wr_rst <= 0;               
  41. 145     else if(wr_load_d0 && !wr_load_d1)
  42. 146         wr_rst <= 1;               
  43. 147     else
  44. 148         wr_rst <= 0;           
  45. 149 end
  46. 150
  47. 151 //对输出源做个帧复位标志
  48. 152 always @(posedge ui_clk or negedge rst_n)  begin
  49. 153     if(~rst_n)
  50. 154         rd_rst <= 0;               
  51. 155     else if(rd_load_d0 && !rd_load_d1)
  52. 156         rd_rst <= 1;               
  53. 157     else
  54. 158         rd_rst <= 0;           
  55. 159 end
  56. 160
  57. 161 //对输出源的读地址做个帧复位脉冲
  58. 162 always @(posedge ui_clk or negedge rst_n)  begin
  59. 163     if(~rst_n)
  60. 164         raddr_rst_h <= 1'b0;
  61. 165     else if(rd_load_d0 && !rd_load_d1)
  62. 166         raddr_rst_h <= 1'b1;
  63. 167     else if(app_addr_rd == app_addr_rd_min_a)   
  64. 168         raddr_rst_h <= 1'b0;
  65. 169     else
  66. 170         raddr_rst_h <= raddr_rst_h;              
  67. 171 end
  68. 172
  69. 173 //对输出源的帧复位脉冲进行计数
  70. 174 always @(posedge ui_clk or negedge rst_n)  begin
  71. 175     if(~rst_n)
  72. 176         raddr_rst_h_cnt <= 11'b0;
  73. 177     else if(raddr_rst_h)
  74. 178         raddr_rst_h_cnt <= raddr_rst_h_cnt + 1'b1;
  75. 179     else
  76. 180         raddr_rst_h_cnt <= 11'b0;            
  77. 181 end
  78. 182
  79. 183 //对输出源帧的读地址高位切换
  80. 184 always @(posedge ui_clk or negedge rst_n)  begin
  81. 185     if(~rst_n)
  82. 186         raddr_page <= 1'b0;
  83. 187     else if( rd_end)
  84. 188         raddr_page <= ~waddr_page;         
  85. 189     else
  86. 190         raddr_page <= raddr_page;           
  87. 191 end
  88. 192
复制代码
在程序的第106行至119行,这段代码是对输入进来的更新信号进行打拍处理,为了同步时钟域,方便后面产生复位信号。

在程序的第122行至139行,这段代码是对输入进来的更新信号进行打拍处理,为了同步时钟域,减少信号扇出和延迟。

在程序的第142行至159行,这段代码是更新信号的上升沿来做复位信号的。

在程序的第162行至181行,设计这几行的目的是为了保证在FIFO完全复位后再对MIG IP核进行操作,这样就防止了FIFO还没有复位完全就写入数据的情况,如下图所示,当复位结束后,full信号还将持续一段时间,在这段时间内是不能写入数据的,必须让信号raddr_rst_h拉高一段时间。
image025.png
图 32.4.4 rfifo的复位时序图

  1. 193 //对输入源帧的写地址高位切换
  2. 194 always @(posedge ui_clk or negedge rst_n)  begin
  3. 195     if(~rst_n)
  4. 196         waddr_page <= 1'b1;
  5. 197     else if( wr_end)
  6. 198         waddr_page <= ~waddr_page ;         
  7. 199     else
  8. 200         waddr_page <= waddr_page;           
  9. 201 end   
  10. 202
  11. 203 //ddr4读写逻辑实现
  12. 204 always @(posedge ui_clk or negedge rst_n) begin
  13. 205     if(~rst_n) begin
  14. 206         state_cnt    <= IDLE;              
  15. 207         wr_addr_cnt  <= 24'd0;      
  16. 208         rd_addr_cnt  <= 24'd0;      
  17. 209         app_addr_wr  <= 28'd0;   
  18. 210         app_addr_rd  <= 28'd0;
  19. 211         wr_end       <= 1'b0;
  20. 212         rd_end       <= 1'b0;      
  21. 213     end
  22. 214     else begin
  23. 215         case(state_cnt)
  24. 216             IDLE:begin
  25. 217                 if(init_calib_complete)
  26. 218                     state_cnt <= ddr4_DONE ;
  27. 219                 else
  28. 220                     state_cnt <= IDLE;
  29. 221             end
  30. 222             ddr4_DONE:begin
  31. 223                 if(wr_rst)begin   //当帧复位到来时,对寄存器进行复位
  32. 224                     state_cnt <= ddr4_DONE;
  33. 225                     wr_addr_cnt  <= 24'd0;  
  34. 226                     app_addr_wr <= app_addr_wr_min_a;                  
  35. 227                 end    //当读到结束地址对寄存器复位
  36. 228                 else if(app_addr_rd >= app_addr_rd_max_a - 8)begin  
  37. 229                         state_cnt <= ddr4_DONE;
  38. 230                         rd_addr_cnt  <= 24'd0;      
  39. 231                         app_addr_rd <= app_addr_rd_min_a;
  40. 232                         rd_end <= 1'b1;
  41. 233                 end    //当写到结束地址对寄存器复位
  42. 234                 else if(app_addr_wr >= app_addr_wr_max_a - 8)begin  
  43. 235                         state_cnt <= ddr4_DONE;
  44. 236                         rd_addr_cnt  <= 24'd0;      
  45. 237                         app_addr_wr <= app_addr_wr_min_a;
  46. 238                         wr_end <= 1'b1;
  47. 239                 end                           
  48. 240                 else if(wfifo_rcount >= wr_bust_len_a - 2 )begin  
  49. 241                     state_cnt <= WRITE;              //跳到写操作
  50. 242                     wr_addr_cnt  <= 24'd0;                       
  51. 243                     app_addr_wr <= app_addr_wr;      //写地址保持不变
  52. 244                 end
  53. 245                 else if(raddr_rst_h)begin           //当帧复位到来时,对寄存器进行复位
  54. 246                     if(raddr_rst_h_cnt >= 1000 && ddr4_read_valid)begin  
  55. 247                         state_cnt <= READ;         //保证读fifo在复位时不回写入数据
  56. 248                         rd_addr_cnt  <= 24'd0;      
  57. 249                         app_addr_rd <= app_addr_rd_min_a;
  58. 250                     end
  59. 251                     else begin
  60. 252                         state_cnt <= ddr4_DONE;
  61. 253                         rd_addr_cnt  <= 24'd0;      
  62. 254                         app_addr_rd <= app_addr_rd;                                
  63. 255                     end                                
  64. 256                 end      //当rfifo存储数据少于一次突发长度时,并且ddr已经写入了1帧数据
  65. 257                 else if(rfifo_wcount < rd_bust_len_a && ddr4_read_valid )begin  
  66. 258                     state_cnt <= READ;                             //跳到读操作
  67. 259                     rd_addr_cnt <= 24'd0;
  68. 260                     app_addr_rd <= app_addr_rd;      //读地址保持不变
  69. 261                 end
  70. 262                 else begin
  71. 263                     state_cnt <= state_cnt;   
  72. 264                     wr_addr_cnt  <= 24'd0;      
  73. 265                     rd_addr_cnt  <= 24'd0;  
  74. 266                     rd_end <= 1'b0;   
  75. 267                     wr_end <= 1'b0;                                       
  76. 268                 end
  77. 269             end   
  78. 270             WRITE:   begin
  79. 271                 if((wr_addr_cnt == (wr_bust_len_a - 1)) &&
  80. 272                 (app_rdy && app_wdf_rdy))begin    //写到设定的长度跳到等待状态                  
  81. 273                     state_cnt    <= ddr4_DONE;       //写到设定的长度跳到等待状态               
  82. 274                     app_addr_wr <= app_addr_wr + 8;  //一次性写进8个数,故加8
  83. 275                 end      
  84. 276                 else if(app_rdy && app_wdf_rdy)begin   //写条件满足
  85. 277                     wr_addr_cnt  <= wr_addr_cnt + 1'd1;//写地址计数器自加
  86. 278                     app_addr_wr  <= app_addr_wr + 8;   //一次性写进8个数,故加8
  87. 279                 end
  88. 280                 else begin                             //写条件不满足,保持当前值     
  89. 281                     wr_addr_cnt  <= wr_addr_cnt;
  90. 282                     app_addr_wr  <= app_addr_wr;
  91. 283                 end
  92. 284             end
  93. 285             READ:begin                      //读到设定的地址长度   
  94. 286                 if((rd_addr_cnt == (rd_bust_len_a - 1)) && app_rdy)begin
  95. 287                     state_cnt   <=ddr4_DONE;          //则跳到空闲状态
  96. 288                     app_addr_rd <= app_addr_rd + 8;
  97. 289
  98. 290                 end      
  99. 291                 else if(app_rdy)begin               //若MIG已经准备好,则开始读
  100. 292                     rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址计数器每次加一
  101. 293                     app_addr_rd <= app_addr_rd + 8; //一次性读出8个数,ddr4地址加8
  102. 294                 end
  103. 295                 else begin                         //若MIG没准备好,则保持原值
  104. 296                     rd_addr_cnt <= rd_addr_cnt;
  105. 297                     app_addr_rd <= app_addr_rd;
  106. 298                 end
  107. 299             end            
  108. 300             default:begin
  109. 301                 state_cnt    <= IDLE;
  110. 302                 wr_addr_cnt  <= 24'd0;
  111. 303                 rd_addr_cnt  <= 24'd0;
  112. 304             end
  113. 305         endcase
  114. 306     end
  115. 307 end                          
  116. 308
  117. 309 endmodule
复制代码
程序中第184至201行定义了两个用于切换BANK的信号(waddr_page信号和raddr_page信号), waddr_page信号根据写DDR4的结束地址标志的高电平进行翻转,raddr_page信号是读DDR4的结束地址标志的高电平时将waddr_page信号取反赋值得到的。程序中定义了两个信号app_addr_wr和信号app_addr_rd分别代表DDR4的写入地址和读出地址,其最高三位表示BANK的地址,切换BANK时改变app_addr_wr和app_addr_rd的高三位地址,相当于数据在BANK0和BANK2之间切换。当waddr_page=1时,数据写入BANK0,从BANK2中读出数据;当waddr_page=0时,数据写入BANK2,从BANK0中读出数据。
image027.png
图 32.4.5 ddr读写状态跳转图

如程序中第204至307行所示,这段代码是DDR4读写逻辑实现,转态跳转如上图所示。

在复位结束后,如果DDR4没有初始化完成,那么状态一直在空闲状态(IDLE),否则跳到DDR4空闲状态(ddr4_DONE)。在DDR4空闲状态,当输入端的帧复位到来时,对写地址计数器和写地址进行复位操作。

程序中第228至239行,当读写地址达到设定值时,对地址和计数器进行复位操作,同时拉高结束地址标志信号。

如程序中第240至244行所示,FIFO控制模块优先处理DDR4写请求,以免写FIFO溢出时,用于写入DDR4的数据丢失。当写FIFO中的数据量大于写突发长度时,执行写DDR4操作;当读FIFO中的数据量小于读突发长度时,执行读DDR4操作。
image029.png
图 32.4.6 写状态时序

image031.png
图 32.4.7 读状态时序
当写fifo的存储数据达到一次写操作的长度时,就将状态跳到写状态(WRITE),如图 32.4.6所示。这里说明下在代码第240行减2的原因,因为在FIFO调度模块中用的FIFO不是标准FIFO的模式,是First Word Fall Through模式,这种模式的特点是数据和读使能同时出来。在地址复位寄存器为高,且持续的时间大于1000个时钟后,将状态跳转到读状态(READ),否则状态继续留在DDR4空闲状态,这样做的目的是为了保证在FIFO复位的时候不会向其写入数据。当rfifo里面的数据不满一次读操作的情况下,将状态跳转到读状态(READ),如图 32.4.7所示,否则留在DDR4空闲状态。

在写状态中,当写地址计数器达到一次写操作的时候,并且握手信号命令使能app_rdy和写数据有效使能app_wdf_rdy同时为高的时候,将状态跳转到DDR4空闲状态。细心的朋友可能发现,这里的地址为什么加8,这是因为用户端在每一个用户时钟进行一个128bit的数据的传输,在DDR4物理芯片端需要分8次传输,每次传输一个地址位宽16bit,8次就需要8个地址。同理读状态也是如此,此处不在详述。

接下来我们再来看看FIFO读写模块的代码,如下所示:
  1. 1  module ddr4_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       ,  //从ddr4读出数据的有效使能
  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  [127:0 datain_t         ;  //由16bit输入源数据移位拼接得到
  23. 23 reg  [7:0   cam_data_d0       ;
  24. 24 reg  [4:0   i_d0              ;
  25. 25 reg  [30:0 rd_load_d         ;  //由输出源场信号移位拼接得到           
  26. 26 reg  [6:0   byte_cnt          ;  //写数据移位计数器
  27. 27 reg  [127:0 data             ;  //rfifo输出数据打拍得到
  28. 28 reg  [15:0  pic_data          ;  //有效数据
  29. 29 reg  [4:0   i                 ;  //读数据移位计数器
  30. 30 reg  [15:0 wr_load_d         ;  //由输入源场信号移位拼接得到
  31. 31 reg  [3:0   cmos_ps_cnt       ;  //等待帧数稳定计数器
  32. 32 reg          cam_href_d0       ;
  33. 33 reg          cam_href_d1       ;
  34. 34 reg          wr_load_d0        ;
  35. 35 reg          rd_load_d0        ;
  36. 36 reg          rdfifo_rst_h      ;  //rfifo复位信号,高有效
  37. 37 reg          wr_load_d1        ;
  38. 38 reg          wfifo_rst_h       ;  //wfifo复位信号,高有效
  39. 39 reg          wfifo_wren        ;  //wfifo写使能信号
  40. 40
  41. 41 //wire define
  42. 42 wire [127:0 rfifo_dout       ;  //rfifo输出数据   
  43. 43 wire [127:0 wfifo_din        ;  //wfifo写数据
  44. 44 wire [15:0  dataout[0:15     ;  //定义输出数据的二维数组
  45. 45 wire         rfifo_rden        ;  //rfifo的读使能
  46. 46
  47. 47 //*****************************************************
  48. 48 //**                    main code
  49. 49 //*****************************************************  
  50. 50
  51. 51 //rfifo输出的数据存到二维数组
  52. 52 assign dataout[0 = data[127:112];
  53. 53 assign dataout[1 = data[111:96];
  54. 54 assign dataout[2 = data[95:80];
  55. 55 assign dataout[3 = data[79:64];
  56. 56 assign dataout[4 = data[63:48];
  57. 57 assign dataout[5 = data[47:32];
  58. 58 assign dataout[6 = data[31:16];
  59. 59 assign dataout[7 = data[15:0];
  60. 60
  61. 61 assign wfifo_din = datain_t ;
  62. 62
  63. 63 //移位寄存器计满时,从rfifo读出一个数据
  64. 64 assign rfifo_rden = (rdata_req && (i==7)) ? 1'b1  :  1'b0;
  65. 65
  66. 66 //16位数据转128位RGB565数据        
  67. 67 always @(posedge wr_clk or negedge rst_n) begin
  68. 68      if(!rst_n) begin
  69. 69          datain_t <= 0;
  70. 70          byte_cnt <= 0;
  71. 71      end
  72. 72      else if(datain_valid) begin
  73. 73          if(byte_cnt == 7)begin
  74. 74              byte_cnt <= 0;
  75. 75              datain_t <= {datain_t[111:0],datain};
  76. 76          end
  77. 77          else begin
  78. 78              byte_cnt <= byte_cnt + 1;
  79. 79              datain_t <= {datain_t[111:0],datain};
  80. 80          end
  81. 81      end
  82. 82      else begin
  83. 83          byte_cnt <= byte_cnt;
  84. 84          datain_t <= datain_t;
  85. 85      end   
  86. 86 end
  87. 87
复制代码
在程序的第51行至59行是将DDR4输出的128bit的数据存入一个二维数组(dataout)中,方便后面的数据拆解。

在程序的第64行,表示当读请求信号(rdata_req)拉高8个时钟周期后,rfifo的读使能(rfifo_rden)拉高一个周期,这么做的原因是 rfifo的数据输出为128bit而本模块的数据输出(pic_data)为16bit。
image033.png
图 32.4.8 写数据数据转换

image035.png
图 32.4.9 读数据数据转换
在程序的第66行至86行对摄像头输出的数据进行了移位拼接,具体时序如图 32.4.8所示。因为摄像头的输入数据(datain)为16bit每周期,而MIG核每一个时钟周期写入的信号位宽为128bit,所以写数据移位计数器(byte_cnt)信号计到7时清零,同时FIFO的写使能wfifo_wren拉高一个周期。同理LCD顶层模块的输入也是16bit一个时钟周期,所以在程序的第107行至131行对128bit的数据进行了拆解,具体时序如图 32.4.9所示。
  1. 88 //wfifo写使能产生
  2. 89 always @(posedge wr_clk or negedge rst_n) begin
  3. 90      if(!rst_n)
  4. 91          wfifo_wren <= 0;
  5. 92      else if(wfifo_wren == 1)
  6. 93          wfifo_wren <= 0;
  7. 94      else if(byte_cnt ==7 && datain_valid )  //输入源数据传输8次,写使能拉高一次
  8. 95          wfifo_wren <= 1;
  9. 96      else
  10. 97          wfifo_wren <= 0;
  11. 98 end
  12. 99      
  13. 100 always @(posedge rd_clk or negedge rst_n) begin
  14. 101     if(!rst_n)
  15. 102         data <= 127'b0;
  16. 103     else
  17. 104         data <= rfifo_dout;
  18. 105 end     
  19. 106
  20. 107 //对rfifo出来的128bit数据拆解成16个16bit数据
  21. 108 always @(posedge rd_clk or negedge rst_n) begin
  22. 109     if(!rst_n) begin
  23. 110         pic_data <= 16'b0;
  24. 111         i <=0;
  25. 112         i_d0 <= 0;
  26. 113     end
  27. 114     else if(rdata_req)begin
  28. 115         if(i == 7)begin
  29. 116             pic_data <= dataout[i_d0];
  30. 117             i <= 0;
  31. 118             i_d0 <= i;
  32. 119         end
  33. 120         else begin
  34. 121             pic_data <= dataout[i_d0];
  35. 122             i <= i + 1;
  36. 123             i_d0 <= i;
  37. 124         end
  38. 125     end
  39. 126     else begin
  40. 127         pic_data <= pic_data;
  41. 128         i <=0;
  42. 129         i_d0 <= 0;
  43. 130     end
  44. 131 end  
  45. 132
  46. 133 always @(posedge clk_100 or negedge rst_n) begin
  47. 134     if(!rst_n)
  48. 135         rd_load_d0 <= 1'b0;
  49. 136     else
  50. 137         rd_load_d0 <= rd_load;      
  51. 138 end
  52. 139
  53. 140 //对输出源场信号进行移位寄存
  54. 141 always @(posedge clk_100 or negedge rst_n) begin
  55. 142     if(!rst_n)
  56. 143         rd_load_d <= 1'b0;
  57. 144     else
  58. 145         rd_load_d <= {rd_load_d[30:0],rd_load_d0};      
  59. 146 end
  60. 147
复制代码
在程序的第88行至98行,当写数据有效使能信号(datain_valid)拉高8个时钟周期后,wfifo的写使能才拉高一个周期,这么做的原因是wfifo的数据输入为128bit而本模块的数据输入(datain)为16bit。

在程序的第132行至138行,是对LCD的场信号进行时钟域同步,方便后面代码的调用,以减少信号的跨时钟域。程序的第158行至168行,原因也是如此。

在程序的第140行至146行,对LCD的场信号进行移位寄存,方便后面产生一段复位电平信号。
  1. 148 //产生一段复位电平,满足fifo复位时序
  2. 149 always @(posedge clk_100 or negedge rst_n) begin
  3. 150     if(!rst_n)
  4. 151         rdfifo_rst_h <= 1'b0;
  5. 152     else if(rd_load_d[0 && !rd_load_d[29])
  6. 153         rdfifo_rst_h <= 1'b1;   
  7. 154     else
  8. 155         rdfifo_rst_h <= 1'b0;              
  9. 156 end  
  10. 157
  11. 158 //对输入源场信号进行移位寄存
  12. 159 always @(posedge wr_clk or negedge rst_n) begin
  13. 160     if(!rst_n)begin
  14. 161         wr_load_d0 <= 1'b0;
  15. 162         wr_load_d  <= 16'b0;        
  16. 163     end     
  17. 164     else begin
  18. 165         wr_load_d0 <= wr_load;
  19. 166         wr_load_d <= {wr_load_d[14:0],wr_load_d0};      
  20. 167     end               
  21. 168 end  
  22. 169
  23. 170 //产生一段复位电平,满足fifo复位时序
  24. 171 always @(posedge wr_clk or negedge rst_n) begin
  25. 172     if(!rst_n)
  26. 173     wfifo_rst_h <= 1'b0;         
  27. 174     else if(wr_load_d[0 && !wr_load_d[15])
  28. 175     wfifo_rst_h <= 1'b1;      
  29. 176     else
  30. 177     wfifo_rst_h <= 1'b0;                     
  31. 178 end   
  32. 179
  33. 180 rd_fifo u_rd_fifo (
  34. 181 .rst               (~rst_n|rdfifo_rst_h),                  
  35. 182 .wr_clk            (clk_100),   
  36. 183 .rd_clk            (rd_clk),   
  37. 184 .din               (rfifo_din),
  38. 185 .wr_en             (rfifo_wren),
  39. 186 .rd_en             (rfifo_rden),
  40. 187 .dout              (rfifo_dout),
  41. 188 .full              (),         
  42. 189 .empty             (),         
  43. 190 .rd_data_count     (),  
  44. 191 .wr_data_count     (rfifo_wcount),  
  45. 192 .wr_rst_busy       (),      
  46. 193 .rd_rst_busy       ()      
  47. 194 );
  48. 195
  49. 196 wr_fifo u_wr_fifo (
  50. 197 .rst               (~rst_n|wfifo_rst_h),
  51. 198 .wr_clk            (wr_clk),            
  52. 199 .rd_clk            (clk_100),           
  53. 200 .din               (wfifo_din),         
  54. 201 .wr_en             (wfifo_wren),        
  55. 202 .rd_en             (wfifo_rden),        
  56. 203 .dout              (wfifo_dout ),      
  57. 204 .full              (),                 
  58. 205 .empty             (),                 
  59. 206 .rd_data_count     (wfifo_rcount),  
  60. 207 .wr_data_count     (),  
  61. 208 .wr_rst_busy       (),      
  62. 209 .rd_rst_busy       ()   
  63. 210 );
  64. 211
  65. 212 endmodule
复制代码
在程序的第148行至156行和第170行至178行是分别对rfifo和wfifo进行帧复位,这里进行复位是为了在帧结束后清空FIFO,保证下帧数据到来之前FIFO为空。细心的朋友应该发现了一般复位是一个时钟周期,为什么这里的复位是一段电平,因为XILINX的手册规定FIFO的复位周期必须大于读写时钟的5个时钟周期,才能使FIFO完全复位。

看完了DDR4控制模块的代码我们再来看看图像分辨率模块的代码,如下所示:
  1. 1  module picture_size (
  2. 2       input             rst_n         ,
  3. 3       input              clk           ,         
  4. 4       input       [15:0 ID_lcd        ,
  5. 5               
  6. 6       output      [12:0 cmos_h_pixel  ,
  7. 7       output      [12:0 cmos_v_pixel  ,   
  8. 8       output      [12:0 total_h_pixel ,
  9. 9       output      [12:0 total_v_pixel ,
  10. 10      output      [23:0 sdram_max_addr
  11. 11 );
  12. 12
  13. 13 reg [12:0 cmos_h_pixel;
  14. 14 reg [12:0 cmos_v_pixel;   
  15. 15 reg [12:0 total_h_pixel;
  16. 16 reg [12:0 total_v_pixel;
  17. 17 reg [23:0 sdram_max_addr;
  18. 18
  19. 19 //parameter define
  20. 20 parameter  ID_4342 =   16'h4342;
  21. 21 parameter  ID_7084 =   16'h7084;
  22. 22 parameter  ID_7016 =   16'h7016;
  23. 23 parameter  ID_1018 =   16'h1018;
  24. 24
  25. 25 //*****************************************************
  26. 26 //**                    main code                     
  27. 27 //*****************************************************
  28. 28
  29. 29 //配置摄像头输出尺寸的大小
  30. 30 always @(posedge clk or negedge rst_n) begin
  31. 31      if(!rst_n) begin
  32. 32          cmos_h_pixel <= 13'b0;
  33. 33         cmos_v_pixel <= 13'd0;
  34. 34          sdram_max_addr <= 23'd0;        
  35. 35      end
  36. 36      else begin   
  37. 37          case(ID_lcd )
  38. 38              16'h4342 : begin
  39. 39                  cmos_h_pixel   =13'd480;   
  40. 40                  cmos_v_pixel   =13'd272;
  41. 41                  sdram_max_addr = 23'd130560;
  42. 42              end
  43. 43              16'h7084 : begin
  44. 44                  cmos_h_pixel   =13'd800;   
  45. 45                  cmos_v_pixel   =13'd480;           
  46. 46                  sdram_max_addr = 23'd384000;
  47. 47              end
  48. 48              16'h7016 : begin
  49. 49                  cmos_h_pixel   =13'd1024;   
  50. 50                  cmos_v_pixel   =13'd600;           
  51. 51                  sdram_max_addr = 23'd614400;
  52. 52              end   
  53. 53              16'h1018 : begin
  54. 54                  cmos_h_pixel   =13'd1280;   
  55. 55                  cmos_v_pixel   =13'd800;           
  56. 56                  sdram_max_addr = 23'd1024000;
  57. 57              end
  58. 58          default : begin
  59. 59                  cmos_h_pixel   =13'd800;   
  60. 60                  cmos_v_pixel   =13'd480;           
  61. 61                  sdram_max_addr = 23'd384000;
  62. 62          end
  63. 63          endcase
  64. 64      end   
  65. 65 end
  66. 66
  67. 67 //对HTS及VTS的配置会影响摄像头输出图像的帧率
  68. 68 always @(*) begin
  69. 69      case(ID_lcd)
  70. 70          ID_4342 : begin
  71. 71              total_h_pixel = 13'd1800;
  72. 72              total_v_pixel = 13'd1000;
  73. 73          end
  74. 74          ID_7084 : begin  
  75. 75              total_h_pixel = 13'd1800;
  76. 76              total_v_pixel = 13'd1000;
  77. 77          end
  78. 78          ID_7016 : begin  
  79. 79              total_h_pixel = 13'd2200;
  80. 80              total_v_pixel = 13'd1000;
  81. 81          end
  82. 82          ID_1018 : begin
  83. 83              total_h_pixel = 13'd2570;
  84. 84              total_v_pixel = 13'd980;
  85. 85          end
  86. 86      default : begin
  87. 87              total_h_pixel = 13'd1800;
  88. 88              total_v_pixel = 13'd1000;
  89. 89      end
  90. 90      endcase
  91. 91 end
  92. 92
  93. 93 endmodule
复制代码
图像分辨率模块的代码时是非常简单的就是根据LCD驱动模块传递过来的不同ID值去配置不同的图像分辨率参数(cmos_h_pixel、cmos_v_pixel、total_h_pixel、total_v_pixel)和DDR4最大读写地址(sdram_max_addr),所以这里关于图像分辨率模块的代码这里就不作过多讲解了。

看完图像分辨率模块的代码后我们再来看看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  [23: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
  25. 25 //wire define
  26. 26 wire  [15:0 lcd_rgb_565;           //输出的16位lcd数据
  27. 27 wire  [23:0 lcd_rgb_o ;            //LCD 输出颜色数据
  28. 28 wire  [23:0 lcd_rgb_i ;            //LCD 输入颜色数据
  29. 29 wire         lcd_pclk_180;
  30. 30 //*****************************************************
  31. 31 //**                    main code
  32. 32 //*****************************************************
  33. 33 //将摄像头16bit数据转换为24bit的lcd数据
  34. 34 assign lcd_rgb_o = {lcd_rgb_565[15:11],3'b000,lcd_rgb_565[10:5],2'b00,
  35. 35                      lcd_rgb_565[4:0],3'b000};         
  36. 36 assign lcd_pclk= (lcd_id==16'h1018)?~lcd_pclk_180:lcd_pclk_180;
  37. 37 //像素数据方向切换
  38. 38 assign lcd_rgb = lcd_de ?  lcd_rgb_o :  {24{1'bz}};
  39. 39 assign lcd_rgb_i = lcd_rgb;
  40. 40              
  41. 41 //时钟分频模块   
  42. 42 clk_div u_clk_div(
  43. 43      .clk                    (sys_clk  ),
  44. 44      .rst_n                  (sys_rst_n),
  45. 45      .lcd_id                 (lcd_id   ),
  46. 46      .lcd_pclk               (lcd_clk  )
  47. 47      );  
  48. 48
  49. 49 //读LCD ID模块
  50. 50 rd_id u_rd_id(
  51. 51      .clk                    (sys_clk  ),
  52. 52      .rst_n                  (sys_rst_n),
  53. 53      .lcd_rgb                (lcd_rgb_i),
  54. 54      .lcd_id                 (lcd_id   )
  55. 55      );  
  56. 56
  57. 57 //lcd驱动模块
  58. 58 lcd_driver u_lcd_driver(           
  59. 59      .lcd_clk        (lcd_clk),   
  60. 60      .sys_rst_n      (sys_rst_n & sys_init_done),
  61. 61      .lcd_id         (lcd_id),   
  62. 62
  63. 63      .lcd_hs         (lcd_hs),      
  64. 64      .lcd_vs         (lcd_vs),      
  65. 65      .lcd_de         (lcd_de),      
  66. 66      .lcd_rgb        (lcd_rgb_565),
  67. 67      .lcd_bl         (lcd_bl),
  68. 68      .lcd_rst        (lcd_rst),
  69. 69      .lcd_pclk       (lcd_pclk_180),
  70. 70      
  71. 71      .pixel_data     (data_in),
  72. 72      .data_req       (data_req),
  73. 73      .out_vsync      (out_vsync),
  74. 74      .h_disp         (h_disp),
  75. 75      .v_disp         (v_disp),
  76. 76      .pixel_xpos     (pixel_xpos),
  77. 77      .pixel_ypos     (pixel_ypos)
  78. 78      );
  79. 79                  
  80. 80 endmodule
复制代码
LCD驱动模块代码的顶层例化了三个子模块,分别是时钟分频模块(clk_div)、ID读取模块(rd_id)以及LCD驱动时序模块(lcd_driver)。其中时钟分频模块的作用是根据外部不同的显示屏提供与显示屏相对应的驱动时钟;ID读取模块的作用就是读取外部显示屏的ID;LCD驱动时序模块主要就是生成LCD的驱动时序。LCD驱动模块顶层主要作用就是例化子模块没什么好讲的,但是有两个地方需要注意,一个是代码第34~35行的数据转换,我们的摄像头数据是rgb565的数据格式而显示屏的数据格式是rgb888格式因此在这里需要通过补零的手段去将rgb565格式转换成rgb888格式。第二个需要注意的点就是代码第38~39行,因为lcd_rgb信号是一个双向通道(24bit位宽),因此我们用零个assign语句来生成一个三态门,由lcd_de信号决定lcd_rgb通道是输入还是输出。关于例化的三个子模块代码在前面“LCD彩条显示实验”已经讲解过了这里就不在重复啰嗦了。

到这里整个OV5640 LCD显示实验的代码就全部讲解完了。

32.5 下载验证
首先将FPC排线一端与RGB-LCD模块上的RGB接口连接,另一端与DFZU2EG/4EV MPSoC开发板上的RGB-LCD接口连接。与RGB-LCD模块连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝里(靠近FPGA端)插入连接器,最后将黑色翻盖压下以固定FPC排线,如图 32.5.1所示。
image038.png
图 32.5.1 正点原子RGBLCD模块FPC连接器

与DFZU2EG/4EV MPSoC开发板上的RGB TFTLCD接口连接时,先掀起开发板上的棕色的翻盖到垂直开发板方向,将FPC排线蓝色面朝棕色翻盖垂直插入连接器,最后将棕色的翻盖下按至水平方向,如图 32.5.2所示。
image039.png
图 32.5.2 DFZU2EG/4EV MPSoC开发板连接RGB-LCD液晶屏

然后将双目OV5640摄像头(本节实验只使用双目摄像头模块的其中一个摄像头)模块插在DFZU2EG/4EV MPSoC开发板J19扩展口上,摄像头实物连接如上图所示。最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,接下来连接电源线后拨动开关按键给开发板上电。

接下来我们下载程序,验证OV5640 RGB-LCD实时显示功能。下载完成后观察RGB-LCD模块显示的图案如下图所示,说明OV5640 RGB-LCD实时显示程序下载验证成功。
image041.png
图 32.5.3 RGB-LCD实时显示图像
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2024-10-4 06:48

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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