发 帖  
原厂入驻New
[资料] 【正点原子FPGA连载】第三十一章双目OV5640摄像头LCD显示领航者 ZYNQ 之嵌入式开发指南
2020-9-7 15:54:04  108 正点原子FPGA
分享

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/FPGA/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
5)关注正点原子公众号,获取最新资料


第三十一章双目OV5640摄像头LCD显示实验

双目摄像头是在一个模组上集成了两个摄像头,实现了双通道的图像采集。双目摄像头一般应用于安防监控、立体视觉测距、三维重建等领域。本章我们将使用领航者ZYNQ开发板实现对双目OV5640摄像头的图像采集并通过LCD屏实时显示。
263131.1简介
31.2实验任务
31.3硬件设计
31.4软件设计
31.5下载验证
31.1简介
摄像头在日常生活中非常常见,一般分为单目摄像头、双目摄像头和多目摄像头。单目摄像头凭借着成本低和易用的特点,目前使用最为广泛;双目摄像头主要应用于单目摄像头无法胜任的场合,如三维坐标定位等;当然针对一些特殊的应用,目前市场上也出现了多目摄像头,以应对更加复杂的场景。
我们在前面的例程中实现了单目OV5640摄像头LCD屏的实时显示,本章我们在此基础上实现双目OV5640摄像头的实时显示,即两路摄像头的图像视频显示在LCD屏上,并叠加字符的显示。值得一提的是,xilinx官方提供了专门用于视频混合显示和简单文本叠加的视频处理模块,即OSD(On Screen display)IP核。OSD IP核最大支持8层外部视频的输入(来自帧缓冲区或者AXI4数据流接口视频)和内部图形控制器(如:文本控制),该IP核通过一个寄存器接口来设置和控制屏幕大小、背景颜色、图层位置等。
下图为通过Xilinx OSD IP核实现图像视频和文本叠加显示的例子。



图 31.1.1 OSD IP核跌在字符显示Demo

图 31.1.1是一个具有多个视频源、文本和方框通过OSD叠加输出的示例。图中的Video1、Video2和Video3这三个视频层可以是静态图像或实时视频,位于可编程的背景色之上;图中的黄色文本和菜单栏是通过OSD自带的内部图形控制器生成,并与其它图层混合;另外,图中的Xilinx logo同样是一个视频层,可以由片内RAM或者片外存储器生成。
OSD IP核的输入接口和输出支持AXI4-Stream接口协议。输入接口为AXI4-Stream Slave InteRFace,输出接口为AXI4-Stream Master Interface,因此OSD IP核可以非常容易的和Xilinx其它的IP核集成在一起,如AXI VDMA、Video Scaler和Video timing Controller等,同样也支持其它的AXI4-Stream Video IP核。
OSD IP核能够工作在1080P 60Hz,最大支持的分辨率为4096x4096。OSD IP核实例化了一组内部图形控制器,每个图形控制器可以独立配置。图形控制器包括方框和文本生成器,可以在运行时通过AXI4-Lite接口重新配置,以移动或调整文本和方框的大小。其中文本可以按照比例放大至2倍、4倍或者8倍。
OSD配置为三个图层的示例框图如图 31.1.2所示:



图 31.1.2 OSD框图

由上图可知,图层0和图层1配置为外部视频输入,用于连接AXI4-Stream类型接口;图层2配置为内部图形控制器,用于显示字符或者方框等。每个图层分配一个优先级,数值越大表示优先级越高,优先级高的图层会叠加在优先级低的图层之上,没有叠加图层的区域将会显示背景色。除此之外,OSD IP核也有一个可选的AXI4-Lite接口和可选的中断状态输出接口,处理器可以通过AXI4-Lite实时配置OSD IP核的控制器寄存器,从而控制每个图层的分辨率、背景色、字符的显示和位置等;而中断状态输出接口用于输出中断状态位包括图形控制器指令错误等。
31.2实验任务
本章的实验任务是通过调用OSD IP核,在领航者Zynq开发板上实现双目OV5640摄像头LCD屏的实时显示,并在LCD屏上叠加字符。
31.3硬件设计
我们的领航者Zynq开发板上有一个扩展接口(位于ATK MODULE附近),该接口可以用来连接双目OV5640摄像头、高速ADDA等模块,扩展接口原理图如图 31.3.1所示:



图 31.3.1 扩展口接口原理图

由上图可知,扩展口的引脚和开发板上的部分外设是复用的,因此当扩展口上连接双目OV5640摄像头时,开发板上复用至扩展口上的外设将不能使用。需要注意的是,UART2_TX、UART2_RX、CAN_TX和CAN_RX也是复用至扩展口上的,因此需要拿掉开发板上P3和P1排针上的跳帽,否则会影响双目OV5640摄像头的使用。
ATK-Dual-OV5640是正点原子推出的一款双目OV5640摄像头,该模块通过2*20排母(2.54mm间距)同外部连接,连接时将双目摄像头的排母直接插在开发板上的扩展口即可,模块外观如图 31.3.2所示:



图 31.3.2 双目OV5640模块

