本帖最后由 正点原子运营 于 2023-4-25 14:49 编辑
第三十二章 OV5640摄像头RGB-LCD显示实验
1)实验平台:正点原子 DFZU2EG/4EV MPSoC开发板
2) 章节摘自【正点原子】DFZU2EG/4EV MPSoC之FPGA开发指南 V1.0
6)FPGA技术交流QQ群:994244016
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的功能框图如下图所示: 由上图可知,时序发生器(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的写传输协议如下图所示: 上图中的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的读传输协议。 由上图可知,SCCB读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据。上图中的NA位由主机(这里指FPGA)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。
需要注意的是,对于OV5640摄像头来说,由于其可配置的寄存器非常多,所以OV5640摄像头的寄存器地址位数是16位(两个字节),OV5640 SCCB的写传输协议如下图所示: 图 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”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。 OV5640的寄存器较多,对于其它寄存器的描述可以参考OV5640的数据手册。需要注意的是,OV5640的数据手册并没有提供全部的寄存器描述,而大多数必要的寄存器配置在ov5640的软件应用手册中可以找到,可以结合这两个手册学习如何对OV5640进行配置。
输出图像参数设置 接下来,我们介绍一下OV5640的ISP输入窗口设置、预缩放窗口设置和输出大小窗口设置,这几个设置与我们的正常使用密切相关,有必要了解一下,它们的设置关系如下图所示: 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格式。下图为摄像头输出的时序图。 在介绍时序图之前先了解几个基本的概念。
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格式的时序图: 上图中的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摄像头图像的采集和显示。原理图如下图所示: ATK-Dual-OV5640是正点原子推出的一款高性能双目OV5640 500W像素高清摄像头模块。该模块通过2*20排母(2.54mm间距)同外部连接,我们将摄像头的排母直接插在开发板上的扩展接口即可,模块外观如图 32.3.2所示: 图 32.3.2 ATK-OV5640摄像头模块实物图 我们在前面说过,OV5640在RGB565模式中只有高8位数据是有效的即D[9:2],而我们的摄像头排母上数据引脚的个数是8位。实际上,摄像头排母上的8位数据连接的就是OV5640传感器的D[9:2],所以我们直接使用摄像头排母上的8位数据引脚即可。
由于RGB LCD屏的引脚数目较多,且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配, 如下表所示: 摄像头XDC约束文件如下: - set_propertyCLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_IBUF_inst/O]
- set_property-dict {PACKAGE_PIN C13 IOSTANDARD LVCMOS33} [get_ports cam_pclk]
- set_property-dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports cam_rst_n]
- set_property-dict {PACKAGE_PIN B15 IOSTANDARD LVCMOS33} [get_ports cam_pwdn]
- set_property-dict {PACKAGE_PIN E15 IOSTANDARD LVCMOS33} [get_ports {cam_data[0]}]
- set_property-dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33} [get_ports {cam_data[1]}]
- set_property-dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33} [get_ports {cam_data[2]}]
- set_property-dict {PACKAGE_PIN D14 IOSTANDARD LVCMOS33} [get_ports {cam_data[3]}]
- set_property-dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33} [get_ports {cam_data[4]}]
- set_property-dict {PACKAGE_PIN B13 IOSTANDARD LVCMOS33} [get_ports {cam_data[5]}]
- set_property-dict {PACKAGE_PIN C14 IOSTANDARD LVCMOS33} [get_ports {cam_data[6]}]
- set_property-dict {PACKAGE_PIN A13 IOSTANDARD LVCMOS33} [get_ports {cam_data[7]}]
- set_property-dict {PACKAGE_PIN G14 IOSTANDARD LVCMOS33} [get_ports cam_vsync]
- set_property-dict {PACKAGE_PIN G13 IOSTANDARD LVCMOS33} [get_ports cam_href]
- set_property-dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33} [get_ports cam_scl]
- set_property-dict {PACKAGE_PIN F15 IOSTANDARD LVCMOS33} [get_ports cam_sda]
- set_propertyPULLUP true [get_ports cam_scl]
- set_propertyPULLUP true [get_ports cam_sda]]
复制代码 32.4 程序设计图 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显示屏可以正常的显示摄像头捕捉的图像数据。
顶层模块的原理图如下图所示: 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 module ov5640_dri (
- 2 input clk , //时钟
- 3 input rst_n , //复位信号,低电平有效
- 4 //摄像头接口
- 5 input cam_pclk , //cmos 数据像素时钟
- 6 input cam_vsync , //cmos 场同步信号
- 7 input cam_href , //cmos 行同步信号
- 8 input [7:0 cam_data , //cmos 数据
- 9 output cam_rst_n , //cmos 复位信号,低电平有效
- 10 output cam_pwdn , //cmos 电源休眠模式选择信号
- 11 output cam_scl , //cmos SCCB_SCL线
- 12 input cam_sda_i , //cmos SCCB_SDA输入
- 13 output cam_sda_o , //cmos SCCB_SDA输出
- 14 output cam_sda_t , //cmos SCCB_SDA使能
- 15
- 16 //摄像头分辨率配置接口
- 17 input [12:0 cmos_h_pixel , //水平方向分辨率
- 18 input [12:0 cmos_v_pixel , //垂直方向分辨率
- 19 input [12:0 total_h_pixel , //水平总像素大小
- 20 input [12:0 total_v_pixel , //垂直总像素大小
- 21 input capture_start , //图像采集开始信号
- 22 output cam_init_done , //摄像头初始化完成
- 23
- 24 //用户接口
- 25 output cmos_frame_vsync, //帧有效信号
- 26 output cmos_frame_href , //行有效信号
- 27 output cmos_frame_valid, //数据有效使能信号
- 28 output [15:0 cmos_frame_data //有效数据
- 29 );
- 30
- 31 //parameter define
- 32 parameter SLAVE_ADDR = 7'h3c ;//OV5640的器件地址7'h3c
- 33 parameter BIT_CTRL =1'b1 ; //OV5640的字节地址为16位 0:8位 1:16位
- 34 parameter CLK_FREQ =27'd50_000_000; //i2c_dri模块的驱动时钟频率
- 35 parameter I2C_FREQ =18'd250_000 ; //I2C的SCL时钟频率,不超过400KHz
- 36
- 37 //wire difine
- 38 wire i2c_exec ; //I2C触发执行信号
- 39 wire [23:0 i2c_data ; //I2C要配置的地址与数据(高8位地址,低8位数据)
- 40 wire i2c_done ; //I2C寄存器配置完成信号
- 41 wire i2c_dri_clk ; //I2C操作时钟
- 42 wire [ 7:0 i2c_data_r ; //I2C读出的数据
- 43 wire i2c_rh_wl ; //I2C读写控制信号
- 44
- 45 //*****************************************************
- 46 //** main code
- 47 //*****************************************************
- 48
- 49 //电源休眠模式选择 0:正常模式 1:电源休眠模式
- 50 assign cam_pwdn = 1'b0;
- 51 assign cam_rst_n = 1'b1;
- 52
- 53 //I2C配置模块
- 54 i2c_ov5640_rgb565_cfg u_i2c_cfg(
- 55 .clk (i2c_dri_clk),
- 56 .rst_n (rst_n),
- 57
- 58 .i2c_exec (i2c_exec),
- 59 .i2c_data (i2c_data),
- 60 .i2c_rh_wl (i2c_rh_wl), //I2C读写控制信号
- 61 .i2c_done (i2c_done),
- 62 .i2c_data_r (i2c_data_r),
- 63
- 64 .cmos_h_pixel (cmos_h_pixel), //CMOS水平方向像素个数
- 65 .cmos_v_pixel (cmos_v_pixel) , //CMOS垂直方向像素个数
- 66 .total_h_pixel (total_h_pixel), //水平总像素大小
- 67 .total_v_pixel (total_v_pixel), //垂直总像素大小
- 68
- 69 .init_done (cam_init_done)
- 70 );
- 71
- 72 //I2C驱动模块
- 73 i2c_dri #(
- 74 .SLAVE_ADDR (SLAVE_ADDR), //参数传递
- 75 .CLK_FREQ (CLK_FREQ ),
- 76 .I2C_FREQ (I2C_FREQ )
- 77 )
- 78 u_i2c_dr(
- 79 .clk (clk),
- 80 .rst_n (rst_n ),
- 81
- 82 .i2c_exec (i2c_exec ),
- 83 .bit_ctrl (BIT_CTRL ),
- 84 .i2c_rh_wl (i2c_rh_wl), //固定为0,只用到了IIC驱动的写操作
- 85 .i2c_addr (i2c_data[23:8]),
- 86 .i2c_data_w (i2c_data[7:0]),
- 87 .i2c_data_r (i2c_data_r),
- 88 .i2c_done (i2c_done ),
- 89
- 90 .scl (cam_scl ),
- 91 .sda_i (cam_sda_i ),
- 92 .sda_o (cam_sda_o ),
- 93 .sda_t (cam_sda_t ),
- 94 .dri_clk (i2c_dri_clk) //I2C操作时钟
- 95 );
- 96
- 97 //CMOS图像数据采集模块
- 98 cmos_capture_data u_cmos_capture_data( //系统初始化完成之后再开始采集数据
- 99 .rst_n (rst_n & capture_start),
- 100
- 101 .cam_pclk (cam_pclk),
- 102 .cam_vsync (cam_vsync),
- 103 .cam_href (cam_href),
- 104 .cam_data (cam_data),
- 105
- 106 .cmos_frame_vsync (cmos_frame_vsync),
- 107 .cmos_frame_href (cmos_frame_href ),
- 108 .cmos_frame_valid (cmos_frame_valid), //数据有效使能信号
- 109 .cmos_frame_data (cmos_frame_data ) //有效数据
- 110 );
- 111
- 112 endmodule
复制代码摄像头驱动模块的顶层同样例化了三个模块,分别是摄像头配置模块(i2c_ov5640_rgb565_cfg)、IIC驱动模块(i2c_dri)以及数据采集模块(cmos_capture_data)。其中摄像头配置模块主要是包含了OV5640摄像头的寄存器信息,而IIC驱动模块就是把这些寄存器信息配置到OV5640摄像头中去;数据采集模块的作用就是接收摄像头采集到的数据并将数据传递给DDR4驱动模块。
下面我们先来看看摄像头配置模块(i2c_ov5640_rgb565_cfg)的代码,如下所示: - 1 modulei2c_ov5640_rgb565_cfg
- 2 (
- 3 input clk , //时钟信号
- 4 input rst_n , //复位信号,低电平有效
- 5
- 6 input [7:0] i2c_data_r, //I2C读出的数据
- 7 input i2c_done , //I2C寄存器配置完成信号
- 8 input [12:0] cmos_h_pixel ,
- 9 input [12:0] cmos_v_pixel ,
- 10 input [12:0] total_h_pixel, //水平总像素大小
- 11 input [12:0] total_v_pixel, //垂直总像素大小
- 12 output reg i2c_exec , //I2C触发执行信号
- 13 output reg [23:0] i2c_data , //I2C要配置的地址与数据(高16位地址,低8位数据)
- 14 output reg i2c_rh_wl, //I2C读写控制信号
- 15 output reg init_done //初始化完成信号
- 16 );
- 17
- 18 //parameter define
- 19 localparam REG_NUM = 8'd250 ; //总共需要配置的寄存器个数
- 20
- 21 //reg define
- 22 reg [12:0] start_init_cnt; //等待延时计数器
- 23 reg [7:0] init_reg_cnt ; //寄存器配置个数计数器
- 24
- 25 //*****************************************************
- 26 //** main code
- 27 //*****************************************************
- 28
- 29 //clk时钟配置成1Mhz,周期为1000ns 20000*1000ns = 20ms
- 30 //OV5640上电到开始配置IIC至少等待20ms
- 31 always @(posedge clk or negedge rst_n) begin
- 32 if(!rst_n)
- 33 start_init_cnt <= 13'b0;
- 34 else if(start_init_cnt < 13'd20000) begin
- 35 start_init_cnt <= start_init_cnt + 1'b1;
- 36 end
- 37 end
- 38
- 39 //寄存器配置个数计数
- 40 always @(posedge clk or negedge rst_n) begin
- 41 if(!rst_n)
- 42 init_reg_cnt <= 8'd0;
- 43 else if(i2c_exec)
- 44 init_reg_cnt <= init_reg_cnt + 8'b1;
- 45 end
- 46
- 47 //i2c触发执行信号
- 48 always @(posedge clk or negedge rst_n) begin
- 49 if(!rst_n)
- 50 i2c_exec <= 1'b0;
- 51 else if(start_init_cnt == 13'd19999)
- 52 i2c_exec <= 1'b1;
- 53 else if(i2c_done && (init_reg_cnt < REG_NUM))
- 54 i2c_exec <= 1'b1;
- 55 else
- 56 i2c_exec <= 1'b0;
- 57 end
- 58
- 59 //配置I2C读写控制信号
- 60 always @(posedge clk or negedge rst_n) begin
- 61 if(!rst_n)
- 62 i2c_rh_wl <= 1'b1;
- 63 else if(init_reg_cnt == 8'd2)
- 64 i2c_rh_wl <= 1'b0;
- 65 end
- 66
- 67 //初始化完成信号
- 68 always @(posedge clk or negedge rst_n) begin
- 69 if(!rst_n)
- 70 init_done <= 1'b0;
- 71 else if((init_reg_cnt == REG_NUM) && i2c_done)
- 72 init_done <= 1'b1;
- 73 end
- 74
- 75 //配置寄存器地址与数据
- 76 always @(posedge clk or negedge rst_n) begin
- 77 if(!rst_n)
- 78 i2c_data <= 24'b0;
- 79 else begin
- 80 case(init_reg_cnt)
- 81 //先对寄存器进行软件复位,使寄存器恢复初始值
- 82 //寄存器软件复位后,需要延时1ms才能配置其它寄存器
- 83 8'd0 : i2c_data <= {16'h300a,8'h0}; //
- 84 8'd1 : i2c_data <= {16'h300b,8'h0}; //
复制代码配置代码较长,省略部分源代码…… - 295 8'd204: i2c_data <= {16'h5025,8'h00};
- 296 //系统时钟分频 Bit[7:4]:系统时钟分频 inputclock =24Mhz, PCLK = 48Mhz
- 297 8'd205: i2c_data <= {16'h3035,8'h11};
- 298 8'd206: i2c_data <= {16'h3036,8'h3c}; //PLL倍频
- 299 8'd207: i2c_data <= {16'h3c07,8'h08};
- 300 //时序控制 16'h3800~16'h3821
- 301 8'd208: i2c_data <= {16'h3820,8'h46};
- 302 8'd209: i2c_data <= {16'h3821,8'h01};
- 303 8'd210: i2c_data <= {16'h3814,8'h31};
- 304 8'd211: i2c_data <= {16'h3815,8'h31};
- 305 8'd212: i2c_data <= {16'h3800,8'h00};
- 306 8'd213: i2c_data <= {16'h3801,8'h00};
- 307 8'd214: i2c_data <= {16'h3802,8'h00};
- 308 8'd215: i2c_data <= {16'h3803,8'h04};
- 309 8'd216: i2c_data <= {16'h3804,8'h0a};
- 310 8'd217: i2c_data <= {16'h3805,8'h3f};
- 311 8'd218: i2c_data <= {16'h3806,8'h07};
- 312 8'd219: i2c_data <= {16'h3807,8'h9b};
- 313 //设置输出像素个数
- 314 //DVP 输出水平像素点数高4位
- 315 8'd220: i2c_data <= {16'h3808,{4'd0,cmos_h_pixel[11:8]}};
- 316 //DVP 输出水平像素点数低8位
- 317 8'd221: i2c_data <= {16'h3809,cmos_h_pixel[7:0]};
- 318 //DVP 输出垂直像素点数高3位
- 319 8'd222: i2c_data <= {16'h380a,{5'd0,cmos_v_pixel[10:8]}};
- 320 //DVP 输出垂直像素点数低8位
- 321 8'd223: i2c_data <= {16'h380b,cmos_v_pixel[7:0]};
- 322 //水平总像素大小高5位
- 323 8'd224: i2c_data <= {16'h380c,{3'd0,total_h_pixel[12:8]}};
- 324 //水平总像素大小低8位
- 325 8'd225: i2c_data <= {16'h380d,total_h_pixel[7:0]};
- 326 //垂直总像素大小高5位
- 327 8'd226: i2c_data <= {16'h380e,{3'd0,total_v_pixel[12:8]}};
- 328 //垂直总像素大小低8位
- 329 8'd227: i2c_data <= {16'h380f,total_v_pixel[7:0]};
复制代码配置代码较长,省略部分源代码…… - 350 8'd246: i2c_data <= {16'h3016,8'h02};
- 351 8'd247: i2c_data <= {16'h301c,8'h02};
- 352 8'd248: i2c_data <= {16'h3019,8'h02}; //打开闪光灯
- 353 8'd249: i2c_data <= {16'h3019,8'h00}; //关闭闪光灯
- 354 //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
- 355 default : i2c_data <= {16'h300a,8'h00}; //器件ID高8位
- 356 endcase
- 357 end
- 358 end
- 359
- 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读写测试实验”的例程中有详细的讲解,所以本节实验不再讲解了。最后我们来看看数据采集模块的代码,如下所示: 摄像头在配置完寄存器后就会输出图像数据,但是此时输出的数据有可能不是特别稳定,所以我们的数据采集模块并没有立刻把摄像头输出的数据接收过来,而是在代码的67~82行做了一个帧计数器,当数到摄像头已经传输完10帧数据后(此时我们认为摄像头数据已经处于稳定状态)才开始接收数据并且拉高帧有效标志(frame_val_flag)。代码第85~104行是将8位的摄像头原始数据拼接成16位rgb565格式的像素数据。代码第106~114行是每当一个数据拼接完成就输出一个数据有效信号。
接下来我们来看看DDR控制模块,代码如下所示: - 1 module ddr4_top(
- 2 input sys_rst_n , //复位,低有效
- 3 input sys_init_done , //系统初始化完成
- 4 //DDR4接口信号
- 5 input [27:0 app_addr_rd_min , //读DDR4的起始地址
- 6 input [27:0 app_addr_rd_max , //读DDR4的结束地址
- 7 input [7:0 rd_bust_len , //从DDR4中读数据时的突发长度
- 8 input [27:0 app_addr_wr_min , //读DDR4的起始地址
- 9 input [27:0 app_addr_wr_max , //读DDR4的结束地址
- 10 input [7:0 wr_bust_len , //从DDR4中读数据时的突发长度
- 11 // DDR4 IO接口
- 12 input c0_sys_clk_p ,
- 13 input c0_sys_clk_n ,
- 14 output c0_ddr4_act_n ,
- 15 output [16:0 c0_ddr4_adr ,
- 16 output [1:0 c0_ddr4_ba ,
- 17 output [0:0 c0_ddr4_bg ,
- 18 output [0:0 c0_ddr4_cke ,
- 19 output [0:0 c0_ddr4_odt ,
- 20 output [0:0 c0_ddr4_cs_n ,
- 21 output [0:0 c0_ddr4_ck_t ,
- 22 output [0:0 c0_ddr4_ck_c ,
- 23 output c0_ddr4_reset_n ,
- 24 inout [1:0 c0_ddr4_dm_dbi_n,
- 25 inout [15:0 c0_ddr4_dq ,
- 26 inout [1:0 c0_ddr4_dqs_c ,
- 27 inout [1:0 c0_ddr4_dqs_t ,
- 28
- 29 //用户
- 30 input ddr4_read_valid , //DDR4 读使能
- 31 input ddr4_pingpang_en , //DDR4 乒乓操作使能
- 32 input wr_clk , //wfifo时钟
- 33 input rd_clk , //rfifo的读时钟
- 34 input datain_valid , //数据有效使能信号
- 35 input [15:0 datain , //有效数据
- 36 input rdata_req , //请求像素点颜色数据输入
- 37 input rd_load , //输出源更新信号
- 38 input wr_load , //输入源更新信号
- 39 output [15:0 dataout , //rfifo输出数据
- 40 output clk_50m ,
- 41 output init_calib_complete //ddr4初始化完成信号
- 42 );
- 43
- 44 //wire define
- 45 wire ui_clk ; //用户时钟
- 46 wire [27:0 app_addr ; //ddr4 地址
- 47 wire [2:0 app_cmd ; //用户读写命令
- 48 wire app_en ; //MIG IP核使能
- 49 wire app_rdy ; //MIG IP核空闲
- 50 wire [127:0 app_rd_data ; //用户读数据
- 51 wire app_rd_data_end ; //突发读当前时钟最后一个数据
- 52 wire app_rd_data_valid ; //读数据有效
- 53 wire [127:0 app_wdf_data ; //用户写数据
- 54 wire app_wdf_end ; //突发写当前时钟最后一个数据
- 55 wire [15:0 app_wdf_mask ; //写数据屏蔽
- 56 wire app_wdf_rdy ; //写空闲
- 57 wire app_sr_active ; //保留
- 58 wire app_ref_ack ; //刷新请求
- 59 wire app_zq_ack ; //ZQ 校准请求
- 60 wire app_wdf_wren ; //ddr4 写使能
- 61 wire clk_ref_i ; //ddr4参考时钟
- 62 wire sys_clk_i ; //MIG IP核输入时钟
- 63 wire ui_clk_sync_rst ; //用户复位信号
- 64 wire [20:0 rd_cnt ; //实际读地址计数
- 65 wire [3 :0 state_cnt ; //状态计数器
- 66 wire [23:0 rd_addr_cnt ; //用户读地址计数器
- 67 wire [23:0 wr_addr_cnt ; //用户写地址计数器
- 68 wire rfifo_wren ; //从ddr4读出数据的有效使能
- 69 wire [10:0 wfifo_rcount ; //rfifo剩余数据计数
- 70 wire [10:0 rfifo_wcount ; //wfifo写进数据计数
- 71
- 72
- 73 //*****************************************************
- 74 //** main code
- 75 //*****************************************************
- 76
- 77 //读写模块
- 78 ddr4_rw u_ddr4_rw(
- 79 .ui_clk (ui_clk) ,
- 80 .ui_clk_sync_rst (ui_clk_sync_rst) ,
- 81 //MIG 接口
- 82 .init_calib_complete (init_calib_complete) , //ddr4初始化完成信号
- 83 .app_rdy (app_rdy) , //MIG IP核空闲
- 84 .app_wdf_rdy (app_wdf_rdy) , //写空闲
- 85 .app_rd_data_valid (app_rd_data_valid) , //读数据有效
- 86 .app_addr (app_addr) , //ddr4 地址
- 87 .app_en (app_en) , //MIG IP核使能
- 88 .app_wdf_wren (app_wdf_wren) , //ddr4 写使能
- 89 .app_wdf_end (app_wdf_end) , //突发写当前时钟最后一个数据
- 90 .app_cmd (app_cmd) , //用户读写命令
- 91 //ddr4 地址参数
- 92 .app_addr_rd_min (app_addr_rd_min) , //读ddr4的起始地址
- 93 .app_addr_rd_max (app_addr_rd_max) , //读ddr4的结束地址
- 94 .rd_bust_len (rd_bust_len) , //从ddr4中读数据时的突发长度
- 95 .app_addr_wr_min (app_addr_wr_min) , //写ddr4的起始地址
- 96 .app_addr_wr_max (app_addr_wr_max) , //写ddr4的结束地址
- 97 .wr_bust_len (wr_bust_len) , //从ddr4中写数据时的突发长度
- 98 //用户接口
- 99 .rfifo_wren (rfifo_wren) , //从ddr4读出数据的有效使能
- 100 .rd_load (rd_load) , //输出源更新信号
- 101 .wr_load (wr_load) , //输入源更新信号
- 102 .ddr4_read_valid (ddr4_read_valid) , //ddr4 读使能
- 103 .ddr4_pingpang_en (ddr4_pingpang_en) , //ddr4 乒乓操作使能
- 104 .wfifo_rcount (wfifo_rcount) , //rfifo剩余数据计数
- 105 .rfifo_wcount (rfifo_wcount) //wfifo写进数据计数
- 106 );
- 107
- 108 ddr4_0 u_ddr4_0 (
- 109 .c0_init_calib_complete(init_calib_complete), //初始化完成
- 110 .dbg_clk(),
- 111 .c0_sys_clk_p(c0_sys_clk_p), // 系统时钟p
- 112 .c0_sys_clk_n(c0_sys_clk_n), // 系统时钟n
- 113 .dbg_bus(), //output wire [511 : 0] dbg_bus
- 114 .c0_ddr4_adr(c0_ddr4_adr), // 行列地址
- 115 .c0_ddr4_ba(c0_ddr4_ba), // bank地址
- 116 .c0_ddr4_cke(c0_ddr4_cke), // 时钟使能
- 117 .c0_ddr4_cs_n(c0_ddr4_cs_n), //片选信号
- 118 .c0_ddr4_dm_dbi_n(c0_ddr4_dm_dbi_n), //数据掩码
- 119 .c0_ddr4_dq(c0_ddr4_dq), //数据线
- 120 .c0_ddr4_dqs_c(c0_ddr4_dqs_c), // 数据选通信号
- 121 .c0_ddr4_dqs_t(c0_ddr4_dqs_t), //数据选通信号
- 122 .c0_ddr4_odt(c0_ddr4_odt), //终端电阻使能
- 123 .c0_ddr4_bg(c0_ddr4_bg), //bank组地址
- 124 .c0_ddr4_reset_n(c0_ddr4_reset_n), // 复位信号
- 125 .c0_ddr4_act_n(c0_ddr4_act_n), // 指令激活
- 126 .c0_ddr4_ck_c(c0_ddr4_ck_c), //ddr时钟
- 127 .c0_ddr4_ck_t(c0_ddr4_ck_t), //ddr时钟
- 128 //user interface
- 129 .c0_ddr4_ui_clk(ui_clk), // 用户时钟
- 130 .c0_ddr4_ui_clk_sync_rst(ui_clk_sync_rst), //用户复位
- 131 .c0_ddr4_app_en(app_en), // 指令使能
- 132 .c0_ddr4_app_hi_pri(1'b0),
- 133 .c0_ddr4_app_wdf_end(app_wdf_end), // 写数据最后一个数据
- 134 .c0_ddr4_app_wdf_wren(app_wdf_wren), // 写数据使能
- 135 .c0_ddr4_app_rd_data_end(app_rd_data_end), // 读数据最后一个数据
- 136 .c0_ddr4_app_rd_data_valid(app_rd_data_valid), // 读数据有效
- 137 .c0_ddr4_app_rdy(app_rdy), // 指令接收准备完成,可以接收指令
- 138 .c0_ddr4_app_wdf_rdy(app_wdf_rdy), // 数据接收准备完成,可以接收数据
- 139 .c0_ddr4_app_addr(app_addr), // 用户地址
- 140 .c0_ddr4_app_cmd(app_cmd), // 读写指令
- 141 .c0_ddr4_app_wdf_data(app_wdf_data), //写数据
- 142 .c0_ddr4_app_wdf_mask(16'b0), // 写数据掩码
- 143 .c0_ddr4_app_rd_data(app_rd_data), //读数据
- 144 .addn_ui_clkout1(clk_50m), // 锁相环时钟输出
- 145 .sys_rst(~sys_rst_n) // 系统复位
- 146 );
- 147
- 148 ddr4_fifo_ctrl u_ddr4_fifo_ctrl (
- 149
- 150 .rst_n (sys_rst_n &&sys_init_done ) ,
- 151 //输入源接口
- 152 .wr_clk (wr_clk) ,
- 153 .rd_clk (rd_clk) ,
- 154 .clk_100 (ui_clk) , //用户时钟
- 155 .datain_valid (datain_valid) , //数据有效使能信号
- 156 .datain (datain) , //有效数据
- 157 .rfifo_din (app_rd_data) , //用户读数据
- 158 .rdata_req (rdata_req) , //请求像素点颜色数据输入
- 159 .rfifo_wren (rfifo_wren) , //ddr4读出数据的有效使能
- 160 .wfifo_rden (app_wdf_wren) , //ddr4 写使能
- 161 //用户接口
- 162 .wfifo_rcount (wfifo_rcount) , //rfifo剩余数据计数
- 163 .rfifo_wcount (rfifo_wcount) , //wfifo写进数据计数
- 164 .wfifo_dout (app_wdf_data) , //用户写数据
- 165 .rd_load (rd_load) , //输出源更新信号
- 166 .wr_load (wr_load) , //输入源更新信号
- 167 .pic_data (dataout) //rfifo输出数据
- 168 );
- 169
- 170 endmodule
复制代码DDR4控制模块例化了三个子模块,分别是DDR4读写模块、MIG IP核以及FIFO控制模块。大家学习了前面的“DDR4读写实验例程”后,再来看我们本节实验的DDR4控制模块就比较轻松了,因为DDR4控制模块是在“DDR4读写实验例程”的基础上修改过来的。相比较“DDR4读写实验例程”,本节实验DDR4控制模块最大的区别就是添加了BANK切换和FIFO的调用。首先我们先来看一下我们为什么要添加BANK切换。
在“DDR4读写测试实验”的程序中,读写操作地址都是DDR的同一存储空间,如果只使用一个存储空间缓存图像数据,那么这么做虽然保证了数据不会出现错乱的问题,但是会导致当前读取的图像与上一次存入的图像存在交错,如下图所示: 图 32.4.3 DDR4单个BANK缓存图像机制 为了避免这一情况,我们在DDR的其它BANK中开辟一个相同大小的存储空间,使用乒乓操作的方式来写入和读取数据,所以本次实验在“DDR4读写测试实验”的程序里做了改动。
我们在DDR中开辟出2个存储空间进行乒乓操作用于缓存帧图像。在摄像头初始化结束后输出的第一个数据对应图像的第一个像素点,将其写入存储空间的首地址中。通过在DDR控制模块中对输出的图像数据进行计数,从而将它们分别写入相应的地址空间。计数达存入DDR的最大写地址后,完成一帧图像的存储,然后当帧复位到来时来切换BANK以达到乒乓操作的目的,并同时回到存储空间的首地址继续下一帧图像的存储。在显示图像时,LCD顶层模块从DDR存储空间的首地址开始读数据,同样对读过程进行计数,并将读取的图像数据分别显示到显示器相应的像素点位置。
图像数据总是在两个存储空间之间不断切换写入,而读请求信号在读完当前存储空间后判断哪个存储空间没有被写入,然后去读取没有被写入的存储空间。对于本次程序设计来说,数据写入较慢而读出较快,因此会出现同一存储空间被读取多次的情况,但保证了读出的数据一定是一帧完整的图像而不是两帧数据拼接的图像。当正在读取其中一个缓存空间,另一个缓存空间已经写完,并开始切换写入下一个缓存空间时,由于图像数据读出的速度总是大于写入的速度,因此,读出的数据仍然是一帧完整的图像。下面给出ddr4_rw模块的代码,一起来看看BANK是如何切换的。 - 1 module ddr4_rw(
- 2 input ui_clk , //用户时钟
- 3 input ui_clk_sync_rst , //复位,高有效
- 4 input init_calib_complete , //ddr4初始化完成
- 5 input app_rdy , //MIG IP核空闲
- 6 input app_wdf_rdy , //MIG写FIFO空闲
- 7 input app_rd_data_valid , //读数据有效
- 8 input [10:0 wfifo_rcount , //写端口FIFO中的数据量
- 9 input [10:0 rfifo_wcount , //读端口FIFO中的数据量
- 10 input rd_load , //输出源更新信号
- 11 input wr_load , //输入源更新信号
- 12 input [27:0 app_addr_rd_min , //读ddr4的起始地址
- 13 input [27:0 app_addr_rd_max , //读ddr4的结束地址
- 14 input [7:0 rd_bust_len , //从ddr4中读数据时的突发长度
- 15 input [27:0 app_addr_wr_min , //写ddr4的起始地址
- 16 input [27:0 app_addr_wr_max , //写ddr4的结束地址
- 17 input [7:0 wr_bust_len , //从ddr4中写数据时的突发长度
- 18
- 19 input ddr4_read_valid , //ddr4 读使能
- 20 input ddr4_pingpang_en , //ddr4 乒乓操作使能
- 21 output rfifo_wren , //从ddr4读出数据的有效使能
- 22 output [27:0 app_addr , //ddr4地址
- 23 output app_en , //MIG IP核操作使能
- 24 output app_wdf_wren , //用户写使能
- 25 output app_wdf_end , //突发写当前时钟最后一个数据
- 26 output [2:0 app_cmd //MIG IP核操作命令,读或者写
- 27 );
- 28
- 29 //localparam
- 30 localparam IDLE = 4'b0001; //空闲状态
- 31 localparam ddr4_DONE =4'b0010; //ddr4初始化完成状态
- 32 localparam WRITE = 4'b0100; //读FIFO保持状态
- 33 localparam READ = 4'b1000; //写FIFO保持状态
- 34
- 35 //reg define
- 36 reg [27:0 app_addr; //ddr4地址
- 37 reg [27:0 app_addr_rd; //ddr4读地址
- 38 reg [27:0 app_addr_wr; //ddr4写地址
- 39 reg [3:0 state_cnt; //状态计数器
- 40 reg [23:0 rd_addr_cnt; //用户读地址计数
- 41 reg [23:0 wr_addr_cnt; //用户写地址计数
- 42 reg [8:0 burst_rd_cnt; //突发读次数计数器
- 43 reg [8:0 burst_wr_cnt; //突发写次数计数器
- 44 reg [10:0 raddr_rst_h_cnt; //输出源的帧复位脉冲进行计数
- 45 reg [27:0 app_addr_rd_min_a; //读ddr4的起始地址
- 46 reg [27:0 app_addr_rd_max_a; //读ddr4的结束地址
- 47 reg [7:0 rd_bust_len_a; //从ddr4中读数据时的突发长度
- 48 reg [27:0 app_addr_wr_min_a; //写ddr4的起始地址
- 49 reg [27:0 app_addr_wr_max_a; //写ddr4的结束地址
- 50 reg [7:0 wr_bust_len_a; //从ddr4中写数据时的突发长度
- 51 reg star_rd_flag; //复位后写入2帧的标志信号
- 52 reg rd_load_d0;
- 53 reg rd_load_d1;
- 54 reg raddr_rst_h; //输出源的帧复位脉冲
- 55 reg wr_load_d0;
- 56 reg wr_load_d1;
- 57 reg wr_rst; //输入源帧复位标志
- 58 reg rd_rst; //输出源帧复位标志
- 59 reg raddr_page; //ddr4读地址切换信号
- 60 reg waddr_page; //ddr4写地址切换信号
- 61 reg burst_done_wr; //一次突发写结束信号
- 62 reg burst_done_rd; //一次读发写结束信号
- 63 reg wr_end; //一次突发写结束信号
- 64 reg rd_end; //一次读发写结束信号
- 65
- 66 wire rst_n;
- 67
- 68 //*****************************************************
- 69 //** main code
- 70 //*****************************************************
- 71
- 72 //将数据有效信号赋给wfifo写使能
- 73 assign rfifo_wren = app_rd_data_valid;
- 74
- 75 assign rst_n = ~ui_clk_sync_rst;
- 76
- 77 //在写状态MIG空闲且写有效,或者在读状态MIG空闲,此时使能信号为高,其他情况为低
- 78 assign app_en = ((state_cnt == WRITE && (app_rdy && app_wdf_rdy))
- 79 ||(state_cnt == READ && app_rdy)) ? 1'b1:1'b0;
- 80
- 81 //在写状态,MIG空闲且写有效,此时拉高写使能
- 82 assign app_wdf_wren = (state_cnt == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
- 83
- 84 //由于我们ddr4芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
- 85 assign app_wdf_end = app_wdf_wren;
- 86
- 87 //处于读的时候命令值为1,其他时候命令值为0
- 88 assign app_cmd = (state_cnt == READ) ? 3'd1 :3'd0;
- 89
- 90 //将数据读写地址赋给ddr地址
- 91 always @(*) begin
- 92 if(~rst_n)
- 93 app_addr <= 0;
- 94 else if(state_cnt == READ )
- 95 if(ddr4_pingpang_en)
- 96 app_addr <= {2'b0,raddr_page,app_addr_rd[24:0]};
- 97 else
- 98 app_addr <= {3'b0,app_addr_rd[24:0]};
- 99 else if(ddr4_pingpang_en)
- 100 app_addr <= {2'b0,waddr_page,app_addr_wr[24:0]};
- 101 else
- 102 app_addr <= {3'b0,app_addr_wr[24:0]};
- 103 end
- 104
复制代码在程序的第73行至88行,这些代码在“DDR4读写测试实验”程序中已有解释,此处不再详述。
在程序的第91行至103行,设计的代码的意义是根据状态计数器(state_cnt)的状态来判断写入ddr4的地址是写地址还是读地址,在第95行和99行,表示的意义是当乒乓使能为高时,对读写存储空间进行乒乓操作,保证读写的存储不会在同一个空间,反之,就不进行乒乓操作,使读写的存储在同一个空间。 - 105 //对信号进行打拍处理
- 106 always @(posedge ui_clk or negedge rst_n) begin
- 107 if(~rst_n)begin
- 108 rd_load_d0 <= 0;
- 109 rd_load_d1 <= 0;
- 110 wr_load_d0 <= 0;
- 111 wr_load_d1 <= 0;
- 112 end
- 113 else begin
- 114 rd_load_d0 <= rd_load;
- 115 rd_load_d1 <= rd_load_d0;
- 116 wr_load_d0 <= wr_load;
- 117 wr_load_d1 <= wr_load_d0;
- 118 end
- 119 end
- 120
- 121 //对异步信号进行打拍处理
- 122 always @(posedge ui_clk or negedge rst_n) begin
- 123 if(~rst_n)begin
- 124 app_addr_rd_min_a <= 0;
- 125 app_addr_rd_max_a <= 0;
- 126 rd_bust_len_a <= 0;
- 127 app_addr_wr_min_a <= 0;
- 128 app_addr_wr_max_a <= 0;
- 129 wr_bust_len_a <= 0;
- 130 end
- 131 else begin
- 132 app_addr_rd_min_a <= app_addr_rd_min;
- 133 app_addr_rd_max_a <= app_addr_rd_max;
- 134 rd_bust_len_a <= rd_bust_len;
- 135 app_addr_wr_min_a <= app_addr_wr_min;
- 136 app_addr_wr_max_a <= app_addr_wr_max;
- 137 wr_bust_len_a <= wr_bust_len;
- 138 end
- 139 end
- 140
- 141 //对输入源做个帧复位标志
- 142 always @(posedge ui_clk or negedge rst_n) begin
- 143 if(~rst_n)
- 144 wr_rst <= 0;
- 145 else if(wr_load_d0 && !wr_load_d1)
- 146 wr_rst <= 1;
- 147 else
- 148 wr_rst <= 0;
- 149 end
- 150
- 151 //对输出源做个帧复位标志
- 152 always @(posedge ui_clk or negedge rst_n) begin
- 153 if(~rst_n)
- 154 rd_rst <= 0;
- 155 else if(rd_load_d0 && !rd_load_d1)
- 156 rd_rst <= 1;
- 157 else
- 158 rd_rst <= 0;
- 159 end
- 160
- 161 //对输出源的读地址做个帧复位脉冲
- 162 always @(posedge ui_clk or negedge rst_n) begin
- 163 if(~rst_n)
- 164 raddr_rst_h <= 1'b0;
- 165 else if(rd_load_d0 && !rd_load_d1)
- 166 raddr_rst_h <= 1'b1;
- 167 else if(app_addr_rd == app_addr_rd_min_a)
- 168 raddr_rst_h <= 1'b0;
- 169 else
- 170 raddr_rst_h <= raddr_rst_h;
- 171 end
- 172
- 173 //对输出源的帧复位脉冲进行计数
- 174 always @(posedge ui_clk or negedge rst_n) begin
- 175 if(~rst_n)
- 176 raddr_rst_h_cnt <= 11'b0;
- 177 else if(raddr_rst_h)
- 178 raddr_rst_h_cnt <= raddr_rst_h_cnt + 1'b1;
- 179 else
- 180 raddr_rst_h_cnt <= 11'b0;
- 181 end
- 182
- 183 //对输出源帧的读地址高位切换
- 184 always @(posedge ui_clk or negedge rst_n) begin
- 185 if(~rst_n)
- 186 raddr_page <= 1'b0;
- 187 else if( rd_end)
- 188 raddr_page <= ~waddr_page;
- 189 else
- 190 raddr_page <= raddr_page;
- 191 end
- 192
复制代码在程序的第106行至119行,这段代码是对输入进来的更新信号进行打拍处理,为了同步时钟域,方便后面产生复位信号。
在程序的第122行至139行,这段代码是对输入进来的更新信号进行打拍处理,为了同步时钟域,减少信号扇出和延迟。
在程序的第142行至159行,这段代码是更新信号的上升沿来做复位信号的。
在程序的第162行至181行,设计这几行的目的是为了保证在FIFO完全复位后再对MIG IP核进行操作,这样就防止了FIFO还没有复位完全就写入数据的情况,如下图所示,当复位结束后,full信号还将持续一段时间,在这段时间内是不能写入数据的,必须让信号raddr_rst_h拉高一段时间。 程序中第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中读出数据。 如程序中第204至307行所示,这段代码是DDR4读写逻辑实现,转态跳转如上图所示。
在复位结束后,如果DDR4没有初始化完成,那么状态一直在空闲状态(IDLE),否则跳到DDR4空闲状态(ddr4_DONE)。在DDR4空闲状态,当输入端的帧复位到来时,对写地址计数器和写地址进行复位操作。
程序中第228至239行,当读写地址达到设定值时,对地址和计数器进行复位操作,同时拉高结束地址标志信号。
如程序中第240至244行所示,FIFO控制模块优先处理DDR4写请求,以免写FIFO溢出时,用于写入DDR4的数据丢失。当写FIFO中的数据量大于写突发长度时,执行写DDR4操作;当读FIFO中的数据量小于读突发长度时,执行读DDR4操作。 当写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 module ddr4_fifo_ctrl(
- 2 input rst_n , //复位信号
- 3 input wr_clk , //wfifo时钟
- 4 input rd_clk , //rfifo时钟
- 5 input clk_100 , //用户时钟
- 6 input datain_valid , //数据有效使能信号
- 7 input [15:0 datain , //有效数据
- 8 input [127:0 rfifo_din , //用户读数据
- 9 input rdata_req , //请求像素点颜色数据输入
- 10 input rfifo_wren , //从ddr4读出数据的有效使能
- 11 input wfifo_rden , //wfifo读使能
- 12 input rd_load , //输出源场信号
- 13 input wr_load , //输入源场信号
- 14
- 15 output [127:0 wfifo_dout , //用户写数据
- 16 output [10:0 wfifo_rcount , //rfifo剩余数据计数
- 17 output [10:0 rfifo_wcount , //wfifo写进数据计数
- 18 output [15:0 pic_data //有效数据
- 19 );
- 20
- 21 //reg define
- 22 reg [127:0 datain_t ; //由16bit输入源数据移位拼接得到
- 23 reg [7:0 cam_data_d0 ;
- 24 reg [4:0 i_d0 ;
- 25 reg [30:0 rd_load_d ; //由输出源场信号移位拼接得到
- 26 reg [6:0 byte_cnt ; //写数据移位计数器
- 27 reg [127:0 data ; //rfifo输出数据打拍得到
- 28 reg [15:0 pic_data ; //有效数据
- 29 reg [4:0 i ; //读数据移位计数器
- 30 reg [15:0 wr_load_d ; //由输入源场信号移位拼接得到
- 31 reg [3:0 cmos_ps_cnt ; //等待帧数稳定计数器
- 32 reg cam_href_d0 ;
- 33 reg cam_href_d1 ;
- 34 reg wr_load_d0 ;
- 35 reg rd_load_d0 ;
- 36 reg rdfifo_rst_h ; //rfifo复位信号,高有效
- 37 reg wr_load_d1 ;
- 38 reg wfifo_rst_h ; //wfifo复位信号,高有效
- 39 reg wfifo_wren ; //wfifo写使能信号
- 40
- 41 //wire define
- 42 wire [127:0 rfifo_dout ; //rfifo输出数据
- 43 wire [127:0 wfifo_din ; //wfifo写数据
- 44 wire [15:0 dataout[0:15 ; //定义输出数据的二维数组
- 45 wire rfifo_rden ; //rfifo的读使能
- 46
- 47 //*****************************************************
- 48 //** main code
- 49 //*****************************************************
- 50
- 51 //rfifo输出的数据存到二维数组
- 52 assign dataout[0 = data[127:112];
- 53 assign dataout[1 = data[111:96];
- 54 assign dataout[2 = data[95:80];
- 55 assign dataout[3 = data[79:64];
- 56 assign dataout[4 = data[63:48];
- 57 assign dataout[5 = data[47:32];
- 58 assign dataout[6 = data[31:16];
- 59 assign dataout[7 = data[15:0];
- 60
- 61 assign wfifo_din = datain_t ;
- 62
- 63 //移位寄存器计满时,从rfifo读出一个数据
- 64 assign rfifo_rden = (rdata_req && (i==7)) ? 1'b1 : 1'b0;
- 65
- 66 //16位数据转128位RGB565数据
- 67 always @(posedge wr_clk or negedge rst_n) begin
- 68 if(!rst_n) begin
- 69 datain_t <= 0;
- 70 byte_cnt <= 0;
- 71 end
- 72 else if(datain_valid) begin
- 73 if(byte_cnt == 7)begin
- 74 byte_cnt <= 0;
- 75 datain_t <= {datain_t[111:0],datain};
- 76 end
- 77 else begin
- 78 byte_cnt <= byte_cnt + 1;
- 79 datain_t <= {datain_t[111:0],datain};
- 80 end
- 81 end
- 82 else begin
- 83 byte_cnt <= byte_cnt;
- 84 datain_t <= datain_t;
- 85 end
- 86 end
- 87
复制代码在程序的第51行至59行是将DDR4输出的128bit的数据存入一个二维数组(dataout)中,方便后面的数据拆解。
在程序的第64行,表示当读请求信号(rdata_req)拉高8个时钟周期后,rfifo的读使能(rfifo_rden)拉高一个周期,这么做的原因是 rfifo的数据输出为128bit而本模块的数据输出(pic_data)为16bit。 在程序的第66行至86行对摄像头输出的数据进行了移位拼接,具体时序如图 32.4.8所示。因为摄像头的输入数据(datain)为16bit每周期,而MIG核每一个时钟周期写入的信号位宽为128bit,所以写数据移位计数器(byte_cnt)信号计到7时清零,同时FIFO的写使能wfifo_wren拉高一个周期。同理LCD顶层模块的输入也是16bit一个时钟周期,所以在程序的第107行至131行对128bit的数据进行了拆解,具体时序如图 32.4.9所示。 - 88 //wfifo写使能产生
- 89 always @(posedge wr_clk or negedge rst_n) begin
- 90 if(!rst_n)
- 91 wfifo_wren <= 0;
- 92 else if(wfifo_wren == 1)
- 93 wfifo_wren <= 0;
- 94 else if(byte_cnt ==7 && datain_valid ) //输入源数据传输8次,写使能拉高一次
- 95 wfifo_wren <= 1;
- 96 else
- 97 wfifo_wren <= 0;
- 98 end
- 99
- 100 always @(posedge rd_clk or negedge rst_n) begin
- 101 if(!rst_n)
- 102 data <= 127'b0;
- 103 else
- 104 data <= rfifo_dout;
- 105 end
- 106
- 107 //对rfifo出来的128bit数据拆解成16个16bit数据
- 108 always @(posedge rd_clk or negedge rst_n) begin
- 109 if(!rst_n) begin
- 110 pic_data <= 16'b0;
- 111 i <=0;
- 112 i_d0 <= 0;
- 113 end
- 114 else if(rdata_req)begin
- 115 if(i == 7)begin
- 116 pic_data <= dataout[i_d0];
- 117 i <= 0;
- 118 i_d0 <= i;
- 119 end
- 120 else begin
- 121 pic_data <= dataout[i_d0];
- 122 i <= i + 1;
- 123 i_d0 <= i;
- 124 end
- 125 end
- 126 else begin
- 127 pic_data <= pic_data;
- 128 i <=0;
- 129 i_d0 <= 0;
- 130 end
- 131 end
- 132
- 133 always @(posedge clk_100 or negedge rst_n) begin
- 134 if(!rst_n)
- 135 rd_load_d0 <= 1'b0;
- 136 else
- 137 rd_load_d0 <= rd_load;
- 138 end
- 139
- 140 //对输出源场信号进行移位寄存
- 141 always @(posedge clk_100 or negedge rst_n) begin
- 142 if(!rst_n)
- 143 rd_load_d <= 1'b0;
- 144 else
- 145 rd_load_d <= {rd_load_d[30:0],rd_load_d0};
- 146 end
- 147
复制代码在程序的第88行至98行,当写数据有效使能信号(datain_valid)拉高8个时钟周期后,wfifo的写使能才拉高一个周期,这么做的原因是wfifo的数据输入为128bit而本模块的数据输入(datain)为16bit。
在程序的第132行至138行,是对LCD的场信号进行时钟域同步,方便后面代码的调用,以减少信号的跨时钟域。程序的第158行至168行,原因也是如此。
在程序的第140行至146行,对LCD的场信号进行移位寄存,方便后面产生一段复位电平信号。 - 148 //产生一段复位电平,满足fifo复位时序
- 149 always @(posedge clk_100 or negedge rst_n) begin
- 150 if(!rst_n)
- 151 rdfifo_rst_h <= 1'b0;
- 152 else if(rd_load_d[0 && !rd_load_d[29])
- 153 rdfifo_rst_h <= 1'b1;
- 154 else
- 155 rdfifo_rst_h <= 1'b0;
- 156 end
- 157
- 158 //对输入源场信号进行移位寄存
- 159 always @(posedge wr_clk or negedge rst_n) begin
- 160 if(!rst_n)begin
- 161 wr_load_d0 <= 1'b0;
- 162 wr_load_d <= 16'b0;
- 163 end
- 164 else begin
- 165 wr_load_d0 <= wr_load;
- 166 wr_load_d <= {wr_load_d[14:0],wr_load_d0};
- 167 end
- 168 end
- 169
- 170 //产生一段复位电平,满足fifo复位时序
- 171 always @(posedge wr_clk or negedge rst_n) begin
- 172 if(!rst_n)
- 173 wfifo_rst_h <= 1'b0;
- 174 else if(wr_load_d[0 && !wr_load_d[15])
- 175 wfifo_rst_h <= 1'b1;
- 176 else
- 177 wfifo_rst_h <= 1'b0;
- 178 end
- 179
- 180 rd_fifo u_rd_fifo (
- 181 .rst (~rst_n|rdfifo_rst_h),
- 182 .wr_clk (clk_100),
- 183 .rd_clk (rd_clk),
- 184 .din (rfifo_din),
- 185 .wr_en (rfifo_wren),
- 186 .rd_en (rfifo_rden),
- 187 .dout (rfifo_dout),
- 188 .full (),
- 189 .empty (),
- 190 .rd_data_count (),
- 191 .wr_data_count (rfifo_wcount),
- 192 .wr_rst_busy (),
- 193 .rd_rst_busy ()
- 194 );
- 195
- 196 wr_fifo u_wr_fifo (
- 197 .rst (~rst_n|wfifo_rst_h),
- 198 .wr_clk (wr_clk),
- 199 .rd_clk (clk_100),
- 200 .din (wfifo_din),
- 201 .wr_en (wfifo_wren),
- 202 .rd_en (wfifo_rden),
- 203 .dout (wfifo_dout ),
- 204 .full (),
- 205 .empty (),
- 206 .rd_data_count (wfifo_rcount),
- 207 .wr_data_count (),
- 208 .wr_rst_busy (),
- 209 .rd_rst_busy ()
- 210 );
- 211
- 212 endmodule
复制代码在程序的第148行至156行和第170行至178行是分别对rfifo和wfifo进行帧复位,这里进行复位是为了在帧结束后清空FIFO,保证下帧数据到来之前FIFO为空。细心的朋友应该发现了一般复位是一个时钟周期,为什么这里的复位是一段电平,因为XILINX的手册规定FIFO的复位周期必须大于读写时钟的5个时钟周期,才能使FIFO完全复位。
看完了DDR4控制模块的代码我们再来看看图像分辨率模块的代码,如下所示: - 1 module picture_size (
- 2 input rst_n ,
- 3 input clk ,
- 4 input [15:0 ID_lcd ,
- 5
- 6 output [12:0 cmos_h_pixel ,
- 7 output [12:0 cmos_v_pixel ,
- 8 output [12:0 total_h_pixel ,
- 9 output [12:0 total_v_pixel ,
- 10 output [23:0 sdram_max_addr
- 11 );
- 12
- 13 reg [12:0 cmos_h_pixel;
- 14 reg [12:0 cmos_v_pixel;
- 15 reg [12:0 total_h_pixel;
- 16 reg [12:0 total_v_pixel;
- 17 reg [23:0 sdram_max_addr;
- 18
- 19 //parameter define
- 20 parameter ID_4342 = 16'h4342;
- 21 parameter ID_7084 = 16'h7084;
- 22 parameter ID_7016 = 16'h7016;
- 23 parameter ID_1018 = 16'h1018;
- 24
- 25 //*****************************************************
- 26 //** main code
- 27 //*****************************************************
- 28
- 29 //配置摄像头输出尺寸的大小
- 30 always @(posedge clk or negedge rst_n) begin
- 31 if(!rst_n) begin
- 32 cmos_h_pixel <= 13'b0;
- 33 cmos_v_pixel <= 13'd0;
- 34 sdram_max_addr <= 23'd0;
- 35 end
- 36 else begin
- 37 case(ID_lcd )
- 38 16'h4342 : begin
- 39 cmos_h_pixel =13'd480;
- 40 cmos_v_pixel =13'd272;
- 41 sdram_max_addr = 23'd130560;
- 42 end
- 43 16'h7084 : begin
- 44 cmos_h_pixel =13'd800;
- 45 cmos_v_pixel =13'd480;
- 46 sdram_max_addr = 23'd384000;
- 47 end
- 48 16'h7016 : begin
- 49 cmos_h_pixel =13'd1024;
- 50 cmos_v_pixel =13'd600;
- 51 sdram_max_addr = 23'd614400;
- 52 end
- 53 16'h1018 : begin
- 54 cmos_h_pixel =13'd1280;
- 55 cmos_v_pixel =13'd800;
- 56 sdram_max_addr = 23'd1024000;
- 57 end
- 58 default : begin
- 59 cmos_h_pixel =13'd800;
- 60 cmos_v_pixel =13'd480;
- 61 sdram_max_addr = 23'd384000;
- 62 end
- 63 endcase
- 64 end
- 65 end
- 66
- 67 //对HTS及VTS的配置会影响摄像头输出图像的帧率
- 68 always @(*) begin
- 69 case(ID_lcd)
- 70 ID_4342 : begin
- 71 total_h_pixel = 13'd1800;
- 72 total_v_pixel = 13'd1000;
- 73 end
- 74 ID_7084 : begin
- 75 total_h_pixel = 13'd1800;
- 76 total_v_pixel = 13'd1000;
- 77 end
- 78 ID_7016 : begin
- 79 total_h_pixel = 13'd2200;
- 80 total_v_pixel = 13'd1000;
- 81 end
- 82 ID_1018 : begin
- 83 total_h_pixel = 13'd2570;
- 84 total_v_pixel = 13'd980;
- 85 end
- 86 default : begin
- 87 total_h_pixel = 13'd1800;
- 88 total_v_pixel = 13'd1000;
- 89 end
- 90 endcase
- 91 end
- 92
- 93 endmodule
复制代码图像分辨率模块的代码时是非常简单的就是根据LCD驱动模块传递过来的不同ID值去配置不同的图像分辨率参数(cmos_h_pixel、cmos_v_pixel、total_h_pixel、total_v_pixel)和DDR4最大读写地址(sdram_max_addr),所以这里关于图像分辨率模块的代码这里就不作过多讲解了。
看完图像分辨率模块的代码后我们再来看看LCD驱动模块的代码,如下所示: - 1 module lcd_rgb_top(
- 2 input sys_clk , //系统时钟
- 3 input sys_rst_n, //复位信号
- 4 input sys_init_done,
- 5 //lcd接口
- 6 output lcd_clk, //LCD驱动时钟
- 7 output lcd_hs, //LCD 行同步信号
- 8 output lcd_vs, //LCD 场同步信号
- 9 output lcd_de, //LCD 数据输入使能
- 10 inout [23:0 lcd_rgb, //LCD RGB颜色数据
- 11 output lcd_bl, //LCD 背光控制信号
- 12 output lcd_rst, //LCD 复位信号
- 13 output lcd_pclk, //LCD 采样时钟
- 14 output [15:0 lcd_id, //LCD屏ID
- 15 output out_vsync, //lcd场信号
- 16 output [10:0 pixel_xpos, //像素点横坐标
- 17 output [10:0 pixel_ypos, //像素点纵坐标
- 18 output [10:0 h_disp, //LCD屏水平分辨率
- 19 output [10:0 v_disp, //LCD屏垂直分辨率
- 20 input [15:0 data_in, //数据输入
- 21 output data_req //请求数据输入
- 22
- 23 );
- 24
- 25 //wire define
- 26 wire [15:0 lcd_rgb_565; //输出的16位lcd数据
- 27 wire [23:0 lcd_rgb_o ; //LCD 输出颜色数据
- 28 wire [23:0 lcd_rgb_i ; //LCD 输入颜色数据
- 29 wire lcd_pclk_180;
- 30 //*****************************************************
- 31 //** main code
- 32 //*****************************************************
- 33 //将摄像头16bit数据转换为24bit的lcd数据
- 34 assign lcd_rgb_o = {lcd_rgb_565[15:11],3'b000,lcd_rgb_565[10:5],2'b00,
- 35 lcd_rgb_565[4:0],3'b000};
- 36 assign lcd_pclk= (lcd_id==16'h1018)?~lcd_pclk_180:lcd_pclk_180;
- 37 //像素数据方向切换
- 38 assign lcd_rgb = lcd_de ? lcd_rgb_o : {24{1'bz}};
- 39 assign lcd_rgb_i = lcd_rgb;
- 40
- 41 //时钟分频模块
- 42 clk_div u_clk_div(
- 43 .clk (sys_clk ),
- 44 .rst_n (sys_rst_n),
- 45 .lcd_id (lcd_id ),
- 46 .lcd_pclk (lcd_clk )
- 47 );
- 48
- 49 //读LCD ID模块
- 50 rd_id u_rd_id(
- 51 .clk (sys_clk ),
- 52 .rst_n (sys_rst_n),
- 53 .lcd_rgb (lcd_rgb_i),
- 54 .lcd_id (lcd_id )
- 55 );
- 56
- 57 //lcd驱动模块
- 58 lcd_driver u_lcd_driver(
- 59 .lcd_clk (lcd_clk),
- 60 .sys_rst_n (sys_rst_n & sys_init_done),
- 61 .lcd_id (lcd_id),
- 62
- 63 .lcd_hs (lcd_hs),
- 64 .lcd_vs (lcd_vs),
- 65 .lcd_de (lcd_de),
- 66 .lcd_rgb (lcd_rgb_565),
- 67 .lcd_bl (lcd_bl),
- 68 .lcd_rst (lcd_rst),
- 69 .lcd_pclk (lcd_pclk_180),
- 70
- 71 .pixel_data (data_in),
- 72 .data_req (data_req),
- 73 .out_vsync (out_vsync),
- 74 .h_disp (h_disp),
- 75 .v_disp (v_disp),
- 76 .pixel_xpos (pixel_xpos),
- 77 .pixel_ypos (pixel_ypos)
- 78 );
- 79
- 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所示。 图 32.5.1 正点原子RGBLCD模块FPC连接器 与DFZU2EG/4EV MPSoC开发板上的RGB TFTLCD接口连接时,先掀起开发板上的棕色的翻盖到垂直开发板方向,将FPC排线蓝色面朝棕色翻盖垂直插入连接器,最后将棕色的翻盖下按至水平方向,如图 32.5.2所示。 图 32.5.2 DFZU2EG/4EV MPSoC开发板连接RGB-LCD液晶屏 然后将双目OV5640摄像头(本节实验只使用双目摄像头模块的其中一个摄像头)模块插在DFZU2EG/4EV MPSoC开发板J19扩展口上,摄像头实物连接如上图所示。最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,接下来连接电源线后拨动开关按键给开发板上电。
接下来我们下载程序,验证OV5640 RGB-LCD实时显示功能。下载完成后观察RGB-LCD模块显示的图案如下图所示,说明OV5640 RGB-LCD实时显示程序下载验证成功。 |