本帖最后由 正点原子 于 2021-1-23 15:30 编辑
1)实验平台:正点原子达芬奇FPGA开发板
2) 章节摘自【正点原子】达芬奇之FPGA开发指南
3)购买链接:https://detail.tmall.com/item.htm?id=624335496505
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入:
第四十八章双目OV5640摄像头RGB-LCD显示实验
双目摄像头是在一个模组上集成了两个摄像头,实现双通道图像采集的功能。双目摄像头一般应用于安防监控、立体视觉测距、三维重建等领域。本试验只做最基础的工作,把双目OV5640摄像头实时采集到的图像分左右两半显示在LCD屏幕上。本章包括以下几个部分:
4848.1简介
48.2实验任务
48.3硬件设计
48.4程序设计
48.5下载验证
48.1简介
摄像头在日常生活中非常常见,一般分为单目摄像头、双目摄像头和多目摄像头。单目摄像头目前使用最为广泛;双目摄像头主要应用于单目摄像头无法胜任的场合,如测距领域,根据两个摄像头的视差,辅以一定的算法,人们可以计算物体的距离;当然针对一些特殊的应用,目前市场上也出现了多目摄像头,以应对更加复杂的场景。在“OV5640摄像头LCD显示实验”中对OV5640的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考之前的实验。本次实验将在前面单目OV5640摄像头的基础上学习双目摄像头的LCD显示。
48.2实验任务
本章实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边。
48.3硬件设计
达芬奇开发板上有两个扩展口,分别是J2和J3。达芬奇开发板的J3扩展口与LCD屏的管脚复用,故本次实验采用J2扩展口来连接双目OV5640摄像头。J2扩展口原理图如图 48.3.1所示:
图 48.3.1 J2扩展接口原理图
ATK-Dual-OV5640是正点原子推出的一款双目OV5640摄像头模块,其硬件原理图如下图所示:
图 48.3.2 ATK-Dual-OV5640原理图
该模块通过2*20排母(2.54mm间距)同外部连接,连接时将双目摄像头的排母直接插在开发板上的J2扩展口即可,模块实物连接图如图 48.3.3所示:
图 48.3.3 双目摄像头模块实物连接图
由于LCD接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出双目摄像头相关管脚分配,如下表所示:
表 48.3.1 OV5640摄像头管脚分配
双目摄像头XDC约束文件如下:
#CAMERA
################ J2 ###########################
set_property -dict {PACKAGE_PIN L19 IOSTANDARD LVCMOS33} [get_ports cam_scl_1]
set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports cam_sda_1]
set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[0]}]
set_property -dict {PACKAGE_PIN N20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[2]}]
set_property -dict {PACKAGE_PIN J20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[4]}]
set_property -dict {PACKAGE_PIN H20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[6]}]
set_property -dict {PACKAGE_PIN M18 IOSTANDARD LVCMOS33} [get_ports cam_pclk_1]
create_clock -period 40.000 -name cmos_pclk [get_ports cam_pclk_1]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_1_IBUF]
set_property -dict {PACKAGE_PIN L20 IOSTANDARD LVCMOS33} [get_ports cam_vsync_1]
set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports cam_href_1]
set_property -dict {PACKAGE_PIN N19 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_1]
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[1]}]
set_property -dict {PACKAGE_PIN J21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[3]}]
set_property -dict {PACKAGE_PIN G20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[5]}]
set_property -dict {PACKAGE_PIN L18 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[7]}]
set_property -dict {PACKAGE_PIN F21 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_1]
#摄像头2接口的时钟
set_property -dict {PACKAGE_PIN F18 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_2]
set_property -dict {PACKAGE_PIN D17 IOSTANDARD LVCMOS33} [get_ports cam_scl_2]
set_property -dict {PACKAGE_PIN M22 IOSTANDARD LVCMOS33} [get_ports cam_sda_2]
set_property -dict {PACKAGE_PIN L21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[0]}]
set_property -dict {PACKAGE_PIN K21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[2]}]
set_property -dict {PACKAGE_PIN H22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[4]}]
set_property -dict {PACKAGE_PIN G21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[6]}]
set_property -dict {PACKAGE_PIN D19 IOSTANDARD LVCMOS33} [get_ports cam_pclk_2]
create_clock -period 40.000 -name cmos_pclk [get_ports cam_pclk_2]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_2_IBUF]
set_property -dict {PACKAGE_PIN E17 IOSTANDARD LVCMOS33} [get_ports cam_vsync_2]
set_property -dict {PACKAGE_PIN N22 IOSTANDARD LVCMOS33} [get_ports cam_href_2]
set_property -dict {PACKAGE_PIN M21 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_2]
set_property -dict {PACKAGE_PIN K22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[1]}]
set_property -dict {PACKAGE_PIN J22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[3]}]
set_property -dict {PACKAGE_PIN G22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[5]}]
set_property -dict {PACKAGE_PIN E19 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[7]}]
48.4程序设计
根据实验任务,首先设计如图 48.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头RGB-LCD显示实验”的整体架构。包括以下模块:时钟模块、图像分辨率设置模块、DDR控制器模块、摄像头驱动模块和LCD顶层模块。其中时钟模块、图像分辨率设置模块和摄像头驱动模块没有做任何修改,这些模块在“OV5640摄像头RGB-LCD显示实验”中已经说明过,这里不再详述,本次实验对DDR控制模块和LCD顶层模块做了修改。
图 48.4.1 顶层系统框图
时钟模块(clk_wiz_0):时钟模块通过调用MMCM IP核实现,共输出2个时钟,频率分别为200Mhz(DDR3参考时钟)和50Mhz时钟。200Mhz时钟作为DDR控制模块的参考时钟,由MIG IP核产生的ui_clk(本次设计为100Mhz)作为DDR控制模块的驱动时钟,50Mhz时钟作为OV5640驱动模块、摄像头图像分辨率设置模块和LCD顶层模块的驱动时钟。
图像分辨率设置模块(picture_size):图像尺寸配置模块用于配置摄像头输出图像尺寸的大小,此外还完成了DDR3的读写结束地址设置。有关图像分辨率设置模块的详细介绍请大家参考“OV5640摄像头RGB-LCD显示实验”章节。
LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。
摄像头驱动模块:本模块由原先的IIC驱动,OV5640 IIC配置,摄像头图像采集封装而成,这样做是为了减少顶层模块的代码量,增强可读性,同时有利于代码的维护和管理。由于本次实验连接了两个相同的摄像头,因此我们会对摄像头驱动模块例化两次,将两个驱动模块输出的数据和数据有效使能全部连接到DDR控制模块。有关摄像头驱动模块的详细介绍请大家参考“OV5640摄像头RGBT-LCD显示实验”章节。
DDR控制模块(ddr3_top):DDR读写控制器模块负责驱动DDR片外存储器,缓存图像传感器输出的图像数据。该模块将MIG IP核复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。
下面是顶层模块的原理图:
图 48.4.2 顶层模块原理图
本次实验是在“OV5640摄像头RGB-LCD显示实验”的基础上作修改,主要修改了DDR控制模块和LCD顶层模块,而其余模块基本相同,因此接下来只介绍DDR控制模块模块和LCD顶层模块。前面的实验,使用读写各一个FIFO,并在DDR3中开辟了两个缓冲区,而本实验有两个摄像头,所以读写需要各增加一个FIFO,总共四个,同时在从DDR3中再开辟了两个缓冲区,达到四个缓冲区,而满足两组摄像头数据的处理需要。
下面就顶层模块与“OV5640摄像头RGB-LCD显示实验”的顶层代码的改动的部分做介绍。
- 115 //ov5640 驱动
- 116 ov5640_dri u_ov5640_dri_1(
- 117 .clk (clk_50m),
- 118 .rst_n (rst_n),
- 119
- 120 .cam_pclk (cam_pclk_1),
- 121 .cam_vsync (cam_vsync_1),
- 122 .cam_href (cam_href_1 ),
- 123 .cam_data (cam_data_1 ),
- 124 .cam_rst_n (cam_rst_n_1),
- 125 .cam_pwdn (cam_pwdn_1),
- 126 .cam_scl (cam_scl_1 ),
- 127 .cam_sda (cam_sda_1 ),
- 128
- 129 .capture_start (init_calib_complete),
- 130 .cmos_h_pixel (h_disp[12:1]),
- 131 .cmos_v_pixel (v_disp),
- 132 .total_h_pixel (total_h_pixel),
- 133 .total_v_pixel (total_v_pixel),
- 134 .cmos_frame_vsync (cmos_frame_vsync_1),
- 135 .cmos_frame_href (),
- 136 .cmos_frame_valid (cmos_frame_valid_1),
- 137 .cmos_frame_data (wr_data_1)
- 138 );
- 139
- 140 //ov5640 驱动
- 141 ov5640_dri u_ov5640_dri_2(
- 142 .clk (clk_50m),
- 143 .rst_n (rst_n),
- 144
- 145 .cam_pclk (cam_pclk_2 ),
- 146 .cam_vsync (cam_vsync_2),
- 147 .cam_href (cam_href_2 ),
- 148 .cam_data (cam_data_2),
- 149 .cam_rst_n (cam_rst_n_2),
- 150 .cam_pwdn (cam_pwdn_2 ),
- 151 .cam_scl (cam_scl_2 ),
- 152 .cam_sda (cam_sda_2 ),
- 153
- 154 .capture_start (init_calib_complete),
- 155 .cmos_h_pixel (h_disp[12:1]),
- 156 .cmos_v_pixel (v_disp),
- 157 .total_h_pixel (total_h_pixel),
- 158 .total_v_pixel (total_v_pixel),
- 159 .cmos_frame_vsync (cmos_frame_vsync_2),
- 160 .cmos_frame_href (),
- 161 .cmos_frame_valid (cmos_frame_valid_2),
- 162 .cmos_frame_data (wr_data_2)
- 163 );
复制代码
这段代码是对OV5640驱动模块的一个顶层例化,负责驱动OV5640外设,采集图像等作用。因为本次实验用的是双目摄像头,所以顶层需要例化两次。
在程序的第120行至第127行和第145至第152行,这几行的信号都是与外设OV5640相连的,负责驱动摄像头。
在程序的第129行和154行,这个信号的作用是保证OV5640驱动模块在DDR3初始化没有完成之前不会对图像进行采集。
在程序的第130行至第133行和第155行至第158行,这四个信号是摄像头配置时需要的参数,使摄像头输出本次实验需要的行场分辨率和计算帧率的相关参数。在130行和155行将LCD屏的行分辨率除以2的原因是本次实验用的是双目摄像头,最终显示的效果是将双目OV5640摄像头实时采集到的图像分左右两半显示在LCD屏幕上,故摄像头需要采集的图像的行分辨率是LCD屏的行分辨率一半,场分辨率两者是相同的。
在程序的第136行至第137行和第161行至第162行,OV5640驱动模块输出的数据有效使能信号和有效数据连接到DDR控制模块,实现了图像数据的缓存。
- 166 ddr3_top u_ddr3_top (
- 167 .clk_200 (clk_200), //系统时钟
- 168 .sys_rst_n (rst_n), //复位,低有效
- 169 .sys_init_done (sys_init_done), //系统初始化完成
- 170 .init_calib_complete (init_calib_complete), //ddr3初始化完成信号
- 171 //ddr3接口信号
- 172 .app_addr_rd_min (28'd0), //读DDR3的起始地址
- 173 .app_addr_rd_max (ddr3_addr_max[27:1]), //读DDR3的结束地址
- 174 .rd_bust_len (h_disp[10:3]), //从DDR3中读数据时的突发长度
- 175 .app_addr_wr_min (28'd0), //写DDR3的起始地址
- 176 .app_addr_wr_max (ddr3_addr_max[27:1]), //写DDR3的结束地址
- 177 .wr_bust_len (h_disp[10:3]), //从DDR3中写数据时的突发长度
- 178 // DDR3 IO接口
- 179 .ddr3_dq (ddr3_dq), //DDR3 数据
- 180 .ddr3_dqs_n (ddr3_dqs_n), //DDR3 dqs负
- 181 .ddr3_dqs_p (ddr3_dqs_p), //DDR3 dqs正
- 182 .ddr3_addr (ddr3_addr), //DDR3 地址
- 183 .ddr3_ba (ddr3_ba), //DDR3 banck 选择
- 184 .ddr3_ras_n (ddr3_ras_n), //DDR3 行选择
- 185 .ddr3_cas_n (ddr3_cas_n), //DDR3 列选择
- 186 .ddr3_we_n (ddr3_we_n), //DDR3 读写选择
- 187 .ddr3_reset_n (ddr3_reset_n), //DDR3 复位
- 188 .ddr3_ck_p (ddr3_ck_p), //DDR3 时钟正
- 189 .ddr3_ck_n (ddr3_ck_n), //DDR3 时钟负
- 190 .ddr3_cke (ddr3_cke), //DDR3 时钟使能
- 191 .ddr3_cs_n (ddr3_cs_n), //DDR3 片选
- 192 .ddr3_dm (ddr3_dm), //DDR3_dm
- 193 .ddr3_odt (ddr3_odt), //DDR3_odt
- 194 //用户
- 195 .wr_clk_1 (cam_pclk_1), //摄像头1时钟
- 196 .wr_vsync_1 (cmos_frame_vsync_1), //摄像头1场信号
- 197 .datain_valid_1 (cmos_frame_valid_1), //数据1有效使能信号
- 198 .datain_1 (wr_data_1), //有效数据1
- 199 .wr_clk_2 (cam_pclk_2), //摄像头2时钟
- 200 .wr_vsync_2 (cmos_frame_vsync_2), //摄像头2场信号
- 201 .datain_valid_2 (cmos_frame_valid_2), //数据有效使能信号
- 202 .datain_2 (wr_data_2), //有效数据
- 203
- 204 .h_disp (h_disp),
- 205 .rd_clk (lcd_clk), //rfifo的读时钟
- 206 .rd_vsync (rd_vsync), //lcd场信号
- 207 .dataout (rd_data), //rfifo输出数据
- 208 .rdata_req (rdata_req) //请求像素点颜色数据输入
- 209 );
复制代码
在程序的第173和第176行,本次实验摄像头采集的图像的行分辨率只有LCD屏的一半,场分辨率是相同的,故最大读写地址信号(ddr3_addr_max)必须要除以二才是最后存到DDR的最大读写地址,但代码中将最低一位舍去,相当于信号右移一位,等同于除以二。
在程序的第174和第177行,信号(wr_bust_len)和信号(rd_bust_len)表示一次向DDR3读或写的长度,因为输入DDR3顶层模块的数据和从DDR3顶层模块输出的数据都为16bit,而MIG的数据位宽为128bit,所以一次写两行或读一行的长度是LCD屏水平分辨率除以8(128bit/16bit),这里将wr_bust_len和rd_bust_len的低三位舍去,相当于信号右移三位,等同于除以八。本次实验摄像头采集的图像的行分辨率只有LCD屏的一半,所以这次实验往DDR3一次读写的数据大小是写两行或者读一行。
本次实验的时钟模块、摄像头驱动模块、图像分辨率设置模块和摄像头驱动模块没有做任何修改,这些模块在“OV5640摄像头RGB-LCD显示实验”中已经说明过,这里不再详述。本次实验只对DDR控制模块和LCD顶层模块做了修改,本次实验着重讲解这两个模块。下面是DDR控制模块的系统框图:
图 48.4.3 DDR控制模块的系统框图
结合图可以看出相较于“OV5640摄像头RGB-LCD显示实验”,本次实验将FIFO调度模块替换成了FIFO顶层调度模块。本次实验是两个摄像头采集数据并且把数据存入两个写FIFO,两个写FIFO再将数据写入DDR3的两个不同的存储空间中,接着两个读FIFO分别从两个对应的DDR3地址空间中取出数据,最后两组数据一起拼接为完整一帧LCD图像,达到一个屏幕同时显示两幅图像的效果。在DDR3处理数据的时候,两组数据流相当于过独木桥,需要分时“排队”进出;同样在显示端,两组数据也要“排队”显示,所以本次实验的FIFO顶层调度模块的作用就是根据不同的情况对两个FIFO调度模块的信号进行切换。
下面是DDR控制模块的原理图:
图 48.4.4 DDR控制模块的原理图
DDR读写模块:该模块负责与MIG模块的命令和地址的交互,根据FIFO顶层调度模块中各个FIFO的剩余数据量来切换DDR3的读写命令和地址。
MIG模块:MIG模块(mig_7series_0)负责连接外设和FPGA,详细说明请看“DDR3读写测试实验”。
FIFO顶层调度模块:负责对输入和输出的数据进行时钟域的切换和位宽的转换,并根据DDR读写模块输出的使能来调度四个FIFO的数据。
下面是DDR控制模块的代码:
- 1 module ddr3_top(
- 2 input clk_200m , //ddr3参考时钟
- 3 input sys_rst_n , //复位,低有效
- 4 input sys_init_done , //系统初始化完成
- 5 //DDR3接口信号
- 6 input [27:0] app_addr_rd_min , //读ddr3的起始地址
- 7 input [27:0] app_addr_rd_max , //读ddr3的结束地址
- 8 input [7:0] rd_bust_len , //从ddr3中读数据时的突发长度
- 9 input [27:0] app_addr_wr_min , //读ddr3的起始地址
- 10 input [27:0] app_addr_wr_max , //读ddr3的结束地址
- 11 input [7:0] wr_bust_len , //从ddr3中读数据时的突发长度
- 12 // DDR3 IO接口
- 13 inout [15:0] ddr3_dq , //ddr3 数据
- 14 inout [1:0] ddr3_dqs_n , //ddr3 dqs负
- 15 inout [1:0] ddr3_dqs_p , //ddr3 dqs正
- 16 output [13:0] ddr3_addr , //ddr3 地址
- 17 output [2:0] ddr3_ba , //ddr3 banck 选择
- 18 output ddr3_ras_n , //ddr3 行选择
- 19 output ddr3_cas_n , //ddr3 列选择
- 20 output ddr3_we_n , //ddr3 读写选择
- 21 output ddr3_reset_n , //ddr3 复位
- 22 output [0:0] ddr3_ck_p , //ddr3 时钟正
- 23 output [0:0] ddr3_ck_n , //ddr3 时钟负
- 24 output [0:0] ddr3_cke , //ddr3 时钟使能
- 25 output [0:0] ddr3_cs_n , //ddr3 片选
- 26 output [1:0] ddr3_dm , //ddr3_dm
- 27 output [0:0] ddr3_odt , //ddr3_odt
- 28 //用户
- 29 input wr_clk_1 , //wfifo时钟
- 30 input datain_valid_1 , //数据有效使能信号
- 31 input [15:0] datain_1 , //有效数据
- 32 input wr_load_1 , //输入源场信号
- 33 input wr_clk_2 , //wfifo时钟
- 34 input datain_valid_2 , //数据有效使能信号
- 35 input [15:0] datain_2 , //有效数据
- 36 input wr_load_2 , //输入源场信号
- 37
- 38 input [12:0] h_disp , //摄像头水平分辨率
- 39 input rd_clk , //rfifo的读时钟
- 40 input rdata_req , //请求像素点颜色数据输入
- 41 input rd_load , //输出源场信号
- 42 output [15:0] dataout , //rfifo输出数据
- 43 output init_calib_complete //ddr3初始化完成信号
- 44
- 45 );
- 46
- 47 //wire define
- 48 wire ui_clk ; //用户时钟
- 49 wire [27:0] app_addr ; //ddr3 地址
- 50 wire [2:0] app_cmd ; //用户读写命令
- 51 wire app_en ; //MIG IP核使能
- 52 wire app_rdy ; //MIG IP核空闲
- 53 wire [127:0] app_rd_data ; //用户读数据
- 54 wire app_rd_data_end ; //突发读当前时钟最后一个数据
- 55 wire app_rd_data_valid ; //读数据有效
- 56 wire [127:0] app_wdf_data ; //用户写数据
- 57 wire app_wdf_end ; //突发写当前时钟最后一个数据
- 58 wire [15:0] app_wdf_mask ; //写数据屏蔽
- 59 wire app_wdf_rdy ; //写空闲
- 60 wire app_sr_active ; //保留
- 61 wire app_ref_ack ; //刷新请求
- 62 wire app_zq_ack ; //ZQ 校准请求
- 63 wire app_wdf_wren ; //ddr3 写使能
- 64 wire clk_ref_i ; //ddr3参考时钟
- 65 wire sys_clk_i ; //MIG IP核输入时钟
- 66 wire ui_clk_sync_rst ; //用户复位信号
- 67 wire [20:0] rd_cnt ; //实际读地址计数
- 68 wire [3 :0] state_cnt ; //状态计数器
- 69 wire [23:0] rd_addr_cnt ; //用户读地址计数器
- 70 wire [23:0] wr_addr_cnt ; //用户写地址计数器
- 71 wire rfifo_wren ; //从ddr3读出数据的有效使能
- 72 wire init_calib_complete ; //ddr3初始化完成信号
- 73 wire [127:0] rfifo_wdata_1 ; //rfifo1输入数据
- 74 wire [127:0] rfifo_wdata_2 ; //rfifo2输入数据
- 75 wire [10:0] wfifo_rcount_1 ; //wfifo1剩余数据计数
- 76 wire [10:0] wfifo_rcount_2 ; //wfifo2剩余数据计数
- 77 wire [10:0] rfifo_wcount_1 ; //rfifo1写进数据计数
- 78 wire [10:0] rfifo_wcount_2 ; //rfifo2写进数据计数
- 79
- 80 //*****************************************************
- 81 //** main code
- 82 //*****************************************************
- 83
- 84 //读写模块
- 85 ddr3_rw u_ddr3_rw(
- 86 .ui_clk (ui_clk) ,
- 87 .ui_clk_sync_rst (ui_clk_sync_rst) ,
- 88 //MIG 接口
- 89 .init_calib_complete (init_calib_complete) , //ddr3初始化完成信号
- 90 .app_rdy (app_rdy) , //MIG IP核空闲
- 91 .app_wdf_rdy (app_wdf_rdy) , //写空闲
- 92 .app_rd_data_valid (app_rd_data_valid) , //读数据有效
- 93 .app_rd_data (app_rd_data) , //读数据
- 94 .app_addr (app_addr) , //ddr3 地址
- 95 .app_en (app_en) , //MIG IP核使能
- 96 .app_wdf_wren (app_wdf_wren) , //ddr3 写使能
- 97 .app_wdf_end (app_wdf_end) , //突发写当前时钟最后一个数据
- 98 .app_cmd (app_cmd) , //用户读写命令
- 99 //DDR3 地址参数
- 100 .app_addr_rd_min (app_addr_rd_min) , //读ddr3的起始地址
- 101 .app_addr_rd_max (app_addr_rd_max) , //读ddr3的结束地址
- 102 .rd_bust_len (rd_bust_len) , //从ddr3中读数据时的突发长度
- 103 .app_addr_wr_min (app_addr_wr_min) , //写ddr3的起始地址
- 104 .app_addr_wr_max (app_addr_wr_max) , //写ddr3的结束地址
- 105 .wr_bust_len (wr_bust_len) , //从ddr3中写数据时的突发长度
- 106 //用户接口
- 107 .rfifo_wren_1 (rfifo_wren_1) , //rfifo写使能
- 108 .rfifo_wdata_1 (rfifo_wdata_1) , //rfifo写数据
- 109 .rfifo_wren_2 (rfifo_wren_2) , //rfifo写使能
- 110 .rfifo_wdata_2 (rfifo_wdata_2) , //rfifo写数据
- 111 .wfifo_rden_1 (wfifo_rden_1) , //写端口FIFO1中的读使能
- 112 .wfifo_rden_2 (wfifo_rden_2) , //写端口FIFO2中的读使能
- 113 .rd_load (rd_load) , //输出源场信号
- 114 .wr_load_1 (wr_load_1) , //输入源场信号
- 115 .wr_load_2 (wr_load_2) , //输入源场信号
- 116 .wfifo_rcount_1 (wfifo_rcount_1) , //wfifo剩余数据计数
- 117 .rfifo_wcount_1 (rfifo_wcount_1) , //rfifo写进数据计数
- 118 .wfifo_rcount_2 (wfifo_rcount_2) , //wfifo剩余数据计数
- 119 .rfifo_wcount_2 (rfifo_wcount_2) , //rfifo写进数据计数
- 120 .wr_clk_2 (wr_clk_2) , //wfifo时钟
- 121 .wr_clk_1 (wr_clk_1) //wfifo时钟
- 122 );
- 123
- 124 //MIG IP核模块
- 125 mig_7series_0 u_mig_7series_0 (
- 126 // Memory interface ports
- 127 .ddr3_addr (ddr3_addr) ,
- 128 .ddr3_ba (ddr3_ba) ,
- 129 .ddr3_cas_n (ddr3_cas_n) ,
- 130 .ddr3_ck_n (ddr3_ck_n) ,
- 131 .ddr3_ck_p (ddr3_ck_p) ,
- 132 .ddr3_cke (ddr3_cke) ,
- 133 .ddr3_ras_n (ddr3_ras_n) ,
- 134 .ddr3_reset_n (ddr3_reset_n) ,
- 135 .ddr3_we_n (ddr3_we_n) ,
- 136 .ddr3_dq (ddr3_dq) ,
- 137 .ddr3_dqs_n (ddr3_dqs_n) ,
- 138 .ddr3_dqs_p (ddr3_dqs_p) ,
- 139 .ddr3_cs_n (ddr3_cs_n) ,
- 140 .ddr3_dm (ddr3_dm) ,
- 141 .ddr3_odt (ddr3_odt) ,
- 142 // Application interface ports
- 143 .app_addr (app_addr) ,
- 144 .app_cmd (app_cmd) ,
- 145 .app_en (app_en) ,
- 146 .app_wdf_data (app_wdf_data) ,
- 147 .app_wdf_end (app_wdf_end) ,
- 148 .app_wdf_wren (app_wdf_wren) ,
- 149 .app_rd_data (app_rd_data) ,
- 150 .app_rd_data_end (app_rd_data_end) ,
- 151 .app_rd_data_valid (app_rd_data_valid) ,
- 152 .init_calib_complete (init_calib_complete) ,
- 153
- 154 .app_rdy (app_rdy) ,
- 155 .app_wdf_rdy (app_wdf_rdy) ,
- 156 .app_sr_req () ,
- 157 .app_ref_req () ,
- 158 .app_zq_req () ,
- 159 .app_sr_active (app_sr_active) ,
- 160 .app_ref_ack (app_ref_ack) ,
- 161 .app_zq_ack (app_zq_ack) ,
- 162 .ui_clk (ui_clk) ,
- 163 .ui_clk_sync_rst (ui_clk_sync_rst) ,
- 164 .app_wdf_mask (31'b0) ,
- 165 // System Clock Ports
- 166 .sys_clk_i (clk_200m) ,
- 167 // Reference Clock Ports
- 168 .clk_ref_i (clk_200m) ,
- 169 .sys_rst (sys_rst_n)
- 170 );
- 171
- 172 ddr3_fifo_ctrl_top u_ddr3_fifo_ctrl_top(
- 173 .rst_n (sys_rst_n &&sys_init_done), //复位信号
- 174 .rd_clk (rd_clk) , //rfifo时钟
- 175 .clk_100 (ui_clk) , //用户时钟
- 176 //fifo1接口信号
- 177 .wr_clk_1 (wr_clk_1) , //wfifo时钟
- 178 .datain_valid_1 (datain_valid_1) , //数据有效使能信号
- 179 .datain_1 (datain_1) , //有效数据
- 180 .wr_load_1 (wr_load_1) , //输入源场信号
- 181 .rfifo_din_1 (rfifo_wdata_1) , //rfifo写数据
- 182 .rfifo_wren_1 (rfifo_wren_1) , //rfifo写使能
- 183 .wfifo_rden_1 (wfifo_rden_1) , //wfifo读使能
- 184 .wfifo_rcount_1 (wfifo_rcount_1) , //wfifo剩余数据计数
- 185 .rfifo_wcount_1 (rfifo_wcount_1) , //rfifo写进数据计数
- 186 //fifo2接口信号
- 187 .wr_clk_2 (wr_clk_2) , //wfifo时钟
- 188 .datain_valid_2 (datain_valid_2) , //数据有效使能信号
- 189 .datain_2 (datain_2) , //有效数据
- 190 .wr_load_2 (wr_load_2) , //输入源场信号
- 191 .rfifo_din_2 (rfifo_wdata_2) , //rfifo写数据
- 192 .rfifo_wren_2 (rfifo_wren_2) , //rfifo写使能
- 193 .wfifo_rden_2 (wfifo_rden_2) , //wfifo读使能
- 194 .wfifo_rcount_2 (wfifo_rcount_2) , //wfifo剩余数据计数
- 195 .rfifo_wcount_2 (rfifo_wcount_2) , //rfifo写进数据计数
- 196
- 197 .h_disp (h_disp) , //摄像头水平分辨率
- 198 .rd_load (rd_load) , //输出源场信号
- 199 .rdata_req (rdata_req) , //请求像素点颜色数据输入
- 200 .pic_data (dataout) , //有效数据
- 201 .wfifo_dout (app_wdf_data) //用户写数据
- 202
- 203 );
- 204
- 205 endmodule
复制代码
在“OV5640摄像头RGB-LCD显示实验”的程序中,读写操作地址用了DDR3的两个存储空间,但本次实验开辟了四个存储空间,使得两个摄像头的数据在DDR3的存储中互不影响,也方便调度,所以本次实验需要在“OV5640摄像头RGB-LCD显示实验”的程序方面做些改动。具体框图如下图所示:
图 48.4.5 DDR读写控制流程图
图像数据是由两个摄像头分别采集得来的,所以将图像分别存入两个wfifo中。当两个wfifo任意一个fifo剩余的数据量大于本次实验设定的阈值时,DDR读写模块就对其发出读数据请求信号,使fifo中的数据写入DDR3中,保证wfifo不会写满。当两个rfifo任意一个fifo剩余的数据量小于本次实验设定的阈值时,DDR读写模块就向对应的rfifo中写入数据,保证rfifo不会读空。DDR3中开辟了四个存储空间,每个输入源使用两个存储空间,这么做的好处一是对输入源做乒乓操作,防止画面撕裂,另一个是保证两个输入源的数据不会相互干扰。
本次实验中的DDR读写模块是基于“OV5640摄像头RGB-LCD显示实验”做的修改,本次实验着重对代码的改动部分做讲解。
- 40 //localparam
- 41 localparam IDLE = 7'b0000001; //空闲状态
- 42 localparam DDR3_DONE = 7'b0000010; //DDR3初始化完成状态
- 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 == DDR3_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 == DDR3_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进行了赋值,这两个信号在读操作开始后拉高,进入DDR3空闲状态拉低。信号为高时表示此次读操作所需要的数据没有全部从DDR3读出来,为低时,表示数据已经全部读出来了。
- 147 //从ddr3读出的有效数据使能进行计数
- 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 == DDR3_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行,对DDR3读出数据的有效使能(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将要空了,并把DDR3读出的数据写入这个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行所示,这段代码是DDR3读写逻辑实现,状态跳转图如下图所示:
图 48.4.6 状态跳转图
本次实验的状态跳转相对于“OV7725摄像头RGB-LCD显示实验”中的状态跳转只是多了一个读状态、一个写状态和读等待状态。两个实验当中的写状态跳转的条件是没有变的,读状态的跳转出现了变化。本次实验读状态不直接跳到DDR的空闲状态,而是跳到读等待状态。本次实验的实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边,所以在输出端存在两个rfifo。在下面的时序图中可以发现当读状态操作完后,而此时的数据没有全部读出来。如果此时直接跳转到DDR的空闲状态,下一刻状态就会跳到另一个读状态,那么DDR3读出的数据就不容易区分是哪个rfifo的数据,容易造成数据错乱。为了保证数据可以准确的写入到对应的rfifo中,必须在数据完全读出后才能跳转到DDR的空闲状态,这是添加读等待状态的原因。时序图如下:
图 48.4.7 状态时序图
下面是FIFO顶层调度模块的原理图:
图 48.4.8 FIFO顶层调度模块的原理图
本次实验增加了一个rfifo和一个wfifo,所以对FIFO调度模块例化了两次。由原理图可知,在FIFO顶层调度模块,对写fifo的输出数据进行了判断,也对读fifo的输出数据进行判断。
FIFO调度模块:负责对输入和输出的数据进行时钟域的切换和位宽的转换。详细说明请看“OV5640摄像头RGB-LCD显示实验”。
FIFO顶层调度模块的代码如下:
- 1 module ddr3_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 , //从ddr3读出数据的有效使能
- 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 , //从ddr3读出数据的有效使能
- 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 //写入DDR3的像素数据切换
- 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 ddr3_fifo_ctrl u_ddr3_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) , //ddr3读出数据的有效使能
- 87 .wfifo_rden (wfifo_rden_1) , //ddr3 写使能
- 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 ddr3_fifo_ctrl u_ddr3_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) , //ddr3读出数据的有效使能
- 110 .wfifo_rden (wfifo_rden_2) , //ddr3 写使能
- 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行,表示的是像素数据在写入DDR3前的切换。
在代码66至73行,对LCD顶层模块发出的对读请求信号进行计数。
在代码75至119行是两个FIFO调度模块的例化,我们只给出代码注释,方便大家了解各信号的连接关系,这里不做分析了。
接下来介绍LCD顶层模块(lcd_rgb_top),这里先给出该模块的顶层代码:
- 1 module lcd_rgb_top(
- 2 input sys_clk , //系统时钟
- 3 input sys_rst_n, //复位信号
- 4 input sys_init_done,
- 5 //lcd接口
- 6 output lcd_clk, //LCD驱动时钟
- 7 output lcd_hs, //LCD 行同步信号
- 8 output lcd_vs, //LCD 场同步信号
- 9 output lcd_de, //LCD 数据输入使能
- 10 inout [23:0] lcd_rgb, //LCD RGB颜色数据
- 11 output lcd_bl, //LCD 背光控制信号
- 12 output lcd_rst, //LCD 复位信号
- 13 output lcd_pclk, //LCD 采样时钟
- 14 output [15:0] lcd_id, //LCD屏ID
- 15 output out_vsync, //lcd场信号
- 16 output [9:0] pixel_xpos, //像素点横坐标
- 17 output [9:0] pixel_ypos, //像素点纵坐标
- 18 output [10:0] h_disp, //LCD屏水平分辨率
- 19 output [10:0] v_disp, //LCD屏垂直分辨率
- 20 input [15:0] data_in, //数据输入
- 21 output data_req //请求像素点颜色数据输入
- 22
- 23 );
- 24
- 25 //wire define
- 26 wire [9:0] pixel_xpos; //像素点横坐标
- 27 wire [9:0] pixel_ypos; //像素点纵坐标
- 28 wire out_vsync; //帧复位,高有效
- 29 wire [10:0] h_disp; //LCD屏水平分辨率
- 30 wire [10:0] v_disp; //LCD屏垂直分辨率
- 31 wire [15:0] lcd_data; //选择屏后的数据
- 32 wire [15:0] lcd_rgb_565; //输出的16位lcd数据
- 33 wire [23:0] lcd_rgb_o ; //LCD 输出颜色数据
- 34 wire [23:0] lcd_rgb_i ; //LCD 输入颜色数据
- 35
- 36 //*****************************************************
- 37 //** main code
- 38 //*****************************************************
- 39
- 40 //将摄像头16bit数据转换为24bit的lcd数据
- 41 assign lcd_rgb_o = {lcd_rgb_565[15:11],3'b000,lcd_rgb_565[10:5],2'b00,
- 42 lcd_rgb_565[4:0],3'b000};
- 43
- 44 //像素数据方向切换
- 45 assign lcd_rgb = lcd_de ? lcd_rgb_o : {24{1'bz}};
- 46 assign lcd_rgb_i = lcd_rgb;
- 47
- 48 //*****************************************************
- 49 //** main code
- 50 //*****************************************************
- 51
- 52 //时钟分频模块
- 53 clk_div u_clk_div(
- 54 .clk (sys_clk ),
- 55 .rst_n (sys_rst_n),
- 56 .lcd_id (lcd_id ),
- 57 .lcd_pclk (lcd_clk )
- 58 );
- 59
- 60 //读LCD ID模块
- 61 rd_id u_rd_id(
- 62 .clk (sys_clk ),
- 63 .rst_n (sys_rst_n),
- 64 .lcd_rgb (lcd_rgb_i),
- 65 .lcd_id (lcd_id )
- 66 );
- 67
- 68 //lcd驱动模块
- 69 lcd_driver u_lcd_driver(
- 70 .lcd_clk (lcd_clk),
- 71 .sys_rst_n (sys_rst_n & sys_init_done),
- 72 .lcd_id (lcd_id),
- 73
- 74 .lcd_hs (lcd_hs),
- 75 .lcd_vs (lcd_vs),
- 76 .lcd_de (lcd_de),
- 77 .lcd_rgb (),
- 78 .lcd_bl (lcd_bl),
- 79 .lcd_rst (lcd_rst),
- 80 .lcd_pclk (lcd_pclk),
- 81
- 82 .pixel_data (data_in),
- 83 .data_req (data_req),
- 84 .out_vsync (out_vsync),
- 85 .h_disp (h_disp),
- 86 .v_disp (v_disp),
- 87 .pixel_xpos (pixel_xpos),
- 88 .pixel_ypos (pixel_ypos)
- 89 );
- 90
- 91 lcd_disply u_lcd_disply(
- 92
- 93 .lcd_clk (lcd_clk), //lcd模块驱动时钟
- 94 .sys_rst_n (sys_rst_n & sys_init_done), //复位信号
- 95 //RGB LCD接口
- 96 .pixel_xpos (pixel_xpos), //像素点横坐标
- 97 .pixel_ypos (pixel_ypos), //像素点纵坐标
- 98 .rd_data (data_in), //图像数据
- 99 .rd_h_pixel (h_disp), //图像水平像素大小
- 100 .pixel_data (lcd_rgb_565) //像素点数据
- 101 );
- 102
- 103 endmodule
复制代码
以上主要是LCD顶层模块的顶层例化,从代码中可以看到LCD顶层模块一共包含四个模块,同时可以看到各信号的定义。代码第45行做了一个判断,lcd_de为高则把像素值赋给RGB,否则RGB为高阻态,即不显示任何图像。
关于四个模块,本次实验只对lcd_disply模块进行讲解。另外三个模块未作改动,具体代码讲解,可以参看“OV7725摄像头RGB-LCD显示”实验。
最后讲解lcd_disply模块,以下是lcd_disply模块的代码:
本模块实现了五种RGB-LCD屏不同区域显示内容的逻辑判断,同时实现了字符叠加功能。
代码第30行到139行,定义了四个二维数组char,用于存储对英文取模得到的点阵数据,具体代码讲解,可以参看“RGB-LCD字符和图片显示实验”实验。
代码第13行到15行,定义了不同颜色对应的参数。代码第23行到132行给我们要用到的四个字符的模值进行赋值。在本次实验中LCD屏幕上要显示的字符大小为32*128,左右半边分别叠加上的“OV5640 1”和“OV5640 2”。
代码从135行往后是显示逻辑的具体实现,由于代码涉及到了判断条件之间的多重嵌套,为了让我们快速理清思路,我们结合代码制作了如图 48.4.9所示的显示逻辑图。结合代码和图,我们来具体介绍。
首先我们对屏幕类型和图像像素纵坐标进行判断,对于所有屏幕,如果纵坐标值不在字符显示区域内,我们执行代码第144行,让屏幕显示图像像素的值。只有当纵坐标值位于字符显示区域内,我们正式开始字符叠加处理。
代码第136行,通过判断了像素点纵坐标的位置。代码第138行和139行,此时开始判断像素点横坐标位置,如果此时横坐标处于左半边的中间位置,此时执行代码第142行,开始读取“OV5640 1”的字符数组的值。代码第141到144行,将字模数组中为“1”的点的像素值赋值为蓝色,为“0”的点像素值赋值为图像像素的值。这就是LCD屏左半边字符叠加的实现。代码第146到156行,实现的是LCD屏右半边字符叠加,实现的代码形式一模一样,只是字模数组变为了“OV5640 2”,在这里我们不再重复。代码第158行,非字符纵坐标区域,显示图片像素值。
图 48.4.9 显示逻辑
48.5下载验证
首先将FPC排线一端与RGB-LCD模块上的J1接口连接,另一端与达芬奇开发板上的RGB-LCD接口连接。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线,如图 48.5.1和图 48.5.2所示。
图 48.5.1 正点原子RGBLCD模块FPC连接器
|