由于双目OV5640的引脚较多,这里不再给出引脚分配列表,在硬件设计的最后给出了本次实验管脚分配的约束代码,大家在搭建工程时可以直接拷贝。
我们在前面的例程中实现了单目OV5640摄像头LCD屏的实时显示,本章我们在此基础上,增加了一路摄像头的图像采集与显示。Xilinx官方提供的OSD(On Ccreen Display)最大支持8层外部视频的输入(来自帧缓冲区或者AXI4数据流接口视频)和内部图形控制器(如:文本控制),可以很好的帮助我们实现这一功能。OSD IP核的输入接口为AXI4-Stream Slave Interface,输出接口为AXI4-Stream Master Interface,因此输入接口可以直接和VDMA IP核连接,输出接口和AXI4-Stream to Video Out IP核连接。本次实验的系统框图如下图所示:


图 31.3.3 系统框图

由于本次实验增加了一路OV5640摄像头的图像采集与显示,因此框图中增加了OV5640图像采集IP核、Video In to AXI4-Stream IP核和VDMA IP核,两个VDMA IP核连接至OSD IP核,然后将OSD IP核的输出接口连接至AXI4-Stream to Video Out IP核的输入接口。另外,本次实验还需要对新增的一路摄像头进行配置,SCCB配置驱动同样是在PS中完成。
需要注意的是,在“OV5640摄像头LCD显示”的例程中,通过AXI SmartConnect IP核将一个(或多个)AXI存储器映射的主器件连接到一个(或多个)存储器映射的从器件,由于AXI SmartConnect IP核和OSD等IP核会消耗较多的逻辑资源,甚至超出了ZYNQ-7010芯片的最大逻辑单元,因此这里统一将AXI SmartConnect IP核替换成AXI Interconnect IP核,以节省逻辑单元。
最终图中的数据流走向为:OV5640图像采集是我们自定义的IP核,负责将OV5640的数据转成视频流数据;视频流数据经过Video in to AXI4-Stream IP核转换成AXI4-Stream IP格式数据流,然后通过VDMA的写通道转成AXI4 Memory Map格式,并最终写入DDR内存中。VDMA通过AXI Smartconnect IP核与AXI_HP端口进行连接,从而高效访问ddr3。由于本次实验是对双目OV5640摄像头进行图像采集和显示,因此OV5640图像采集IP核、Video In to AXI4-Stream IP核和VDMA IP核需要添加两次。
两路VDMA输出的AXI4-Stream格式的图像数据连接至OSD IP核,经OSD IP核拼接和叠加字符后,连接至AXI4-Stream to Video Out IP核。AXI4-Stream to Video Out IP核在VTC IP核的控制下,把AXI4-Stream格式的数据转换成视频输出的数据格式(如RGB888),并将输出的视频数据流连接至RGB2LCD IP核(rgb2lcd)的输入端。RGB2LCD IP核是本次实验自定义的IP核,实现了获取LCD屏的ID,以及将LCD屏的引脚封装到总线接口上,以方便将LCD引脚引出至顶层模块端口上。
本次实验的硬件平台在“OV5640摄像头LCD显示”实验基础上搭建。由于本次实验需要对两个摄像头的SCCB接口进行配置,因此ZYNQ7处理系统中的EMIO引脚的个数由2改为4,如下图所示:



图 31.3.4 修改EMIO GPIO位宽

接下来按照系统框图中的架构,依次添加OV5640图像采集IP核、Video In to AXI4-Stream IP核和VDMA IP核,如下图所示:



图 31.3.5 添加IP核

这三个IP核的配置和框图里已经存在的三个IP核保持一致。
接下来添加OSD IP核,点击框图界面上方的“ADD IP”按钮,在弹出的IP目录中搜索“OSD”,最后双击搜索结果中的“AXI GPIO”将其添加到设计中,如下图所示:



图 31.3.6 添加OSD IP核

双击打开新添加的OSD IP核,对OSD IP核进行配置,弹出的界面如下图所示:


图 31.3.7 OSD IP核配置界面

Include AXI4-Lite Interface:可选的配置接口,处理器可以通过AXI4-Lite接口实现对OSD的动态配置。由于本次实验需要根据不同分辨率的LCD屏修改OSD IP核的分辨率,并且需要配置OSD IP核来叠加字符,因此这里勾选这个配置接口。
Include INTC Interface:可选的中断接口,如果勾选这个选项,OSD IP核将会产生一个INTC_IF端口,这个端口提供了对中断状态的并行访问,包括帧处理的状态和产生错误的条件等。本次实验不需要对OSD IP核的状态进行监控,因此这里不勾选。
Video Format:视频格式,本次实验的视频格式为RGB888。先点击Video Format左侧的AUTO按钮将其切换至MANUAL,然后将视频格式修改为RGB。
Video Component Width:视频组件宽度,这里可以理解为RGB颜色分量的位宽,本次实验的颜色分量位宽为8位。先点击Video Component Width左侧的AUTO按钮将其切换至MANUAL,然后将颜色分量位宽修改为8。
Number of Layers:图层的数量,范围是1~8。本次实验需要显示两路摄像头的视频数据,且每路视频上各叠加一组字符显示,因此图层的数量配置为4。
Maximum Screen Width:OSD最大屏幕分辨率的宽度,范围是128~4095。本次实验连接的最大分辨率的屏幕为10.1寸LCD屏,分辨率为1280*800,因此这里设置为1280。
Layer Configuration:各图层的类型,External AXIS指外部输入的AXI4-Stream数据流,Internal Graphics Controller指内部的图形控制器,用于叠加字符或者方框等。本次实验显示两路摄像头+两路字符叠加,因此LAYER0 Type和LAYER1 Type为External AXIS,LAYER2 Type和LAYER3 Type为Internal Graphics Controller。当LAYER2 Type和LAYER3 Type修改为Internal Graphics Controller之后,标签配置页会多出LAYER 2 Options和LAYER 3 Options。
接下来点击Screen Layout Options标签页,配置如下图所示:



图 31.3.8 OSD IP核图层选项配置

Screen Layout Options标签页的选项这里全部保持默认,这是因为这些配置我们都可以在PS软件中通过AXI4-Lite接口进行修改,这里介绍下各个选项的含义。
Background Size:背景分辨率。
Background Color:背景颜色。需要说明的是,OSD的背景颜色是按照RBG来显示的,而我们给OSD 的数据是按RGB的数据格式,因此当背景颜色设置成绿色(G:255 B:0 R:0)时,实际上背景颜色显示的蓝色。本次实验两路视频数据全部填充了LCD屏,因此看不到背景颜色。
界面最下面分别对四个图层进行设置,依次为:
Horizontal Position:各图层的水平起始位置;
Vertical Position:各图层的垂直起始位置;
Width:各图层的宽度;
Height:各图层的高度;
Layer Priority:图层的优先级显示,高优先级的图层显示在低优先级图层之上;
Layer Enable:配置图层的使能状态;
Global Alpha Value:透明度;
Global Alpha Enable:配置透明度使能状态。
接下来点击Layout 2 Options标签页,配置如下图所示:



图 31.3.9 OSD IP核LAYER 2配置

图层2配置选项,只有Internal Graphics Controller类型的图层才会有此选项。这里仅介绍该页面修改的两个配置参数。
Number of Characters:内部字体RAM中的字符个数,这里设置为128,否则字符显示不支持小写字母;
ASCII Offset:第一个ASCII码位于字体RAM中的位置,这里设置为0,否则在叠加字符显示时出现错位的现象。
其余选项保持默认即可。
接下来点击Layout 3 Options标签页,配置和Layout 2 Options保持一致,如下图所示:



图 31.3.10 图 OSD IP核LAYER 3配置

最后点击OK按钮,完成OSD IP核的配置。
我们在前面讲过,AXI SmartConnect IP核(axi_smc)会消耗较多的逻辑资源,因此我们这里将AXI SmartConnect IP核替换成AXI Interconnect IP核(如果大家使用的是ZYNQ-7020核心板,由于XC7Z020逻辑资源充足,也可不替换;如果使用ZYNQ-7010核心板,则必须替换,否则超过XC7Z010的逻辑资源,编译会报错)。先将图 31.3.11的AXI SmartConnect IP核删除,如下图所示:




图 31.3.11 删除AXI SmartConnect IP核
然后添加AXI Interconnect IP核,点击框图界面上方的“ADD IP”按钮,在弹出的IP目录中搜索“axi inter”,最后双击搜索结果中的“AXI Interconnect”将其添加到设计中,如下图所示:



图 31.3.12 添加 AXI Interconnect IP核

双击打开新添加的AXI Interconnect IP核,对AXI Interconnect IP核进行配置,弹出的界面如下图所示:



图 31.3.13 AXI Interconnect配置

AXI Interconnect IP核用于将一个(或多个)AXI存储器映射的主器件连接到一个(或多个)存储器映射的从器件。本次实验共添加两个VDMA IP核,每个VDMA IP核开启了一个写通道和一个读通道,因此将从器件设置为4,主器件和PS的HP接口连接,主器件个数设置为1。
接下来开始连线。由于VDMA IP核输出的AXI-Stream需要先经过OSD IP核,再输出给AXI4-Stream to Video Out IP核,因此原先VDMA IP核和AXI4-Stream to Video Out IP核连接的线需要删除,如下图所示:




图 31.3.14 删除连线

将axi_vdma_0 IP核的M_AXIS_MM2S端口和v_osd_0 IP核的video_s0_in端口连接;将v_osd_0 IP核的video_out端口axi4s_vid_out_0 IP 核的video_in端口连接,如下图所示:



图 31.3.15 连接OSD IP核端口

新增加的OV5640图像采集IP核、Video In to AXI4-Stream IP核和VDMA IP核,连线同已经存在的这三个IP核类似,这里需手动连线部分端口,连线完成后如下图所示:



图 31.3.16 新增的IP核手动连线

接下来点击图 31.3.17箭头指向的位置,让软件自动完成剩余连线。如果自动连线后,仍然出现这个自动连线的提示,则继续点击。



图 31.3.17 自动连线

接下来引出新增的OV5640图像采集IP核(ov5640_capture_data_1)的端口信号,为了和ov5640_capture_data_0 IP核引出的端口进行区分,为端口信号添加后缀名“_0”和“_1”进行区分。先重命名ov5640_capture_data_0 IP核引出的全部端口信号,端口后缀名添加“_0”,如下图所示:




图 31.3.18 v5640_capture_data_0 IP核端口重命名

将ov5640_capture_data_1 IP核的端口引出,在选择端口后,右键点击“Make External”,此时软件会自动为端口添加后缀名“_1”,引出所有端口后,如下图所示:




图 31.3.19 v5640_capture_data_1 IP核端口引出

