OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 6600|回复: 1

[XILINX] 【正点原子FPGA连载】第二十章 OV7725摄像头LCD显示--摘自【正点原子】领航者ZYNQ之嵌入式开发指南_V1.2

[复制链接]

1107

主题

1118

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4615
金钱
4615
注册时间
2019-5-8
在线时间
1218 小时
发表于 2020-9-8 11:42:22 | 显示全部楼层 |阅读模式
本帖最后由 正点原子01 于 2020-9-8 11:42 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入群聊
5)关注正点原子公众号,获取最新资料更新
1.jpg
1120.png
第二十章OV7725摄像头LCD显示

OV7725是OmniVision(豪威科技)公司生产的一颗CMOS图像传感器,该传感器功耗低、可靠性高以及采集速率快,主要应用在玩具、安防监控、电脑多媒体等领域。本章我们将使用领航者Zynq开发板实现对OV7725的数字图像采集并通过LCD实时显示。
本章包括以下几个部分:
1         
1.1        简介
1.2        实验任务
1.3        硬件设计
1.4        软件设计
1.5        下载验证
简介
OV7725是一款1/4英寸单芯片图像传感器,其感光阵列达到640*480,能实现最快60fps分辨率的图像采集。传感器内部集成了图像处理的功能,包括自动曝光控制(AEC)、自动增益控制(AGC)和自动白平衡(AWB)等。同时传感器具有较高的感光灵敏度,适合低照度的应用,下图为OV7725的功能框图。
image002.png
图 0.1 OV7725功能框图
由上图可知,感光阵列(imagearray)在XCLK时钟的驱动下进行图像采样,输出640*480阵列的模拟数据;接着模拟信号处理器在时序发生器(video timing generator)的控制下对模拟数据进行算法处理(analogprocessing);模拟数据处理完成后分成G(绿色)和R/B(红色/蓝色)两路通道经过AD转换器后转换成数字信号,并且通过DSP进行相关图像处理,最终输出所配置格式的10位视频数据流。模拟信号处理以及DSP等都可以通过寄存器(registers)来配置,配置寄存器的接口就是SCCB接口,该接口协议兼容IIC协议。
SCCB(Serial Camera Control Bus,串行摄像头控制总线)是由OV(OmniVision的简称)公司定义和发展的三线式串行总线,该总线控制着摄像头大部分的功能,包括图像数据格式、分辨率以及图像处理参数等。OV公司为了减少传感器引脚的封装,现在SCCB总线大多采用两线式接口总线。
OV7725使用的是两线式接口总线,该接口总线包括SIO_C串行时钟输入线和SIO_D串行双向数据线,分别相当于IIC协议的SCL信号线和SDA信号线。我们在前面提到过SCCB协议兼容IIC协议,是因为SCCB协议和IIC协议非常相似,有关IIC协议的详细介绍请大家参考“EEPROM读写实验”章节。
SCCB的写传输协议如下图所示:
image004.png
图 0.2 SCCB写传输协议
上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写 1:读),OV7725的器件地址为7’h21,所以在写传输协议中,ID Address(W) =8’h42(器件地址左移1位,低位补0);Sub-address为8位寄存器地址,在OV7725的数据手册中定义了0x00~0xAC共173个寄存器,有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;WriteData为8位写数据,每一个寄存器地址对应8位的配置数据。上图中的第9位X表示Don’t Care(不必关心位),该位是由从机(此处指OV7725)发出应答信号来响应主机表示当前ID Address、Sub-address和Write Data是否传输完成,但是从机有可能不发出应答信号,因此主机(此处指FPGA)可不用判断此处是否有应答,直接默认当前传输完成即可。
我们可以发现,SCCB和IIC写传输协议是极为相似的,只是在SCCB写传输协议中,第9位为不必关心位,而IIC写传输协议为应答位。SCCB的读传输协议和IIC有些差异,在IIC读传输协议中,写完寄存器地址后会有restart即重复开始的操作;而SCCB读传输协议中没有重复开始的概念,在写完寄存器地址后,发起总线停止信号,下图为SCCB的读传输协议。
image006.png
图 0.3 SCCB读传输协议
由上图可知,SCCB读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据,注意ID Address(R) = 8’h43(器件地址左移1位,低位补1)。上图中的NA位由主机(这里指FPGA)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。
在OV7725正常工作前,必须先对传感器进行初始化,即通过配置寄存器使其工作在预期的工作模式,以及得到较好画质的图像。因为SCCB的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。OV公司提供了OV7725的软件使用手册(OV7725 Software Application Note,位于开发板所随附的资料“7_硬件资料/6_OV7725资料/OV7725Software Application Note.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。
表 0.1 OV7725关键寄存器配置说明

OV7725的寄存器较多,对于其它寄存器的描述可以参OV7725的数据手册。
下图为OV7725的一些特性。
image008.png
图 0.4 OV7725的特性
从上图可以看出,OV7725的输入时钟频率的范围是10Mhz~48Mhz;SCCB总线的SIO_C的时钟频率最大为400KHz;配置寄存器软件复位(寄存器地址0x12 Bit[7]位)和硬件复位(cam_rst_n引脚)后需要等待最大1ms才能配置其它寄存器;每次配置完寄存器后,需要最大300ms时间的延迟,也就是10帧图像输出的时间才能输出稳定的视频流。
OV7725支持多种不同分辨率图像的输出,包括VGA(640*480)、QVGA(320*240)以及CIF(一种常用的标准化图像格式,分辨率为352*288)到40*30等任意尺寸。可通过寄存器地址0x12(COM7)、0x17(HSTART)、0x18(HSIZE)、0x19(VSTRT)、0x1A(VSIZE)、0x32(HREF)、0x29(HoutSize)、0x2C(VOutSize)、0x2A(EXHCH)来配置输出图像的分辨率。
OV7725支持多种不同的数据像素格式,包括YUV(亮度参量和色度参量分开表示的像素格式)、RGB(其中RGB格式包含RGB565、RGB555等)以及8位的RAW(原始图像数据)和10位的RAW,通过寄存器地址0x12(COM7)配置不同的数据像素格式。
由于摄像头采集的图像最终要通过RGBLCD接口显示在LCD液晶屏上,且领航者开发板上的LCD接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此我们将OV7725摄像头输出的图像像素数据配置成RGB565格式,然后通过颜色分量低位补零的方式将RGB565格式转换为RGB888格式。本次实验采用OV7725支持的最大分辨率640*480,下图为摄像头输出的VGA帧模式时序图。
image010.jpg
图 0.5 VGA帧模式输出时序图
在介绍时序图之前先了解几个基本的概念。
VSYNC:场同步信号,由摄像头输出,用于标志一帧数据的开始与结束。上图中VSYNC的高电平作为一帧的同步信号,在低电平时输出的数据有效。需要注意的是场同步信号是可以通过设置寄存器0x15 Bit[1]位进行取反的,即低电平同步高电平有效,本次实验使用的是和上图一致的默认设置;
HREF/HSYNC:行同步信号,由摄像头输出,用于标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的,只是数据的同步方式不一样。本次实验使用的是HREF格式输出,当HREF为高电平时,图像输出有效,可以通过寄存器0x15 Bit[6]进行配置。本次实验使用的是HREF格式输出;
D[9:0]:数据信号,由摄像头输出,在RGB格式输出中,只有高8位D[9:2]是有效的;
tPCLK:一个像素时钟周期;
tp:单个数据周期,这里需要注意的是上图中左下角红框标注的部分,在RGB模式中,tp代表两个tPCLK(像素时钟)。以RGB565数据格式为例,RGB565采用16bit数据表示一个像素点,而OV7725在一个像素周期(tPCLK)内只能传输8bit数据,因此需要两个时钟周期才能输出一个RGB565数据;
tLine:摄像头输出一行数据的时间,共784个tp,包含640tp个高电平和144tp个低电平,其中640tp为有效像素数据输出的时间。以RGB565数据格式为例,640tp实际上是640*2=1280个tPCLK;
由图 21.1.5可知,VSYNC的上升沿作为一帧的开始,高电平同步脉冲的时间为4*tLine,紧接着等待18*tLine时间后,HREF开始拉高,此时输出有效数据;HREF由640tp个高电平和144tp个低电平构成;输出480行数据之后等待8*tLine时间一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame =(4 + 20 + 480 + 6)*tLine = 510tLine。
由此我们可以计算出摄像头的输出帧率,以PCLK=25Mhz(周期为40ns)为例,计算出OV7725输出一帧图像所需的时间如下:
一帧图像输出时间:tFrame= 510*tLine = 510*784tp = 510*784*2tPCLK = 799680*40ns = 31.9872ms;
摄像头输出帧率:1000ms/31.9872ms≈ 31Hz。
如果把像素时钟频率提高到摄像头的最大时钟频率48Mhz,通过上述计算方法,摄像头的输出帧率约为60Hz。
下图为OV7725输出RGB565格式的时序图:
image012.png
图 0.6 RGB565模式时序图
上图中的PCLK为OV7725输出的像素时钟,HREF为行同步信号,D[9:2]为8位像素数据。OV7725最大可以输出10位数据,在RGB565输出模式中,只有高8位是有效的。像素数据在HREF为高电平时有效,第一次输出的数据为RGB565数据的高8位,第二次输出的数据为RGB565数据的低8位,firstbyte和second byte组成一个16位RGB565数据。由上图可知,数据是在像素时钟的下降沿改变的,为了在数据最稳定的时刻采集图像数据,所以我们需要在像素时钟的上升沿采集数据。
实验任务
本节实验任务是使用领航者开发板及OV7725摄像头实现图像采集,并通过RGB LCD屏实时显示。
硬件设计
我们的领航者Zynq开发板上有一个摄像头扩展接口,该接口可以用来连接OV7725/OV5640等摄像头模块。由于SCCB接口通信需要接上拉电阻,因此,将CMOS_SCL信号和CMOS_SDA信号连接上拉电阻,原理图如图 21.3.1所示:
image014.jpg
图 0.1 摄像头扩展接口原理图
ATK-OV7725是正点原子推出的一款高性能30W像素高清摄像头模块。该模块通过2*9排针(2.54mm间距)同外部连接,我们将摄像头的排针直接插在开发板上的摄像头接口即可,模块外观如图 21.3.2所示:
image016.png
图 0.2 ATK-OV7725摄像头模块实物图
我们在前面说过,OV7725在RGB565模式中只有高8位数据是有效的即D[9:2],而我们的摄像头排针上数据引脚的个数是8位。实际上,摄像头排针上的8位数据连接的就是OV7725传感器的D[9:2],所以我们直接使用摄像头排针上的8位数据引脚即可。
需要注意的是,由图 21.3.1可知,摄像头扩展口的第18个引脚定义为CMOS_PWDN,而我们的OV7725摄像头模块的PWDN引脚固定为低电平,也就是一直处于正常工作模式。OV7725摄像头模块的第18个引脚定义为SGM_CTRL,这个引脚是摄像头驱动时钟的选择引脚。OV7725摄像头模块内部自带晶振的,当SGM_CTRL引脚为低电平时,选择使用摄像头的外部时钟,也就是FPGA需要输出时钟给摄像头;当SGM_CTRL引脚为高电平时,选择使用摄像头的晶振提供时钟。本次实验将SGM_CTRL引脚驱动为高电平,这样就不用为摄像头提供驱动时钟,即不用在CMOS_XCLK引脚上输出时钟。
由于RGB LCD屏的引脚数目较多,且在前面相应的章节中已经给出它们的管脚列表,这里
只列出摄像头相关管脚分配, 如下表所示:
表 0.1 OV7725摄像头管脚分配
D002D06D-BD77-4ca1-82AE-D9BA307D2D8B.png
在“PS通过VDMA驱动LCD显示”实验中,我们使用了Xilinx提供的VDMA IP核,实现了RGB LCD液晶屏显示彩条的功能。在该实验中,数据源由PS通过向DDR3中写入彩条数据产生的,我们只使能了VDMA IP核的读通道,通过读通道来将AXI4 Memory Map格式的数据流转换成AXI4-Stream类的数据流。而对于本次实验来说,只需要在此基础上,将彩条数据替换成由OV7725摄像头输出的图像数据即可,因此对于本次实验的重点是将摄像头的图像数据写入VDMAIP核的帧缓存中,即DDR3内存中,即可实现摄像头的图像显示。
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 toAXI4-Stream IP核实现数据格式的转换。
Video in toAXI4-Stream IP核的输入端口为视频数据流,而OV7725摄像头输出的数据为行场同步信号控制的8位数据,这两个端口不可以直接连接,需要先经过数据的转换才能连接,因此本次实验通过添加了一个图像采集的IP核来实现这个数据的转换,本次实验的框图如下图所示:
image018.png
图 0.3 系统架构框图
通过对比上图中的框图和“PS通过VDMA驱动LCD显示”实验的框图可知,我们只是在此基础上,添加了OV7725图像采集IP核和Videoin to AXI4-Stream IP核,其它的IP核仍然和“PS通过VDMA驱动LCD显示”实验一致,只不过在VDMA IP核的配置上,需要使能VDMA的写通道。并且对于本次使用来说,需要对帧缓存进行频繁的写入和读出,为了避免读通道和写通道同时访问同一帧缓存,那么VMDA必须配置成动态同步锁相的模式,且帧缓存数量要大于等于3。由于分配过多的帧缓存区域对效率的提升已经微乎其微,且会占用更多的存储空间和消耗CPU的时间,因此本次实验将帧缓存空间设置为3,并采用动态同步锁相的模式。
由于OV7725摄像头需要经过初始化之后,才能正常工作。本次实验通过SCCB接口对OV7725进行配置,SCCB端口是通过EMIO连接至PS中,SCCB的驱动由PS实现。
最后我们再来看下图 21.3.3 中数据流的走向。OV7725图像采集是我们自定义的IP核,负责将OV7725的数据转成视频流数据;视频流数据经过Video in to AXI4-StreamIP核转换成AXI4-Stream IP格式数据流,然后通过VDMA的写通道转成AXI4 Memory Map格式,并最终写入DDR内存中。VDMA通过AXI Smartconnect IP核与AXI_HP端口进行连接,从而高效访问DDR3,VDMA从DDR3中读取的视频或图像数据传输给AXI4-Stream to Video Out IP核。AXI4-Stream toVideo Out IP核在VTC IP核的控制下,把AXI4-Stream格式的数据转换成视频输出的数据格式(如RGB888),并将输出的视频数据流连接至RGB2LCD IP核(rgb2lcd)的输入端。RGB2LCD IP核是本次实验自定义的IP核,实现了获取LCD屏的ID,以及将LCD屏的引脚封装到总线接口上,以方便将LCD引脚引出至顶层模块端口上。
本次实验的硬件平台在“PS通过VDMA驱动LCD显示”实验基础上搭建。首先需要对ZYNQ7处理系统进行修改,使能EMIO的两个引脚,用来连接摄像头的SCCB接口,配置如下:
image020.jpg
图 0.4 ZYNQ处理系统EMIO配置
接下来引出这个端口,并命名为“emio_sccb”,如下图所示:
image022.jpg
图 0.5 引出引脚
接下来修改VDMA IP核的配置,修改后的配置界面如下图所示:
image024.jpg
图 0.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的同步锁相模式配置如下:
image026.jpg
图 0.7 VDMA IP核同步锁相配置
接下来添加Video in toAXI4-Stream IP核,在IP核搜索框中输入“videoin”,选择“Video in to AXI4-Stream”IP核,如下图所示:
image028.jpg
图 0.8 添加Video in to AXI4-Stream IP核
Video in toAXI4-Stream IP核配置界面如下图所示:
image030.jpg
图 0.9 Video in to AXI4-Stream IP核配置
Clock Mode时钟模式用于指定AXI4-Stream输入和视频输出信号,使用公共时钟还是独立时钟进行时钟控制,此处我们使用独立时钟进行控制,即勾选Independent,其它选项保持默认即可。
接下来添加自定义的OV7725图像采集IP核,该IP核位于例程的ip_repo文件下,将该文件拷贝至工程目录下,并在工程中添加该IP核,添加的方法这里不再赘述。在IP核搜索框中输入“ov7725”,如下图所示:
image031.png
图 0.10 添加OV7725图像采集IP核
OV7725图像采集IP核无需设置,该IP核实现了OV7725的数据流转换成视频数据流格式,即转换成符合Video in to AXI-Stream IP核输入端的数据格式。视频数据流格式的时序图如下图所示:
image033.jpg
图 0.11 视频流格式时序图
vblank_out(output vertical blank):垂直方向空白信号,为高电平时,表示当前处于垂直方向的无效数据区域;
vsync_out(output vertical synchronization): 垂直方向同步信号,用于帧同步;
hblank_out(output horizontal blank):水平方向空白信号,为高电平时,表示当前处于水平方向的无效数据区域;
hsync_out(output horizontal synchronization):水平方向同步信号,用于行同步;
active_video_out:图像有效信号,高电平有效。
图 21.3.11是视频流的数据格式,除了这些信号外,还需要输出时钟(clk)、时钟使能(ce)和图像数据,我们只需要将摄像头输出的数据按照视频流的格式输出,即可连接至Video in to AXI-Stream IP核的输入端。由于摄像头只输出了cam_vsync和cam_href这两个用于控制数据是否有效的信号,为了简化设计,本次实验简化了视频数据流的接口,只输出了时钟信号、时钟有效信号、场同步信号、图像数据有效信号和图像数据。
OV7725图像采集IP核源代码如下:
  1.   module  ov7725_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_sgm_ctrl    ,  //cmos 时钟选择信号, 1:使用摄像头自带的晶振                    

  11.      

  12.       //RGB888接口

  13.       output                cmos_frame_clk,    //时钟信号

  14.       output                cmos_frame_ce,     //时钟使能信号

  15.      

  16.       output                cmos_vsync,        //场同步信号   

  17.       output                cmos_active_video ,//数据有效信号

  18.       output       [23:0]   cmos_data          //有效数据   

  19.      

  20.   );



  21.   //paremeter define



  22.   //寄存器全部配置完成后,先等待10帧数据

  23.   //待寄存器配置生效后再开始采集图像

  24.   localparam  WAIT_FRAME = 4'd10  ;            //寄存器数据稳定等待的帧个数      



  25.   //reg define

  26.   reg          cam_vsync_d0 ;

  27.   reg          cam_vsync_d1 ;

  28.   reg          cam_href_d0 ;

  29.   reg          cam_href_d1 ;

  30.   reg   [3:0]  cmos_ps_cnt ;                   //等待帧数稳定计数器

  31.   reg          wait_done ;

  32.   reg          byte_flag ;

  33.   reg   [7:0]  cam_data_d0 ;            

  34.   reg  [15:0]  cmos_data_16b ;                 //用于8位转16位的临时寄存器

  35.   reg          byte_flag_d0 ;



  36.   //wire define

  37.   wire  pos_vsync ;



  38.   //*****************************************************

  39.   //**                    main code

  40.   //*****************************************************



  41.   //不对摄像头硬件复位,固定高电平

  42.   assign  cam_rst_n = 1'b1;   



  43.   //cmos 时钟选择信号,0:使用引脚XCLK提供的时钟 1:使用摄像头自带的晶振

  44.   assign  cam_sgm_ctrl = 1'b1;

  45.   assign  pos_vsync  = (~cam_vsync_d1) & cam_vsync_d0 ;   //采输入场同步信号的上升沿

  46.   assign  cmos_frame_clk   = cam_pclk;

  47.   assign  cmos_frame_ce    = wait_done  ?  (byte_flag_d0 & cmos_active_video)

  48.                              || (!cmos_active_video) : 1'b0;

  49.   assign  cmos_vsync = wait_done  ?  cam_vsync_d1  :  1'b0;          //输出帧有效信号

  50.   assign  cmos_active_video  = wait_done  ?  cam_href_d1   :  1'b0;  //输出数据有效信号

  51.   assign  cmos_data  = wait_done  ?

  52.       { cmos_data_16b[15:11],3'd0 , cmos_data_16b[10:5],2'd0 , cmos_data_16b[4:0],3'd0 }  

  53.       :  24'd0;                                                      //输出数据



  54.   always @(posedge cam_pclk or negedge rst_n) begin

  55.       if(!rst_n) begin

  56.           cam_vsync_d0 <= 1'b0;

  57.           cam_vsync_d1 <= 1'b0;

  58.          

  59.           cam_href_d0 <= 1'b0;

  60.           cam_href_d1 <= 1'b0;

  61.       end

  62.       else begin

  63.           cam_vsync_d0 <= cam_vsync;

  64.           cam_vsync_d1 <= cam_vsync_d0;

  65.          

  66.           cam_href_d0 <= cam_href;

  67.           cam_href_d1 <= cam_href_d0;

  68.       end

  69.   end



  70.   //寄存器全部配置完成后,先等待10帧数据

  71.   //待寄存器配置生效后再开始采集图像

  72.   always @(posedge cam_pclk or negedge rst_n) begin

  73.       if(!rst_n)

  74.           cmos_ps_cnt <= 4'd0;

  75.       else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))

  76.           cmos_ps_cnt <= cmos_ps_cnt + 4'd1;

  77.   end



  78.   //等待完成后 给出 等待完成信号

  79.   always @(posedge cam_pclk or negedge rst_n) begin

  80.       if(!rst_n)

  81.           wait_done <= 1'b0;

  82.       else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)

  83.           wait_done <= 1'b1;

  84.   end           



  85.   //8位数据转16位RGB565数据

  86.   always @(posedge cam_pclk  or negedge rst_n) begin

  87.       if(!rst_n) begin

  88.          cmos_data_16b <= 16'd0;

  89.          cam_data_d0 <= 8'd0;

  90.          byte_flag <= 1'b0;

  91.      end

  92.      else if( cam_href ) begin  //cam 行同步信号

  93.          byte_flag   <= ~byte_flag;

  94.          cam_data_d0 <= cam_data;

  95.          if(byte_flag)

  96.              cmos_data_16b <= {cam_data_d0,cam_data};

  97.      end

  98.      else begin

  99.          byte_flag <= 1'b0;

  100.          cam_data_d0 <= 8'b0;

  101.      end   

  102. 3 end      

  103. 4

  104. 5 //产生输出数据有效信号

  105. 6 always @(posedge cam_pclk or negedge rst_n) begin

  106. 7     if(!rst_n)

  107. 8         byte_flag_d0 <= 1'b0;

  108. 9     else

  109. 0         byte_flag_d0 <= byte_flag;   

  110. 1 end   

  111. 2

  112. 3 endmodule
