《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​
  95kVyaJuybju 2023年11月12日 17 0

OV5640摄像头LCD显示​

OV5640是OmniVision(豪威科技)公司生产的一颗CMOS图像传感器,该传感器功耗低、分辨率高以及采集速率快,主要应用在玩具、手机、电脑多媒体等领域本章我们将使用MPSOC开发板实现对OV5640的数字图像采集并通过LCD实时显示。

本章包括以下几个部分:

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


简介

OV5640是一款1/4英寸单芯片图像传感器,感光阵列达到2592*1944(即500W像素),能实现最快15fps QSXVGA(2592*1944)或者90fps VGA(640*480)分辨率图像采集传感器采用OmniVision推出OmniBSI(背面照度)技术,使传感器达到更高的性能,如高灵敏度扰和低噪声。传感器内部集成了图像处理功能,包括自动曝光控制(AEC)、自动白平衡(AWB等。同时该传感器支持LED补光MIPI(移动产业处理器接口)输出接口和DVP(数字视频并行)输出接口选择、ISP(图像信号处理)以及AFC(自动聚焦控制)等功能

OV5640功能框图下图所示

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据


27.1.1 OV5640功能框图

上图可知,时序发生器(timing控制着感光阵列(image array)、放大器(AMP)、AD转换以及输出外部时序信号(VSYNC、HREF和PCLK,外部时钟XVCLK经过PLL锁相环输出的时钟作为系统的控制时钟;感光阵列光信号转化模拟信号,经过增益放大器之后进入10位AD转换器;AD转换器将模拟信号转化成数字信号并且经过ISP进行相关图像处理,最终输出所配置格式的10位视频数据流。增益放大器控制以及ISP等都可以通过寄存器(registers)来配置配置寄存器的接口就是SCCB接口接口协议兼容IIC协议。

SCCB(Serial Camera Control Bus串行摄像头控制总线)是由OV(OmniVision简称)公司定义和发展的三线式串行总线,该总线控制着摄像头大部分的功能包括图像数据格式、分辨率以及图像处理参数等。OV公司为了减少传感器引脚的封装,现在SCCB总线大多采用两线接口总线

OV5640使用的是两线接口总线,该接口总线包括SIO_C串行时钟输入线和SIO_D串行双向数据线,分别相当于IIC协议SCL信号线和SDA信号线。我们在面提到过SCCB协议兼容IIC协议因为SCCB协议IIC协议非常相似,有关IIC协议的详细介绍请大家参考“EEPROM读写实验”章节。

SCCB的写传输协议如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_02


27.1.2 SCCB写传输协议

上图中的ID ADDRESS是7位器件地址和1位读写控制位成(0:);Sub-address为8位寄存器地址,一般有些寄存器是可改写的,有些是只读的只有可改写的寄存器才能正确写入;Write Data为8位据,每一个寄存器地址对应8位的配置数据。上图中的第9位X表示Don’t Care(不必关心位),该位是由从机(此处指摄像头)发出应答信号来响应主机表示当前ID Address、Sub-address和Write Data是否传输完成,但是从机有可能不发出应答信号,因此主机(此处FPGA不用判断此处是否有应答,直接默认当前传输完成即可。

我们可以发现,SCCB和IIC写传输协议是极为相似的,只是在SCCB写传输协议中,第9位不必关心位,而IIC写传输协议应答位。SCCB的传输协议和IIC有些差异,在IIC读传输协议中,写完寄存器地址后会有restart即重复开始的操作;而SCCB读传输协议中没有重复开始的概念,在写完寄存器地址后,发起总线停止信号,下图SCCB的读传输协议

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_寄存器_03


27.1.3 SCCB读传输协议

由上图可知,SCCB读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据。上图中的NA位由主机(这里FPGA)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。

需要注意的是,对于OV5640摄像头来说,由于其可配置的寄存器非常多,所以OV5640摄像头的寄存器地址位数是16位(两个字节),OV5640 SCCB写传输协议下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_04

27.1.4 OV5640 SCCB写传输协议

上图中的ID ADDRESS是7位器件地址和1位读写控制位成(0:),OV5640器件地址为7’h3c,所以在写传输协议中,ID Address(W)= 8’h78(器件地址左移1位,低位补0);Sub-address(H)为高8位寄存器地址,Sub-address(L)为低8位寄存器地址,在OV5640众多寄存器中,有些寄存器是可改写的,有些是只读的只有可改写的寄存器才能正确写入;Write Data为8位据,每一个寄存器地址对应8位的配置数据。

在OV5640正常工作前,必须先对传感器进行初始化,通过配置寄存器使其工作在预期的工作模式,以及得到较好画质的图像。因为SCCB的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。OV公司提供了OV5640的软件应用手册OV5640 Software Application Note位于开发板随附的资料7_硬件资料/4_OV5640资料/OV5640_camera_module_software_application_notes.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。

27.1.1 OV5640关键寄存器配置说明

地址

(HEX)

寄存器

默认值

(HEX

详细说明

0x3008

SYSTEM CTROL0

0x02

Bit[7]:软件复位

Bit[6]:软件电源休眠

0x3016

PAD OUTPUT ENABLE 00

0x00

Bit[1]闪光灯输出使能

0x3017

PAD OUTPUT ENABLE 01

0x00

输入/输出控制(0:输入 1:输出)

Bit[7]:FREX输出使能

Bit[6]:VSYNC输出使能

Bit[5]:HREF输出使能

Bit[4]:PCLK输出使能

Bit[3:0]:D[9:6]输出使能

0x3018

PAD OUTPUT ENABLE 02

0x00

输入/输出控制(0:输入 1:输出)

Bit[7:2]:D[5:0]输出使能

Bit[1]:GPIO1输出使能

Bit[0]:GPIO0输出使能

0x3019

PAD OUTPUT VALUE 00

0x00

Bit[1]: 闪光灯操作

0:关闭闪光灯

1打开闪光灯

0x301C

PAD SELECT 00

0x00

Bit[1]:闪光灯IO选择

0x3035

SC PLL CONTRL1

0x11

Bit[7:4]:系统时钟分频,用于降低所有的时钟频率

Bit[3:0]:MIPI分频

0x3036

SC PLL CONTRL2

0x69

Bit[7:0]:PLL倍频器(4~252

在4~127范围支持任意整数分频

在128~252范围内仅支持偶数分频

0x3808

TIMING DVPHO

0x0A

Bit[3:0]:DVP 输出水平像素点数高4位

0x3809

TIMING DVPHO

0x20

Bit[7:0]:DVP 输出水平像素点数低8位

0x380A

TIMING DVPVO

0x07

Bit[2:0]:DVP输出垂直像素点数高3位

0x380B

TIMING DVPVO

0x98

Bit[7:0]:输出垂直像素点数低8位

0x4300

FORMAT CONTROL

0xF8

Bit[7:4]:数据输出格式

0:RAW

1:Y8

2:YUV444/RGB888

3YUV422

4:YUV420

5YUV420(仅在MIPI输出接口有效

6:RGB565

Bit[3:0]:输出顺序

0{b[4:0],g[5:3]},{g[2:0],r[4:0]}

1{r[4:0],g[5:3]},{g[2:0],b[4:0]}

2{g[4:0],r[5:3]},{r[2:0],b[4:0]}

3{b[4:0],r[5:3]},{r[2:0],g[4:0]}

4{g[4:0],b[5:3]},{b[2:0],r[4:0]}

5{r[4:0],b[5:3]},{b[2:0],g[4:0]}

6~14:不允许

15{g[2:0],b[4:0]},{r[4:0],g[5:3]}

7:RGB555格式1

8:RGB555格式2

9:RGB444格式1

10:RGB444格式2

11~14:不允许

15:Bypass formatter module

OV5640寄存器较多,对于其它寄存器的描述可以参考OV5640的数据手册。需要注意的是,OV5640的数据手册并没有提供全部的寄存器描述,而大多数必要的寄存器配置在ov5640的软件应用手册可以找到,可以结合这两个手册学习如何对OV5640进行配置

输出图像参数设置

接下来,我们介绍一下OV5640的ISP输入窗口设置、预缩放窗口设置和输出大小窗口设置,这几个设置与我们的正常使用密切相关,有必要了解一下,它们的设置关系如下图所示

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_05


27.1.5 图像窗口设置

ISP输入窗口设置(ISP Input Size)允许用户设置整个传感器显示区域(physical pixel size,2632*1951其中2592*1944像素是有效的),开窗范围从0*0~2632*1951可以任意设置。也就是上图中的X_ADDR_ST(寄存器地址0x38000x3801)、Y_ADDR_ST(寄存器地址0x38020x3803)、X_ADDR_END(寄存器地址0x38040x3805)和Y_ADDR_END(寄存器地址0x38060x3807)寄存器窗口设置范围中的像素数据将ISP进行图像处理

预缩放窗口设置(pre-scaling size允许用户在ISP输入窗口的基础上进行裁剪,用于设置将进行缩放的窗口大小,该设置仅在ISP输入窗口内进行X/Y方向偏移。可以通过X_OFFSET(寄存器地址0x38100x3811)和Y_OFFSET(寄存器地址0x38120x3813)进行配置。

输出大小窗口设置(data是在预缩放窗口基础上,经过内部DSP进行缩放处理将处理后的数据输出给外部的图像窗口,图像窗口控制着最终的图像输出尺寸。可以通过X_OUTPUT_SIZE(寄存器地址0x38080x3809)和Y_OUTPUT_SIZE(寄存器地址0x380A0x380B)进行配置。注意:当输出大小窗口与预缩放窗口比例不一致时,图像将进行缩放处理图像变形)仅当两者比例一致时,输出比例才是1:1(正常图像)。

27.1.5,右侧data output size区域,才是OV5640输出给外部的图像尺寸,也就是显示在显示器或者液晶屏上面的图像大小。输出大小窗口与预缩放窗口比例不一致时,会进行缩放处理,在显示器上面看到的图像将会变形。

输出像素格式

OV5640支持多种不同的数据像素格式,包括YUV(亮度参量和色度参量分开表示的像素格式)、RGB(其中RGB格式包含RGB565、RGB555等)以及RAW(原始图像数据),通过寄存器地址0x4300配置不同的数据像素格式

由于数据像素格式常用RGB565,我们这里也将ov5640配置为RGB565格式。由上(表27.1.1可知寄存器0x4300寄存器Bit[7:4]设置0x6即可。OV5640支持调节RGB565输出格式中各颜色变量的顺序,对于我们常见的应用来说,一般是使用RGB或BGR序列。其中RGB序列最为常用,因此将寄存器0x4300寄存器Bit[3:0]设置0x1

由于摄像头采集的图像最终要通过RGB LCD接口显示LCD液晶屏上,MPSOC开发板上的LCD接口RGB888格式详情请参考“LCD彩条显示实验”章节,因此我们将OV5640摄像头输出的图像像素数据配置成RGB565格式,然后通过颜色分量低位补零的方式将RGB565格式转换为RGB888格式。下图为摄像头输出的时序图

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据_06


27.1.6 OV5640输出时序图

介绍时序图之前先了解几个基本的概念。

VSYNC:场同步信号,由摄像头输出,用于标志一帧数据的开始结束。上图中VSYNC的高电平作为一帧的同步信号,低电平输出的数据有效。需要注意同步信号可以通过设置寄存器0x4740 Bit[0]位进行取反的,即低电平同步高电平有效,本次实验使用的是和上一致默认设置

HREF/HSYNC:行同步信号,摄像头输出,用于标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的,只是数据的同步方式不一样本次实验使用的是HREF格式输出,当HREF为高电平时,图像输出有效可以通过寄存器0x4740 Bit[1]进行配置。本次实验使用的是HREF格式输出

D[9:0]:数据信号摄像头输出,在RGB格式输出中,只有高8位D[9:2]是有效的

tPCLK:一个像素时钟周期

下图OV5640输出RGB565格式时序图

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_07


27.1.7 RGB565模式时序图

上图中的PCLK为OV5640输出的像素时钟,HREF为行同步信号,D[9:2]为8位像素数据。OV5640最大可以输出10位数据,在RGB565输出模式中,只有8位是有效的像素数据在HREF为高电平时有效,第一次输出的数据为RGB565数据的高8位,第二次输出的数据为RGB565数据的低8位first byte和second byte组成一个16位RGB565数据。由上图可知,数据是在像素时钟的下降沿改变的,为了在数据最稳定的时刻采集图像数据,所以我们需要像素时钟的上升沿采集数据

实验任务

本节实验任务是使用MPSOC开发板及双目OV5640摄像头(实际只用到了其中一路)实现图像采集,并通过RGB实时显示。

硬件设计

我们的MPSOC开发板上一个扩展接口(J19),该接口可以用来连接一些扩展模块,如双目OV5640摄像头、高速ADDA模块、IO扩展板模块等。本次实验就是通过连接双目OV5640摄像头,实现单个OV5640摄像头图像的采集和显示。扩展口原理图如27.3.1所示

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_08


27.3.1 扩展接口原理图

ATK-Dual-OV5640是正点原子推出的一款高性能双目OV5640 500W像素高清摄像头模块。该模块通过2*20排母(2.54mm间距)同外部连接,我们将摄像头的排母直接插在开发板上的扩展接口即可,模块外观如27.3.2所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_寄存器_09


27.3.2 ATK-OV5640摄像头模块实物图

我们在前面说过,OV5640在RGB565模式中只有高8数据有效的即D[9:2]我们的摄像头排母上数据引脚的个数是8位。实际上摄像头排母的8位数据连接的就是OV5640传感器D[9:2],所以我们直接使用摄像头排上的8位数据引脚即可

由于RGB LCD屏的引脚数目较多,且在前面相应的章节中已经给出它们的管脚列表,这里
只列出摄像头相关管脚分配,如下表所示:

27.3.1 OV5640摄像头管脚分配

信号名

方向

管脚

端口说明

IO电平

cam_pclk

input

C13

cmos 数据像素时钟

LVCMOS33

cam_vsync

input

G14

cmos 场同步信号

LVCMOS33

cam_href

input

G13

cmos 行同步信号

LVCMOS33

cam_rst_n

output

F13

cmos 复位信号

LVCMOS33

cam_pwdn

output

B15

cmos 电源休眠模式选择信号

LVCMOS33

cam_data[0]

input

E15

cmos 数据

LVCMOS33

cam_data[1]

input

D15

cmos 数据

LVCMOS33

cam_data[2]

input

E14

cmos 数据

LVCMOS33

cam_data[3]

input

D14

cmos 数据

LVCMOS33

cam_data[4]

input

E13

cmos 数据

LVCMOS33

cam_data[5]

input

B13

cmos 数据

LVCMOS33

cam_data[6]

input

C14

cmos 数据

LVCMOS33

cam_data[7]

input

A13

cmos 数据

LVCMOS33

emio_sccb_tri_io[0]

output

H13

cmos SCCB_SCL线

LVCMOS33

emio_sccb_tri_io[1]

inout

F15

cmos SCCB_SDA线

LVCMOS33

在“PS通过VDMA驱动LCD显示”实验中,我们使用了Xilinx提供的VDMA IP核,实现了RGB LCD液晶屏显示彩条的功能。在该实验中,数据源由PS通过向DDR4中写入彩条数据产生的,我们只使能了VDMA IP核的读通道,通过读通道来将AXI4 Memory Map格式的数据流转换成AXI4-Stream类的数据流。而对于本次实验来说,只需要在此基础上,将彩条数据替换成由OV5640摄像头输出的图像数据即可,因此对于本次实验的重点是将摄像头的图像数据写入VDMA IP核的帧缓存中,即DDR4内存中,即可实现摄像头的图像显示。

VDMA支持AXI4 Memory Map格式转换成AXI4-Stream格式,同时也支持AXI4-Stream格式转成AXI4 Memory Map格式,因此可以通过VDMA将图像数据写入帧缓存中,也就是使能VDMA的写通道,来将图像数据写入帧缓存中。需要注意的是,我们需要先将图像数据转换成AXI4-Stream的格式,才能通过VDMA的写通道写入帧缓存中,Xilinx提供了Video in to AXI4-Stream IP核,可以实现视频数据流转成AXI4-Stream流,因此本次实验可以通过添加Video in to AXI4-Stream IP核实现数据格式的转换。

Video in to AXI4-Stream IP核的输入端口为视频数据流,而OV5640摄像头输出的数据为行场同步信号控制的8位数据,这两个端口不可以直接连接,需要先经过数据的转换才能连接,因此本次实验通过添加了一个图像采集的IP核来实现这个数据的转换,本次实验的框图如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_10


27.3.3 系统架构框图

通过对比上图中的框图和“PS通过VDMA驱动LCD显示”实验的框图可知,我们只是在此基础上,添加了OV5640图像采集IP核和Video in to AXI4-Stream IP核,其它的IP核仍然和“PS通过VDMA驱动LCD显示”实验一致,只不过在VDMA IP核的配置上,需要使能VDMA的写通道。并且对于本次使用来说,需要对帧缓存进行频繁的写入和读出,为了避免读通道和写通道同时访问同一帧缓存,那么VMDA必须配置成动态同步锁相的模式,且帧缓存数量要大于等于3。由于分配过多的帧缓存区域对效率的提升已经微乎其微,且会占用更多的存储空间和消耗CPU的时间,因此本次实验将帧缓存空间设置为3,并采用动态同步锁相的模式。

由于OV5640摄像头需要经过初始化之后,才能正常工作。本次实验通过SCCB接口对OV5640进行配置,SCCB端口是通过EMIO连接至PS中,SCCB的驱动由PS实现。

最后我们再来看下27.3.3中数据流的走向。OV5640图像采集是我们自定义的IP核,负责将OV5640的数据转成视频流数据;视频流数据经过Video in to AXI4-Stream IP核转换成AXI4-Stream IP格式数据流,然后通过VDMA的写通道转成AXI4 Memory Map格式,并最终写入DDR内存中。VDMA通过AXIAXI_HP端口进行连接,从而高效访问DDR4,VDMADDR4中读取的视频或图像数据传输给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引脚引出至顶层模块端口上。

本次实验的硬件平台在“PS通过VDMA驱动LCD显示”实验基础上搭建。首先需要对Zynq UltraScale+ MPSOC模块进行修改,使能EMIO的两个引脚,用来连接摄像头的SCCB接口,配置如下:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_11


27.3.4 MPSOC处理系统EMIO配置

接下来引出这个端口,并命名为“emio_sccb”,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据_12


27.3.5 引出引脚

接下来修改VDMA IP核的配置,修改后的配置界面如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_13


27.3.6 VDMA IP核Basic配置页面

Frame Buffers(帧缓存)个数配置为3,使能写通道(Enable Write Channel),写通道的参数和读通道保持一致。注意这里的Stream Data Width(Stream数据宽度)是软件自动设置的,其位宽由连接至其它IP核的Stream数据位宽而定。

在“PS通过VDMA驱动LCD显示”实验中向大家详细介绍了VDMA的帧缓存机制和同步锁相模式。为了避免读通道和写通道同时访问同一帧缓存,本次实验将帧缓存空间设置为3,并采用动态同步锁相的模式。另外,读通道需要实时读取写通道写入的帧缓存,因此将写通道设置为Dynamic-Master,读通道设置为Dynamic-Slave

VDMA的同步锁相模式配置如下:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据_14


27.3.7 VDMA IP核同步锁相配置

接下来添加Video in to AXI4-Stream IP核,在IP核搜索框中输入“video in”,选择“Video in to AXI4-StreamIP核,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_寄存器_15


27.3.8 添加Video in to AXI4-Stream IP

Video in to AXI4-Stream IP核配置界面如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据_16


27.3.9 Video in to AXI4-Stream IP核配置

Clock Mode时钟模式用于指定AXI4-Stream输入和视频输出信号,使用公共时钟还是独立时钟进行时钟控制,此处我们使用独立时钟进行控制,即勾选Independent,其它选项保持默认即可。

接下来添加自定义的OV5640图像采集IP核,该IP核位于例程的ip_repo文件下,将该文件拷贝至工程目录下,并在工程中添加该IP核,添加的方法这里不再赘述。在IP核搜索框中输入“ov5640”,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_17


27.3.10 添加OV5640图像采集IP

OV5640图像采集IP核无需设置,该IP核实现了OV5640的数据流转换成视频数据流格式,即转换成符合Video in to AXI-Stream IP核输入端的数据格式。视频数据流格式的时序图如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_寄存器_18


27.3.11 视频流格式时序图

vblank_out(output vertical blank):垂直方向空白信号,为高电平时,表示当前处于垂直方向的无效数据区域;

vsync_out(output vertical synchronization): 垂直方向同步信号,用于帧同步;

hblank_out(output horizontal blank):水平方向空白信号,为高电平时,表示当前处于水平方向的无效数据区域;

hsync_out(output horizontal synchronization):水平方向同步信号,用于行同步;

active_video_out:图像有效信号,高电平有效。

27.3.11是视频流的数据格式,除了这些信号外,还需要输出时钟(clk)、时钟使能(ce)和图像数据,我们只需要将摄像头输出的数据按照视频流的格式输出,即可连接至Video in to AXI-Stream IP核的输入端。由于摄像头只输出了cam_vsynccam_href这两个用于控制数据是否有效的信号,为了简化设计,本次实验简化了视频数据流的接口,只输出了时钟信号、时钟有效信号、场同步信号、图像数据有效信号和图像数据。

OV5640图像采集IP核源代码如下:

1 module ov5640_capture_data(​
2 input rst_n , //复位信号​
3 ​
4 //摄像头接口​
5 input cam_pclk , //cam 数据像素时钟​
6 input cam_vsync , //cam 场同步信号​
7 input cam_href , //cam 行同步信号​
8 input [7:0] cam_data , //cam 数据​
9 output cam_rst_n , //cmos 复位信号,低电平有效​
10 output cam_pwdn , //电源休眠模式选择​
11 ​
12 //RGB888接口​
13 output cmos_frame_clk, //时钟信号​
14 output cmos_frame_ce, //时钟使能信号​
15 ​
16 output cmos_frame_vsync, //帧有效信号​
17 output cmos_active_video ,//数据有效​
18 output [23:0] cmos_frame_data //有效数据​
19 );​
20 ​
21 //parameter define​
22 ​
23 //寄存器全部配置完成后,先等待10帧数据​
24 //待寄存器配置生效后再开始采集图像​
25 localparam WAIT_FRAME = 4'd10 ; //寄存器数据稳定等待的帧个数​
26 ​
27 //reg define​
28 reg rst_n_d0 =1;​
29 reg rst_n_syn =1;​
30 reg cam_vsync_d0 ;​
31 reg cam_vsync_d1 ;​
32 reg cam_href_d0 ;​
33 reg cam_href_d1 ;​
34 reg [3:0] cmos_ps_cnt ; //等待帧数稳定计数器​
35 reg wait_done ;​
36 reg byte_flag ;​
37 reg [7:0] cam_data_d0 ;​
38 reg [15:0] cmos_data_16b ; //用于8位转16位的临时寄存器​
39 reg byte_flag_d0 ;​
40 ​
41 //wire define​
42 wire pos_vsync ;​
43 ​
44 //*****************************************************​
45 //** main code​
46 //*****************************************************​
47 ​
48 //采输入场同步信号的上升沿​
49 assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0 ;​
50 ​
51 //不对摄像头硬件复位,固定高电平​
52 assign cam_rst_n = 1'b1;​
53 ​
54 //电源休眠模式选择 0:正常模式 1:电源休眠模式​
55 assign cam_pwdn = 1'b0;​
56 ​
57 assign cmos_frame_clk = cam_pclk;​
58 //由于摄像头输入接口的两个时钟周期对应于RGB888输出接口的一个有效时钟周期​
59 //所以要给出数据有效标志即RGB888输出接口的时钟使能信号​
60 assign cmos_frame_ce = wait_done ? (byte_flag_d0 & cmos_active_video) || (!cmos_active_video) : 1'b0;​
61 assign cmos_frame_vsync = wait_done ? cam_vsync_d1 : 1'b0; //输出帧有效信号​
62 assign cmos_active_video = wait_done ? cam_href_d1 : 1'b0; //输出行有效信号​
63 assign cmos_frame_data = wait_done ?​
64 { cmos_data_16b[15:11],3'd0 , cmos_data_16b[10:5],2'd0 , cmos_data_16b[4:0],3'd0 }​
65 : 24'd0; //输出数据​
66 ​
67 //复位信号的异步复位、同步释放处理​
68 always @(posedge cam_pclk or negedge rst_n) begin​
69 if(!rst_n) begin​
70 rst_n_d0 <= 1'b0;​
71 rst_n_d0 <= 1'b0;​
72 end​
73 else begin​
74 rst_n_d0 <= 1'b1;​
75 rst_n_syn <= rst_n_d0;​
76 end​
77 end​
78 ​
79 //对行、场同步信号的延迟打拍​
80 always @(posedge cam_pclk or negedge rst_n_syn) begin​
81 if(!rst_n_syn) begin​
82 cam_vsync_d0 <= 1'b0;​
83 cam_vsync_d1 <= 1'b0;​
84 ​
85 cam_href_d0 <= 1'b0;​
86 cam_href_d1 <= 1'b0;​
87 end​
88 else begin​
89 cam_vsync_d0 <= cam_vsync;​
90 cam_vsync_d1 <= cam_vsync_d0;​
91 ​
92 cam_href_d0 <= cam_href;​
93 cam_href_d1 <= cam_href_d0;​
94 end​
95 end​
96 ​
97 //寄存器全部配置完成后,先等待10帧数据​
98 //待寄存器配置生效后再开始采集图像​
99 always @(posedge cam_pclk or negedge rst_n_syn) begin​
100 if(!rst_n_syn)​
101 cmos_ps_cnt <= 4'd0;​
102 else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))​
103 cmos_ps_cnt <= cmos_ps_cnt + 4'd1; //对帧数进行计数​
104 end​
105 ​
106 //等待完成后 给出 等待完成信号​
107 always @(posedge cam_pclk or negedge rst_n_syn) begin​
108 if(!rst_n_syn)​
109 wait_done <= 1'b0;​
110 else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)​
111 wait_done <= 1'b1;​
112 end​
113 ​
114 ​
115 //8位数据转16位RGB565数据​
116 always @(posedge cam_pclk or negedge rst_n_syn) begin​
117 if(!rst_n_syn) begin​
118 cmos_data_16b <= 16'd0;​
119 cam_data_d0 <= 8'd0;​
120 byte_flag <= 1'b0;​
121 end​
122 else if( cam_href ) begin //cam 行同步信号​
123 byte_flag <= ~byte_flag;​
124 cam_data_d0 <= cam_data;​
125 if(byte_flag)​
126 cmos_data_16b <= {cam_data_d0,cam_data};​
127 end​
128 else begin​
129 byte_flag <= 1'b0;​
130 cam_data_d0 <= 8'b0;​
131 end​
132 end​
133 ​
134 always @(posedge cam_pclk or negedge rst_n_syn) begin​
135 if(!rst_n_syn)​
136 byte_flag_d0 <= 1'b0;​
137 else​
138 byte_flag_d0 <= byte_flag;​
139 end​
140 ​
141 endmodule

该模块实现了摄像头输出数据流到视频数据流的转换,在程序的第13行至第18行代码,为模块输出的视频流端口信号。

OV5640摄像头输出的数据格式为RGB565,而数据位宽是8位,因此需要将两次输入的8位数据拼接成一个RGB565的数据格式,如代码中第116至第132行代码所示。我们知道,RGB LCD液晶屏是RGB888的数据格式,因此程序中将RGB565格式的各颜色分量的低位补0,拼成RGB888的数据格式,如代码中第63行至第65行代码所示。

在程序的第57行代码至第65行代码,分别为输出的视频格式数据赋值。这里着重介绍cmos_frame_ce(时钟使能信号)和cmos_active_video(图像数据有效信号)的赋值。cmos_active_video信号在行有效期间,一直为高电平,因此在wait_done为高后,将摄像头的行有效信号赋值给cmos_active_video。我们知道,实际上输出的24位RGB888数据在行有效期间并不是一直有效的,这个由cmos_frame_ce信号来控制,分为行有效和行无效两种情况,只有当cmos_frame_ce信号和cmos_active_video信号同时为高电平时,输出的图像数据才有效。

介绍完OV5640图像采集模块后,接下来对新添加IP核的端口进行手动连线,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据_19


27.3.12 手动连线

连线完成后点击框图界面上方的“Run Connection Automation”,下面列出了会自动连接的模块及其接口,勾选“All Automation”,然后点击“OK”按钮。

接下来将OV5640图像采集IP核的摄像头引脚引出至顶层模块端口,并重新命名,如27.3.13和图27.3.14所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_20


27.3.13 引出摄像头引脚

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_IP_21


27.3.14 引出摄像头其余引脚

然后点击“Run Connnection Automation”,下面列出了会自动连接的模块及其接口,勾选“All Automation”,然后点击“OK”按钮。

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

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_寄存器_22


27.3.15 整体系统框图

这里我们的Block Design设计完成了,在Diagram窗口空白处右击,然后选择Validate Design”验证设计。验证完成后弹出对框提示Validation Successful”表明设计无误点击“OK”确认。最后按快捷键Ctrl + S”保存设计

下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。

为工程添加约束文件,约束文件如下:

#----------------------摄像头接口的时钟---------------------------​
create_clock -period 20 -name cam_pclk [get_ports cam_pclk]​
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_IBUF]​

set_property -dict {PACKAGE_PIN C13 IOSTANDARD LVCMOS33} [get_ports cam_pclk]​
set_property -dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports cam_rst_n]​
set_property -dict {PACKAGE_PIN B15 IOSTANDARD LVCMOS33} [get_ports cam_pwdn]​
set_property -dict {PACKAGE_PIN E15 IOSTANDARD LVCMOS33} [get_ports {cam_data[0]}]​
set_property -dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33} [get_ports {cam_data[1]}]​
set_property -dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33} [get_ports {cam_data[2]}]​
set_property -dict {PACKAGE_PIN D14 IOSTANDARD LVCMOS33} [get_ports {cam_data[3]}]​
set_property -dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33} [get_ports {cam_data[4]}]​
set_property -dict {PACKAGE_PIN B13 IOSTANDARD LVCMOS33} [get_ports {cam_data[5]}]​
set_property -dict {PACKAGE_PIN C14 IOSTANDARD LVCMOS33} [get_ports {cam_data[6]}]​
set_property -dict {PACKAGE_PIN A13 IOSTANDARD LVCMOS33} [get_ports {cam_data[7]}]​
set_property -dict {PACKAGE_PIN G14 IOSTANDARD LVCMOS33} [get_ports cam_vsync]​
set_property -dict {PACKAGE_PIN G13 IOSTANDARD LVCMOS33} [get_ports cam_href]​
set_property -dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[0]}]​
set_property -dict {PACKAGE_PIN F15 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[1]}]​
set_property PULLUP true [get_ports {emio_sccb_tri_io[0]}]​
set_property PULLUP true [get_ports {emio_sccb_tri_io[1]}]​