整体系统框图,如下图所示:



图 31.3.20 整体系统框图

到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
为工程添加约束文件,约束文件如下:

  • #----------------------摄像头接口的时钟---------------------------
  • #72M
  • create_clock -period 13.888 -name cam_pclk [get_ports cam_pclk_0]
  • set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_0_IBUF]
  • create_clock -period 13.888 -name cam_pclk [get_ports cam_pclk_1]
  • set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_1_IBUF]
  • #----------------------LCD接口---------------------------
  • set_property -dict {PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[0]}]
  • set_property -dict {PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[1]}]
  • set_property -dict {PACKAGE_PIN W20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[2]}]
  • set_property -dict {PACKAGE_PIN v20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[3]}]
  • set_property -dict {PACKAGE_PIN U14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[4]}]
  • set_property -dict {PACKAGE_PIN U15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[5]}]
  • set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[6]}]
  • set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[7]}]
  • set_property -dict {PACKAGE_PIN W14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[8]}]
  • set_property -dict {PACKAGE_PIN Y14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[9]}]
  • set_property -dict {PACKAGE_PIN N15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[10]}]
  • set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[11]}]
  • set_property -dict {PACKAGE_PIN V16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[12]}]
  • set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[13]}]
  • set_property -dict {PACKAGE_PIN W18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[14]}]
  • set_property -dict {PACKAGE_PIN W19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[15]}]
  • set_property -dict {PACKAGE_PIN T10 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[16]}]
  • set_property -dict {PACKAGE_PIN T11 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[17]}]
  • set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[18]}]
  • set_property -dict {PACKAGE_PIN R14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[19]}]
  • set_property -dict {PACKAGE_PIN V13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[20]}]
  • set_property -dict {PACKAGE_PIN U13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[21]}]
  • set_property -dict {PACKAGE_PIN G15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[22]}]
  • set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[23]}]
  • set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [get_ports lcd_hs]
  • set_property -dict {PACKAGE_PIN P20 IOSTANDARD LVCMOS33} [get_ports lcd_vs]
  • set_property -dict {PACKAGE_PIN N20 IOSTANDARD LVCMOS33} [get_ports lcd_de]
  • set_property -dict {PACKAGE_PIN Y16 IOSTANDARD LVCMOS33} [get_ports lcd_bl]
  • set_property -dict {PACKAGE_PIN T16 IOSTANDARD LVCMOS33} [get_ports lcd_clk]
  • #----------------------摄像头接口1---------------------------
  • set_property -dict {PACKAGE_PIN V12 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_0]
  • set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_0]
  • set_property -dict {PACKAGE_PIN W13 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[0]}]
  • set_property -dict {PACKAGE_PIN T12 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[1]}]
  • set_property -dict {PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[2]}]
  • set_property -dict {PACKAGE_PIN V15 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[3]}]
  • set_property -dict {PACKAGE_PIN W15 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[4]}]
  • set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[5]}]
  • set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[6]}]
  • set_property -dict {PACKAGE_PIN T17 IOSTANDARD LVCMOS33} [get_ports {cam_data_0[7]}]
  • set_property -dict {PACKAGE_PIN T14 IOSTANDARD LVCMOS33} [get_ports cam_href_0]
  • set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports cam_pclk_0]
  • set_property -dict {PACKAGE_PIN M18 IOSTANDARD LVCMOS33} [get_ports cam_vsync_0]
  • #cam_scl:
  • set_property -dict {PACKAGE_PIN M17 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[0]}]
  • #cam_sda:
  • set_property -dict {PACKAGE_PIN T15 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[1]}]
  • #pull up
  • set_property PULLUP true [get_ports {emio_sccb_tri_io[0]}]
  • set_property PULLUP true [get_ports {emio_sccb_tri_io[1]}]
  • #----------------------摄像头接口2---------------------------
  • set_property -dict {PACKAGE_PIN U19 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_1]
  • set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_1]
  • set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[0]}]
  • set_property -dict {PACKAGE_PIN B19 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[1]}]
  • set_property -dict {PACKAGE_PIN B20 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[2]}]
  • set_property -dict {PACKAGE_PIN C20 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[3]}]
  • set_property -dict {PACKAGE_PIN H20 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[4]}]
  • set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[5]}]
  • set_property -dict {PACKAGE_PIN R16 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[6]}]
  • set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33} [get_ports {cam_data_1[7]}]
  • set_property -dict {PACKAGE_PIN K18 IOSTANDARD LVCMOS33} [get_ports cam_href_1]
  • set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} [get_ports cam_pclk_1]
  • set_property -dict {PACKAGE_PIN R19 IOSTANDARD LVCMOS33} [get_ports cam_vsync_1]
  • #cam_scl:
  • set_property -dict {PACKAGE_PIN T19 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[2]}]
  • #cam_sda:
  • set_property -dict {PACKAGE_PIN J14 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[3]}]
  • #pull up
  • set_property PULLUP true [get_ports {emio_sccb_tri_io[2]}]
  • set_property PULLUP true [get_ports {emio_sccb_tri_io[3]}]

