正点原子 发表于 2020-12-1 15:54:18

【正点原子FPGA连载】第三十三章OV5640摄像头RGB-LCD显示实验

本帖最后由 正点原子 于 2021-1-23 15:52 编辑

1)实验平台:正点原子达芬奇FPGA开发板
2)购买链接:https://detail.tmall.com/item.htm?id=624335496505
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入:





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


OV5640同OV7725一样,都是OmniVision(豪威科技)公司生产的CMOS图像传感器。不同的是,OV5640支持更高的分辨率、采集速率,具有更高的图像处理性能,主要应用在手机、数码相机、电脑多媒体等领域。本章将使用FPGA开发板实现对OV5640的数字图像采集并通过LCD实时显示。
本章包括以下几个部分:
3333.1简介
33.2实验任务
33.3硬件设计
33.4程序设计
33.5下载验证



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

图 33.1.1 OV5640功能框图
由上图可知,时序发生器(timing generator)控制着感光阵列(image array)、放大器(AMP)、AD转换以及输出外部时序信号(VSYNC、HREF和PCLK),外部时钟XVCLK经过PLL锁相环后输出的时钟作为系统的控制时钟;感光阵列将光信号转化成模拟信号,经过增益放大器之后进入10位AD转换器;AD转换器将模拟信号转化成数字信号,并且经过ISP进行相关图像处理,最终输出所配置格式的10位视频数据流。增益放大器控制以及ISP等都可以通过寄存器(registers)来配置,配置寄存器的接口就是SCCB接口,该接口协议兼容IIC协议。
OV5640使用的是两线式SCCB接口总线,有关SCCB总线的详细介绍可以参考“OV7725摄像头RGB-LCD显示实验”中OV7725简介部分。虽然OV5640和OV7725都是采用SCCB接口总线来配置寄存器,但不同的是,OV7725是用8位(1个字节)来表示寄存器地址,而OV5640是用16位(两个字节)表示寄存器地址。
OV5640 SCCB的写传输协议如下图所示:

图 33.1.2 SCCB写传输协议
上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写 1:读),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”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。
表 33.1.1 OV5640关键寄存器配置说明


OV5640的寄存器较多,对于其它寄存器的描述可以参OV5640的数据手册。需要注意的是,OV5640的数据手册并没有提供全部的寄存器描述,而大多数必要的寄存器配置在ov5640的软件应用手册中可以找到,可以结合这两个手册学习如何对OV5640进行配置。
输出图像参数设置
接下来,我们介绍一下OV5640的ISP输入窗口设置、预缩放窗口设置和输出大小窗口设置,这几个设置与我们的正常使用密切相关,有必要了解一下,它们的设置关系如下图所示:

图 33.1.3 图像窗口设置
ISP输入窗口设置(ISP Input Size)允许用户设置整个传感器显示区域(physical pixel size,2632*1951,其中2592*1944像素是有效的),开窗范围从0*0~2632*1951都可以任意设置。也就是上图中的X_ADDR_ST(寄存器地址0x3800、0x3801)、Y_ADDR_ST(寄存器地址0x3802、0x3803)、X_ADDR_END(寄存器地址0x3804、0x3805)和Y_ADDR_END(寄存器地址0x3806、0x3807)寄存器。该窗口设置范围中的像素数据将进入ISP进行图像处理。
预缩放窗口设置(pre-scaling size)允许用户在ISP输入窗口的基础上进行裁剪,用于设置将进行缩放的窗口大小,该设置仅在ISP输入窗口内进行X/Y方向的偏移。可以通过X_OFFSET(寄存器地址0x3810、0x3811)和Y_OFFSET(寄存器地址0x3812、0x3813)进行配置。
输出大小窗口设置(data output size)是在预缩放窗口的基础上,经过内部DSP进行缩放处理,并将处理后的数据输出给外部的图像窗口,图像窗口控制着最终的图像输出尺寸。可以通过X_OUTPUT_SIZE(寄存器地址0x3808、0x3809)和Y_OUTPUT_SIZE(寄存器地址0x380A、0x380B)进行配置。注意:当输出大小窗口与预缩放窗口比例不一致时,图像将进行缩放处理(图像变形),仅当两者比例一致时,输出比例才是1:1(正常图像)。
图 33.1.3 图像窗口设置中,右侧data output size区域,才是OV5640输出给外部的图像尺寸,也就是显示在显示器或者液晶屏上面的图像大小。输出大小窗口与预缩放窗口比例不一致时,会进行缩放处理,在显示器上面看到的图像将会变形。
输出像素格式
OV5640支持多种不同的数据像素格式,包括YUV(亮度参量和色度参量分开表示的像素格式)、RGB(其中RGB格式包含RGB565、RGB555等)以及RAW(原始图像数据),通过寄存器地址0x4300配置成不同的数据像素格式。
由于摄像头采集的图像最终要通过LCD显示,故我们将OV5640摄像头输出的图像像素数据配置成RGB565格式。由上表(表 33.1.1)可知,将寄存器0x4300寄存器的Bit设置成0x6即可。OV5640支持调节RGB565输出格式中各颜色变量的顺序,对于我们常见的应用来说,一般是使用RGB或BGR序列。我们在“OV7725摄像头RGB-LCD显示实验”的章节中介绍过,OV7725摄像头按照RGB的顺序输出,本章我们将OV5640输出的RGB565的颜色顺序和OV7725保持一致,将寄存器0x4300寄存器的Bit设置成0x1。因此,“OV7725摄像头RGB-LCD显示实验”章节中的图像采集模块可以直接用来采集OV5640输出的图像。
彩条测试模式
图像传感器配置成彩条测试模式后,会输出彩色的条纹,方便测试图像传感器是否正常工作,通过配置寄存器0x503d的Bit位打开和关闭彩条模式。当需要打开彩条模式时,寄存器0x503d配置成0x80,关闭时配置成0x00,下图为打开彩条模式后图像输出的条纹。

