搜索
bottom↓
回复: 0

《DFZU2EG_4EV MPSoc之FPGA开发指南_V1.0》 第三十四章

[复制链接]

出0入234汤圆

发表于 2023-1-7 15:48:23 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2023-1-7 15:48 编辑

1)实验平台:正点原子MPSoC开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340252-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:994244016 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png
lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png


第三十四章双目OV5640摄像头RGB-LCD显示实验

双目摄像头是在一个模组上集成了两个摄像头,实现双通道图像采集的功能。双目摄像头一般应用于安防监控、立体视觉测距、三维重建等领域。本试验只做最基础的工作,把双目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引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出双目摄像头相关管脚分配,如下表所示:
表 34.3.1 OV5640摄像头管脚分配
706294D6-6BF3-471c-BDD8-AA46A40DA8A9.png

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

我们先来看看改动最小的一个模块,即OV5640驱动模块,这个模块从本质上来讲是没有做任何修改的,学习过之前“OV5640摄像头RGB-LCD显示实验”的同学应该都知道,OV5640驱动模块其实还包含了三个子模块,分别对应摄像头的寄存器配置、IIC驱动以及数据采集三个功能。双目摄像头其实就是将两个OV5640合在一起工作,所以针对OV5640驱动模块我们只需要把它例化两次就可以,给两个摄像头都进行寄存器配置、IIC驱动以及数据采集。因此在本节实验的顶层文件中调用了两次OV5640驱动模块以达到同时控制两个摄像头的目的,顶层RTL视图如下所示:
双目OV5640摄像头RGB6003.png
图 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”的字符数组的值。代码第154到156行,将字模数组中为“1”的点的像素值赋值为蓝色,为“0”的点像素值赋值为图像像素的值。这就是LCD屏左半边字符叠加的实现。代码第158到164行,实现的是LCD屏右半边字符叠加,实现的代码形式一模一样,只是字模数组变为了“OV5640 2”,在这里我们不再重复。代码第170行,非字符纵坐标区域,显示图片像素值。
双目OV5640摄像头RGB15534.png
图 34.4.3 显示逻辑

接下来我们重点来看看DDR4顶层模块,它包含了MIG IP核、DDR4读写模块以及FIFO调度模块。这三个子模块,除了官方的MIG IP核配置没改变之外,其他的像DDR4读写模块(ddr4_rw)和FIFO调度模块都做了修改。
下面是DDR4顶层模块的系统框图:
双目OV5640摄像头RGB15726.png
图 34.4.4 DDR控制模块的系统框图

结合图可以看出相较于“OV5640摄像头RGB-LCD显示实验”,本次实验将FIFO调度模块替换成了FIFO顶层调度模块。本次实验是两个摄像头采集数据并且把数据存入两个写FIFO,两个写FIFO再将数据写入DDR4的两个不同的存储空间中,接着两个读FIFO分别从两个对应的DDR4地址空间中取出数据,最后两组数据一起拼接为完整一帧LCD图像,达到一个屏幕同时显示两幅图像的效果。在DDR4处理数据的时候,两组数据流相当于过独木桥,需要分时“排队”进出;同样在显示端,两组数据也要“排队”显示,所以本次实验的FIFO顶层调度模块的作用就是根据不同的情况对两个FIFO调度模块的信号进行切换。
下面是DDR控制模块的原理图:
双目OV5640摄像头RGB16104.png
图 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
126 ddr4_0 u_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_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显示实验”的程序方面做些改动。具体框图如下图所示:
双目OV5640摄像头RGB28939.png
图 34.4.6 DDR读写控制流程图

图像数据是由两个摄像头分别采集得来的,所以将图像分别存入两个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 //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读写逻辑实现,状态跳转图如下图所示:
DC97BF4F-41E7-446f-B68C-08E1D8FACC0E.png
图 34.4.7 状态跳转图

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

下面是FIFO顶层调度模块的原理图:
双目OV5640摄像头RGB45419.png
图 34.4.9 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所示。
双目OV5640摄像头RGB51291.png
图 34.5.1 正点原子RGBLCD模块FPC连接器

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

然后将双目OV5640摄像头模块插在DFZU2EG/4EV MPSoC开发板J19扩展口上,摄像头实物连接如上图所示。最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,然后连接电源线后拨动开关按键给开发板上电。
接下来我们下载程序,验证双目OV5640 RGB-LCD实时显示功能。下载完成后观察RGB-LCD模块显示的图案如下图所示,说明双目OV5640 RGB-LCD实时显示程序下载验证成功。
双目OV5640摄像头RGB51808.png
图 34.5.3 RGB RGB-LCD实时显示图像
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-29 18:37

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

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