管脚分配完成后按快捷键Ctrl+S保存管脚约束。
最后在左侧Flow Navigator导航栏中找到PROGRAM AND debug,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > Launch SDK,启动SDK软件。
31.4软件设计
在 软 件 设 计 部 分 中 ,和“OV5640摄像头LCD显示”实 验 相 比, 源 文 件 下 新 增 了osd文件,实现了对OSD IP核配置的功能。需要说明的是,osd文件夹下的osd.c和osd.h是从Xilinx官方提供的xapp742应用文档下的源码xapp742.zip移植而来,本次实验对这些函数做了部分的修改。除此之外,由于本次实验需要分别对两个摄像头进行配置,因此对其它文件中的源码也进行了修改。
main文件的代码如下所示:

  • 1 #include <stdio.h>
  • 2 #include <stdlib.h>
  • 3 #include <string.h>
  • 4 #include "xil_types.h"
  • 5 #include "xil_cache.h"
  • 6 #include "xparameters.h"
  • 7 #include "xgpio.h"
  • 8 #include "xaxivdma.h"
  • 9 #include "xaxivdma_i.h"
  • 10 #include "display_ctrl/display_ctrl.h"
  • 11 #include "vdma_api/vdma_api.h"
  • 12 #include "emio_sccb_cfg/emio_sccb_cfg.h"
  • 13 #include "ov5640/ov5640_init.h"
  • 14 #include "osd/osd.h"
  • 15
  • 16 //宏定义
  • 17 #define FRAME_BUFFER_NUM 3 //帧缓存个数3
  • 18 #define DYNCLK_BASEADDR XPAR_AXI_DYNCLK_0_BASEADDR //动态时钟基地址
  • 19 #define VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID //VDMA器件ID
  • 20 #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID
  • 21 //PL端 AXI GPIO 0(lcd_id)器件 ID
  • 22 #define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID
  • 23 //使用AXI GPIO(lcd_id)通道1
  • 24 #define AXI_GPIO_0_CHANEL 1
  • 25
  • 26 //全局变量
  • 27 //frame buffer的起始地址
  • 28 unsigned int frame_buffer_addr[DISPLAY_VDMA_NUM];
  • 29 unsigned int vdma_id[DISPLAY_VDMA_NUM] = {XPAR_AXIVDMA_0_DEVICE_ID,
  • 30 XPAR_AXIVDMA_1_DEVICE_ID};
  • 31
  • 32 XAxiVdma vdma[DISPLAY_VDMA_NUM];
  • 33 DisplayCtrl dispCtrl;
  • 34 XGpio axi_gpio_inst; //PL端 AXI GPIO 驱动实例
  • 35 VideoMode vd_mode;
  • 36 unsigned int lcd_id;
  • 37
  • 38 int main(void)
  • 39 {
  • 40 u32 status0,status1;
  • 41 u16 cmos_h_pixel; //ov5640 DVP 输出水平像素点数
  • 42 u16 cmos_v_pixel; //ov5640 DVP 输出垂直像素点数
  • 43 u16 total_h_pixel; //ov5640 水平总像素大小
  • 44 u16 total_v_pixel; //ov5640 垂直总像素大小
  • 45
  • 46 //获取LCD的ID
  • 47 XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_0_ID);
  • 48 lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
  • 49 xil_printf("lcd_id = %x\r\n",lcd_id);
  • 50
  • 51 //根据获取的LCD的ID号来进行ov5640显示分辨率参数的选择
  • 52 switch(lcd_id){
  • 53 case 0x4342 :
  • 54 cmos_h_pixel = 480;
  • 55 cmos_v_pixel = 272;
  • 56 total_h_pixel = 1800;
  • 57 total_v_pixel = 1000;
  • 58 break;
  • 59 case 0x4384:
  • 60 cmos_h_pixel = 800;
  • 61 cmos_v_pixel = 480;
  • 62 total_h_pixel = 1800;
  • 63 total_v_pixel = 1000;
  • 64 break;
  • 65 case 0x7084:
  • 66 cmos_h_pixel = 800;
  • 67 cmos_v_pixel = 480;
  • 68 total_h_pixel = 1800;
  • 69 total_v_pixel = 1000;
  • 70 break;
  • 71 case 0x7016 :
  • 72 cmos_h_pixel = 1024;
  • 73 cmos_v_pixel = 600;
  • 74 total_h_pixel = 2200;
  • 75 total_v_pixel = 1000;
  • 76 break;
  • 77 case 0x1018 :
  • 78 cmos_h_pixel = 1280;
  • 79 cmos_v_pixel = 800;
  • 80 total_h_pixel = 2570;
  • 81 total_v_pixel = 980;
  • 82 break;
  • 83 default :
  • 84 cmos_h_pixel = 480;
  • 85 cmos_v_pixel = 272;
  • 86 total_h_pixel = 1800;
  • 87 total_v_pixel = 1000;
  • 88 break;
  • 89 }
  • 90
  • 91 emio_init(); //初始化EMIO
  • 92 status0 = ov5640_init(CMOS_CH0,cmos_h_pixel,//初始化ov5640 0
  • 93 cmos_v_pixel,
  • 94 total_h_pixel,
  • 95 total_v_pixel);
  • 96 status1 = ov5640_init(CMOS_CH1,cmos_h_pixel,//初始化ov5640 1
  • 97 cmos_v_pixel,
  • 98 total_h_pixel,
  • 99 total_v_pixel);
  • 100 if(status0 == 0 && status1 == 0)
  • 101 xil_printf("Dual OV5640 detected successful!\r\n");
  • 102 else
  • 103 xil_printf("Dual OV5640 detected faiLED!\r\n");
  • 104
  • 105 //根据获取的LCD的ID号来进行video参数的选择
  • 106 switch(lcd_id){
  • 107 case 0x4342 : vd_mode = VMODE_480x272; break;
  • 108 case 0x4384 : vd_mode = VMODE_800x480; break;
  • 109 case 0x7084 : vd_mode = VMODE_800x480; break;
  • 110 case 0x7016 : vd_mode = VMODE_1024x600; break;
  • 111 case 0x1018 : vd_mode = VMODE_1280x800; break;
  • 112 default : vd_mode = VMODE_800x480; break;
  • 113 }
  • 114
  • 115 for(u8 i=0;i<DISPLAY_VDMA_NUM;i++){
  • 116 frame_buffer_addr = XPAR_PS7_DDR_0_S_AXI_BASEADDR+0x1000000*i;
  • 117 //配置VDMA
  • 118 run_vdma_frame_buffer(&vdma, vdma_id, vd_mode.width/2, vd_mode.height,
  • 119 frame_buffer_addr,0,0,BOTH);
  • 120 }
  • 121 //OSD初始化
  • 122 osd_init(lcd_id,vd_mode.width,vd_mode.height);
  • 123 //初始化Display controller
  • 124 DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
  • 125 //设置VideoMode
  • 126 DisplaySetMode(&dispCtrl, &vd_mode);
  • 127 DisplayStart(&dispCtrl);
  • 128
  • 129 return 0;
  • 130 }