图 33.1.4 彩条模式下的图像条纹
LED闪光灯
当外界环境光较暗时,传感器采集图像会受到较大影响,此时可以通过打开LED补光灯来弥补光照不足所带来的影响,就像手机在夜晚拍照时也会打开闪光灯来提高图像质量。通过配置寄存器0x3016=0x02,0x301c=0x02来使能LED补光灯功能;配置寄存器0x3019=0x02打开闪光灯,0x3019=0x00关闭闪光灯。
图像输出时序
接下来,我们介绍一下OV5640的图像数据输出时序,首先我们简单介绍一些定义。
QSXGA,这里指:分辨率为2592*1944的输出格式,类似的还有:QXGA(2048*1536)、UXGA(1600*1200)、SXGA(1280*1024)、WXGA(1440*900)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、VGA(640*480)、QVGA(320*240)和QQVGA(160*120)等。
PCLK:像素时钟,一个PCLK时钟输出一个像素或者半个像素(像素数据的高8位或者低8位)。
VSYNC:帧同步信号。
HREF/HSYNC:行同步信号。
D:像素数据,在RGB565格式中,只有高8位是有效的。
tPclk:一个时钟周期 。
tp:一个像素点的周期,在RGB565和YUV422输出格式下,tp=2*tPclk;Raw输出格式下,tp=tPclk。
下图为OV5640输出图像数据的行时序图。

图 33.1.5 OV5640行时序图
从上图可以看出,传感器在HREF为高电平的时候输出图像数据,当HREF变高后,每一个 PCLK时钟,输出一个8位或者10位像素数据。比如我们采用QSXGA时序,RGB565格式输出,tp=2*tPclk,每2个字节组成一个像素的颜色,这样每行总共输出2592*2个PCLK,也就是2592*2个字节。
再来看看帧时序(QSXGA模式,分辨率2592*1944),如下图所示:

图 33.1.6 OV5640 QSXGA帧时序
由上图可知,VSYNC的上升沿作为一帧的开始,高电平同步脉冲的时间为5688tp,紧接着等待48276tp时间后,HREF开始拉高,此时输出有效数据;HREF由2592tp个高电平和252tp个低电平构成;最后一行图像数据输出完成之后等待14544tp时间,一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame = 5596992tp。
从OV5640的行时序图和帧时序图可以发现,其输出时序和OV7725是非常相似的,只是时间参数不同而已,大家可以参考“OV7725摄像头RGB-LCD显示实验”中帧时序的介绍来学习OV5640的输出时序。
33.2实验任务
本节实验任务是使用达芬奇开发板及OV5640摄像头实现图像采集,并通过RGB-LCD接口驱动RGB-LCD液晶屏(支持目前正点原子推出的所有RGB-LCD屏),并实时显示出图像。
33.3硬件设计
我们的达芬奇FPGA开发板上有一个摄像头扩展接口,该接口可以用来连接OV7725/OV5640等摄像头模块,摄像头扩展接口原理图如下图所示:

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

