0
点赞
收藏
分享

微信扫一扫

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​

双目OV5640摄像头RGB-LCD显示实验​

双目摄像头是在一个模组上集成了两个摄像头,实现双通道图像采集的功能。双目摄像头一般应用于安防监控、立体视觉测距、三维重建等领域。本试验只做最基础的工作,把双目OV5640摄像头实时采集到的图像分左右两半显示在LCD屏幕上。本章包括以下几个部分

  1. 简介
  2. 实验任务
  3. 硬件设计
  4. 程序设计
  5. 下载验证​

简介

摄像头在日常生活中非常常见,一般分为单目摄像头、双目摄像头和多目摄像头。单目摄像头目前使用最为广泛;双目摄像头主要应用于单目摄像头无法胜任的场合,如测距领域,根据两个摄像头的视差,辅以一定的算法,人们可以计算物体的距离;当然针对一些特殊的应用,目前市场上也出现了多目摄像头,以应对更加复杂的场景。在“OV5640摄像头LCD显示实验”中对OV5640的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考之前的实验。本次实验将在前面单目OV5640摄像头的基础上学习双目摄像头的LCD显示。

实验任务

本章实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边。

硬件设计

摄像头扩展接口原理图及OV5640模块说明与“OV5640摄像头RGB-LCD显示实验完全相同,请参考“OV5640摄像头RGB-LCD显示实验硬件设计部分。HDMI接口部分的硬件设计参考“HDMI彩条显示实验”硬件设计部分

由于LCD接口DDR4引脚数目且在前面相应章节中已经给出们的管脚列表,这里只列出双目摄像头相关管脚分配下表所示:

34.3.1 OV5640摄像头管脚分配

信号名

方向

管脚

端口说明

IO电平

cam_pclk_1

input

C13

cmos 数据像素时钟

LVCMOS33

cam_vsync_1

input

G14

cmos 场同步信号

LVCMOS33

cam_href_1

input

G13

cmos 行同步信号

LVCMOS33

cam_rst_n_1

output

F13

cmos 复位信号,低电平有效

LVCMOS33

cam_pwdn_1

output

B15

cmos 电源休眠模式选择信号

LVCMOS33

cam_scl_1

output

H13

cmos SCCB_SCL线

LVCMOS33

cam_sda_1

inout

F15

cmos SCCB_SDA线

LVCMOS33

cam_data_1[0]

input

E15

cmos 数据

LVCMOS33

cam_data_1[1]

input

D15

cmos 数据

LVCMOS33

cam_data_1[2]

input

E14

cmos 数据

LVCMOS33

cam_data_1[3]

input

D14

cmos 数据

LVCMOS33

cam_data_1[4]

input

E13

cmos 数据

LVCMOS33

cam_data_1[5]

input

B13

cmos 数据

LVCMOS33

cam_data_1[6]

input

C14

cmos 数据

LVCMOS33

cam_data_1[7]

input

A13

cmos 数据

LVCMOS33

cam_pclk_2

input

D11

cmos 数据像素时钟

LVCMOS33

cam_vsync_2

input

A15

cmos 场同步信号

LVCMOS33

cam_href_2

input

A12

cmos 行同步信号

LVCMOS33

cam_rst_n_2

output

A11

cmos 复位信号,低电平有效

LVCMOS33

cam_pwdn_2

output

B14

cmos 电源休眠模式选择信号

LVCMOS33

cam_scl_2

output

A14

cmos SCCB_SCL线

LVCMOS33

cam_sda_2

inout

D12

cmos SCCB_SDA线

LVCMOS33

cam_data_2[0]

input

C12

cmos 数据

LVCMOS33

cam_data_2[1]

input

C11

cmos 数据

LVCMOS33

cam_data_2[2]

input

B11

cmos 数据

LVCMOS33

cam_data_2[3]

input

B10

cmos 数据

LVCMOS33

cam_data_2[4]

input

A10

cmos 数据

LVCMOS33

cam_data_2[5]

input

E10

cmos 数据

LVCMOS33

cam_data_2[6]

input

E12

cmos 数据

LVCMOS33

cam_data_2[7]

input

D10

cmos 数据

LVCMOS33