本次实验使用了两个VDMA,因此宏定义DISPLAY_VDMA_NUM的值等于2。在代码的第28行将帧缓存起始地址声明了数组的形式,共两个元素,分别表示VDMA 0和VDMA 1的帧缓存起始地址。
在main函数中,同样是先获取LCD屏的ID,然后根据LCD屏的ID来对摄像头进行初始化,并选择VIDEO的参数,初始化函数为ov5640_init()。本次实验对ov5640_init()函数进行了修改,为函数增加了一个输入参数,用于表示对哪个摄像头进行初始化,并将检测双目OV5640摄像头的结果值打印出来,如程序中第46行至第113行代码所示。
在程序的第115行至第120行代码中,首先为每个VDMA分配帧缓存起始地址,然后配置VDMA。需要注意的是,VDMA帧缓存的宽度进行了除以2的处理,这是因为两路摄像头的图像数据同时显示在LCD屏上,单路摄像头图像数据只显示一半,因此这里对VDMA帧缓存的宽度进行了除2的处理。
在程序的第122行代码中,通过osd_init()函数对OSD进行配置以及初始化。输入的参数分别为LCD ID,OSD输出分辨率的宽度和高度,这里的LCD ID主要用于调节叠加字符的大小。
在程序的第124行至第127行代码中,通过DisplayInitialize()函数对显示相关的IP核进行初始化,包括VTC和动态时钟配置;DisplaySetMode函数设置VTC输出的分辨率;最后通过DisplayStart()函数启动VTC开始工作。
OSD的配置和初始化是本次实验的重点,由osd_init()函数实现,源代码如下:

  • 177 //OSD初始化
  • 178 void osd_init(u16 lcd_id,int screen_width,int screen_height)
  • 179 {
  • 180 u8 text_size = 1; //字符文本放大比例,范围1、2、4、8
  • 181 OsdCfgPtr = XOSD_LookupConfig(OSD_DEVICE_ID);
  • 182 XOSD_CfgInitialize(&Osd, OsdCfgPtr, OsdCfgPtr->BaseAddress); //初始化OSD IP核
  • 183 XOSD_RegUpdateEnable(&Osd); //更新OSD寄存器配置使能
  • 184 XOSD_Enable(&Osd); //使能OSD IP核
  • 185 osd_layer_cfg(screen_width,screen_height); //OSD图层配置
  • 186
  • 187 if(lcd_id == 0x4342)
  • 188 text_size = 2; //4.3'屏,放大2倍
  • 189 else
  • 190 text_size = 4; //其它屏,放大4倍
  • 191 //参数:XOSD实例,图层索引,X方向起始位置,Y方向起始位置,颜色索引,文本索引,文本大小
  • 192 //图层2显示文本设置
  • 193 osd_draw_text(Osd,2,screen_width/10,10,11,0,text_size);
  • 194 //图层3显示文本设置
  • 195 osd_draw_text(Osd,3,screen_width/2+screen_width/10,10,11,1,text_size);
  • 196 }
复制代码

