搜索
bottom↓
回复: 0

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

[复制链接]

出0入234汤圆

发表于 2020-12-2 11:06:49 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2021-1-23 15:47 编辑

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 点击加入:
QQ群头像.png

100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

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




MT9V034是ON Semiconductor(安森美半导体)公司生产的一颗CMOS图像传感器,该传感器功耗低、可靠性高以及采集速率快,主要应用机器视觉,双目视觉,宽温度工业场合等领域。本章我们将使用FPGA开发板实现对MT9V034的数字图像采集并通过LCD实时显示。
本章包括以下几个部分:
3535.1简介
35.2实验任务
35.3硬件设计
35.4程序设计
35.5下载验证


35.1简介
MT9V034是一款1/3英寸单芯片图像传感器,其感光阵列最大可达到752*480,能实现最快60fps VGA分辨率的图像采集,具有全局曝光和高动态范围(HDR)操作。这款CMOS图像传感器具有安森美半导体的突破性功能,实现了CCD图像质量的低噪声和CMOS成像技术(基于信噪比和低光灵敏度),同时保持固有尺寸、成本和CMOS的集成优势。下表为几个摄像头的功能对比。
3511.png

通过上述的对比可以看出,相对于其他2款摄像头MT9V034的优势在于HDR模式和全局曝光。HDR模式的原理是根据不同的曝光时间的LDR(Low-Dynamic Range)图像,利用每个曝光时间相对应最佳细节的LDR图像来合成最终HDR图像,与普通的图像相比,可以提供更多的动态范围和图像细节。下图是线性模式和HDR模式的对比图。
OV5640摄像头HDMI显示实验1510.png

图 35.1.1 HDR模式

OV5640摄像头HDMI显示实验1570.png

图 35.1.2 线性模式

通过对比可以发现,开启了 HDR,会使拍到的图像亮度比较高的地方变暗,亮度比较低的地方变亮,总的来说就是使图像显示的更均衡。
卷帘曝光的原理是通过Sensor逐行曝光的方式实现的。在曝光开始的时候,Sensor逐行扫描逐行进行曝光,直至所有像素点都被曝光。与卷帘曝光不同,全局曝光整幅场景在同一时间曝光实现的,Sensor所有像素点同时收集光线,同时曝光。下面为两种模式的对比图。
OV5640摄像头HDMI显示实验1820.png

图 35.1.3 全局曝光拍摄图

OV5640摄像头HDMI显示实验1882.png

图 35.1.4 卷帘曝光拍摄图

当拍摄快速移动的物体时,全局曝光拍摄到的物体比较清晰,不会发生形变,而卷帘曝光拍摄的图片会出现部分曝光(partial exposure)、斜坡图形(skew)、晃动(wobble) 等现象,也就是传说中的果冻了。这是全局曝光其优势的一面,相对于卷帘曝光也有其劣势的一面。当全局曝光的曝光时间长(如大于500μs)时,其噪点情况严重,而运用卷帘曝光后,图片会有更低的噪声和更快的帧速。
下图为MT9V034的功能框图:
OV5640摄像头HDMI显示实验2155.png

图 35.1.5 MT9V034功能框图

由上图可知,除了传统的并行逻辑输出,MT9V034还具有串行低压差分信号(LVDS)输出。该传感器可以在立体声相机中操作,并且该传感器被指定为立体声主机时,可以合并来自本身的立体声,还可以将从属传感器的数据合并为一个串行LVDS流。本次实验只采用传统的并行逻辑输出。
MT9V034通过两线串行接口将寄存器写入MT9V034和从中读取,以此来配置窗口大小和行场分辨率等寄存器。MT9V034是具有四个可能ID(0x90、0x98、0xB0和0xB8)由S_CTRL_ADR0和S_CTRL_ADR1输入引脚确定,本次实验所用的MT9V034的写器件ID是0x90。下图是器件ID的相关内容。
OV5640摄像头HDMI显示实验2517.png

图 35.1.6 器件地址

MT9V034使用的是两线式接口总线,该接口总线包括SCLK串行时钟输入线和SDATA串行双向数据线,分别相当于IIC协议的SCL信号线和SDA信号线。本次实验所用的两线式接口总线兼容IIC协议,是所以不对相关协议详细介绍,有关IIC协议的详细介绍请大家参考“EEPROM读写实验”章节。
两线式接口总线的写传输协议如下图所示:
OV5640摄像头HDMI显示实验2742.png

图 35.1.7写传输协议

上图中的ADDR是由7位器件地址和1位读写控制位构成(0:写 1:读),MT9V034的器件地址为7’h5c,所以在写传输协议中,ID Address(W) = 8’hb8(器件地址左移1位,低位补0);R0x09为8位寄存器地址,在MT9V034的数据手册中有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;Write Data为16位写数据,每一个寄存器地址对应16位的配置数据。上图中的第9位ACK表示从机应答,该位是由从机(此处指MT9V034)发出应答信号来响应主机表示当前器件地址、寄存器地址和写数据是否传输完成,但是从机有可能不发出应答信号,因此主机(此处指FPGA)在此必须判断此处是否有应答,有应答即说明当前传输完成,无应答表示传输未完成。
我们可以发现,MT9V034的两线式接口总线和IIC写传输协议是极为相似的,只是在两线式接口总线写传输协议中,一个寄存器地址写入16位数据,而IIC写传输协议一个地址只写入8位数据。两线式接口总线的读传输协议和IIC有些差异,在IIC读传输协议中,一个寄存器地址只读出8位数据;而两线式接口总线传输协议中一个寄存器地址只读出16位数据,下图为两线式接口总线的读传输协议。
OV5640摄像头HDMI显示实验3326.png