双目摄像头XDC约束文件如下:

#时钟​
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_p]​
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_n]​
set_property PACKAGE_PIN AE5 [get_ports sys_clk_p]​
set_property PACKAGE_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_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {cam_pclk_1_IBUF_inst/O}]​
set_property CLOCK_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_property PULLUP true [get_ports cam_scl_1]​
set_property PULLUP 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_property PULLUP true [get_ports cam_scl_2]​
set_property PULLUP true [get_ports cam_sda_2]

程序设计

根据实验任务,首先设计如34.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头RGB-LCD显示实验”的整体架构。顶层同样是例化了DDR4顶层模块、OV5640驱动顶层模块、图像分辨率处理模块以及LCD驱动顶层模块。其中除了图像分辨率处理模块没有做任何修改之外,其他三个模块都有一点小小的改动。下面就来给大家重点讲解改动的部分内容。

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_字符数组


34.4.1顶层系统框图

我们先来看看改动最小的一个模块,即OV5640驱动模块,这个模块从本质上来讲是没有做任何修改的,学习过之前“OV5640摄像头RGB-LCD显示实验”的同学应该都知道,OV5640驱动模块其实还包含了三个子模块,分别对应摄像头的寄存器配置、IIC驱动以及数据采集三个功能。双目摄像头其实就是将两个OV5640合在一起工作,所以针对OV5640驱动模块我们只需要把它例化两次就可以,给两个摄像头都进行寄存器配置、IIC驱动以及数据采集。因此在本节实验的顶层文件中调用了两次OV5640驱动模块以达到同时控制两个摄像头的目的,顶层RTL视图如下所示:

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_字符数组_02


34.4.2顶层模块原理图

由于摄像头驱动模块内部的代码没有做任何修改,所以这里就不贴出代码了,下面我们一起来看看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”