图 33.3.2 OV5640摄像头连接开发板图
从图 33.3.2可以看出,模块自带有源晶振,用于产生24MHz时钟作为OV5640的输入时钟。模块的闪光灯(LEDI&LED2)由OV5640的STROBE引脚控制,用户可通过SCCB接口总线控制STROBE引脚输出高低电平,从而控制LED闪光灯的亮灭。用户在使用LED灯时不建议一直点亮或者点亮时间太长。因为LED闪光灯功率较高,发光强度较强,模块温度上升会比较快,会造成器件的可靠性降低,同时注意避免直接照射人眼。
我们在前面说过,OV5640在RGB565模式下只有高8位数据是有效的即D,而我们的摄像头排针上数据引脚的个数是8位。实际上,摄像头排针上的8位数据连接的就是OV5640传感器的D,所以我们直接使用摄像头排针上的8位数据引脚即可。
由于LCD接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配,如下表所示:
表 33.3.1 OV5640摄像头管脚分配

摄像头XDC约束文件如下:
create_clock -period 40.000 -name cmos_pclk
set_property CLOCK_DEDICATED_ROUTE FALSE
set_property -dict {PACKAGE_PIN F14 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN C15 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN F16 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN C14 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN B13 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN C13 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN D14 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33 IOB TRUE} }]
set_property -dict {PACKAGE_PIN E16 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN B15 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN D16 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN B16 IOSTANDARD LVCMOS33}
33.4程序设计
图 33.4.1是根据本章实验任务画出的系统框图。对比“OV7725摄像头RGB-LCD显示实验”的系统框图可以发现,本次实验只是把外设OV7725模块替换成了OV5640模块,将图像图像采集顶层模块替换成OV5640的驱动模块,并增加了一个摄像头图像分辨率设置模块,其余模块基本相同。图像采集顶层模块和OV5640驱动模块的区别在于,OV5640驱动模块删除了摄像头裁剪模块。这是由于OV5640摄像头分辨率的配置更灵活,可以与LCD屏的分辨率配置达成一致,因此本次实验不需要对摄像头图像进行裁剪。除此之外,本次实验还新增了一个摄像头图像分辨率设置模块,这个模块会根据LCD屏的ID,为OV5640驱动模块在配置摄像头分辨率时提供分辨率参数,也为DDR控制器模块提供最大读写地址。时钟模块为OV5640驱动模块、LCD顶层模块以及DDR控制器模块提供驱动时钟;OV5640驱动模块负责驱动外设OV5640摄像头和采集摄像头图像数据,并且把图像数据写入DDR控制模块;DDR控制模块负责将用户数据写入和读出片外DDR3存储器;LCD顶层模块负责驱动LCD屏和读取器件ID。
OV5640摄像头LCD显示系统框图如下图所示:

图 33.4.1 顶层系统框图
由上图可知,时钟模块(clk_wiz_0)为LCD顶层模块、图像分辨率设置模块、DDR控制模块以及OV5640驱动模块提供驱动时钟,OV5640驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入DDR控制模块,LCD顶层模块从DDR控制模块中读出数据,完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在DDR3和传感器都初始化完成之后才开始输出数据的,避免了在DDR3初始化过程中向里面写入数据。
顶层模块的原理图如下图所示:

图 33.4.2 顶层模块原理图
FPGA顶层模块(ov5640_lcd)例化了以下五个模块:时钟模块(clk_wiz_0)、OV5640驱动模块(ov5640_dri)、摄像头图像分辨率设置模块(picture_size)、DDR控制模块(ddr3_top)和LCD顶层模块(lcd_rgb_top)。
时钟模块(clk_wiz_0):时钟模块通过调用MMCM IP核实现,共输出2个时钟,频率分别为200Mhz(DDR3参考时钟)和50Mhz时钟。200Mhz时钟作为DDR控制模块的参考时钟,由MIG IP核产生的ui_clk(本次设计为100Mhz)作为DDR控制模块的驱动时钟,50Mhz时钟作为OV5640驱动模块、摄像头图像分辨率设置模块和LCD顶层模块的驱动时钟。
OV5640驱动模块(ov5640_dri):OV5640驱动模块负责驱动OV5640 SCCB接口总线,将像素时钟驱动下的传感器输出的场同步信号、行同步信号以及8位数据转换成DDR读写控制模块的写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。
图像分辨率设置模块(picture_size):图像尺寸配置模块用于配置摄像头输出图像尺寸的大小,此外还完成了DDR3的读写结束地址设置。
DDR控制模块(ddr3_top):DDR读写控制器模块负责驱动DDR片外存储器,缓存图像传感器输出的图像数据。该模块将MIG IP核复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。
有关DDR控制模块的详细介绍请大家参考“OV7725摄像头RGB-LCD显示实验”章节。
LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。有关LCD驱动模块的详细介绍请大家参考“OV7725摄像头RGB-LCD显示实验”章节。
顶层模块大部分的代码在介绍“OV7725摄像头RGB-LCD显示实验”章节时已经介绍过了,这里不再详述,但还有部分代码做了改动,改动的代码如下:
90//摄像头图像分辨率设置模块
91picture_size u_picture_size (
92      .rst_n            (rst_n),
93      .clk                (clk_50m),   
94      .ID_lcd             (lcd_id),         //LCD的器件ID
95                        
96      .cmos_h_pixel       (h_disp),         //摄像头水平分辨率
97      .cmos_v_pixel       (v_disp),         //摄像头垂直分辨率
98      .total_h_pixel      (total_h_pixel ),   //水平总像素大小
99      .total_v_pixel      (total_v_pixel ),   //垂直总像素大小
100   .sdram_max_addr   (ddr3_addr_max)   //ddr3最大读写地址
101   );
这段代码是对图像分辨率设置模块的一个顶层例化,图像尺寸配置模块用于配置摄像头输出图像尺寸的大小,此外还完成了DDR3的读写结束地址设置。
在程序的第96和第99行,其中信号h_disp和信号v_disp是摄像头将要配置的分辨率,信号total_h_pixel和信号total_v_pixel连接到OV5640驱动模块,这两个信号的配置最终会影响摄像头的帧率。
在程序的第100行,表示的是写入一块DDR3的最大读写地址。
103 //ov5640 驱动
104 ov5640_dri u_ov5640_dri(
105   .clk               (clk_50m),
106   .rst_n             (rst_n),
107
108   .cam_pclk          (cam_pclk ),
109   .cam_vsync         (cam_vsync),
110   .cam_href          (cam_href ),
111   .cam_data          (cam_data ),
112   .cam_rst_n         (cam_rst_n),
113   .cam_pwdn          (cam_pwdn ),
114   .cam_scl         (cam_scl),
115   .cam_sda         (cam_sda),
116   
117   .capture_start   (init_calib_complete),
118   .cmos_h_pixel      (h_disp),
119   .cmos_v_pixel      (v_disp),
120   .total_h_pixel   (total_h_pixel),
121   .total_v_pixel   (total_v_pixel),
122   .cmos_frame_vsync(),
123   .cmos_frame_href   (),
124   .cmos_frame_valid(cmos_frame_valid),
125   .cmos_frame_data   (wr_data)
126   );   
这段代码是对OV5640驱动模块的一个顶层例化,负责驱动OV5640外设,采集图像等作用。
在程序的第108行至第115行,这几行的信号都是与外设OV5640相连的,负责驱动摄像头。
在程序的第117行,这个信号的作用是保证OV5640驱动模块在DDR3初始化没有完成之前不会对图像进行采集。
在程序的第118行至第121行,这四个信号是摄像头配置时需要的参数,使摄像头输出本次实验需要的行场分辨率和计算帧率的相关参数。
在程序的第124行和第125行,OV5640驱动模块输出的cmos_frame_valid(数据有效使能信号)和wr_data(有效数据)连接到DDR控制模块,实现了图像数据的缓存。
134   .app_addr_rd_min       (28'd0),               //读DDR3的起始地址
135   .app_addr_rd_max       (ddr3_addr_max),   //读DDR3的结束地址
136   .rd_bust_len         (h_disp),          //从DDR3中读数据时的突发长度
137   .app_addr_wr_min       (28'd0),               //写DDR3的起始地址
138   .app_addr_wr_max       (ddr3_addr_max),   //写DDR3的结束地址
139   .wr_bust_len         (v_disp),          //从DDR3中写数据时的突发长度
在程序的第135和第138行,因为本次实验用了一块DDR3,DDR的数据位宽为16bit,而OV5640驱动模块的输出数据的位宽为16bit,所以最大读写地址就是摄像头图像分辨率设置模块输出的最大地址。
在程序的第136和第139行,信号(wr_bust_len)和信号(rd_bust_len)表示一次向DDR3读或写的长度,因为输入DDR3顶层模块的数据和DDR3顶层模块输出的数据都为16bit,而MIG的数据位宽为128bit,所以一次写或读一行的长度是水平分辨率除以8(128bit/16bit),这里将wr_bust_len和rd_bust_len的低三位舍去,相当于信号右移3位,等同于除以8。
图像尺寸配置模块用于配置摄像头输出图像尺寸的大小,此外还完成了DDR3的读写结束地址设置。图像分辨率设置模块的代码如下:
1module picture_size (
2      input            rst_n       ,
3      input            clk         ,         
4      input       ID_lcd      ,
5               
6      output    cmos_h_pixel   ,
7      output    cmos_v_pixel   ,   
8      output    total_h_pixel,
9      output    total_v_pixel,
10   output    sdram_max_addr
11 );
12
13 reg cmos_h_pixel;
14 reg cmos_v_pixel;   
15 reg total_h_pixel;
16 reg total_v_pixel;
17 reg sdram_max_addr;
18
19 //parameter define
20 parameterID_4342 =   16'h4342;
21 parameterID_7084 =   16'h7084;
22 parameterID_7016 =   16'h7016;
23 parameterID_1018 =   16'h1018;
24
25 //*****************************************************
26 //**                  main code                     
27 //*****************************************************
28
29 //配置摄像头输出尺寸的大小
30 always @(posedge clk or negedge rst_n) begin
31   if(!rst_n) begin
32         cmos_h_pixel <= 13'b0;
33         cmos_v_pixel <= 13'd0;
34         sdram_max_addr <= 23'd0;      
35   end
36   else begin   
37         case(ID_lcd )
38             16'h4342 : begin
39               cmos_h_pixel   = 13'd480;   
40               cmos_v_pixel   = 13'd272;
41               sdram_max_addr = 23'd130560;
42             end
43             16'h7084 : begin
44               cmos_h_pixel   = 13'd800;   
45               cmos_v_pixel   = 13'd480;         
46               sdram_max_addr = 23'd384000;
47             end
48             16'h7016 : begin
49               cmos_h_pixel   = 13'd1024;   
50               cmos_v_pixel   = 13'd600;         
51               sdram_max_addr = 23'd614400;
52             end   
53             16'h1018 : begin
54               cmos_h_pixel   = 13'd1280;   
55               cmos_v_pixel   = 13'd800;         
56               sdram_max_addr = 23'd1024000;
57             end
58         default : begin
59               cmos_h_pixel   = 13'd800;   
60               cmos_v_pixel   = 13'd480;         
61               sdram_max_addr = 23'd384000;
62         end
63         endcase
64   end   
65 end
66
67 //对HTS及VTS的配置会影响摄像头输出图像的帧率
68 always @(*) begin
69   case(ID_lcd)
70         ID_4342 : begin
71             total_h_pixel = 13'd1800;
72             total_v_pixel = 13'd1000;
73         end
74         ID_7084 : begin
75             total_h_pixel = 13'd1800;
76             total_v_pixel = 13'd1000;
77         end
78         ID_7016 : begin
79             total_h_pixel = 13'd2200;
80             total_v_pixel = 13'd1000;
81         end
82         ID_1018 : begin
83             total_h_pixel = 13'd2570;
84             total_v_pixel = 13'd980;
85         end
86   default : begin
87             total_h_pixel = 13'd1800;
88             total_v_pixel = 13'd1000;
89   end
90   endcase
91 end
92
93 endmodule
在代码的第29至第65行,根据输入的LCD屏ID对摄像头输出图像尺寸相关的参数(cmos_h_pixel、cmos_v_pixel)进行了配置。同时还配置了一帧图像在一块DDR3里的结束缓存地址sdram_max_addr,它是cmos_h_pixel、cmos_v_pixel的乘积。
在代码的67至91行,对影响帧率的参数total_h_pixel、total_v_pixel进行了配置。
本次实验的OV5640驱动模块包含以下三个模块,原理图如下:

图 33.4.3 OV5640驱动模块原理图
由上图可知,OV5640驱动模块包含I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov5640_rgb565_cfg)和图像采集模块(cmos_capture_data)。
I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV5640 SCCB接口总线,用户可根据该模块提供的用户接口可以很方便的对OV5640的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
I2C配置模块(i2c_ov5640_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV5640的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV5640传感器的初始化。
图像采集模块(cmos_capture_data):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成DDR读写控制模块的写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。OV5640和OV7725图像输出时序非常相似,有关该模块的详细介绍请大家参考“OV7725摄像头RGB-LCD显示实验”章节。
OV5640驱动模块的代码如下:
1   module ov5640_dri (
2       input         clk             ,//时钟
3       input         rst_n         ,//复位信号,低电平有效
4       //摄像头接口
5       input         cam_pclk      ,//cmos 数据像素时钟
6       input         cam_vsync       ,//cmos 场同步信号
7       input         cam_href      ,//cmos 行同步信号
8       input    cam_data      ,//cmos 数据
9       output          cam_rst_n       ,//cmos 复位信号,低电平有效
10      output          cam_pwdn      ,//cmos 电源休眠模式选择信号
11      output          cam_scl         ,//cmos SCCB_SCL线
12      inout         cam_sda         ,//cmos SCCB_SDA线
13      
14      //摄像头分辨率配置接口
15      input    cmos_h_pixel    ,//水平方向分辨率
16      input    cmos_v_pixel    ,//垂直方向分辨率
17      input    total_h_pixel   ,//水平总像素大小
18      input    total_v_pixel   ,//垂直总像素大小
19      input         capture_start   ,//图像采集开始信号
20      output          cam_init_done   ,//摄像头初始化完成
21      
22      //用户接口
23      output          cmos_frame_vsync,//帧有效信号   
24      output          cmos_frame_href ,//行有效信号
25      output          cmos_frame_valid,//数据有效使能信号
26      outputcmos_frame_data    //有效数据
27);
28
29//parameter define
30parameter SLAVE_ADDR = 7'h3c          ; //OV5640的器件地址7'h3c
31parameter BIT_CTRL   = 1'b1         ; //OV5640的字节地址为16位0:8位 1:16位
32parameter CLK_FREQ   = 27'd50_000_000 ; //i2c_dri模块的驱动时钟频率
33parameter I2C_FREQ   = 18'd250_000    ; //I2C的SCL时钟频率,不超过400KHz
34
35//wire difine
36wire      i2c_exec       ;//I2C触发执行信号
37wire i2c_data       ;//I2C要配置的地址与数据(高8位地址,低8位数据)         
38wire      i2c_done       ;//I2C寄存器配置完成信号
39wire      i2c_dri_clk    ;//I2C操作时钟
40wire [ 7:0] i2c_data_r   ;//I2C读出的数据
41wire      i2c_rh_wl      ;//I2C读写控制信号
42
43//*****************************************************
44//**                  main code                     
45//*****************************************************
46
47//电源休眠模式选择 0:正常模式 1:电源休眠模式
48assigncam_pwdn= 1'b0;
49assigncam_rst_n = 1'b1;
50      
51//I2C配置模块
52i2c_ov5640_rgb565_cfg u_i2c_cfg(
53      .clk                (i2c_dri_clk),
54      .rst_n            (rst_n),
55            
56      .i2c_exec         (i2c_exec),
57      .i2c_data         (i2c_data),
58      .i2c_rh_wl          (i2c_rh_wl),      //I2C读写控制信号
59      .i2c_done         (i2c_done),
60      .i2c_data_r         (i2c_data_r),   
61                  
62      .cmos_h_pixel       (cmos_h_pixel),   //CMOS水平方向像素个数
63      .cmos_v_pixel       (cmos_v_pixel) ,    //CMOS垂直方向像素个数
64      .total_h_pixel      (total_h_pixel),    //水平总像素大小
65      .total_v_pixel      (total_v_pixel),    //垂直总像素大小
66         
67      .init_done          (cam_init_done)
68      );   
69
70//I2C驱动模块
71i2c_dri #(
72      .SLAVE_ADDR         (SLAVE_ADDR),       //参数传递
73      .CLK_FREQ         (CLK_FREQ),            
74      .I2C_FREQ         (I2C_FREQ)
75      )
76u_i2c_dr(
77      .clk                (clk),
78      .rst_n            (rst_n   ),
79
80      .i2c_exec         (i2c_exec),   
81      .bit_ctrl         (BIT_CTRL),   
82      .i2c_rh_wl          (i2c_rh_wl),      //固定为0,只用到了IIC驱动的写操作   
83      .i2c_addr         (i2c_data),   
84      .i2c_data_w         (i2c_data),   
85      .i2c_data_r         (i2c_data_r),   
86      .i2c_done         (i2c_done),
87      
88      .scl                (cam_scl   ),   
89      .sda                (cam_sda   ),   
90
91      .dri_clk            (i2c_dri_clk)       //I2C操作时钟
92      );
93
94//CMOS图像数据采集模块
95cmos_capture_data u_cmos_capture_data(      //系统初始化完成之后再开始采集数据
96      .rst_n            (rst_n & capture_start),
97      
98      .cam_pclk         (cam_pclk),
99      .cam_vsync          (cam_vsync),
100   .cam_href         (cam_href),
101   .cam_data         (cam_data),         
102   
103   .cmos_frame_vsync   (cmos_frame_vsync),
104   .cmos_frame_href    (cmos_frame_href ),
105   .cmos_frame_valid   (cmos_frame_valid), //数据有效使能信号
106   .cmos_frame_data    (cmos_frame_data )//有效数据
107   );
108
109 endmodule
在代码的第30行定义了OV5640的器件地址,其器件地址为7’h3c;第31行定义了寄存器地址的位宽,BIT_CTRL=0表示地址位宽为8位,BIT_CTRL=1表示地址位宽为16位。因为OV5640的地址位宽为16位,所以BIT_CTRL设置为1。
在代码的第62行至65行,添加了四个信号,用以配置摄像头输出的分辨率和计算帧率的相关参数。
OV5640和OV7725的寄存器配置差异较大,首先是OV5640是用16位数据来表示寄存器地址,而OV7725用8位数据表示寄存器地址。其次是OV5640集成更强大的图像处理功能,为了使OV5640输出比较清晰的图像,需要配置的寄存器更多。OV5640的寄存器配置模块部分代码如下:
1   module i2c_ov5640_rgb565_cfg
2      (
3       input                clk      ,   //时钟信号
4       input                rst_n    ,   //复位信号,低电平有效
5      
6       input         i2c_data_r,    //I2C读出的数据
7       input                i2c_done ,   //I2C寄存器配置完成信号
8       input      cmos_h_pixel ,
9       input      cmos_v_pixel ,
10      input      total_h_pixel, //水平总像素大小
11      input      total_v_pixel, //垂直总像素大小
12      outputreg          i2c_exec ,   //I2C触发执行信号   
13      outputregi2c_data ,   //I2C要配置的地址与数据(高16位地址,低8位数据)
14      outputreg          i2c_rh_wl,   //I2C读写控制信号
15      outputreg          init_done      //初始化完成信号
16      );
17
18//parameter define
19localparamREG_NUM = 8'd250;       //总共需要配置的寄存器个数
20
21//reg define
22reg      start_init_cnt;      //等待延时计数器
23reg       init_reg_cnt;      //寄存器配置个数计数器
24
25//*****************************************************
26//**                  main code
27//*****************************************************
28
29//clk时钟配置成250khz,周期为4us 5000*4us = 20ms
30//OV5640上电到开始配置IIC至少等待20ms
31always @(posedge clk or negedge rst_n) begin
32      if(!rst_n)
33          start_init_cnt <= 13'b0;
34      else if(start_init_cnt < 13'd5000) begin
35          start_init_cnt <= start_init_cnt + 1'b1;                  
36      end
37end
38
39//寄存器配置个数计数   
40always @(posedge clk or negedge rst_n) begin
41      if(!rst_n)
42          init_reg_cnt <= 8'd0;
43      else if(i2c_exec)   
44          init_reg_cnt <= init_reg_cnt + 8'b1;
45end
46
47//i2c触发执行信号   
48always @(posedge clk or negedge rst_n) begin
49      if(!rst_n)
50          i2c_exec <= 1'b0;
51      else if(start_init_cnt == 13'd4999)
52          i2c_exec <= 1'b1;
53      else if(i2c_done && (init_reg_cnt < REG_NUM))
54          i2c_exec <= 1'b1;
55      else
56          i2c_exec <= 1'b0;
57end
58
59//配置I2C读写控制信号
60always @(posedge clk or negedge rst_n) begin
61      if(!rst_n)
62          i2c_rh_wl <= 1'b1;
63      else if(init_reg_cnt == 8'd2)
64          i2c_rh_wl <= 1'b0;
65end
66
67//初始化完成信号
68always @(posedge clk or negedge rst_n) begin
69      if(!rst_n)
70          init_done <= 1'b0;
71      else if((init_reg_cnt == REG_NUM) && i2c_done)
72          init_done <= 1'b1;
73end
74
75//配置寄存器地址与数据
76always @(posedge clk or negedge rst_n) begin
77      if(!rst_n)
78          i2c_data <= 24'b0;
79      else begin
80          case(init_reg_cnt)
81            //先对寄存器进行软件复位,使寄存器恢复初始值
82            //寄存器软件复位后,需要延时1ms才能配置其它寄存器
83            8'd0: i2c_data <= {16'h300a,8'h0}; //
84            8'd1: i2c_data <= {16'h300b,8'h0}; //
配置代码较长,省略部分源代码……

295             8'd204: i2c_data <= {16'h5025,8'h00};
296             //系统时钟分频 Bit:系统时钟分频 input clock =24Mhz, PCLK = 48Mhz
297             8'd205: i2c_data <= {16'h3035,8'h11};
298             8'd206: i2c_data <= {16'h3036,8'h3c}; //PLL倍频
299             8'd207: i2c_data <= {16'h3c07,8'h08};
300             //时序控制 16'h3800~16'h3821
301             8'd208: i2c_data <= {16'h3820,8'h46};
302             8'd209: i2c_data <= {16'h3821,8'h01};
303             8'd210: i2c_data <= {16'h3814,8'h31};
304             8'd211: i2c_data <= {16'h3815,8'h31};
305             8'd212: i2c_data <= {16'h3800,8'h00};
306             8'd213: i2c_data <= {16'h3801,8'h00};
307             8'd214: i2c_data <= {16'h3802,8'h00};
308             8'd215: i2c_data <= {16'h3803,8'h04};
309             8'd216: i2c_data <= {16'h3804,8'h0a};
310             8'd217: i2c_data <= {16'h3805,8'h3f};
311             8'd218: i2c_data <= {16'h3806,8'h07};
312             8'd219: i2c_data <= {16'h3807,8'h9b};
313             //设置输出像素个数
314             //DVP 输出水平像素点数高4位
315             8'd220: i2c_data <= {16'h3808,{4'd0,cmos_h_pixel}};
316             //DVP 输出水平像素点数低8位
317             8'd221: i2c_data <= {16'h3809,cmos_h_pixel};
318             //DVP 输出垂直像素点数高3位
319             8'd222: i2c_data <= {16'h380a,{5'd0,cmos_v_pixel}};
320             //DVP 输出垂直像素点数低8位
321             8'd223: i2c_data <= {16'h380b,cmos_v_pixel};
322             //水平总像素大小高5位
323             8'd224: i2c_data <= {16'h380c,{3'd0,total_h_pixel}};
324             //水平总像素大小低8位
325             8'd225: i2c_data <= {16'h380d,total_h_pixel};
326             //垂直总像素大小高5位
327             8'd226: i2c_data <= {16'h380e,{3'd0,total_v_pixel}};
328             //垂直总像素大小低8位   
329             8'd227: i2c_data <= {16'h380f,total_v_pixel};
配置代码较长,省略部分源代码……

350             8'd246: i2c_data <= {16'h3016,8'h02};
351             8'd247: i2c_data <= {16'h301c,8'h02};
352             8'd248: i2c_data <= {16'h3019,8'h02}; //打开闪光灯
353             8'd249: i2c_data <= {16'h3019,8'h00}; //关闭闪光灯
354             //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
355             default : i2c_data <= {16'h300a,8'h00}; //器件ID高8位
356         endcase
357   end
358 end
359
360 endmodule
I2C配置模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束。需要注意的是,由OV5640的数据手册可知,图像传感器上电后到开始配置寄存器需要延时20ms,所以程序中定义了一个延时计数器(start_init_cnt),用于延时20ms。当计数器计数到预设值之后,开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。在代码的第19行定义了总共需要配置的寄存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。
在程序的第313行至第329行,是对摄像头需要输出的行场分辨率和行场总像素进行设置的寄存器配置。
我们在前面说过,本章实验程序设计和“OV7725摄像头RGB-LCD显示实验”相比,我们只是把外设OV7725模块替换成了OV5640模块,将图像图像采集顶层模块替换成OV5640的驱动模块,并增加了一个摄像头图像分辨率设置模块,其余模块基本相同。本章节主要介绍下I2C配置模块,而其它相同模块的程序设计详情请大家参考“OV7725摄像头RGB-LCD显示实验”章节。
33.5下载验证
首先将FPC排线一端与RGB-LCD模块上的J1接口连接,另一端与达芬奇开发板上的RGB-LCD接口连接。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线,如图 33.5.1和图 33.5.2所示。

图 33.5.1 正点原子RGBLCD模块FPC连接器

图 33.5.2 达芬奇开发板连接RGB-LCD液晶屏
接下来分别连接JTAG接口和电源线,并打开电源开关。
最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。
接下来我们下载程序,验证OV5640 RGB-LCD实时显示功能。下载完成后观察RGB-LCD模块显示的图案如下图所示,说明OV5640 RGB-LCD实时显示程序下载验证成功。

图 33.5.3 RGB-LCD实时显示图像

页: [1]
查看完整版本: 【正点原子FPGA连载】第三十三章OV5640摄像头RGB-LCD显示实验