#RGB LCD​
set_property -dict {PACKAGE_PIN W14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[0]}]​
set_property -dict {PACKAGE_PIN Y14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[1]}]​
set_property -dict {PACKAGE_PIN W13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[2]}]​
set_property -dict {PACKAGE_PIN Y13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[3]}]​
set_property -dict {PACKAGE_PIN W12 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[4]}]​
set_property -dict {PACKAGE_PIN Y12 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[5]}]​
set_property -dict {PACKAGE_PIN W11 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[6]}]​
set_property -dict {PACKAGE_PIN AA12 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[7]}]​
set_property -dict {PACKAGE_PIN AC14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[8]}]​
set_property -dict {PACKAGE_PIN AD15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[9]}]​
set_property -dict {PACKAGE_PIN AC13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[10]}]​
set_property -dict {PACKAGE_PIN AD14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[11]}]​
set_property -dict {PACKAGE_PIN AE15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[12]}]​
set_property -dict {PACKAGE_PIN AA13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[13]}]​
set_property -dict {PACKAGE_PIN AE14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[14]}]​
set_property -dict {PACKAGE_PIN AB13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[15]}]​
set_property -dict {PACKAGE_PIN AG14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[16]}]​
set_property -dict {PACKAGE_PIN AB15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[17]}]​
set_property -dict {PACKAGE_PIN AH14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[18]}]​
set_property -dict {PACKAGE_PIN AB14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[19]}]​
set_property -dict {PACKAGE_PIN AG13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[20]}]​
set_property -dict {PACKAGE_PIN AE13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[21]}]​
set_property -dict {PACKAGE_PIN AH13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[22]}]​
set_property -dict {PACKAGE_PIN AF13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[23]}]​