图 35.1.8 SCCB读传输协议

由上图可知,两线式接口总线读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据,注意ID Address(R) = 8’hB9(器件地址左移1位,低位补1)。上图中的NACK位由主机(这里指FPGA)产生,由于两线式接口总线不支持连续读写,因此NACK位必须为高电平。
MT9V034在上电后是存在默认寄存器的,即上电后就可以输出752x480分辨率的图像,如果大家需要其他的分辨率或模式就必须先对传感器进行初始化,可通过配置寄存器使其工作在预期的工作模式,以得到较好画质的图像。因为两线式接口总线的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。ON Semiconductor公司提供了MT9V034的软件使用手册(MT9V034,位于开发板所随附的资料“7_硬件资料/7_MT9V034资料/MT9V034.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。
表 35.1.1 MT9V034关键寄存器配置说明
11.png

22.png

MT9V034的寄存器较多,对于其它寄存器的描述可以参MT9V034的数据手册。
下图为MT9V034的一些特性。
OV5640摄像头HDMI显示实验5068.png

图 35.1.9 MT9V034的特性

从上图可以看出,MT9V034的输入时钟频率的范围是13Mhz~27Mhz;本次实验摄像头的输入时钟为24Mhz,是由外部晶振提供的;两线式接口总线的SCLK的时钟频率最大为400KHz。
OV5640摄像头HDMI显示实验5228.png

图 35.1.10 PIXCLK和SYSCLK的关系

结合图 35.1.10和图 35.1.9可知,PIXCLK和SYSCLK是同频不同相的2个时钟,本次实验摄像头的输入时钟为24Mhz,所以摄像头的输出时钟也为24Mhz。
MT9V034在并行逻辑输出的模式下仅支持10bit的YUV(亮度参量和色度参量分开表示的像素格式),不支持其他格式。由于摄像头采集的图像最终要在LCD上显示,且达芬奇开发板上的数据接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此必须将MT9V034摄像头输出的YUV格式的图像像素数据转换为RGB888格式。下图为摄像头输出的行时序图。
OV5640摄像头HDMI显示实验5614.png

图 35.1.11 行时序图

LINE_VALID:数据有效使能。当其为高时,输出的数据为有效数据。
PIXCLK:像素时钟。由MT9V034产生的对外输出的时钟信号。
DOUT:有效数据。摄像头采集得到的像素数据,本次实验取其高8位。
OV5640摄像头HDMI显示实验5778.png

图 35.1.12 场时序图

OV5640摄像头HDMI显示实验5838.png

图 35.1.13 信号含义

LINE_VALID:数据有效使能。当其为高时,输出的数据为有效数据。
FRAME_VALID:帧(场) 同步信号。当此信号有效的时候就表示开始显示新的一帧数据。
35.2实验任务
本节实验任务是使用达芬奇开发板及MT9V034摄像头实现图像采集,并通过TFT-LCD接口驱动RGB LCD液晶屏(支持目前正点原子推出的所有RGB LCD屏),并实时显示出图像。
35.3硬件设计
达芬奇FPGA开发板上有一个摄像头扩展接口,该接口可以用来连接MT9V034/OV5640等摄像头模块,摄像头扩展接口原理图如图 35.3.1所示:
OV5640摄像头HDMI显示实验6180.png

图 35.3.1 摄像头扩展接口原理图

OV5640摄像头HDMI显示实验6245.png

图 35.3.2 摄像头接口

ATK-MT9V034是正点原子推出的一款高性能36W像素高清摄像头模块。该模块通过2*9排针(2.54mm间距)同外部连接,我们将摄像头的排针直接插在开发板上的摄像头接口即可,如下图所示:
OV5640摄像头HDMI显示实验6401.png

图 35.3.3 MT9V034摄像头连接开发板图

前面说过,MT9V034在YUV模式中有效数据是D[9:0],而我们的摄像头排针上数据引脚的个数是8位,而摄像头排针上的8位数据连接的就是MT9V034传感器的D[9:2],所以我们直接使用摄像头排针上的8位数据引脚即可。
需要注意的是,由图 35.3.1可知,摄像头扩展口的第18个引脚定义为CMOS_PWDN,而我们的MT9V034摄像头模块的STB(CMOS_PWDN)引脚固定为低电平,也就是一直处于正常工作模式。MT9V034摄像头接口的第18个引脚定义为EXP,这个引脚是摄像头外部触发脉冲的引脚,只在快照模式下启用它。MT9V034摄像头模块内部自带24M晶振的,所以不需要FPGA输出时钟给摄像头。
由于LCD接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配,如下表所示:
表 35.3.1 MT9V034摄像头管脚分配
3531.png

摄像头XDC约束文件如下:
create_clock -period 40.000 -name cmos_pclk [get_ports cmos_pclk]
set_property -dict {PACKAGE_PIN C15 IOSTANDARD LVCMOS33} [get_ports cmos_rst_n]
set_property -dict {PACKAGE_PIN F16 IOSTANDARD LVCMOS33} [get_ports cmos_pwdn]
set_property -dict {PACKAGE_PIN C14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[0]}]
set_property -dict {PACKAGE_PIN B13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[1]}]
set_property -dict {PACKAGE_PIN C13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[2]}]
set_property -dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[3]}]
set_property -dict {PACKAGE_PIN D14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[4]}]
set_property -dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[5]}]
set_property -dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[6]}]
set_property -dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cmos_data[7]}]
set_property -dict {PACKAGE_PIN E16 IOSTANDARD LVCMOS33} [get_ports cmos_vsync]
set_property -dict {PACKAGE_PIN B15 IOSTANDARD LVCMOS33} [get_ports cmos_href]
set_property -dict {PACKAGE_PIN F14 IOSTANDARD LVCMOS33} [get_ports cmos_pclk]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cmos_pclk_IBUF]
set_property -dict {PACKAGE_PIN D16 IOSTANDARD LVCMOS33} [get_ports cmos_scl]
set_property -dict {PACKAGE_PIN B16 IOSTANDARD LVCMOS33} [get_ports cmos_sda]
35.4程序设计
图 35.4.1是根据本章实验任务画出的系统框图。
OV5640摄像头HDMI显示实验9128.png