34 //给字符数组0的赋值:OV5640 0 (16*64)​
35 always @(posedge lcd_clk) begin​
36 char0[0] <= 64'h0000000000000000 ;​
37 char0[1] <= 64'h0000000000000000 ;​
38 char0[2] <= 64'h0000000000000000 ;​
39 char0[3] <= 64'h38E77E1804180008 ;​
40 char0[4] <= 64'h444240240C240038 ;​
41 char0[5] <= 64'h824240400C420008 ;​
42 char0[6] <= 64'h8244404014420008 ;​
43 char0[7] <= 64'h8224785C24420008 ;​
44 char0[8] <= 64'h8224446224420008 ;​
45 char0[9] <= 64'h8228024244420008 ;​
46 char0[10] <= 64'h822802427F420008 ;​
47 char0[11] <= 64'h8218424204420008 ;​
48 char0[12] <= 64'h4410442204240008 ;​
49 char0[13] <= 64'h3810381C1F18003E ;​
50 char0[14] <= 64'h0000000000000000 ;​
51 char0[15] <= 64'h0000000000000000 ;​
52 end​
53 ​
54 //给字符数组1的赋值: OV5640 1 (16*64)​
55 always @(posedge lcd_clk) begin​
56 char1[0] <= 64'h0000000000000000 ;​
57 char1[1] <= 64'h0000000000000000 ;​
58 char1[2] <= 64'h0000000000000000 ;​
59 char1[3] <= 64'h38E77E180418003C ;​
60 char1[4] <= 64'h444240240C240042 ;​
61 char1[5] <= 64'h824240400C420042 ;​
62 char1[6] <= 64'h8244404014420042 ;​
63 char1[7] <= 64'h8224785C24420002 ;​
64 char1[8] <= 64'h8224446224420004 ;​
65 char1[9] <= 64'h8228024244420008 ;​
66 char1[10] <= 64'h822802427F420010 ; ​
67 char1[11] <= 64'h8218424204420020 ;​
68 char1[12] <= 64'h4410442204240042 ;​
69 char1[13] <= 64'h3810381C1F18007E ;​
70 char1[14] <= 64'h0000000000000000 ;​
71 char1[15] <= 64'h0000000000000000 ;​
72 end​
73 ​
74 //给字符数组2的赋值: OV5640 0 (32*128)​
75 always @(posedge lcd_clk) begin ​
76 char2[0] <= 128'h00000000000000000000000000000000;​
77 char2[1] <= 128'h00000000000000000000000000000000;​
78 char2[2] <= 128'h00000000000000000000000000000000;​
79 char2[3] <= 128'h00000000000000000000000000000000;​
80 char2[4] <= 128'h00000000000000000000000000000000;​
81 char2[5] <= 128'h00000000000000000000000000000000;​
82 char2[6] <= 128'h03C07C1E0FFC01E0006003C000000080;​
83 char2[7] <= 128'h0C30180C0FFC06180060062000000180;​
84 char2[8] <= 128'h1818180810000C1800E00C3000001F80;​
85 char2[9] <= 128'h100818081000081800E0181800000180;​
86 char2[10] <= 128'h300C1808100018000160181800000180;​
87 char2[11] <= 128'h300C0C10100010000160180800000180;​
88 char2[12] <= 128'h60040C10100010000260300C00000180;​
89 char2[13] <= 128'h60060C10100030000460300C00000180;​
90 char2[14] <= 128'h60060C1013E033E00460300C00000180;​
91 char2[15] <= 128'h60060C20143036300860300C00000180;​
92 char2[16] <= 128'h60060620181838180860300C00000180;​
93 char2[17] <= 128'h60060620100838081060300C00000180;​
94 char2[18] <= 128'h60060620000C300C3060300C00000180;​
95 char2[19] <= 128'h60060640000C300C2060300C00000180;​
96 char2[20] <= 128'h60060340000C300C4060300C00000180;​
97 char2[21] <= 128'h20060340000C300C7FFC300C00000180;​
98 char2[22] <= 128'h300C0340300C300C0060180800000180;​
99 char2[23] <= 128'h300C0380300C180C0060181800000180;​
100 char2[24] <= 128'h10080180201818080060181800000180;​
101 char2[25] <= 128'h1818018020180C1800600C3000000180;​
102 char2[26] <= 128'h0C30010018300E3000600620000003C0;​
103 char2[27] <= 128'h03C0010007C003E003FC03C000001FF8;​
104 char2[28] <= 128'h00000000000000000000000000000000;​
105 char2[29] <= 128'h00000000000000000000000000000000;​
106 char2[30] <= 128'h00000000000000000000000000000000;​
107 char2[31] <= 128'h00000000000000000000000000000000;​
108 end​
109 ​
110 //给字符数组3的赋值: OV5640 1 (32*128)​
111 always @(posedge lcd_clk) begin ​
112 char3[0] <= 128'h00000000000000000000000000000000;​
113 char3[1] <= 128'h00000000000000000000000000000000;​
114 char3[2] <= 128'h00000000000000000000000000000000;​
115 char3[3] <= 128'h00000000000000000000000000000000;​
116 char3[4] <= 128'h00000000000000000000000000000000;​
117 char3[5] <= 128'h00000000000000000000000000000000;​
118 char3[6] <= 128'h03C07C1E0FFC01E0006003C0000007E0;​
119 char3[7] <= 128'h0C30180C0FFC06180060062000000838;​
120 char3[8] <= 128'h1818180810000C1800E00C3000001018;​
121 char3[9] <= 128'h100818081000081800E018180000200C;​
122 char3[10] <= 128'h300C180810001800016018180000200C;​
123 char3[11] <= 128'h300C0C1010001000016018080000300C;​
124 char3[12] <= 128'h60040C10100010000260300C0000300C;​
125 char3[13] <= 128'h60060C10100030000460300C0000000C;​
126 char3[14] <= 128'h60060C1013E033E00460300C00000018;​
127 char3[15] <= 128'h60060C20143036300860300C00000018;​
128 char3[16] <= 128'h60060620181838180860300C00000030;​
129 char3[17] <= 128'h60060620100838081060300C00000060;​
130 char3[18] <= 128'h60060620000C300C3060300C000000C0;​
131 char3[19] <= 128'h60060640000C300C2060300C00000180;​
132 char3[20] <= 128'h60060340000C300C4060300C00000300;​
133 char3[21] <= 128'h20060340000C300C7FFC300C00000200;​
134 char3[22] <= 128'h300C0340300C300C0060180800000404;​
135 char3[23] <= 128'h300C0380300C180C0060181800000804;​
136 char3[24] <= 128'h10080180201818080060181800001004;​
137 char3[25] <= 128'h1818018020180C1800600C300000200C;​
138 char3[26] <= 128'h0C30010018300E300060062000003FF8;​
139 char3[27] <= 128'h03C0010007C003E003FC03C000003FF8;​
140 char3[28] <= 128'h00000000000000000000000000000000;​
141 char3[29] <= 128'h00000000000000000000000000000000;​
142 char3[30] <= 128'h00000000000000000000000000000000;​
143 char3[31] <= 128'h00000000000000000000000000000000; ​
144 end​
145