复制代码
该模块实现了摄像头输出数据流到视频数据流的转换,在程序的第13行至第18行代码,为模块输出的视频流端口信号。
OV7725摄像头输出的数据格式为RGB565,而数据位宽是8位,因此需要将两次输入的8位数据拼接成一个RGB565的数据格式,如代码中第96至第123行代码所示。我们知道,RGBLCD液晶屏是RGB888的数据格式,因此程序中将RGB565格式的各颜色分量的低位补0,拼成RGB888的数据格式,如代码中第58行至第60行代码所示。
在程序的第53行代码至第60行代码,分别为输出的视频数据流格式赋值。这里着重介绍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信号同时为高电平时,输出的图像数据才有效。
介绍完OV7725图像采集模块后,接下来对新添加IP核的端口进行手动连线,如下图所示:
image035.jpg
图 0.12手动连线
连线完成后点击框图界面上方的“RunConnection Automation”,下面列出了会自动连接的模块及其接口,勾选“AllAutomation”,然后点击“OK”按钮。
接下来将OV7725图像采集IP核的摄像头引脚引出至顶层模块端口,并重新命名,如图 21.3.13和图 21.3.14所示:
image037.jpg
图 0.13 引出摄像头引脚
image038.png
图 0.14 引出摄像头其余引脚
整体系统框图,如下图所示:
image040.jpg
图 0.15 整体系统框图
到这里我们的BlockDesign就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“ValidationSuccessful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDLWrapper”。
为工程添加约束文件,约束文件如下:
#摄像头接口的时钟
  1. create_clock -period 40.000 -name cmos_pclk [get_ports cam_pclk]

  2. set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_IBUF]



  3. #RGB LCD

  4. set_property -dict {PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[0]}]

  5. set_property -dict {PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[1]}]

  6. set_property -dict {PACKAGE_PIN W20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[2]}]

  7. set_property -dict {PACKAGE_PIN V20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[3]}]

  8. set_property -dict {PACKAGE_PIN U14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[4]}]

  9. set_property -dict {PACKAGE_PIN U15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[5]}]

  10. set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[6]}]

  11. set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[7]}]

  12. set_property -dict {PACKAGE_PIN W14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[8]}]

  13. set_property -dict {PACKAGE_PIN Y14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[9]}]

  14. set_property -dict {PACKAGE_PIN N15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[10]}]

  15. set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[11]}]

  16. set_property -dict {PACKAGE_PIN V16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[12]}]

  17. set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[13]}]

  18. set_property -dict {PACKAGE_PIN W18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[14]}]

  19. set_property -dict {PACKAGE_PIN W19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[15]}]

  20. set_property -dict {PACKAGE_PIN T10 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[16]}]

  21. set_property -dict {PACKAGE_PIN T11 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[17]}]

  22. set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[18]}]

  23. set_property -dict {PACKAGE_PIN R14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[19]}]

  24. set_property -dict {PACKAGE_PIN V13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[20]}]

  25. set_property -dict {PACKAGE_PIN U13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[21]}]

  26. set_property -dict {PACKAGE_PIN G15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[22]}]

  27. set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[23]}]

  28. set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [get_ports lcd_hs]

  29. set_property -dict {PACKAGE_PIN P20 IOSTANDARD LVCMOS33} [get_ports lcd_vs]

  30. set_property -dict {PACKAGE_PIN N20 IOSTANDARD LVCMOS33} [get_ports lcd_de]

  31. set_property -dict {PACKAGE_PIN Y16 IOSTANDARD LVCMOS33} [get_ports lcd_bl]

  32. set_property -dict {PACKAGE_PIN T16 IOSTANDARD LVCMOS33} [get_ports lcd_clk]



  33. #CAMERA

  34. set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports cam_rst_n]

  35. set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} [get_ports cam_sgm_ctrl]

  36. set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports cam_data[0]]

  37. set_property -dict {PACKAGE_PIN V15 IOSTANDARD LVCMOS33} [get_ports cam_data[1]]

  38. set_property -dict {PACKAGE_PIN W15 IOSTANDARD LVCMOS33} [get_ports cam_data[2]]

  39. set_property -dict {PACKAGE_PIN T12 IOSTANDARD LVCMOS33} [get_ports cam_data[3]]

  40. set_property -dict {PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports cam_data[4]]

  41. set_property -dict {PACKAGE_PIN V12 IOSTANDARD LVCMOS33} [get_ports cam_data[5]]

  42. set_property -dict {PACKAGE_PIN W13 IOSTANDARD LVCMOS33} [get_ports cam_data[6]]

  43. set_property -dict {PACKAGE_PIN T14 IOSTANDARD LVCMOS33} [get_ports cam_data[7]]

  44. set_property -dict {PACKAGE_PIN T17 IOSTANDARD LVCMOS33} [get_ports cam_href]

  45. set_property -dict {PACKAGE_PIN T15 IOSTANDARD LVCMOS33} [get_ports cam_pclk]

  46. set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports cam_vsync]

  47. set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [get_ports emio_sccb_tri_io[0]]

  48. set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports emio_sccb_tri_io[1]]