set_property -dict {PACKAGE_PIN J11 IOSTANDARD LVCMOS33} [get_ports lcd_hs]​
set_property -dict {PACKAGE_PIN K12 IOSTANDARD LVCMOS33} [get_ports lcd_vs]​
set_property -dict {PACKAGE_PIN J10 IOSTANDARD LVCMOS33} [get_ports lcd_de]​
set_property -dict {PACKAGE_PIN J12 IOSTANDARD LVCMOS33} [get_ports lcd_bl]​
set_property -dict {PACKAGE_PIN K13 IOSTANDARD LVCMOS33} [get_ports lcd_clk]​
set_property -dict {PACKAGE_PIN F10 IOSTANDARD LVCMOS33} [get_ports lcd_rst]

在约束文件的开头部分,我们通过create_clock语句对摄像头的像素时钟(cam_pclk)做了时序约束,以满足设计的时序要求。时序约束(Timing Constraints)用来描述设计人员对时序的要求,比如时钟频率,输入输出的延时等。对时钟频率的约束最简单的理解就是,设计者需要告诉EDA工具设计中所使用的时钟的频率是多少;然后工具才能按照所要求的时钟频率去优化布局布线,使设计能够在要求的时钟频率下正常工作。本次实验cam_pclk的时钟频率为72MHz,周期约为13.888ns,在做约束时可以等于这个值或者略低于这个值,不建议周期设置的太小,否则软件在布局布线时很难满足这个要求。