代码从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 //读取字模OV5640 1 (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 //读取字模OV5640 2 (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”的字符数组的值。代码第154156行,将字模数组中为“1”的点的像素值赋值为蓝色,为“0”的点像素值赋值为图像像素的值。这就是LCD屏左半边字符叠加的实现。代码第158164行,实现的是LCD屏右半边字符叠加,实现的代码形式一模一样,只是字模数组变为了“OV5640 2”,在这里我们不再重复。代码第170行,非字符纵坐标区域,显示图片像素值。

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_字符数组_03


34.4.3显示逻辑

接下来我们重点来看看DDR4顶层模块,它包含了MIG IP核、DDR4读写模块以及FIFO调度模块。这三个子模块,除了官方的MIG IP核配置没改变之外,其他的像DDR4读写模块(ddr4_rw)和FIFO调度模块都做了修改。

下面是DDR4顶层模块的系统框图:

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_字符数组_04


34.4.4控制模块的系统框图

结合图可以看出相较于“OV5640摄像头RGB-LCD显示实验”,本次实验将FIFO调度模块替换成了FIFO顶层调度模块。本次实验是两个摄像头采集数据并且把数据存入两个写FIFO,两个写FIFO再将数据写入DDR4的两个不同的存储空间中,接着两个读FIFO分别从两个对应的DDR4地址空间中取出数据,最后两组数据一起拼接为完整一帧LCD图像,达到一个屏幕同时显示两幅图像的效果。在DDR4处理数据的时候,两组数据流相当于过独木桥,需要分时“排队”进出;同样在显示端,两组数据也要“排队”显示,所以本次实验的FIFO顶层调度模块的作用就是根据不同的情况对两个FIFO调度模块的信号进行切换。

下面是DDR控制模块的原理图:

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_数据_05


34.4.5 DDR4顶层模块

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 ​
126ddr4_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_top u_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显示实验”的程序方面做些改动。具体框图如下图所示:


《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_驱动模块_06


34.4.6读写控制流程图

图像数据是由两个摄像头分别采集得来的,所以将图像分别存入两个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_1waddr_page_1是对同一个输入源的两个存储空间的切换信号同理信号raddr_page_2和waddr_page_2也是如此。