复制代码
在约束文件的开头部分,我们通过create_clock语句对摄像头的像素时钟(cam_pclk)做了时序约束,以满足设计的时序要求。时序约束(TimingConstraints)用来描述设计人员对时序的要求,比如时钟频率,输入输出的延时等。对时钟频率的约束最简单的理解就是,设计者需要告诉EDA工具设计中所使用的时钟的频率是多少;然后工具才能按照所要求的时钟频率去优化布局布线,使设计能够在要求的时钟频率下正常工作。本次实验cam_pclk的时钟频率为24MHz,周期约为41.6ns,在做约束时可以等于这个值或者略低于这个值,不建议周期设置的太小,否则软件在布局布线时很难满足这个要求。
当设计变得复杂起来,或者系统的时钟频率比较高的时候,如果不添加时序约束,那么就有可能在验证设计结果的时候出现一些意料之外的情况。那么为什么在前面的例程中很少提到呢?主要是因为我们的设计功能比较简单,像呼吸灯、蜂鸣器这样简单的外设,即使不进行时序约束,也不影响最终的功能。
本次实验除了对输入的像素时钟做时序约束外,我们还需要增加“set_property CLOCK_DEDICATED_ROUTE FALSE”语句来取消软件对时钟专用引脚的检查。这是由于Vivado软件检测到像素时钟(cam_pclk)是一个时钟端口,而分配到芯片上的引脚并不是一个时钟专用引脚,因此增加这一语句可取消软件对cam_pclk时钟专用引脚的检查,否则软件会在综合时会报错。
在约束文件的最后,通过“set_propertyPULLUP true”语句设置SCCB接口的SDA信号引脚上拉。这是由于这个信号是一个双向引脚,双向引脚需要连接上拉电阻才能在引脚作为输入时,读取到正确的值。由于OV7725摄像头在硬件上没有接上拉电阻,因此这里在Vivado软件中设置SCCB接口的SDA引脚上拉。
SCCB接口的SCL信号只作为输出,因此可不必设置上拉。本次实验将EMIO引脚的Bit[0]作为SCCB接口的SCL信号,Bit[1]作为SCCB接口的SDA信号,因此将emio_sccb_tri_io[1]设置为上拉。
设置引脚上拉的方式除了在约束文件里设置外,也可以在引脚分配的图像界面中设置,如下图所示:
image042.jpg
图 0.16 SCCB SDA信号引脚上拉
最后在左侧FlowNavigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > LaunchSDK,启动SDK软件。
软件设计
在软件设计部分中,和“PS通过VDMA驱动LCD显示”实验相比,源文件下新增了emio_sccb_cfg和ov7725文件,分别实现了对SCCB接口的驱动和读OV7725初始化配置的功能,大家可以直接从例程中拷贝。
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 "ov7725/ov7725_init.h"

  12. #include "vdma_api/vdma_api.h"

  13. #include "emio_sccb_cfg/emio_sccb_cfg.h"



  14. //宏定义

  15. #define FRAME_BUFFER_NUM   3                          //帧缓存个数

  16. #define DYNCLK_BASEADDR    XPAR_AXI_DYNCLK_0_BASEADDR //动态时钟基地址

  17. #define VDMA_ID            XPAR_AXIVDMA_0_DEVICE_ID   //VDMA器件ID

  18. #define DISP_VTC_ID        XPAR_VTC_0_DEVICE_ID       //VTC器件ID

  19. #define AXI_GPIO_0_ID      XPAR_AXI_GPIO_0_DEVICE_ID  //PL端  AXI GPIO 0(lcd_id)器件ID

  20. #define AXI_GPIO_0_CHANEL  1                          //使用AXI GPIO(lcd_id)通道1



  21. //全局变量

  22. XAxiVdma     vdma;

  23. DisplayCtrl  dispCtrl;

  24. XGpio        axi_gpio_inst;   //PL端 AXI GPIO 驱动实例

  25. VideoMode    vd_mode;

  26. //frame buffer的起始地址

  27. unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000);

  28. unsigned int lcd_id=0;        //LCD ID



  29. int main(void)

  30. {

  31.      int status = 0;

  32.      emio_init();             //初始化EMIO

  33.   status = ov7725_init();  //初始化ov7725

  34.   if(status == 0)

  35.       xil_printf("OV7725 detected successful!\r\n");

  36.   else

  37.       xil_printf("OV7725 detected failed!\r\n");



  38.   //获取LCD的ID

  39.   XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);

  40.   lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);

  41.   xil_printf("LCD ID: %x\r\n",lcd_id);



  42.   //根据获取的LCD的ID号来进行video参数的选择

  43.   switch(lcd_id){

  44.       case 0x4342 : vd_mode = VMODE_480x272; break;  //4.3寸屏,480*272分辨率

  45.       case 0x4384 : vd_mode = VMODE_800x480; break;  //4.3寸屏,800*480分辨率

  46.       case 0x7084 : vd_mode = VMODE_800x480; break;  //7寸屏,800*480分辨率

  47.       case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率

  48.       case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率

  49.       default : vd_mode = VMODE_800x480; break;

  50.   }



  51.   //配置VDMA

  52.   run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,

  53.                           frame_buffer_addr,0,0,BOTH);

  54.   //因摄像头和RGB LCD屏的分辨率不匹配,清空DDR3帧缓存空间

  55.   //最后一个参数表示清零的字节数,由于RGB888数据格式占用3个字节,因此最后乘以3

  56.   memset(frame_buffer_addr,0,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*3);

  57.   Xil_DCacheFlush();

  58.   //初始化Display controller

  59.   DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);

  60.   //设置VideoMode

  61.   DisplaySetMode(&dispCtrl, &vd_mode);

  62.   DisplayStart(&dispCtrl);

  63.   return 0;

  64. }