当设计变得复杂起来,或者系统的时钟频率比较高的时候,如果不添加时序约束,那么就有可能在验证设计结果的时候出现一些意料之外的情况。那么为什么在前面的例程中很少提到呢?主要是因为我们的设计功能比较简单,像呼吸灯蜂鸣器这样简单的外设,即使不进行时序约束,也不影响最终的功能

本次实验除了对输入的像素时钟做时序约束外,我们还需要增加“set_property CLOCK_DEDICATED_ROUTE FALSE”语句来取消软件对时钟专用引脚的检查。这是由于Vivado软件检测到像素时钟(cam_pclk)是一个时钟端口,而分配到芯片上的引脚并不是一个时钟专用引脚,因此增加这一语句可取消软件对cam_pclk时钟专用引脚的检查,否则软件会在综合时会报错。

在约束文件的最后,通过“set_property PULLUP true”语句设置SCCB接口的SDA信号引脚上拉。这是由于这个信号是一个双向引脚,双向引脚需要连接上拉电阻才能在引脚作为输入时,读取到正确的值。由于OV5640摄像头模块在硬件上没有接上拉电阻,因此这里在Vivado软件中设置SCCB接口的SDA引脚上拉。

SCCB接口的SCL信号只作为输出,因此也可不必设置上拉。本次实验将EMIO引脚的Bit[0]作为SCCB接口的SCL信号,Bit[1]作为SCCB接口的SDA信号,本次实验将emio_sccb_tri_io[0]和emio_sccb_tri_io[1]统一设置为上拉。