322 //DDR4读写逻辑实现​
323 always @(posedge ui_clk or negedge rst_n) begin​
324 if(~rst_n) begin ​
325 state_cnt <= IDLE; ​
326 wr_addr_cnt_1 <= 24'd0; ​
327 rd_addr_cnt_1 <= 24'd0; ​
328 app_addr_wr_1 <= 28'd0; ​
329 app_addr_rd_1 <= 28'd0; ​
330 wr_addr_cnt_2 <= 24'd0; ​
331 rd_addr_cnt_2 <= 24'd0; ​
332 app_addr_wr_2 <= 28'd0; ​
333 app_addr_rd_2 <= 28'd0; ​
334 end​
335 else begin​
336 case(state_cnt)​
337 IDLE:begin​
338 if(init_calib_complete)​
339 state_cnt <= DDR4_DONE ;​
340 else​
341 state_cnt <= IDLE;​
342 end​
343 DDR4_DONE:begin //当wfifo1存储数据超过一次突发长度时,跳到写操作1​
344 if(wfifo_rcount_1 >= wr_bust_len - 2 )begin ​
345 state_cnt <= WRITE_1; ​
346 end //当wfifo2存储数据超过一次突发长度时,跳到写操作2 ​
347 else if(wfifo_rcount_2 >= wr_bust_len - 2 )begin ​
348 state_cnt <= WRITE_2; ​
349 end ​
350 else if(raddr_rst_h)begin //当帧复位到来时,对寄存器进行复位 ​
351 if(raddr_rst_h_cnt >= 1000 && star_rd_flag)begin ​
352 state_cnt <= READ_1; //保证读fifo在复位时不会进行读操作 ​
353 end​
354 else begin​
355 state_cnt <= DDR4_DONE; ​
356 end ​
357 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据 ​
358 else if(rfifo_wcount_1 < 5 && star_rd_flag )begin //跳到读操作1 ​
359 state_cnt <= READ_1; ​
360 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据 ​
361 else if(rfifo_wcount_2 < 5 && star_rd_flag )begin //跳到读操作2 ​
362 state_cnt <= READ_2; ​
363 end ​
364 else begin​
365 state_cnt <= state_cnt; ​
366 end​
367 ​
368 if(raddr_rst_h)begin //当帧复位到来时,对信号进行复位 ​
369 rd_addr_cnt_1 <= 24'd0; ​
370 app_addr_rd_1 <= app_addr_rd_min; ​
371 rd_addr_cnt_2 <= 24'd0; ​
372 app_addr_rd_2 <= app_addr_rd_min; ​
373 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据 ​
374 else if(rfifo_wcount_1 < 5 && star_rd_flag )begin ​
375 rd_addr_cnt_1 <= 24'd0; //计数器清零​
376 app_addr_rd_1 <= app_addr_rd_1; //读地址保持不变​
377 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据 ​
378 else if(rfifo_wcount_2 < 5 && star_rd_flag )begin ​
379 rd_addr_cnt_2 <= 24'd0; //计数器清零​
380 app_addr_rd_2 <= app_addr_rd_2; //读地址保持不变​
381 end ​
382 else begin​
383 wr_addr_cnt_1 <= 24'd0; ​
384 rd_addr_cnt_1 <= 24'd0; ​
385 end ​
386 ​
387 if(wr_rst_2)begin //当帧复位到来时,对信号进行复位​
388 wr_addr_cnt_2 <= 24'd0; ​
389 app_addr_wr_2 <= app_addr_wr_min; ​
390 end //当wfifo存储数据超过一次突发长度时​
391 else if(wfifo_rcount_2 >= wr_bust_len - 2 )begin ​
392 wr_addr_cnt_2 <= 24'd0; //计数器清零 ​
393 app_addr_wr_2 <= app_addr_wr_2; //写地址保持不变​
394 end ​
395 else begin​
396 wr_addr_cnt_2 <= wr_addr_cnt_2;​
397 app_addr_wr_2 <= app_addr_wr_2; ​
398 end ​
399 ​
400 if(wr_rst_1)begin //当帧复位到来时,对信号进行复位​
401 wr_addr_cnt_1 <= 24'd0; ​
402 app_addr_wr_1 <= app_addr_wr_min; ​
403 end //当wfifo存储数据超过一次突发长度时​
404 else if(wfifo_rcount_1 >= wr_bust_len - 2 )begin ​
405 wr_addr_cnt_1 <= 24'd0; //计数器清零 ​
406 app_addr_wr_1 <= app_addr_wr_1; //写地址保持不变​
407 end ​
408 else begin​
409 wr_addr_cnt_1 <= wr_addr_cnt_1;​
410 app_addr_wr_1 <= app_addr_wr_1; ​
411 end​
412 ​
413 end ​
414 WRITE_1: begin ​
415 if((wr_addr_cnt_1 == (wr_bust_len - 1)) && ​
416 (app_rdy && app_wdf_rdy))begin //写到设定的长度跳到等待状态 ​
417 state_cnt <= DDR4_DONE; //写到设定的长度跳到等待状态 ​
418 app_addr_wr_1 <= app_addr_wr_1 + 8; //一次性写进8个数,故加8​
419 end ​
420 else if(app_rdy && app_wdf_rdy)begin //写条件满足​
421 wr_addr_cnt_1 <= wr_addr_cnt_1 + 1'd1;//写地址计数器自加​
422 app_addr_wr_1 <= app_addr_wr_1 + 8; //一次性写进8个数,故加8​
423 end​
424 else begin //写条件不满足,保持当前值 ​
425 wr_addr_cnt_1 <= wr_addr_cnt_1;​
426 app_addr_wr_1 <= app_addr_wr_1; ​
427 end​
428 end​
429 WRITE_2: begin ​
430 if((wr_addr_cnt_2 == (wr_bust_len - 1)) && ​
431 (app_rdy && app_wdf_rdy))begin //写到设定的长度跳到等待状态 ​
432 state_cnt <= DDR4_DONE; //写到设定的长度跳到等待状态 ​
433 app_addr_wr_2 <= app_addr_wr_2 + 8; //一次性写进8个数,故加8​
434 end ​
435 else if(app_rdy && app_wdf_rdy)begin //写条件满足​
436 wr_addr_cnt_2 <= wr_addr_cnt_2 + 1'd1; //写地址计数器自加​
437 app_addr_wr_2 <= app_addr_wr_2 + 8; //一次性写进8个数,故加8​
438 end​
439 else begin //写条件不满足,保持当前值 ​
440 wr_addr_cnt_2 <= wr_addr_cnt_2;​
441 app_addr_wr_2 <= app_addr_wr_2; ​
442 end​
443 end ​
444 READ_1:begin //读到设定的地址长度 ​
445 if((rd_addr_cnt_1 == (rd_bust_len - 1)) && app_rdy)begin​
446 state_cnt <= READ_WAIT; //则跳到空闲状态 ​
447 app_addr_rd_1 <= app_addr_rd_1 + 8;​
448 end ​
449 else if(app_rdy)begin //若MIG已经准备好,则开始读​
450 rd_addr_cnt_1 <= rd_addr_cnt_1 + 1'd1; //用户地址计数器每次加一​
451 app_addr_rd_1 <= app_addr_rd_1 + 8; //一次性读出8个数,DDR4地址加8​
452 end​
453 else begin //若MIG没准备好,则保持原值​
454 rd_addr_cnt_1 <= rd_addr_cnt_1;​
455 app_addr_rd_1 <= app_addr_rd_1; ​
456 end​
457 ​
458 if(wr_rst_2)begin //当帧复位到来时,对信号进行复位​
459 wr_addr_cnt_2 <= 24'd0; ​
460 app_addr_wr_2 <= app_addr_wr_min; ​
461 end ​
462 else begin​
463 wr_addr_cnt_2 <= wr_addr_cnt_2;​
464 app_addr_wr_2 <= app_addr_wr_2; ​
465 end ​
466 ​
467 if(wr_rst_1)begin //当帧复位到来时,对信号进行复位​
468 wr_addr_cnt_1 <= 24'd0; ​
469 app_addr_wr_1 <= app_addr_wr_min; ​
470 end ​
471 else begin​
472 wr_addr_cnt_1 <= wr_addr_cnt_1;​
473 app_addr_wr_1 <= app_addr_wr_1; ​
474 end ​
475 end ​
476 READ_2:begin //读到设定的地址长度 ​
477 if((rd_addr_cnt_2 == (rd_bust_len - 1)) && app_rdy)begin​
478 state_cnt <= READ_WAIT; //则跳到空闲状态 ​
479 app_addr_rd_2 <= app_addr_rd_2 + 8;​
480 end ​
481 else if(app_rdy)begin //若MIG已经准备好,则开始读​
482 rd_addr_cnt_2 <= rd_addr_cnt_2 + 1'd1; //用户地址计数器每次加一​
483 app_addr_rd_2 <= app_addr_rd_2 + 8; //一次性读出8个数,DDR4地址加8​
484 end​
485 else begin //若MIG没准备好,则保持原值​
486 rd_addr_cnt_2 <= rd_addr_cnt_2;​
487 app_addr_rd_2 <= app_addr_rd_2; ​
488 end​
489 ​
490 if(wr_rst_2)begin //当帧复位到来时,对信号进行复位​
491 wr_addr_cnt_2 <= 24'd0; ​
492 app_addr_wr_2 <= app_addr_wr_min; ​
493 end ​
494 else begin​
495 wr_addr_cnt_2 <= wr_addr_cnt_2;​
496 app_addr_wr_2 <= app_addr_wr_2; ​
497 end ​
498 ​
499 if(wr_rst_1)begin //当帧复位到来时,对信号进行复位​
500 wr_addr_cnt_1 <= 24'd0; ​
501 app_addr_wr_1 <= app_addr_wr_min; ​
502 end ​
503 else begin​
504 wr_addr_cnt_1 <= wr_addr_cnt_1;​
505 app_addr_wr_1 <= app_addr_wr_1; ​
506 end ​
507 end​
508 READ_WAIT:begin //计到设定的地址长度 ​
509 if((data_valid_cnt >= rd_bust_len - 1) && app_rd_data_valid)begin​
510 state_cnt <= DDR4_DONE; //则跳到空闲状态 ​
511 end ​
512 else begin ​
513 state_cnt <= READ_WAIT;​
514 end​
515 end ​
516 default:begin​
517 state_cnt <= IDLE; ​
518 wr_addr_cnt_1 <= 24'd0; ​
519 rd_addr_cnt_1 <= 24'd0; ​
520 app_addr_wr_1 <= 28'd0; ​
521 app_addr_rd_1 <= 28'd0; ​
522 wr_addr_cnt_2 <= 24'd0; ​
523 rd_addr_cnt_2 <= 24'd0; ​
524 app_addr_wr_2 <= 28'd0; ​
525 app_addr_rd_2 <= 28'd0; ​
526 end​
527 endcase​
528 end​
529 end