复制代码
在main函数中,首先通过emio_init()函数对emio引脚进行初始化,配置SCCB接口的两个引脚。然后通过ov7725_init()函数对OV7725进行初始化,即通过SCCB接口对摄像头进行配置,让OV7725在工作在预期的工作模式下,包括OV7725的输出格式、分辨率、像素PCLK时钟频率等。ov7725_init()函数的返回值为是否检测到OV7725的标志,程序中将检测的结果打印出来,如代码中第35行至第40行代码所示。
随后同样是先获取LCD屏的ID,以兼容所有的屏幕。需要说明的是,OV7725摄像头输出最大的分辨率为640*480,而LCD屏有各种不同的分辨率,因此在较高分辨率上显示时,LCD屏的部分区域会显示图像数据未写入的缓存区域。
在程序的第58行至第59行代码中,通过调用run_vdma_frame_buffer()函数配置VDMA。本次实验同时用到了VDMA的读通道和写通道,因此函数的最后一个参数设置为BOTH,表示同时配置读通道和写通道。
在程序的第62行至第63行代码中,通过调用memset()函数清空帧缓存中的数据。这是由于OV7725摄像头输出的图像分辨率和LCD屏的分辨率不一致,帧缓存中未写入图像数据的区域默认是随机值,因此通过memset()函数将帧缓存空间清零,否则,未写入的帧缓存区域在LCD屏上将显示比较杂乱的背景色。
在程序的第64行至第68行代码中,通过DisplayInitialize()函数对显示相关的IP核进行初始化,包括VTC和动态时钟配置;DisplaySetMode函数设置VTC输出的分辨率;最后通过DisplayStart()函数启动VTC开始工作。
ov7725_init()函数位于ov7725/ov775_init.c文件内,该函数部分代码如下:
  1. //OV7725初始化

  2. u8 ov7725_init(void)

  3. {

  4.      u16 cam_id = 0;

  5.      //读OV7725摄像头ID

  6.      cam_id = sccb_read_reg8(0x0B);

  7.      cam_id |= sccb_read_reg8(0x0A) << 8;

  8.    

  9.      if(cam_id != 0x7721)  //获取到正确的OV7725 ID

  10.          return 1;

  11.      else{

  12.          sccb_write_reg8(0x12,0x80);    // BIT[7]-Reset all the Reg

  13.          usleep(1000);



  14.          sccb_write_reg8 (0x3d, 0x03) ; //COM12 模拟过程直流补偿

  15.          sccb_write_reg8 (0x15, 0x00) ; //COM10 href/vsync/pclk/data信号控制

  16. 代码较长,省略部分源代码……

  17.          sccb_write_reg8 (0x0e,0x65) ; //COM5

  18.          sccb_write_reg8 (0x09,0x00) ; //COM2  Bit[1:0] 输出电流驱动能力      

  19.          return 0;

  20.      }   

  21. }