设置引脚上拉的方式除了在约束文件里设置外,也可以在引脚分配的图像界面中设置,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_寄存器_23


27.3.16 SCCB SDA信号引脚上拉

最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。

在生成Bitstream之后菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择Tools> Launch Vitis,启动Vitis软件。

软件设计

在软件设计部分中,和“PS通过VDMA驱动LCD显示”实验相比,源文件下新增了emio_sccb_cfgOV5640文件,分别实现了对SCCB接口的驱动和读OV5640初始化配置的功能,大家可以直接从例程中拷贝。

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 "clk_wiz/clk_wiz.h"​
13 #include "emio_sccb_cfg/emio_sccb_cfg.h"​
14 #include "ov5640/ov5640_init.h"​
15 ​
16 //宏定义​
17 #define CLK_WIZ_ID XPAR_CLK_WIZ_0_DEVICE_ID //时钟IP核器件ID​
18 #define VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID //VDMA器件ID​
19 #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID​
20 #define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO 0(lcd_id)器件ID​
21 #define AXI_GPIO_0_CHANEL 1 //使用AXI GPIO(lcd_id)通道1​
22 ​
23 //全局变量​
24 XAxiVdma vdma;​
25 DisplayCtrl dispCtrl;​
26 XGpio axi_gpio_inst; //PL端 AXI GPIO 驱动实例​
27 VideoMode vd_mode;​
28 //frame buffer的起始地址​
29 unsigned int const frame_buffer_addr = (XPAR_PSU_DDR_0_S_AXI_BASEADDR + 0x1000000);​
30 unsigned int lcd_id=0; //LCD ID​
31 ​
32 int main(void)​
33 {​
34 u32 status;​
35 u16 cmos_h_pixel; //ov5640 DVP 输出水平像素点数​
36 u16 cmos_v_pixel; //ov5640 DVP 输出垂直像素点数​
37 u16 total_h_pixel; //ov5640 水平总像素大小​
38 u16 total_v_pixel; //ov5640 垂直总像素大小​
39 ​
40 XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID); //GPIO初始化​
41 XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x07); //设置AXI GPIO为输入​
42 lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL); //获取LCD的ID​
43 XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x00); //设置AXI GPIO为输出​
44 xil_printf("LCD ID: %x\r\n",lcd_id);​
45 ​
46 //根据获取的LCD的ID号来进行ov5640显示分辨率参数的选择​
47 switch(lcd_id){​
48 case 0x4342 : //4.3寸屏,480*272分辨率​
49 cmos_h_pixel = 480;​
50 cmos_v_pixel = 272;​
51 total_h_pixel = 1800;​
52 total_v_pixel = 1000;​
53 break;​
54 case 0x4384 : //4.3寸屏,800*480分辨率​
55 cmos_h_pixel = 800;​
56 cmos_v_pixel = 480;​
57 total_h_pixel = 1800;​
58 total_v_pixel = 1000;​
59 break;​
60 case 0x7084 : //7寸屏,800*480分辨率​
61 cmos_h_pixel = 800;​
62 cmos_v_pixel = 480;​
63 total_h_pixel = 1800;​
64 total_v_pixel = 1000;​
65 break;​
66 case 0x7016 : //7寸屏,1024*600分辨率​
67 cmos_h_pixel = 1024;​
68 cmos_v_pixel = 600;​
69 total_h_pixel = 2200;​
70 total_v_pixel = 1000;​
71 break;​
72 case 0x1018 : //10.1寸屏,1280*800分辨率​
73 cmos_h_pixel = 1280;​
74 cmos_v_pixel = 800;​
75 total_h_pixel = 2570;​
76 total_v_pixel = 980;​
77 break;​
78 default :​
79 cmos_h_pixel = 480;​
80 cmos_v_pixel = 272;​
81 total_h_pixel = 1800;​
82 total_v_pixel = 1000;​
83 break;​
84 }​
85 ​
86 emio_init(); //初始化EMIO​
87 status = ov5640_init( cmos_h_pixel, //初始化ov5640​
88 cmos_v_pixel,​
89 total_h_pixel,​
90 total_v_pixel);​
91 if(status == 0)​
92 xil_printf("OV5640 detected successful!\r\n");​
93 else​
94 xil_printf("OV5640 detected failed!\r\n");​
95 ​
96 //根据获取的LCD的ID号来进行video参数的选择​
97 switch(lcd_id){​
98 case 0x4342 : vd_mode = VMODE_480x272; break; //4.3寸屏,480*272分辨率​
99 case 0x4384 : vd_mode = VMODE_800x480; break; //4.3寸屏,800*480分辨率​
100 case 0x7084 : vd_mode = VMODE_800x480; break; //7寸屏,800*480分辨率​
101 case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率​
102 case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率​
103 default : vd_mode = VMODE_800x480; break;​
104 }​
105 ​
106 //配置VDMA​
107 run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,​
108 frame_buffer_addr,0,0,BOTH);​
109 ​
110 //设置时钟IP核输出的时钟频率​
111 clk_wiz_cfg(CLK_WIZ_ID,vd_mode.freq);​
112 //初始化Display controller​
113 DisplayInitialize(&dispCtrl, DISP_VTC_ID);​
114 //设置VideoMode​
115 DisplaySetMode(&dispCtrl, &vd_mode);​
116 DisplayStart(&dispCtrl);​
117 ​
118 return 0;​
119 }

