本帖最后由 正点原子运营 于 2023-4-27 10:26 编辑
第三十四章 双目OV5640摄像头RGB-LCD显示实验
1)实验平台:正点原子 DFZU2EG/4EV MPSoC开发板
2) 章节摘自【正点原子】DFZU2EG/4EV MPSoC之FPGA开发指南 V1.0
6)FPGA技术交流QQ群:994244016
双目摄像头是在一个模组上集成了两个摄像头,实现双通道图像采集的功能。双目摄像头一般应用于安防监控、立体视觉测距、三维重建等领域。本试验只做最基础的工作,把双目OV5640摄像头实时采集到的图像分左右两半显示在LCD屏幕上。本章包括以下几个部分: 34.1简介 34.2实验任务 34.3硬件设计 34.4程序设计 34.5下载验证
34.1 简介摄像头在日常生活中非常常见,一般分为单目摄像头、双目摄像头和多目摄像头。单目摄像头目前使用最为广泛;双目摄像头主要应用于单目摄像头无法胜任的场合,如测距领域,根据两个摄像头的视差,辅以一定的算法,人们可以计算物体的距离;当然针对一些特殊的应用,目前市场上也出现了多目摄像头,以应对更加复杂的场景。在“OV5640摄像头LCD显示实验”中对OV5640的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考之前的实验。本次实验将在前面单目OV5640摄像头的基础上学习双目摄像头的LCD显示。
34.2 实验任务本章实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边。
34.3 硬件设计摄像头扩展接口原理图及OV5640模块说明与“OV5640摄像头RGB-LCD显示实验”完全相同,请参考“OV5640摄像头RGB-LCD显示实验”硬件设计部分。HDMI接口部分的硬件设计请参考“HDMI彩条显示实验”中的硬件设计部分。 由于LCD接口和DDR4引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出双目摄像头相关管脚分配,如下表所示:
双目摄像头XDC约束文件如下: - #时钟
- set_propertyIOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_p]
- set_propertyIOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_n]
- set_propertyPACKAGE_PIN AE5 [get_ports sys_clk_p]
- set_propertyPACKAGE_PIN AF5 [get_ports sys_clk_n]
- #复位
- set_property-dict {PACKAGE_PIN AH11 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
- #CAMERA
- #摄像头接口的时钟
- create_clock-period 40.000 -name cmos_pclk [get_ports cam_pclk_1]
- create_clock-period 40.000 -name cmos_pclk [get_ports cam_pclk_2]
- set_propertyCLOCK_DEDICATED_ROUTE FALSE [get_nets {cam_pclk_1_IBUF_inst/O}]
- set_propertyCLOCK_DEDICATED_ROUTE FALSE [get_nets {cam_pclk_2_IBUF_inst/O}]
- set_property-dict {PACKAGE_PIN C13 IOSTANDARD LVCMOS33} [get_ports cam_pclk_1]
- set_property-dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_1]
- set_property-dict {PACKAGE_PIN B15 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_1]
- set_property-dict {PACKAGE_PIN E15 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[0]}]
- set_property-dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[1]}]
- set_property-dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[2]}]
- set_property-dict {PACKAGE_PIN D14 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[3]}]
- set_property-dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[4]}]
- set_property-dict {PACKAGE_PIN B13 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[5]}]
- set_property-dict {PACKAGE_PIN C14 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[6]}]
- set_property-dict {PACKAGE_PIN A13 IOSTANDARD LVCMOS33 } [get_ports {cam_data_1[7]}]
- set_property-dict {PACKAGE_PIN G14 IOSTANDARD LVCMOS33} [get_ports cam_vsync_1]
- set_property-dict {PACKAGE_PIN G13 IOSTANDARD LVCMOS33} [get_ports cam_href_1]
- set_property-dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33} [get_ports cam_scl_1]
- set_property-dict {PACKAGE_PIN F15 IOSTANDARD LVCMOS33} [get_ports cam_sda_1]
- set_propertyPULLUP true [get_ports cam_scl_1]
- set_propertyPULLUP true [get_ports cam_sda_1]
- set_property-dict {PACKAGE_PIN D11 IOSTANDARD LVCMOS33} [get_ports cam_pclk_2]
- set_property-dict {PACKAGE_PIN A11 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_2]
- set_property-dict {PACKAGE_PIN B14 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_2]
- set_property-dict {PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[0]}]
- set_property-dict {PACKAGE_PIN C11 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[1]}]
- set_property-dict {PACKAGE_PIN B11 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[2]}]
- set_property-dict {PACKAGE_PIN B10 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[3]}]
- set_property-dict {PACKAGE_PIN A10 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[4]}]
- set_property-dict {PACKAGE_PIN E10 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[5]}]
- set_property-dict {PACKAGE_PIN E12 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[6]}]
- set_property-dict {PACKAGE_PIN D10 IOSTANDARD LVCMOS33 } [get_ports {cam_data_2[7]}]
- set_property-dict {PACKAGE_PIN A15 IOSTANDARD LVCMOS33} [get_ports cam_vsync_2]
- set_property-dict {PACKAGE_PIN A12 IOSTANDARD LVCMOS33} [get_ports cam_href_2]
- set_property-dict {PACKAGE_PIN A14 IOSTANDARD LVCMOS33} [get_ports cam_scl_2]
- set_property-dict {PACKAGE_PIN D12 IOSTANDARD LVCMOS33} [get_ports cam_sda_2]
- set_propertyPULLUP true [get_ports cam_scl_2]
- set_propertyPULLUP true [get_ports cam_sda_2]
复制代码
34.4 程序设计根据实验任务,首先设计如图 34.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头RGB-LCD显示实验”的整体架构。顶层同样是例化了DDR4顶层模块、OV5640驱动顶层模块、图像分辨率处理模块以及LCD驱动顶层模块。其中除了图像分辨率处理模块没有做任何修改之外,其他三个模块都有一点小小的改动。下面就来给大家重点讲解改动的部分内容。 我们先来看看改动最小的一个模块,即OV5640驱动模块,这个模块从本质上来讲是没有做任何修改的,学习过之前“OV5640摄像头RGB-LCD显示实验”的同学应该都知道,OV5640驱动模块其实还包含了三个子模块,分别对应摄像头的寄存器配置、IIC驱动以及数据采集三个功能。双目摄像头其实就是将两个OV5640合在一起工作,所以针对OV5640驱动模块我们只需要把它例化两次就可以,给两个摄像头都进行寄存器配置、IIC驱动以及数据采集。因此在本节实验的顶层文件中调用了两次OV5640驱动模块以达到同时控制两个摄像头的目的,顶层RTL视图如下所示: 由于摄像头驱动模块内部的代码没有做任何修改,所以这里就不贴出代码了,下面我们一起来看看LCD驱动模块做了哪些修改。其实LCD驱动模块仅仅只是在“OV5640摄像头RGB-LCD显示实验”的基础上又添加了一个显示模块(lcd_disply),这个显示模块的主要作用就是用来在LCD显示屏上显示两个字符串“OV5640 1”和“OV5640 2”。因为本节实验是双目摄像头同时将画面一左一右的显示在LCD屏幕上,为了区分哪边的画面对应哪个摄像头,需要在LCD屏幕上左右两边显示“OV5640 1”和“OV5640 2”字符,这样就好区分画面对应哪个摄像头了。
lcd_disply字符串显示模块的代码如下: - 1 module lcd_disply(
- 2 input lcd_clk, //lcd模块驱动时钟
- 3 input sys_rst_n, //复位信号
- 4 //RGB LCD接口
- 5 input [ 10:0 pixel_xpos, //像素点横坐标
- 6 input [ 10:0 pixel_ypos, //像素点纵坐标
- 7 input [15:0 rd_data, //图像像素值
- 8 input [12:0 rd_h_pixel, //摄像头输出的水平方向分辨率
- 9 output reg [15:0 pixel_data //像素点数据,
- 10 );
- 11
- 12 //LCD的ID
- 13 parameter ID_4342 = 16'h4342;
- 14 parameter ID_7084 = 16'h7084;
- 15 parameter ID_7016 = 16'h7016;
- 16 parameter ID_1018 = 16'h1018;
- 17 parameter ID_4384 = 16'h4384;
- 18
- 19 //颜色定义
- 20 localparam RED = 16'b11111_000000_00000; //字符颜色
- 21 localparam BLUE =16'b00000_000000_11111; //字符区域背景色
- 22 localparam BLACK = 16'b00000_000000_00000; //屏幕背景色
- 23
- 24 //reg define
- 25 reg [63:0 char0[15:0]; //字符数组0
- 26 reg [63:0 char1[15:0]; //字符数组1
- 27 reg [127:0 char2[32:0]; //字符数组2
- 28 reg [127:0 char3[32:0]; //字符数组3
- 29
- 30 //*****************************************************
- 31 //** main code
- 32 //*****************************************************
- 33
复制代码
关于如何在LCD显示屏幕上显示字符串我相信大家都不陌生了,在前面专门有一个“LCD字符图片显示实验”的例程来教大家如何在LCD显示屏幕上显示字符串和图片。本模块实现了五种RGB-LCD屏不同区域显示内容的逻辑判断,同时实现了字符叠加功能。
代码第25行到28行,定义了四个二维数组char,用于存储对英文取模得到的点阵数据,具体代码讲解,可以参看“RGB-LCD字符和图片显示实验”实验。
代码第20行到22行,定义了不同颜色对应的参数。代码第35行到144行给我们要用到的四个字符的模值进行赋值。在本次实验中LCD屏幕上要显示的字符大小为32*128,左右半边分别叠加上的“OV5640 1”和“OV5640 2”。 代码从144行往后是显示逻辑的具体实现,由于代码涉及到了判断条件之间的多重嵌套,为了让我们快速理清思路,我们结合代码制作了如图 34.4.3所示的显示逻辑图。结合代码和图,我们来具体介绍。
首先我们对屏幕类型和图像像素纵坐标进行判断,对于所有屏幕,如果纵坐标值不在字符显示区域内,我们执行代码第170行,让屏幕显示图像像素的值。只有当纵坐标值位于字符显示区域内,我们正式开始字符叠加处理。 - 146 //显示逻辑判断
- 147 always@(posedge lcd_clk) begin
- 148 if(pixel_ypos >= 0 && pixel_ypos < 33)begin
- 149 //判断像素坐标是否在左半边屏幕中间
- 150 if(pixel_xpos < (rd_h_pixel[12:2]+64)
- 151 && pixel_xpos >= (rd_h_pixel[12:2]-64) )begin
- 152 //读取字模OV56401 (32*128)
- 153 if(char2[pixel_ypos][127-(pixel_xpos-rd_h_pixel[12:2]+64)])
- 154 pixel_data <=BLUE; //字模数组中的"1"显示蓝色
- 155 else //字模数组中的"0"显示图像像素值
- 156 pixel_data <= rd_data;
- 157 end //判断像素坐标是否在右半边屏幕中间
- 158 else if(pixel_xpos < (rd_h_pixel[12:2]*3+64)
- 159 && pixel_xpos >= (rd_h_pixel[12:2]*3-64))begin
- 160 //读取字模OV56402 (32*128)
- 161 if(char3[pixel_ypos][63-pixel_xpos+(rd_h_pixel[12:2])*3])
- 162 pixel_data <=BLUE; //字模数组中的"1"显示蓝色
- 163 else //字模数组中的"0"显示图像像素值
- 164 pixel_data <= rd_data;
- 165 end
- 166 else //纵坐标位于字符区域内的字符两边区域显示黑色
- 167 pixel_data <= rd_data;
- 168 end
- 169 else //所有屏幕纵坐标位于字符区域外时显示像素值
- 170 pixel_data <= rd_data;
- 171 end
- 172
- 173 endmodule
复制代码代码第148行,通过判断了像素点纵坐标的位置。代码第150行和151行,此时开始判断像素点横坐标位置,如果此时横坐标处于左半边的中间位置,此时执行代码第153行,开始读取“OV5640 1”的字符数组的值。代码第154到156行,将字模数组中为“1”的点的像素值赋值为蓝色,为“0”的点像素值赋值为图像像素的值。这就是LCD屏左半边字符叠加的实现。代码第158到164行,实现的是LCD屏右半边字符叠加,实现的代码形式一模一样,只是字模数组变为了“OV5640 2”,在这里我们不再重复。代码第170行,非字符纵坐标区域,显示图片像素值。 接下来我们重点来看看DDR4顶层模块,它包含了MIG IP核、DDR4读写模块以及FIFO调度模块。这三个子模块,除了官方的MIG IP核配置没改变之外,其他的像DDR4读写模块(ddr4_rw)和FIFO调度模块都做了修改。 下面是DDR4顶层模块的系统框图: 结合图可以看出相较于“OV5640摄像头RGB-LCD显示实验”,本次实验将FIFO调度模块替换成了FIFO顶层调度模块。本次实验是两个摄像头采集数据并且把数据存入两个写FIFO,两个写FIFO再将数据写入DDR4的两个不同的存储空间中,接着两个读FIFO分别从两个对应的DDR4地址空间中取出数据,最后两组数据一起拼接为完整一帧LCD图像,达到一个屏幕同时显示两幅图像的效果。在DDR4处理数据的时候,两组数据流相当于过独木桥,需要分时“排队”进出;同样在显示端,两组数据也要“排队”显示,所以本次实验的FIFO顶层调度模块的作用就是根据不同的情况对两个FIFO调度模块的信号进行切换。
下面是DDR控制模块的原理图: DDR读写模块:该模块负责与MIG模块的命令和地址的交互,根据FIFO顶层调度模块中各个FIFO的剩余数据量来切换DDR4的读写命令和地址。
MIG模块:MIG模块(ddr4_0)负责连接外设和FPGA,详细说明请看“DDR4读写测试实验”。
FIFO顶层调度模块:负责对输入和输出的数据进行时钟域的切换和位宽的转换,并根据DDR读写模块输出的使能来调度四个FIFO的数据。
下面是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 [12:0 h_disp ,
- 31 input ddr4_read_valid , //DDR4 读使能
- 32 input ddr4_pingpang_en , //DDR4 乒乓操作使能
- 33 input wr_clk_1 , //wfifo时钟
- 34 input wr_clk_2 , //wfifo时钟
- 35 input rd_clk , //rfifo的读时钟
- 36 input datain_valid_1 , //数据有效使能信号
- 37 input datain_valid_2 , //数据有效使能信号
- 38 input [15:0 datain_1 , //有效数据
- 39 input [15:0 datain_2 , //有效数据
- 40 input rdata_req , //请求像素点颜色数据输入
- 41 input rd_load , //输出源更新信号
- 42 input wr_load_1 , //输入源更新信号
- 43 input wr_load_2 , //输入源更新信号
- 44 output [15:0 dataout , //rfifo输出数据
- 45 output clk_50m ,
- 46 output init_calib_complete //ddr4初始化完成信号
- 47 );
- 48
- 49 //wire define
- 50 wire ui_clk ; //用户时钟
- 51 wire [27:0 app_addr ; //ddr4 地址
- 52 wire [2:0 app_cmd ; //用户读写命令
- 53 wire app_en ; //MIG IP核使能
- 54 wire app_rdy ; //MIG IP核空闲
- 55 wire [127:0 app_rd_data ; //用户读数据
- 56 wire app_rd_data_end ; //突发读当前时钟最后一个数据
- 57 wire app_rd_data_valid ; //读数据有效
- 58 wire [127:0 app_wdf_data ; //用户写数据
- 59 wire app_wdf_end ; //突发写当前时钟最后一个数据
- 60 wire [15:0 app_wdf_mask ; //写数据屏蔽
- 61 wire app_wdf_rdy ; //写空闲
- 62 wire app_sr_active ; //保留
- 63 wire app_ref_ack ; //刷新请求
- 64 wire app_zq_ack ; //ZQ 校准请求
- 65 wire app_wdf_wren ; //ddr4 写使能
- 66 wire clk_ref_i ; //ddr4参考时钟
- 67 wire sys_clk_i ; //MIG IP核输入时钟
- 68 wire ui_clk_sync_rst ; //用户复位信号
- 69 wire [20:0 rd_cnt ; //实际读地址计数
- 70 wire [3 :0 state_cnt ; //状态计数器
- 71 wire [23:0 rd_addr_cnt ; //用户读地址计数器
- 72 wire [23:0 wr_addr_cnt ; //用户写地址计数器
- 73 wire rfifo_wren ; //从DDR4读出数据的有效使能
- 74 wire [127:0 rfifo_wdata_1 ; //rfifo1输入数据
- 75 wire [127:0 rfifo_wdata_2 ; //rfifo2输入数据
- 76 wire [10:0 wfifo_rcount_1 ; //wfifo1剩余数据计数
- 77 wire [10:0 wfifo_rcount_2 ; //wfifo2剩余数据计数
- 78 wire [10:0 rfifo_wcount_1 ; //rfifo1写进数据计数
- 79 wire [10:0 rfifo_wcount_2 ;
- 80
- 81
- 82 //*****************************************************
- 83 //** main code
- 84 //*****************************************************
- 85
- 86 //读写模块
- 87 ddr4_rw u_ddr4_rw(
- 88 .ui_clk (ui_clk) ,
- 89 .ui_clk_sync_rst (ui_clk_sync_rst) ,
- 90 //MIG 接口
- 91 .init_calib_complete (init_calib_complete) , //ddr4初始化完成信号
- 92 .app_rdy (app_rdy) , //MIG IP核空闲
- 93 .app_wdf_rdy (app_wdf_rdy) , //写空闲
- 94 .app_rd_data_valid (app_rd_data_valid) , //读数据有效
- 95 .app_rd_data (app_rd_data) ,
- 96 .app_addr (app_addr) , //ddr4 地址
- 97 .app_en (app_en) , //MIG IP核使能
- 98 .app_wdf_wren (app_wdf_wren) , //ddr4 写使能
- 99 .app_wdf_end (app_wdf_end) , //突发写当前时钟最后一个数据
- 100 .app_cmd (app_cmd) , //用户读写命令
- 101 //ddr4地址参数
- 102 .app_addr_rd_min (app_addr_rd_min) , //读ddr4的起始地址
- 103 .app_addr_rd_max (app_addr_rd_max) , //读ddr4的结束地址
- 104 .rd_bust_len (rd_bust_len) , //从ddr4中读数据时的突发长度
- 105 .app_addr_wr_min (app_addr_wr_min) , //写ddr4的起始地址
- 106 .app_addr_wr_max (app_addr_wr_max) , //写ddr4的结束地址
- 107 .wr_bust_len (wr_bust_len) , //从ddr4中写数据时的突发长度
- 108 //用户接口
- 109 .rfifo_wren_1 (rfifo_wren_1) , //rfifo写使能
- 110 .rfifo_wdata_1 (rfifo_wdata_1) , //rfifo写数据
- 111 .rfifo_wren_2 (rfifo_wren_2) , //rfifo写使能
- 112 .rfifo_wdata_2 (rfifo_wdata_2) , //rfifo写数据
- 113 .wfifo_rden_1 (wfifo_rden_1) , //写端口FIFO1中的读使能
- 114 .wfifo_rden_2 (wfifo_rden_2) , //写端口FIFO2中的读使能
- 115 .rd_load (rd_load) , //输出源场信号
- 116 .wr_load_1 (wr_load_1) , //输入源场信号
- 117 .wr_load_2 (wr_load_2) , //输入源场信号
- 118 .wfifo_rcount_1 (wfifo_rcount_1) , //wfifo剩余数据计数
- 119 .rfifo_wcount_1 (rfifo_wcount_1) , //rfifo写进数据计数
- 120 .wfifo_rcount_2 (wfifo_rcount_2) , //wfifo剩余数据计数
- 121 .rfifo_wcount_2 (rfifo_wcount_2) , //rfifo写进数据计数
- 122 .wr_clk_2 (wr_clk_2) , //wfifo时钟
- 123 .wr_clk_1 (wr_clk_1)
- 124 );
- 125
- 126 ddr4_0u_ddr4_0 (
- 127 .c0_init_calib_complete(init_calib_complete),
- 128 .dbg_clk(),
- 129 .c0_sys_clk_p(c0_sys_clk_p),
- 130 .c0_sys_clk_n(c0_sys_clk_n),
- 131 .dbg_bus(),
- 132 .c0_ddr4_adr(c0_ddr4_adr),
- 133 .c0_ddr4_ba(c0_ddr4_ba),
- 134 .c0_ddr4_cke(c0_ddr4_cke),
- 135 .c0_ddr4_cs_n(c0_ddr4_cs_n),
- 136 .c0_ddr4_dm_dbi_n(c0_ddr4_dm_dbi_n),
- 137 .c0_ddr4_dq(c0_ddr4_dq),
- 138 .c0_ddr4_dqs_c(c0_ddr4_dqs_c),
- 139 .c0_ddr4_dqs_t(c0_ddr4_dqs_t),
- 140 .c0_ddr4_odt(c0_ddr4_odt),
- 141 .c0_ddr4_bg(c0_ddr4_bg),
- 142 .c0_ddr4_reset_n(c0_ddr4_reset_n),
- 143 .c0_ddr4_act_n(c0_ddr4_act_n),
- 144 .c0_ddr4_ck_c(c0_ddr4_ck_c),
- 145 .c0_ddr4_ck_t(c0_ddr4_ck_t),
- 146 //user interface
- 147 .c0_ddr4_ui_clk(ui_clk),
- 148 .c0_ddr4_ui_clk_sync_rst(ui_clk_sync_rst),
- 149 .c0_ddr4_app_en(app_en),
- 150 .c0_ddr4_app_hi_pri(1'b0),
- 151 .c0_ddr4_app_wdf_end(app_wdf_end),
- 152 .c0_ddr4_app_wdf_wren(app_wdf_wren),
- 153 .c0_ddr4_app_rd_data_end(app_rd_data_end),
- 154 .c0_ddr4_app_rd_data_valid(app_rd_data_valid),
- 155 .c0_ddr4_app_rdy(app_rdy),
- 156 .c0_ddr4_app_wdf_rdy(app_wdf_rdy),
- 157 .c0_ddr4_app_addr(app_addr),
- 158 .c0_ddr4_app_cmd(app_cmd),
- 159 .c0_ddr4_app_wdf_data(app_wdf_data),
- 160 .c0_ddr4_app_wdf_mask(16'b0),
- 161 .c0_ddr4_app_rd_data(app_rd_data),
- 162 .addn_ui_clkout1(clk_50m),
- 163 .sys_rst(~sys_rst_n)
- 164 );
- 165
- 166 ddr4_fifo_ctrl_topu_ddr4_fifo_ctrl_top (
- 167
- 168 .rst_n (sys_rst_n &&sys_init_done), //复位信号
- 169 .rd_clk (rd_clk) , //rfifo时钟
- 170 .clk_100 (ui_clk) , //用户时钟
- 171 //fifo1接口信号
- 172 .wr_clk_1 (wr_clk_1) , //wfifo时钟
- 173 .datain_valid_1 (datain_valid_1) , //数据有效使能信号
- 174 .datain_1 (datain_1) , //有效数据
- 175 .wr_load_1 (wr_load_1) , //输入源场信号
- 176 .rfifo_din_1 (rfifo_wdata_1) , //rfifo写数据
- 177 .rfifo_wren_1 (rfifo_wren_1) , //rfifo写使能
- 178 .wfifo_rden_1 (wfifo_rden_1) , //wfifo读使能
- 179 .wfifo_rcount_1 (wfifo_rcount_1) , //wfifo剩余数据计数
- 180 .rfifo_wcount_1 (rfifo_wcount_1) , //rfifo写进数据计数
- 181 //fifo2接口信号
- 182 .wr_clk_2 (wr_clk_2) , //wfifo时钟
- 183 .datain_valid_2 (datain_valid_2) , //数据有效使能信号
- 184 .datain_2 (datain_2) , //有效数据
- 185 .wr_load_2 (wr_load_2) , //输入源场信号
- 186 .rfifo_din_2 (rfifo_wdata_2) , //rfifo写数据
- 187 .rfifo_wren_2 (rfifo_wren_2) , //rfifo写使能
- 188 .wfifo_rden_2 (wfifo_rden_2) , //wfifo读使能
- 189 .wfifo_rcount_2 (wfifo_rcount_2) , //wfifo剩余数据计数
- 190 .rfifo_wcount_2 (rfifo_wcount_2) , //rfifo写进数据计数
- 191
- 192 .h_disp (h_disp) , //摄像头水平分辨率
- 193 .rd_load (rd_load) , //输出源场信号
- 194 .rdata_req (rdata_req) , //请求像素点颜色数据输入
- 195 .pic_data (dataout) , //有效数据
- 196 .wfifo_dout (app_wdf_data) //用户写数据
- 197 );
- 198
- 199 endmodule
复制代码在“OV5640摄像头RGB-LCD显示实验”的程序中,读写操作地址用了DDR4的两个存储空间,但本次实验开辟了四个存储空间,使得两个摄像头的数据在DDR4的存储中互不影响,也方便调度,所以本次实验需要在“OV5640摄像头RGB-LCD显示实验”的程序方面做些改动。具体框图如下图所示: 图像数据是由两个摄像头分别采集得来的,所以将图像分别存入两个wfifo中。当两个wfifo任意一个fifo剩余的数据量大于本次实验设定的阈值时,DDR读写模块就对其发出读数据请求信号,使fifo中的数据写入DDR4中,保证wfifo不会写满。当两个rfifo任意一个fifo剩余的数据量小于本次实验设定的阈值时,DDR读写模块就向对应的rfifo中写入数据,保证rfifo不会读空。DDR4中开辟了四个存储空间,每个输入源使用两个存储空间,这么做的好处一是对输入源做乒乓操作,防止画面撕裂,另一个是保证两个输入源的数据不会相互干扰。
本次实验中的DDR读写模块是基于“OV5640摄像头RGB-LCD显示实验”做的修改,本次实验着重对代码的改动部分做讲解。 - 40 //localparam
- 41 localparam IDLE = 7'b0000001; //空闲状态
- 42 localparam DDR4_DONE = 7'b0000010; //DDR4初始化完成状态
- 43 localparam WRITE_1 = 7'b0000100; //读FIFO保持状态
- 44 localparam READ_1 = 7'b0001000; //写FIFO保持状态
- 45 localparam WRITE_2 = 7'b0010000; //读FIFO保持状态
- 46 localparam READ_2 = 7'b0100000; //写FIFO保持状态
- 47 localparam READ_WAIT = 7'b1000000; //写FIFO保持状态
复制代码在代码的40行至47行,相比于“OV5640摄像头RGB-LCD显示实验”多添加了三个状态,一个读状态,一个写状态,还有一个读等待状态。 - 105 //在写状态,MIG空闲且写有效,此时拉高FIFO写使能
- 106 assign wfifo_rden_1 = (state_cnt == WRITE_1 && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
- 107
- 108 //在写状态,MIG空闲且写有效,此时拉高FIFO写使能
- 109 assign wfifo_rden_2 = (state_cnt == WRITE_2 && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
复制代码在代码的105行至109行,根据不同的写状态来给不同的WFIFO发出读数据使能信号。 - 117 //读端口FIFO1数据没有写完的使能信号
- 118 always @(posedge ui_clk or negedge rst_n) begin
- 119 if(~rst_n || rd_rst)begin
- 120 rfifo_data_en_1 <= 0;
- 121 end
- 122 else begin
- 123 if(state_cnt == DDR4_DONE )
- 124 rfifo_data_en_1 <= 0;
- 125 else if(state_cnt == READ_1 )
- 126 rfifo_data_en_1 <= 1;
- 127 else
- 128 rfifo_data_en_1 <= rfifo_data_en_1;
- 129 end
- 130 end
- 131
- 132 //读端口FIFO2数据没有写完的使能信号
- 133 always @(posedge ui_clk or negedge rst_n) begin
- 134 if(~rst_n || rd_rst)begin
- 135 rfifo_data_en_2 <= 0;
- 136 end
- 137 else begin
- 138 if(state_cnt == DDR4_DONE)
- 139 rfifo_data_en_2 <= 0;
- 140 else if(state_cnt == READ_2 )
- 141 rfifo_data_en_2 <= 1;
- 142 else
- 143 rfifo_data_en_2 <= rfifo_data_en_2;
- 144 end
- 145 end
复制代码在代码的117行至145行,对rfifo_data_en_1和rfifo_data_en_2进行了赋值,这两个信号在读操作开始后拉高,进入DDR4空闲状态拉低。信号为高时表示此次读操作所需要的数据没有全部从DDR4读出来,为低时,表示数据已经全部读出来了。 - 147 //从DDR4读出的有效数据使能进行计数
- 148 always @(posedge ui_clk or negedge rst_n) begin
- 149 if(~rst_n || rd_rst )begin
- 150 data_valid_cnt <= 0;
- 151 end
- 152 else begin
- 153 if(state_cnt == DDR4_DONE )
- 154 data_valid_cnt <= 0;
- 155 else if(app_rd_data_valid)
- 156 data_valid_cnt <= data_valid_cnt + 1;
- 157 else
- 158 data_valid_cnt <= data_valid_cnt;
- 159 end
- 160 end
复制代码在代码的147行至160行,对DDR4读出数据的有效使能(app_rd_data_valid)进行了计数。 - 162 //对DDR读数据的输出端进行选择
- 163 always @(posedge ui_clk or negedge rst_n) begin
- 164 if(~rst_n || rd_rst)begin
- 165 rfifo_wren_1 <= 0;
- 166 rfifo_wren_2 <= 0;
- 167 rfifo_wdata_1 <= 0;
- 168 rfifo_wdata_2 <= 0;
- 169 end
- 170 else begin
- 171 if(rfifo_data_en_1)begin
- 172 rfifo_wren_1 <= app_rd_data_valid;
- 173 rfifo_wdata_1 <= app_rd_data;
- 174 rfifo_wren_2 <= 0;
- 175 rfifo_wdata_2 <= 0;
- 176 end
- 177 else if(rfifo_data_en_2)begin
- 178 rfifo_wren_2 <= app_rd_data_valid;
- 179 rfifo_wdata_2 <= app_rd_data;
- 180 rfifo_wren_1 <= 0;
- 181 rfifo_wdata_1 <= 0;
- 182 end
- 183 else begin
- 184 rfifo_wren_2 <= 0;
- 185 rfifo_wdata_2 <= 0;
- 186 rfifo_wren_1 <= 0;
- 187 rfifo_wdata_1 <= 0;
- 188 end
- 189
- 190 end
- 191 end
复制代码在代码的162行至191行,根据信号rfifo_data_en_1和rfifo_data_en_2来判断是哪个rfifo将要空了,并把DDR4读出的数据写入这个rfifo。 - 193 //将数据读写地址赋给ddr地址
- 194 always @(*) begin
- 195 if(~rst_n)
- 196 app_addr <= 0;
- 197 else if(state_cnt == READ_1 )
- 198 app_addr <= {3'b0,raddr_page_1,1'b0,app_addr_rd_1[22:0]};
- 199 else if(state_cnt == READ_2 )
- 200 app_addr <= {3'b1,raddr_page_2,1'b0,app_addr_rd_2[22:0]};
- 201 else if(state_cnt == WRITE_1 )
- 202 app_addr <= {3'b0,waddr_page_1,1'b0,app_addr_wr_1[22:0]};
- 203 else
- 204 app_addr <= {3'b1,waddr_page_2,1'b0,app_addr_wr_2[22:0]};
- 205 end
复制代码在代码的193行至205行,根据不同的状态写入相对应的地址。在代码的200行和204行,将信号app_addr的高三位赋1,其用意是为了和另一个输入源的存储空间做区分。信号raddr_page_1和waddr_page_1是对同一个输入源的两个存储空间的切换信号,同理信号raddr_page_2和waddr_page_2也是如此。 程序中第322至529行所示,这段代码是DDR4读写逻辑实现,状态跳转图如下图所示: 图 34.4.7 状态跳转图
本次实验的状态跳转相对于“OV7725摄像头RGB-LCD显示实验”中的状态跳转只是多了一个读状态、一个写状态和读等待状态。两个实验当中的写状态跳转的条件是没有变的,读状态的跳转出现了变化。本次实验读状态不直接跳到DDR的空闲状态,而是跳到读等待状态。本次实验的实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边,所以在输出端存在两个rfifo。在下面的时序图中可以发现当读状态操作完后,而此时的数据没有全部读出来。如果此时直接跳转到DDR的空闲状态,下一刻状态就会跳到另一个读状态,那么DDR4读出的数据就不容易区分是哪个rfifo的数据,容易造成数据错乱。为了保证数据可以准确的写入到对应的rfifo中,必须在数据完全读出后才能跳转到DDR的空闲状态,这是添加读等待状态的原因。时序图如下: 下面是FIFO顶层调度模块的原理图: 本次实验增加了一个rfifo和一个wfifo,所以对FIFO调度模块例化了两次。由原理图可知,在FIFO顶层调度模块,对写fifo的输出数据进行了判断,也对读fifo的输出数据进行判断。
FIFO调度模块:负责对输入和输出的数据进行时钟域的切换和位宽的转换。详细说明请看“OV5640摄像头RGB-LCD显示实验”。
FIFO顶层调度模块的代码如下: - 1 module ddr4_fifo_ctrl_top(
- 2 input rst_n , //复位信号
- 3 input rd_clk , //rfifo时钟
- 4 input clk_100 , //用户时钟
- 5 //fifo1接口信号
- 6 input wr_clk_1 , //wfifo时钟
- 7 input datain_valid_1 , //数据有效使能信号
- 8 input [15:0 datain_1 , //有效数据
- 9 input wr_load_1 , //输入源场信号
- 10 input [127:0 rfifo_din_1 , //用户读数据
- 11 input rfifo_wren_1 , //从ddr4读出数据的有效使能
- 12 input wfifo_rden_1 , //wfifo读使能
- 13 output [10:0 wfifo_rcount_1 , //wfifo剩余数据计数
- 14 output [10:0 rfifo_wcount_1 , //rfifo写进数据计数
- 15 //fifo2接口信号
- 16 input wr_clk_2 , //wfifo时钟
- 17 input datain_valid_2 , //数据有效使能信号
- 18 input [15:0 datain_2 , //有效数据
- 19 input wr_load_2 , //输入源场信号
- 20 input [127:0 rfifo_din_2 , //用户读数据
- 21 input rfifo_wren_2 , //从ddr4读出数据的有效使能
- 22 input wfifo_rden_2 , //wfifo读使能
- 23 output [10:0 wfifo_rcount_2 , //wfifo剩余数据计数
- 24 output [10:0 rfifo_wcount_2 , //rfifo写进数据计数
- 25
- 26 input [12:0 h_disp ,
- 27 input rd_load , //输出源场信号
- 28 input rdata_req , //请求像素点颜色数据输入
- 29 output [15:0 pic_data , //有效数据
- 30 output [127:0 wfifo_dout //用户写数据
- 31
- 32 );
- 33
- 34 //reg define
- 35 reg [12:0 rd_cnt;
- 36
- 37 //wire define
- 38 wire rdata_req_1;
- 39 wire rdata_req_2;
- 40 wire [15:0 pic_data_1;
- 41 wire [15:0 pic_data_2;
- 42 wire [15:0 pic_data;
- 43 wire [127:0 wfifo_dout;
- 44 wire [127:0 wfifo_dout_1;
- 45 wire [127:0 wfifo_dout_2;
- 46 wire [10:0 wfifo_rcount_1;
- 47 wire [10:0 wfifo_rcount_2;
- 48 wire [10:0 rfifo_wcount_1;
- 49 wire [10:0 rfifo_wcount_2;
- 50
- 51 //*****************************************************
- 52 //** main code
- 53 //*****************************************************
- 54
- 55 //像素显示请求信号切换,即显示器左侧请求FIFO1显示,右侧请求FIFO2显示
- 56 assign rdata_req_1 = (rd_cnt <= h_disp[12:1]-1) ? rdata_req :1'b0;
- 57 assign rdata_req_2 = (rd_cnt <= h_disp[12:1]-1) ? 1'b0 :rdata_req;
- 58
- 59 //像素在显示器显示位置的切换,即显示器左侧显示FIFO1,右侧显示FIFO2
- 60 assign pic_data = (rd_cnt <= h_disp[12:1]) ? pic_data_1 : pic_data_2;
- 61
- 62 //写入DDR4的像素数据切换
- 63 assign wfifo_dout = wfifo_rden_1 ? wfifo_dout_1 : wfifo_dout_2;
- 64
- 65 //对读请求信号计数
- 66 always @(posedge rd_clk or negedge rst_n) begin
- 67 if(!rst_n)
- 68 rd_cnt <= 13'd0;
- 69 else if(rdata_req)
- 70 rd_cnt <= rd_cnt + 1'b1;
- 71 else
- 72 rd_cnt <= 13'd0;
- 73 end
- 74
- 75 ddr4_fifo_ctrl u_ddr4_fifo_ctrl_1 (
- 76
- 77 .rst_n (rst_n ) ,
- 78 //摄像头接口
- 79 .wr_clk (wr_clk_1) ,
- 80 .rd_clk (rd_clk) ,
- 81 .clk_100 (clk_100) , //用户时钟
- 82 .datain_valid (datain_valid_1) , //数据有效使能信号
- 83 .datain (datain_1) , //有效数据
- 84 .rfifo_din (rfifo_din_1) , //用户读数据
- 85 .rdata_req (rdata_req_1) , //请求像素点颜色数据输入
- 86 .rfifo_wren (rfifo_wren_1) , //ddr4读出数据的有效使能
- 87 .wfifo_rden (wfifo_rden_1) , //ddr4 写使能
- 88 //用户接口
- 89 .wfifo_rcount (wfifo_rcount_1) , //wfifo剩余数据计数
- 90 .rfifo_wcount (rfifo_wcount_1) , //rfifo写进数据计数
- 91 .wfifo_dout (wfifo_dout_1) , //用户写数据
- 92 .rd_load (rd_load) , //lcd场信号
- 93 .wr_load (wr_load_1) , //摄像头场信号
- 94 .pic_data (pic_data_1) //rfifo输出数据
- 95
- 96 );
- 97
- 98 ddr4_fifo_ctrl u_ddr4_fifo_ctrl_2 (
- 99
- 100 .rst_n (rst_n ) ,
- 101 //摄像头接口
- 102 .wr_clk (wr_clk_2) ,
- 103 .rd_clk (rd_clk) ,
- 104 .clk_100 (clk_100) , //用户时钟
- 105 .datain_valid (datain_valid_2) , //数据有效使能信号
- 106 .datain (datain_2) , //有效数据
- 107 .rfifo_din (rfifo_din_2) , //用户读数据
- 108 .rdata_req (rdata_req_2) , //请求像素点颜色数据输入
- 109 .rfifo_wren (rfifo_wren_2) , //ddr4读出数据的有效使能
- 110 .wfifo_rden (wfifo_rden_2) , //ddr4 写使能
- 111 //用户接口
- 112 .wfifo_rcount (wfifo_rcount_2) , //wfifo剩余数据计数
- 113 .rfifo_wcount (rfifo_wcount_2) , //rfifo写进数据计数
- 114 .wfifo_dout (wfifo_dout_2) , //用户写数据
- 115 .rd_load (rd_load) , //lcd场信号
- 116 .wr_load (wr_load_2) , //摄像头场信号
- 117 .pic_data (pic_data_2) //rfifo输出数据
- 118
- 119 );
- 120
- 121 endmodule
复制代码在代码56至57行,表示的是像素显示请求信号切换,即LCD屏左侧请求FIFO1显示,右侧请求FIFO2显示。
在代码60行,表示的是像素在LCD屏显示位置的切换,即LCD屏左侧显示FIFO1,右侧显示FIFO2。
在代码63行,表示的是像素数据在写入DDR4前的切换。
在代码66至73行,对LCD顶层模块发出的对读请求信号进行计数。
在代码75至119行是两个FIFO调度模块的例化,我们只给出代码注释,方便大家了解各信号的连接关系,这里不做分析了。
34.5 下载验证首先将FPC排线一端与RGB-LCD模块上的RGB接口连接,另一端与DFZU2EG/4EV MPSoC开发板上的RGB-LCD接口连接。与RGB-LCD模块连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝内(靠近FPGA端)插入连接器,最后将黑色翻盖压下以固定FPC排线,如图 34.5.1所示。 图 34.5.1 正点原子RGBLCD模块FPC连接器 与DFZU2EG/4EV MPSoC开发板上的RGB TFTLCD接口连接时,先掀起开发板上的棕色的翻盖到垂直开发板方向,将FPC排线蓝色面朝棕色翻盖垂直插入连接器,最后将棕色的翻盖下按至水平方向,如图 34.5.2所示。 图 34.5.2 DFZU2EG/4EV MPSoC开发板连接RGB-LCD液晶屏 然后将双目OV5640摄像头模块插在DFZU2EG/4EV MPSoC开发板J19扩展口上,摄像头实物连接如上图所示。最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,然后连接电源线后拨动开关按键给开发板上电。
接下来我们下载程序,验证双目OV5640 RGB-LCD实时显示功能。下载完成后观察RGB-LCD模块显示的图案如下图所示,说明双目OV5640 RGB-LCD实时显示程序下载验证成功。 图 34.5.3 RGB RGB-LCD实时显示图像 |