复制代码
该函数读取寄存器地址0x0B和0x0A来获取OV7725摄像头的ID,注意OV7725摄像头的ID为“0X7721”。如果获取的ID不等于0x7721,则返回1;否则开始对OV7725摄像头进行配置,配置的方法参考OV公司提供了OV7725的软件使用手册,直至配置完成后返回0。
下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将USB UART接口与电脑连接,用于串口通信。使用FPC排线一端与RGB LCD液晶屏上的接口连接,另一端连接领航者底板上的RGB TFT-LCD接口。接下来将OV7725摄像头模块插在领航者Zynq开发板的“OLED/CAMERA”插座上,注意在连接时,摄像头镜头方向朝外,如图 21.5.1所示。最后连接开发板的电源,并打开电源开关。
image044.jpg
图 0.1 摄像头镜头方向朝外
在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的BIT文件,来对PL进行配置。最后下载软件程序,下载完成后,在下方的SDK Terminal中可以看到应用程序打印的信息,如下图所示:
image046.jpg
图 0.2 串口打印的信息
同时,RGB LCD液晶屏上显示出OV7725摄像头采集的图像,说明本次OV7725摄像头RGB LCD屏显示的实验在领航者ZYQN开发板上验证成功。需要注意的是,由于OV7725最大输出的分辨率是640*480,本次实验连接的RGB LCD液晶屏的分辨率为800*480,因此液晶屏的右侧填充了黑色区域。如图 21.5.3所示。
image048.jpg
图 0.3 RGB LCD屏显示图像数据
实验的最后再补充一点,我们在动态同步锁相模式下,设置不同的帧数量做了测试。当帧缓存的数量小于等于2,在摄像头前快速移动时,会出现比较明显的卡顿(即前面说的割裂)现象;当帧缓存的数据大于等于3时,可以解决这一问题,且设置的值超过3时,也观察不到显示效果的提升,和前面分析的一致。



正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

0

主题

39

帖子

0

精华

新手入门

积分
5
金钱
5
注册时间
2021-6-13
在线时间
5 小时
发表于 2021-6-14 14:00:41 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-10-3 20:20

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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