OSD初始化函数实现了对OSD的配置并使能OSD。在程序的第181行至第184行代码,为Xilinx官方提供的OSD API函数,实现了对OSD的配置初始化、更新OSD寄存器配置使能和使能OSD。在程序的第185行代码中,osd_layer_cfg()函数是我们自己编写的函数,用于对OSD的图层进行配置,包括配置OSD输出分辨率、各个图层的配置以及内部图形控制器(字体和颜色)配置等。在程序的第193行至第195行代码中,通过osd_draw_text()函数分别在图层2和图层3中显示字符串。
OSD图层的配置函数代码如下:

  • 198 //OSD图层配置
  • 199 void osd_layer_cfg(int screen_width,int screen_height)
  • 200 {
  • 201 //配置OSD输出分辨率
  • 202 XOSD_SetScreenSize(&Osd, screen_width, screen_height);
  • 203 xil_printf("OSD output resolution is %d,%d\r\n",screen_width,screen_height);
  • 204 //配置OSD背景色 颜色参数:RED BLUE GREEN
  • 205 XOSD_SetBackgroundColor(&Osd,0x00,0xff,0x00);
  • 206 //配置图层0:外部视频
  • 207 OSD_LCfg = OSD_Layer0;
  • 208 XOSD_SetLayerAlpha(&Osd,OSD_LCfg.index,OSD_LCfg.alpha_enable,OSD_LCfg.alpha_value);
  • 209 XOSD_SetLayerPriority(&Osd,OSD_LCfg.index,OSD_LCfg.priority);
  • 210 XOSD_SetLayerDimension(&Osd,OSD_LCfg.index,0,0,screen_width/2,screen_height);
  • 211 XOSD_EnableLayer(&Osd,OSD_LCfg.index);
  • 212 //配置图层1:外部视频
  • 213 OSD_LCfg = OSD_Layer1;
  • 214 XOSD_SetLayerAlpha(&Osd,OSD_LCfg.index,OSD_LCfg.alpha_enable,OSD_LCfg.alpha_value);
  • 215 XOSD_SetLayerPriority(&Osd,OSD_LCfg.index,OSD_LCfg.priority);
  • 216 XOSD_SetLayerDimension(&Osd,OSD_LCfg.index,screen_width/2,
  • 217 0,screen_width/2,screen_height);
  • 218 XOSD_EnableLayer(&Osd,OSD_LCfg.index);
  • 219 //配置图层2:内部字符显示
  • 220 OSD_LCfg = OSD_Layer2;
  • 221 XOSD_SetLayerAlpha(&Osd,OSD_LCfg.index,OSD_LCfg.alpha_enable,OSD_LCfg.alpha_value);
  • 222 XOSD_SetLayerPriority(&Osd,OSD_LCfg.index,OSD_LCfg.priority);
  • 223 XOSD_SetLayerDimension(&Osd,OSD_LCfg.index,0,0,screen_width,screen_height);
  • 224 XOSD_EnableLayer(&Osd,OSD_LCfg.index);
  • 225 //向OSD图像控制器BANK 0中加载颜色表
  • 226 XOSD_LoadColorLUTBank(&Osd,OSD_LCfg.index, 0, color_arr);
  • 227 //向OSD图像控制器BANK 0中加载字符(字体)
  • 228 XOSD_LoadCharacterSetBank(&Osd,OSD_LCfg.index, 0, (u32 *)Font);
  • 229 //向OSD图像控制器BANK 0中加载文本
  • 230 XOSD_LoadTextBank(&Osd,OSD_LCfg.index, 0, (u32 *)TextData);
  • 231 //选择OSD图像控制器的有效BANK(BANK 0)
  • 232 XOSD_SetActiveBank(&Osd,OSD_LCfg.index, 0, 0, 0, 0);
  • 233
  • 234 //配置图层3:内部字符显示
  • 235 OSD_LCfg = OSD_Layer3;
  • 236 XOSD_SetLayerAlpha(&Osd,OSD_LCfg.index,OSD_LCfg.alpha_enable,OSD_LCfg.alpha_value);
  • 237 XOSD_SetLayerPriority(&Osd,OSD_LCfg.index,OSD_LCfg.priority);
  • 238 XOSD_SetLayerDimension(&Osd,OSD_LCfg.index,0,0,screen_width,screen_height);
  • 239 XOSD_EnableLayer(&Osd,OSD_LCfg.index);
  • 240 //向OSD图像控制器BANK 0中加载颜色表
  • 241 XOSD_LoadColorLUTBank(&Osd,OSD_LCfg.index, 0, color_arr);
  • 242 //向OSD图像控制器BANK 0中加载字符(字体)
  • 243 XOSD_LoadCharacterSetBank(&Osd,OSD_LCfg.index, 0, (u32 *)Font);
  • 244 //向OSD图像控制器BANK 0中加载文本
  • 245 XOSD_LoadTextBank(&Osd,OSD_LCfg.index, 0, (u32 *)TextData);
  • 246 //选择OSD图像控制器的有效BANK(BANK 0)
  • 247 XOSD_SetActiveBank(&Osd,OSD_LCfg.index, 0, 0, 0, 0);
  • 248
  • 249 xil_printf("OSD config done!\r\n" );
  • 250 }

OSD图层配置函数的输入参数为LCD屏的显示分辨率,根据输入的参数来配置OSD的分辨率,如程序中第202行代码所示,由XOSD_SetScreenSize()函数实现;而配置OSD背景颜色由XOSD_SetBackgroundColor()函数实现。
程序中第206行至第211行代码根据图层0的配置参数对图层0进行配置。OSD_LCfg是用于图层配置参数的结构体变量,其结构体的定义如下:



图 31.4.1 OSD_LayerCfg结构体

结构体中各成员分别表示图层索引、图层透明度、图像优先级和透明度使能。OSD_Layer0是OSD_LCfg结构体变量中图层0各成员的配置初始值,定义如下:



图 31.4.2 OSD图层0配置参数