在main函数中,首先获取LCD屏的ID,将摄像头的分辨率配置成LCD屏的分辨率。OV5640能够对输出图像的分辨率大小进行调整,所以我们可以根据不同分辨率的LCD来使OV5640输出不同分辨率的视频图像。代码中的第35-38行定义了4个变量,用于存储ov5640的分辨率配置参数。代码中第47行的switch语句,根据读取到的LCD的ID号来对ov5640的分辨率配置参数进行赋值,然后这些变量值将作为实参来传递给ov5640_init()函数。

随后通过emio_init()函数对emio引脚进行初始化,配置SCCB接口的两个引脚。然后通过ov5640_init()函数对OV5640进行初始化,即通过SCCB接口对摄像头进行配置,让OV5640在工作在预期的工作模式下,包括OV5640的输出格式、分辨率、像素PCLK时钟频率等。ov5640_init()函数的返回值为是否检测到OV5640的标志,程序中将检测的结果打印出来,如代码中第91行至第94行代码所示。

在程序的第107行至第108行代码中,通过调用run_vdma_frame_buffer()函数配置VDMA。本次实验同时用到了VDMA的读通道和写通道,因此函数的最后一个参数设置为BOTH,表示同时配置读通道和写通道。

在程序的第111行至第116行代码中,通过clk_wiz_cfg()对时钟IP核进行配置,通过DisplayInitialize()函数对显示相关的IP核进行初始化,包括VTCDisplaySetMode函数设置VTC输出的分辨率;最后通过DisplayStart()函数启动VTC开始工作。