图 35.4.1 顶层系统框图

由上图可知,时钟模块(clk_wiz_0)为LCD顶层模块、DDR控制模块以及I2C驱动模块提供驱动时钟。I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入DDR控制模块,LCD顶层模块从DDR控制模块中读出数据,完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在DDR3和传感器都初始化完成之后才开始输出数据的,避免了在DDR3初始化过程中向里面写入数据。
MT9V034虽然在上电后不配置寄存器也能正常工作,但是此时输出的分辨率可能不是实验需要的分辨率,所以必须通过配置寄存器的值来达到实验所需要的分辨率。配置寄存器的协议和I2C协议在写操作时几乎一样,所以需要一个I2C驱动模块。为了使MT9V034在期望的模式下运行并且提高图像显示效果,需要配置较多的寄存器,这么多寄存器的地址与参数需要单独放在一个模块,因此还需要一个寄存配置信息的I2C配置模块。在摄像头配置完成后,开始输出图像数据,因此需要一个摄像头图像采集模块来采集图像;外接DDR3存储器当然离不开DDR3控制器模块的支持,最后LCD顶层模块读取DDR3缓存的数据以达到最终实时显示的效果。
对比“OV7725摄像头TFT-LCD显示实验”的系统框图可以发现,本次实验只是把外设OV7725模块替换成了MT9V034模块,替换了图像采集模块和IIC配置模块,修改了IIC驱动模块和DDR读写模块,其余模块基本相同。替换图像采集模块和修改图像采集顶层模块的原因在于OV7725摄像头输出的是RGB565格式的16bit数据,而MT9V034输出的是YUV格式的8bit数据;替换IIC配置模块和修改IIC驱动模块的原因在于OV7725的一个寄存器地址只写8bit数据,而MT9V034的一个寄存器地址可以写16bit数据;本次实验所采用的摄像头 MT9V034的帧率为62帧,而LCD的屏幕的帧率有的比摄像头低,有的比摄线头高,而之前所采用的摄像头OV5640和OV7725的帧率都比LCD屏的帧率低,因此两帧的乒乓操作足以满足之前的设计需求,但相对于本次实验来说两帧的乒乓操作已经不能满足设计需求,所以需要修改DDR读写模块。DDR读写模块修改后的帧切换原理如下所示:
OV5640摄像头HDMI显示实验10145.png

图 35.4.2 写比读快的示意图


OV5640摄像头HDMI显示实验10210.png


图 35.4.3 读比写快的示意图

在图 35.4.2中的图2是写操作完成了一帧的存储,换了一个存储空间继续写,而读操作还没有读完一帧数据,继续读原来的存储空间;因为读帧率和写帧率的帧率比没有2倍,所以写操作端的第二帧还没有写完,读操作端已经读完了第一帧,换了一个存储空间继续读,如图3所示; 图4中写操作端完成了第二帧的存储,而读操作还没有读完第二帧数据;同理图5中写操作端完成了第三帧的存储,开始第四帧的写入,而读操作将要读完第二帧数据;图6表示读操作读完第二帧数据,而写操作端没有完成第四帧的存储,这里将存储空间跳转了2个,就是为了防止写操作追上读操作,出现画面撕裂的现象。
在图 35.4.3中图2是读操作读出了一帧的数据,而本次实验是根据写存储空间来判断读存储空间的,所以继续读之前的存储空间;图3是写操作端完成了第一帧的存储,而读操作还没有读完第二帧数据;图4是读操作端读完第二帧数据,而写操作还没有完成了第二帧的存储。
顶层模块的原理图如下图所示:
OV5640摄像头HDMI显示实验10735.png

图 35.4.4 顶层模块原理图