这里需要说明的是,在程序的第210行代码中,XOSD_SetLayerDimension()函数用于配置图层0的显示位置和尺寸大小。配置图层0显示的水平方向位置和垂直方向位置都是0,而水平方向宽度和垂直方向宽度分别为screen_width/2和screen_height。因此,图层0的显示位置位于LCD屏左侧的位置,尺寸大小占LCD屏分辨率的一半。
其它图层的配置与图层0类似,这里不再赘述。需要注意的是,由于图层2和图层3的类型为内部图形控制器,因此需要对这两个图层额外配置字体颜色、显示的字符串和字体。图层2的内部图形控制器配置函数如代码中第225行至第232行代码所示。
字体颜色表、显示的字符串和字体的定义如下:

  • 15 //Color table definition
  • 16 u32 color_arr[16] = {
  • 17 0x00000000, 0xa0a25f58, 0xa08080ff, 0xa0808010,
  • 18 0xa0ef5a51, 0x00000000, 0xa0465289, 0x00000000,
  • 19 0xa065ba6b, 0x00000000, 0xa09017c5, 0xa0a9c860,
  • 20 0xa0bc3198, 0xa010a5a9, 0xa0808080, 0xa0ada1ab
  • 21 };
  • 22
  • 23 //Text table definition
  • 24 char __attribute__ ((aligned (4))) TextData[8][32] = {
  • 25 "OV5640 1", //"String #1",
  • 26 "OV5640 2", //"String #2",
  • 27 "Hello",
  • 28 "Xilinx",
  • 29 "String #5",
  • 30 "String #6",
  • 31 "String #7",
  • 32 "String #8"
  • 33 };
  • 34
  • 35 //Font definition
  • 36 unsigned char __attribute__ ((aligned (4))) Font[128][8] = {
  • 37 {0x00, 0x36, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00}, // NULL
  • 38 {0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18},
  • 代码较长,省略部分源代码……
  • 168 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 127 DEL
  • 169 };

这些字体颜色表、显示的字符串和字体由官方提供的xapp742应用文档下的源码移植而来。其中数组颜色表color_arr的每一位元素由32位组成,其中高8位表示了颜色的透明度,低24位为RGB888颜色格式。颜色表中前一个元素用于表示字体颜色的前景色,后一个元素表示颜色表的背景色。
TextData数组存储了需要叠加显示的字符,两路摄像头显示的字符串分别为“OV5640 1”和“OV5640 2”。如果想要修改LCD屏显示的字符,可以直接修改TextData数组元素的字符串。
Font数组存储了字符显示的字体,每一行表示一个字符。字符和像素的对应关系如图 31.4.3所示。



图 31.4.3 字符'A'

每一个字符所占的像素为8*8,通过为每一个像素赋值前景色和背景色,即可显示出字符。TextData数组中的元素是字符串,字符串中的每一个字符对应一个ASCII码,ASCII码对应的十进制为0~127,分别索引Font数组的第一行至第128行。
osd_draw_text()函数用于设置图层显示的字符值、字符位置和大小,函数源代码如下:

  • 247 void osd_draw_text(XOSD Osd,int layer_index,int x_start, int y_start,
  • 248 int color_index, int text_index, int text_size)
  • 249 {
  • 250 u32 instruction[XOSD_INS_SIZE];
  • 251 u16 ins_opcode_type = XOSD_INS_OPCODE_TXT;
  • 252 u8 txt_size = (text_size<<4); //高4位表示文本放大的倍数
  • 253
  • 254 //创建OSD指令
  • 255 XOSD_CreateInstruction(&Osd, instruction, layer_index,ins_opcode_type,txt_size,
  • 256 x_start, y_start, x_start, y_start,text_index, color_index);
  • 257 //加载指令
  • 258 XOSD_LoadInstructionList(&Osd, layer_index, 0, instruction, 1);
  • 259 xil_printf("OSD Layer %d draw text done!\r\n",layer_index);
  • 260 }

osd_draw_text()函数输入的参数分别为XOSD实例、图层索引、X方向起始位置、Y方向起始位置、颜色索引、文本索引和文本大小。该函数通过通过创建一个OSD指令和加载指令,对该图层显示的字符进行配置。
31.5下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将USB UART接口与电脑连接,用于串口通信。使用FPC排线一端与RGB LCD液晶屏上的接口连接,另一端连接领航者底板上的RGB TFT-LCD接口。双目OV5640摄像头连接底板上的扩展口连接(扩展口位于ATK_MODULE旁边,连接时双目摄像头镜头方向朝外),如所示。最后连接开发板的电源,并打开电源开关



图 31.5.1 双目摄像头镜头方向朝外

在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的BIT文件,来对PL进行配置。最后下载软件程序,下载完成后,在下方的SDK Terminal中可以看到应用程序打印的信息,打印的信息如下图所示:



图 31.5.2 串口终端中打印的信息

从串口打印的信息可以看出硬件连接的状态和OSD配置的状态。此时LCD屏实时显示两路摄像头的视频图像,并且在每一路摄像头的上方叠加了字符串,分别为“OV5640 1”和“OV5640 2”,LCD屏显示画面如下图所示:



图 31.5.3 LCD屏显示画面


1
分享淘帖 显示全部楼层

评论

高级模式
您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发资料
关闭

站长推荐 上一条 /7 下一条

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