ov5640_init()函数的代码如下所示:

1 u8 ov5640_init( u16 cmos_h_pixel, u16 cmos_v_pixel, ​
2 u16 total_h_pixel, u16 total_v_pixel )​
3 {​
4 u16 cam_id = 0;​
5 ​
6 usleep(20000); //OV5640上电到开始配置sccb至少等待20ms​
7 ​
8 //读OV5640摄像头ID​
9 cam_id = sccb_read_reg16(0x300b); //LSB 0x40​
10 cam_id |= sccb_read_reg16(0x300a) << 8; //MSB 0x56​
11 ​
12 if(cam_id != 0x5640) //获取到正确的OV5640 ID​
13 return 1;​
14 else{​
15 sccb_write_reg16(0x3008,0x02); //正常工作模式

配置代码较长,省略部分源代码……

244 //设置输出像素个数​
245 sccb_write_reg16(0x3808, cmos_h_pixel >> 8 ); //DVP 输出水平像素点数高4位​
246 sccb_write_reg16(0x3809, cmos_h_pixel & 0x00FF ); //DVP 输出水平像素点数低8位​
247 sccb_write_reg16(0x380a, cmos_v_pixel >> 8 ); //DVP 输出垂直像素点数高3位​
248 sccb_write_reg16(0x380b, cmos_v_pixel & 0x00FF ); //DVP 输出垂直像素点数低8位​
249 sccb_write_reg16(0x380c, total_h_pixel >> 8 ); //水平总像素大小高5位​
250 sccb_write_reg16(0x380d, total_h_pixel & 0x00FF); //水平总像素大小低8位​
251 sccb_write_reg16(0x380e, total_v_pixel >> 8 ); //垂直总像素大小高5位​
252 sccb_write_reg16(0x380f, total_v_pixel & 0x00FF); //垂直总像素大小低8位

配置代码较长,省略部分源代码……

278 return 0;​
279 }

代码中的第9-10行读取寄存器地址0x300B和0x300A来获取OV5640摄像头的ID,注意OV5640摄像头的ID为“0x5640”。如果获取的ID不等于0x5640,则返回1;否则开始对OV5640摄像头进行配置。第245-252行将在main函数中赋值的分辨率配置数据写入到0x5640中,配置完成后函数返回0。

下载验证

首先我将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接然后使用USB连接线将USB UART接口(PS_PORT)与电脑连接,用于口通信。接下来使用FPC排线一端与RGB LCD液晶屏上的接口连接,另一端连接开发板上的RGB LCD接口。

接下来将双目OV5640摄像头模块插在MPSOC开发板的J19扩展口(实际只用到了一路摄像头),注意在连接时,摄像头镜头方向朝外,如下图所示,最后连接开发板的电源。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据_24


27.5.1 摄像头镜头方向朝外

打开Vitis Terminal终端,设置连接串口。然后下载本次实验程序下载完成后,在下方的Terminal中可以看到应用程序打印的信息,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_数据_25


27.5.2 串口打印的信息

同时,RGB LCD液晶屏上显示出OV5640摄像头采集的图像,说明本次OV5640摄像头RGB LCD屏显示的实验在MPSOC开发板上验证成功,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十七章 OV5640摄像头LCD显示​_寄存器_26


27.5.3 RGB LCD屏显示图像数据

实验的最后再补充一点,我们在动态同步锁相模式下,设置不同的帧数量做了测试。当帧缓存的数量小于等于2,在摄像头前快速移动时,会出现比较明显的卡顿(即前面说的割裂)现象;当帧缓存的数据大于等于3时,可以解决这一问题,且设置的值超过3时,也观察不到显示效果的提升,和前面分析的一致。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月12日 0

暂无评论

推荐阅读
95kVyaJuybju