FPGA顶层模块(mt9v034_lcd)例化了以下六个模块:时钟模块(clk_wiz_0)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_cfg)、图像采集顶层模块(top_cmos_data)、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时钟作为I2C驱动模块和LCD顶层模块的驱动时钟。
I2C驱动模块(i2c_dri):I2C驱动模块负责驱动MT9V034的两线式接口总线,用户可根据该模块提供的用户接口可以很方便的对MT9V034的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
I2C配置模块(i2c_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出MT9V034的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对MT9V034传感器的初始化。
图像采集顶层模块(top_cmos_data):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成DDR控制模块的写使能信号和16位写数据信号,完成对MT9V034传感器图像的采集。如果LCD屏的分辨率小于MT9V034的分辨率,还要对MT9V034采集的数据进行裁剪,以匹配LCD屏的分辨率。
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显示实验”章节时已经介绍过了,这里不再详述,但还有部分代码做了改动,改动的代码如下:
  1. 41  //parameter define                     
  2. 42  parameter  SLAVE_ADD = 7'b1001_000     ;  //slave  address         90  
  3. 43  parameter  BIT_CTRL   = 1'b0           ;  //OV7725的字节地址为8位  0:8位 1:16位
  4. 44  parameter  DATA_CTRL  = 1'b1           ;  //OV7725的数据为8位  0:8位 1:16位
  5. 45  parameter  CLK_FREQ   = 26'd50_000_000 ;  //i2c_dri模块的驱动时钟频率 50.0MHz
  6. 46  parameter  I2C_FREQ   = 18'd250_000    ;  //I2C的SCL时钟频率,不超过400KHz
复制代码

程序的第42行,修改了器件的地址。
程序的第44行,添加了参数DATA_CTRL,用以区分一个寄存器地址是写16位数据还是8位数据。
  1. 135 //图像采集顶层模块
  2. 136 cmos_data_top u_cmos_data_top(
  3. 137     .rst_n                 (sys_init_done),         //系统初始化完成之后再开始采集数据
  4. 138     .clk_cmos              (clk_24m),               //24MHz CMOS Driver clock input   
  5. 139     .cam_pclk              (cmos_pclk),            
  6. 140     .cmos_xclk             (cmos_xclk),             //24MHz drive clock   
  7. 141     .cam_vsync             (cmos_vsync),
  8. 142     .cam_href              (cmos_href),
  9. 143     .cam_data              (cmos_data),
  10. 144     .lcd_id                (lcd_id),  
  11. 145     .h_disp                (h_disp),
  12. 146     .v_disp                (v_disp),  
  13. 147     .h_pixel               (h_pixel),
  14. 148     .v_pixel               (v_pixel),
  15. 149     .ddr3_addr_max         (ddr3_addr_max),   
  16. 150     .cmos_frame_vsync      (cmos_frame_vsync),
  17. 151     .cmos_frame_href       (cmos_frame_href),
  18. 152     .cmos_frame_valid      (cmos_frame_valid),      //数据有效使能信号
  19. 153     .cmos_frame_data       (wr_data)                //有效数据
  20. 154     );
复制代码

在程序的第139行,信号cmos_pclk是摄像头产生的输入时钟,即摄像头数据的采样时钟。
在程序的第140行,信号cmos_xclk是摄像头的输入时钟,但本次实验的摄像头采用的是外接24M时钟的晶振,所以不需要cmos_xclk,但这里还是将这个信号保留下来。
I2C配置模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,代码如下所示:
  1. 1  module i2c_cfg(
  2. 2                 input                clk         ,
  3. 3                 input                rst_n       ,
  4. 4                 input                i2c_done    ,
  5. 5                 output  reg          i2c_exec    ,
  6. 6                 output  reg   [7:0]  i2c_addr    ,
  7. 7                 output  reg  [15:0]  i2c_wr_data ,
  8. 8                 output  reg          cfg_done   
  9. 9                 );
  10. 10 //parameter define   
  11. 11 parameter  DELAY_MAX = 8'hff ;
  12. 12 parameter  ROW_NUM = 16'd480;  //行数ʽ
  13. 13 parameter  COL_NUM = 16'd640;  //列数
  14. 14
  15. 15 //reg define
  16. 16 reg   [7:0]  delay_cnt   ;
  17. 17 reg          delay_done  ;
  18. 18 reg   [3:0]  cfg_cnt     ;
  19. 19
  20. 20 //*****************************************************
  21. 21 //**                    main code
  22. 22 //*****************************************************   
  23. 23
  24. 24 always @(posedge clk or negedge rst_n)
  25. 25   begin
  26. 26      if(rst_n==1'b0) begin
  27. 27          delay_cnt <= 1'b0 ;
  28. 28          delay_done <= 1'b0 ;
  29. 29      end
  30. 30      else begin
  31. 31          delay_done <= 1'b0 ;
  32. 32          if(i2c_done) begin
  33. 33              delay_cnt <= 1'b0 ;
  34. 34          end
  35. 35          else if(delay_cnt<DELAY_MAX) begin
  36. 36              delay_cnt <= delay_cnt +1'b1 ;
  37. 37              if(delay_cnt==DELAY_MAX-1'b1) begin
  38. 38                  delay_done <= 1'b1 ;
  39. 39              end
  40. 40          end
  41. 41      end
  42. 42   end            
  43. 43
  44. 44 always @(posedge clk or negedge rst_n)
  45. 45   begin
  46. 46      if(rst_n==1'b0) begin
  47. 47          i2c_exec <= 1'b0;
  48. 48          i2c_addr <= 1'b0;
  49. 49          i2c_wr_data <= 1'b0;
  50. 50          cfg_done <= 1'b0;
  51. 51          cfg_cnt <= 1'b0;
  52. 52      end
  53. 53      else begin
  54. 54          i2c_exec <= 1'b0;
  55. 55          if(cfg_done==1'b0) begin
  56. 56              if(delay_done) begin
  57. 57                  cfg_cnt <= cfg_cnt + 1'b1;
  58. 58                  if(cfg_cnt=='d2) begin
  59. 59                      cfg_done <= 1'b1;
  60. 60                  end
  61. 61                  case(cfg_cnt)
  62. 62                      4'd0 : begin
  63. 63                                 i2c_exec <= 1'b1;
  64. 64                                 i2c_addr <= 8'h03;//03
  65. 65                                 i2c_wr_data <= ROW_NUM;
  66. 66                               end
  67. 67                      4'd1 : begin
  68. 68                                 i2c_exec <= 1'b1;
  69. 69                                 i2c_addr <= 8'h04;
  70. 70                                 i2c_wr_data <= COL_NUM;
  71. 71                               end                                    
  72. 72                      default : ;
  73. 73                  endcase         
  74. 74              end
  75. 75          end
  76. 76      end
  77. 77   end                                                      
  78. 78                                       
  79. 79 endmodule
复制代码

在程序的第11行,定义了一个DELAY_MAX参数,用以保证在上电的时候不会立即配置寄存器,而是等待一段时间后再配置。
在程序的第12和13行定义了2个参数,即本次实验所需要的摄像头的分辨率。
在程序的第24至第42行,是用来对延时的计数器进行计数和清零。
在程序的第44行和77行,是对摄像头的行场分辨率进行配置。在第58行是设置需要配置寄存器的个数。
I2C驱动模块做了以下修改:
  1. 116         st_addr8: begin                         //8位字地址
  2. 117             if(st_done) begin
  3. 118                 if(wr_flag==1'b0)               //读写判断
  4. 119                     if(!data_ctrl)
  5. 120                          next_state = st_data_wr_8;
  6. 121                     else
  7. 122                          next_state = st_data_wr_16;                        
  8. 123                 else
  9. 124                     next_state = st_addr_rd;
  10. 125             end
  11. 126             else begin
  12. 127                 next_state = st_addr8;
  13. 128             end
  14. 129         end
  15. 130         st_data_wr_16: begin                       //写数据(8 bit)
  16. 131             if(st_done)
  17. 132                 next_state = st_data_wr_8;
  18. 133             else
  19. 134                 next_state = st_data_wr_16;
  20. 135         end         
  21. 136         st_data_wr_8: begin                       //写数据(8 bit)
  22. 137             if(st_done)
  23. 138                 next_state = st_stop;                 
  24. 139             else
  25. 140                 next_state = st_data_wr_8;
  26. 141         end  
复制代码

代码116行至129行表示当状态发生跳转并且data_ctrl为1的时候,将状态跳转到st_data_wr_16,否则跳转到其他状态。
代码130行至135行表示当状态发生跳转时,将状态跳转到st_data_wr_8。
  1. 337             st_data_wr_16: begin                      //写高数据(8 bit)
  2. 338                 case(cnt)                           
  3. 339                     7'd0: begin                     
  4. 340                         sda_out <= data_wr_t[15];     //I2C写高8位数据
  5. 341                         sda_dir <= 1'b1;            
  6. 342                     end                              
  7. 343                     7'd1 : scl <= 1'b1;              
  8. 344                     7'd3 : scl <= 1'b0;              
  9. 345                     7'd4 : sda_out <= data_wr_t[14];  
  10. 346                     7'd5 : scl <= 1'b1;              
  11. 347                     7'd7 : scl <= 1'b0;              
  12. 348                     7'd8 : sda_out <= data_wr_t[13];  
  13. 349                     7'd9 : scl <= 1'b1;              
  14. 350                     7'd11: scl <= 1'b0;              
  15. 351                     7'd12: sda_out <= data_wr_t[12];  
  16. 352                     7'd13: scl <= 1'b1;              
  17. 353                     7'd15: scl <= 1'b0;              
  18. 354                     7'd16: sda_out <= data_wr_t[11];  
  19. 355                     7'd17: scl <= 1'b1;              
  20. 356                     7'd19: scl <= 1'b0;              
  21. 357                     7'd20: sda_out <= data_wr_t[10];  
  22. 358                     7'd21: scl <= 1'b1;              
  23. 359                     7'd23: scl <= 1'b0;              
  24. 360                     7'd24: sda_out <= data_wr_t[9];  
  25. 361                     7'd25: scl <= 1'b1;              
  26. 362                     7'd27: scl <= 1'b0;              
  27. 363                     7'd28: sda_out <= data_wr_t[8];  
  28. 364                     7'd29: scl <= 1'b1;              
  29. 365                     7'd31: scl <= 1'b0;              
  30. 366                     7'd32: begin                     
  31. 367                         sda_dir <= 1'b0;           
  32. 368                         sda_out <= 1'b1;                              
  33. 369                     end                              
  34. 370                     7'd33: scl <= 1'b1;              
  35. 371                     7'd34: begin                     //从机应答
  36. 372                         st_done <= 1'b1;     
  37. 373                         if(sda_in == 1'b1)           //高电平表示未应答
  38. 374                             i2c_ack <= 1'b1;         //拉高应答标志位   
  39. 375                     end         
  40. 376                     7'd35: begin                     
  41. 377                         scl  <= 1'b0;               
  42. 378                         cnt  <= 1'b0;               
  43. 379                     end                              
  44. 380                     default  :  ;                    
  45. 381                 endcase                              
  46. 382             end                                                  
  47. 383             st_data_wr_8: begin                      //写数据(8 bit)
  48. 384                 case(cnt)                           
  49. 385                     7'd0: begin                     
  50. 386                         sda_out <= data_wr_t[7];     //I2C写低8位数据
  51. 387                         sda_dir <= 1'b1;            
  52. 388                     end                              
  53. 389                     7'd1 : scl <= 1'b1;              
  54. 390                     7'd3 : scl <= 1'b0;              
  55. 391                     7'd4 : sda_out <= data_wr_t[6];  
  56. 392                     7'd5 : scl <= 1'b1;              
  57. 393                     7'd7 : scl <= 1'b0;              
  58. 394                     7'd8 : sda_out <= data_wr_t[5];  
  59. 395                     7'd9 : scl <= 1'b1;              
  60. 396                     7'd11: scl <= 1'b0;              
  61. 397                     7'd12: sda_out <= data_wr_t[4];  
  62. 398                     7'd13: scl <= 1'b1;              
  63. 399                     7'd15: scl <= 1'b0;              
  64. 400                     7'd16: sda_out <= data_wr_t[3];  
  65. 401                     7'd17: scl <= 1'b1;              
  66. 402                     7'd19: scl <= 1'b0;              
  67. 403                     7'd20: sda_out <= data_wr_t[2];  
  68. 404                     7'd21: scl <= 1'b1;              
  69. 405                     7'd23: scl <= 1'b0;              
  70. 406                     7'd24: sda_out <= data_wr_t[1];  
  71. 407                     7'd25: scl <= 1'b1;              
  72. 408                     7'd27: scl <= 1'b0;              
  73. 409                     7'd28: sda_out <= data_wr_t[0];  
  74. 410                     7'd29: scl <= 1'b1;              
  75. 411                     7'd31: scl <= 1'b0;              
  76. 412                     7'd32: begin                     
  77. 413                         sda_dir <= 1'b0;           
  78. 414                         sda_out <= 1'b1;                              
  79. 415                     end                              
  80. 416                     7'd33: scl <= 1'b1;              
  81. 417                     7'd34: begin                     //从机应答
  82. 418                         st_done <= 1'b1;     
  83. 419                         if(sda_in == 1'b1)           //高电平表示未应答
  84. 420                             i2c_ack <= 1'b1;         //拉高应答标志位   
  85. 421                     end         
  86. 422                     7'd35: begin                     
  87. 423                         scl  <= 1'b0;               
  88. 424                         cnt  <= 1'b0;               
  89. 425                     end                              
  90. 426                     default  :  ;                    
  91. 427                 endcase                              
  92. 428             end
复制代码

代码337行至382行,表示写入数据的高8位。
代码383行至428行,表示写入数据的低8位。
图像采集顶层模块的原理图如下图所示:
OV5640摄像头HDMI显示实验23115.png

图 35.4.5 图像采集顶层模块原理图

图像采集顶层模块(top_cmos_data)例化了以下二个模块:图像采集模块(cmos_capture_raw_gray)、图像裁剪模块(cmos_tailor)。有关图像采集顶层模块的详细介绍请大家参考“OV7725摄像头TFT-LCD显示实验”章节。
由上图可知,图像采集模块(cmos_capture_raw_gray)为其他模块提供摄像头8bit输入数据和数据使能以及摄像头稳定后的行场信号。图像裁剪模块(cmos_tailor)只有在LCD的器件ID为16’h4342时起作用,即摄像头的分辨率大于LCD屏的分辨率,起到裁剪图像数据,使图像的有效数据达到匹配LCD屏的尺寸。有关图像裁剪模块的详细介绍请大家参考“OV7725摄像头TFT-LCD显示实验”章节。
图像采集模块的代码如下:
  1. 1   module cmos_capture_raw_gray
  2. 2   #(
  3. 3       parameter   CMOS_FRAME_WAITCNT  =   4'd10  //等待数据稳定所需要的帧数
  4. 4                                                               
  5. 5   )
  6. 6   (
  7. 7       //global clock
  8. 8       input       clk_cmos,                      //锁相环分频时钟
  9. 9       input       rst_n,                         //复位信号,低有效
  10. 10  
  11. 11      //CMOS Sensor Interface
  12. 12      input       cmos_pclk,                     //摄像头输入时钟
  13. 13      output      cmos_xclk,                     //摄像头驱动时钟
  14. 14      input       cmos_vsync,                    //摄像头场信号
  15. 15      input       cmos_href,                     //摄像头行信号
  16. 16      input [7:0] cmos_data,                     //摄像头数据
  17. 17      
  18. 18      //CMOS SYNC Data output
  19. 19      output      cmos_frame_vsync,              //摄像头场有效信号
  20. 20      output      cmos_frame_href,               //摄像头行有效信号
  21. 21      output[15:0]wr_data,                       //摄像头有效数据   
  22. 22      output      cmos_frame_clken,              //摄像头数据有效使能
  23. 23      
  24. 24      //user interface
  25. 25      output[7:0] cmos_fps_rate                  //摄像头帧率
  26. 26  );
  27. 27  
  28. 28  //parameter define   
  29. 29  localparam  DELAY_TOP = 2 * 24_000000;  //2s delay
  30. 30  
  31. 31  //reg define
  32. 32  reg [27:0]  delay_cnt;
  33. 33  reg         frame_sync_flag;
  34. 34  reg [3:0]   cmos_fps_cnt;
  35. 35  reg [1:0]   cmos_vsync_r, cmos_href_r;
  36. 36  reg [7:0]   cmos_data_r0, cmos_data_r1;
  37. 37  reg [8:0]   cmos_fps_cnt2;
  38. 38  reg [7:0]   cmos_fps_rate;
  39. 39  
  40. 40  //wire define
  41. 41  wire        cmos_vsync_end;
  42. 42  wire        delay_2s;
  43. 43  wire [7:0]  cmos_frame_data;
  44. 44  
  45. 45  //*****************************************************
  46. 46  //**                    main code
  47. 47  //*****************************************************   
  48. 48  
  49. 49  assign  cmos_vsync_end      =   (cmos_vsync_r[1] & ~cmos_vsync_r[0]) ? 1'b1 : 1'b0;
  50. 50  assign  cmos_xclk = clk_cmos;   //24MHz CMOS XCLK output
  51. 51  assign  cmos_frame_clken = frame_sync_flag ? cmos_href_r[1] : 1'b0;
  52. 52  assign  cmos_frame_vsync = frame_sync_flag ? cmos_vsync_r[1] : 1'b0;//DFF 2 clocks
  53. 53  assign  cmos_frame_href  = frame_sync_flag ? cmos_href_r[1] : 1'b0; //DFF 2 clocks
  54. 54  assign  cmos_frame_data  = frame_sync_flag ? cmos_data_r1 : 8'd0;   //DFF 2 clocks
  55. 55  assign  delay_2s = (delay_cnt == DELAY_TOP - 1'b1) ? 1'b1 : 1'b0;
  56. 56  assign  wr_data = {cmos_frame_data[7:3],cmos_frame_data[7:2],cmos_frame_data[7:3]};
  57. 57  
  58. 58  always@(posedge cmos_pclk or negedge rst_n)begin
  59. 59      if(!rst_n)
  60. 60          begin
  61. 61          cmos_vsync_r <= 0;
  62. 62          cmos_href_r <= 0;
  63. 63          {cmos_data_r1, cmos_data_r0} <= 0;
  64. 64          end
  65. 65      else
  66. 66          begin
  67. 67          cmos_vsync_r <= {cmos_vsync_r[0], cmos_vsync};
  68. 68          cmos_href_r <= {cmos_href_r[0], cmos_href};
  69. 69          {cmos_data_r1, cmos_data_r0} <= {cmos_data_r0, cmos_data};
  70. 70          end
  71. 71  end
  72. 72  
  73. 73  //Wait for Sensor output Data valid 10 Frame of OmniVision
  74. 74  always@(posedge cmos_pclk or negedge rst_n)begin
  75. 75      if(!rst_n)
  76. 76          cmos_fps_cnt <= 0;
  77. 77      else    //Wait until cmos init complete
  78. 78          begin
  79. 79          if(cmos_fps_cnt < CMOS_FRAME_WAITCNT)   
  80. 80              cmos_fps_cnt <= cmos_vsync_end ? cmos_fps_cnt + 1'b1 : cmos_fps_cnt;
  81. 81          else
  82. 82              cmos_fps_cnt <= CMOS_FRAME_WAITCNT;
  83. 83          end
  84. 84  end
  85. 85  
  86. 86  //Come ture frame synchronization to ignore error frame or has not capture when vsync begin
  87. 87  always@(posedge cmos_pclk or negedge rst_n)begin
  88. 88      if(!rst_n)
  89. 89          frame_sync_flag <= 0;
  90. 90      else if(cmos_fps_cnt == CMOS_FRAME_WAITCNT && cmos_vsync_end == 1)
  91. 91          frame_sync_flag <= 1;
  92. 92      else
  93. 93          frame_sync_flag <= frame_sync_flag;
  94. 94  end
  95. 95  
  96. 96  //Delay 2s for cmos fps counter
  97. 97  always@(posedge cmos_pclk or negedge rst_n)begin
  98. 98      if(!rst_n)
  99. 99          delay_cnt <= 0;
  100. 100     else if(delay_cnt < DELAY_TOP - 1'b1)
  101. 101         delay_cnt <= delay_cnt + 1'b1;
  102. 102     else
  103. 103         delay_cnt <= 0;
  104. 104 end
  105. 105
  106. 106 //cmos image output rate counter
  107. 107 always@(posedge cmos_pclk or negedge rst_n)begin
  108. 108     if(!rst_n)
  109. 109         begin
  110. 110         cmos_fps_cnt2 <= 0;
  111. 111         cmos_fps_rate <= 0;
  112. 112         end
  113. 113     else if(delay_2s == 1'b0)   //time is not reached
  114. 114         begin
  115. 115         cmos_fps_cnt2 <= cmos_vsync_end ? cmos_fps_cnt2 + 1'b1 : cmos_fps_cnt2;
  116. 116         cmos_fps_rate <= cmos_fps_rate;
  117. 117         end
  118. 118     else    //time up
  119. 119         begin
  120. 120         cmos_fps_cnt2 <= 0;
  121. 121         cmos_fps_rate <= cmos_fps_cnt2[8:1];    //divide by 2
  122. 122         end
  123. 123 end
  124. 124
  125. 125 endmodule
复制代码

CMOS图像采集模块第3行定义了参数CMOS_FRAME_WAITCNT(寄存器数据稳定等待的帧个数),这里设置等待帧是为了保证摄像头输出稳定后才采样数据。这里采集场同步信号的上升沿来统计帧数,计数器计数超过10次之后产生数据有效的标志(frame_sync_flag),开始采集图像。
在程序的第56行实现了8位数据转16位数据的功能,这里将8位的灰度数据按照高位赋值的方式将数据按照RGB565的格式分别赋给各个颜色分量。需要注意的是摄像头的图像数据是在像素时钟(cam_pclk)下输出的,因此摄像头的图像数据必须使用像素钟来采集,否则会造成数据采集错误。
在程序的第97行至104行,是对2s的时间进行计数。
在程序的第107行至123行,是对摄像头的帧率进行计数。在113行至117行,当2s的时间未到且当场信号的下降沿到来时,对帧率计数器进行累加,当2s的时间到来时,对帧率计数器进行清零,把帧率计数器除以2的值赋给帧率寄存器。
DDR读写模块做了以下修改:
  1. 90  //将数据读写地址赋给ddr地址
  2. 91  always @(*)  begin
  3. 92      if(~rst_n)
  4. 93          app_addr <= 0;
  5. 94      else if(state_cnt == READ )
  6. 95          if(ddr3_pingpang_en)
  7. 96              app_addr <= {1'b0,raddr_page,app_addr_rd[24:0]};
  8. 97          else
  9. 98              app_addr <= {3'b0,app_addr_rd[24:0]};            
  10. 99      else if(ddr3_pingpang_en)
  11. 100         app_addr <= {1'b0,waddr_page,app_addr_wr[24:0]};
  12. 101     else
  13. 102         app_addr <= {3'b0,app_addr_wr[24:0]};        
  14. 103 end  
复制代码

这段代码相对于“OV7725摄像头TFT-LCD显示实验”中只是将信号raddr_page和信号waddr_page的位宽从1位扩展为2位,因为LCD屏的帧率相对于摄像头的帧率是不确定的,所以这里做了4帧缓存,以确保读写地址不会出现在同一个存储空间。
本次实验中摄像头的帧率为62帧,而LCD屏的帧率最高为80帧,最低为40帧,读写帧率的关系都没有2倍,所以4帧缓存是足够的。如果两者的帧率比达到2倍以上,那么需要缓存的帧数也要相应的往上加。
  1. 183 //对输出源帧的读地址高位切换
  2. 184 always @(posedge ui_clk or negedge rst_n)  begin
  3. 185     if(~rst_n)
  4. 186         raddr_page <= 1'b0;
  5. 187     else if( rd_end)
  6. 188         raddr_page <= waddr_page + 2;         
  7. 189     else
  8. 190         raddr_page <= raddr_page;           
  9. 191 end
  10. 192   
  11. 193 //对输入源帧的写地址高位切换
  12. 194 always @(posedge ui_clk or negedge rst_n)  begin
  13. 195     if(~rst_n)
  14. 196         waddr_page <= 1'b1;
  15. 197     else if( wr_end)
  16. 198         waddr_page <= waddr_page + 1;         
  17. 199     else
  18. 200         waddr_page <= waddr_page;           
  19. 201 end
复制代码

在程序的198行,本次实验中以写的存储空间来判断读的存储空间,所以写存储空间是一直循环累加的。
在程序的188行,当场同步到来时,读存储空间在写存储空间的基础上加2的原因是防止读写地址出现在同一个存储空间,出现画面撕裂的现象。
我们在前面说过,本章实验程序设计和“OV7725摄像头RGB-LCD显示实验”相比,我们只是把外设OV7725模块替换成了MT9V034模块,替换了图像采集模块和iic配置模块,修改了iic驱动模块和DDR读写模块,其余模块基本相同。本章节主要介绍下I2C配置模块、图像采集模块、DDR读写模块和iic驱动模块,而其它相同模块的程序设计详情请大家参考“OV7725摄像头RGB-LCD显示实验”章节。
35.5下载验证
首先将FPC排线一端与RGB LCD模块上的J1接口连接,另一端与达芬奇开发板上的RGB TFTLCD接口连接。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线,如图 35.5.1和图 35.5.2所示。
OV5640摄像头HDMI显示实验30264.png

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

OV5640摄像头HDMI显示实验30337.png

图 35.5.2 A7开发板连接RGB LCD液晶屏

接下来分别连接JTAG接口和电源线,并打开电源开关。
最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。
接下来我们下载程序,验证MT9V034 TFT-LCD实时显示功能。下载完成后观察RGB LCD模块显示的图案如下图所示,说明MT9V034 TFT-LCD实时显示程序下载验证成功。
OV5640摄像头HDMI显示实验30572.png

图 35.5.3 RGB TFT-LCD实时显示图像


阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 06:14

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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