程序中第322至529行所示,这段代码是DDR4读写逻辑实现,状态跳转图如下图所示:

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_字符数组_07


34.4.7状态跳转图

本次实验的状态跳转相对于“OV7725摄像头RGB-LCD显示实验”中的状态跳转只是多了一个读状态、一个写状态和读等待状态。两个实验当中的写状态跳转的条件是没有变的,读状态的跳转出现了变化。本次实验读状态不直接跳到DDR的空闲状态,而是跳到读等待状态。本次实验的实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边,所以在输出端存在两个rfifo。在下面的时序图中可以发现当读状态操作完后,而此时的数据没有全部读出来。如果此时直接跳转到DDR的空闲状态,下一刻状态就会跳到另一个读状态,那么DDR4读出的数据就不容易区分是哪个rfifo的数据,容易造成数据错乱。为了保证数据可以准确的写入到对应的rfifo中,必须在数据完全读出后才能跳转到DDR的空闲状态,这是添加读等待状态的原因。时序图如下:

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_驱动模块_08


34.4.8状态时序图

下面是FIFO顶层调度模块的原理图:

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_驱动模块_09


34.4.9顶层调度模块的原理图

本次实验增加了一个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调度模块的例化,我们只给出代码注释,方便大家了解各信号的连接关系,这里不做分析了。

下载验证

首先将FPC线与RGB-LCD模块上的RGB接口连接,一端与DFZU2EG/4EV MPSoC开发板的RGB-LCD接口连接。与RGB-LCD模块连接时先掀FPC连接器上的黑色翻盖,将FPC排线蓝色面朝内(靠近FPGA端)插入连接器,最后黑色翻盖固定FPC排线,如34.5.1所示

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_驱动模块_10


34.5.1正点原子RGBLCD模块FPC连接器

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

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_驱动模块_11


34.5.2 DFZU2EG/4EV MPSoC开发板连接RGB-LCD液晶屏

然后将双目OV5640摄像头模块插在DFZU2EG/4EV MPSoC开发板J19扩展口上,摄像头实物连接如上图所示。最后将下载器端连电脑一端开发板上的JTAG端口连接,然后连接电源线后拨动开关按键给开发板上电。

下来我们下载程序,验证双目OV5640 RGB-LCD实时显示功能。下载完成后观察RGB-LCD模块显示的图案如下图示,说明双目OV5640 RGB-LCD实时显示程序下载验证成功

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十四章 双目OV5640摄像头RGB-LCD显示实验​​_数据_12


34.5.3 RGB RGB-LCD实时显示图像


举报

相关推荐

0 条评论