本帖最后由 正点原子运营 于 2023-12-22 16:51 编辑
第四十三章 MT9V034摄像头RGB-LCD显示实验
1)实验平台:正点原子 ATK-DFPGL22G开发板
2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0
6)FPGA技术交流QQ群:435699340
MT9V034是ON Semiconductor(安森美半导体)公司生产的一颗CMOS图像传感器,该传感器功耗低、可靠性高以及采集速率快,主要应用于机器视觉,双目视觉,宽温度工业场合等领域。本章我们将使用FPGA开发板实现对MT9V034的数字图像采集并通过LCD实时显示。 本章包括以下几个部分: 1.1 简介 1.2 实验任务 1.3 硬件设计 1.4 软件设计 1.5 下载验证
1.1 简介MT9V034是一款1/3英寸单芯片图像传感器,其感光阵列最大可达到752*480,能实现最快60fps VGA分辨率的图像采集,具有全局曝光和高动态范围(HDR)操作。这款CMOS图像传感器具有安森美半导体的突破性功能,实现了CCD图像质量的低噪声和CMOS成像技术(基于信噪比和低光灵敏度),同时保持固有尺寸、成本和CMOS的集成优势。下表为几个摄像头的功能对比。 通过上述的对比可以看出,相对于其他2款摄像头MT9V034的优势在于HDR模式和全局曝光。HDR模式的原理是根据不同的曝光时间的LDR(Low-Dynamic Range)图像,利用每个曝光时间相对应最佳细节的LDR图像来合成最终HDR图像,与普通的图像相比,可以提供更多的动态范围和图像细节。下图是线性模式和HDR模式的对比图。 通过对比可以发现,开启了HDR,会使拍到的图像亮度比较高的地方变暗,亮度比较低的地方变亮,总的来说就是使图像显示的更均衡。 卷帘曝光的原理是通过Sensor逐行曝光的方式实现的。在曝光开始的时候,Sensor逐行扫描逐行进行曝光,直至所有像素点都被曝光。与卷帘曝光不同,全局曝光整幅场景在同一时间曝光实现的,Sensor所有像素点同时收集光线,同时曝光。下面为两种模式的对比图。 当拍摄快速移动的物体时,全局曝光拍摄到的物体比较清晰,不会发生形变,而卷帘曝光拍摄的图片会出现部分曝光(partial exposure)、斜坡图形(skew)、晃动(wobble) 等现象,也就是传说中的果冻了。这是全局曝光其优势的一面,相对于卷帘曝光也有其劣势的一面。当全局曝光的曝光时间长(如大于500μs)时,其噪点情况严重,而运用卷帘曝光后,图片会有更低的噪声和更快的帧速。 下图为MT9V034的功能框图: 由上图可知,除了传统的并行逻辑输出,MT9V034还具有串行低压差分信号(LVDS)输出。该传感器可以在立体声相机中操作,并且该传感器被指定为立体声主机时,可以合并来自本身的立体声,还可以将从属传感器的数据合并为一个串行LVDS流。本次实验只采用传统的并行逻辑输出。 MT9V034通过两线串行接口将寄存器写入MT9V034和从中读取,以此来配置窗口大小和行场分辨率等寄存器。MT9V034是具有四个可能ID(0x90、0x98、0xB0和0xB8)由S_CTRL_ADR0和S_CTRL_ADR1输入引脚确定,本次实验所用的MT9V034的写器件ID是0x90。下图是器件ID的相关内容。 MT9V034使用的是两线式接口总线,该接口总线包括SCLK串行时钟输入线和SDATA串行双向数据线,分别相当于IIC协议的SCL信号线和SDA信号线。本次实验所用的两线式接口总线兼容IIC协议,所以不对相关协议详细介绍,有关IIC协议的详细介绍请大家参考“EEPROM读写实验”章节。 两线式接口总线的写传输协议如下图所示: 上图中的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位数据,下图为两线式接口总线的读传输协议。 由上图可知,两线式接口总线读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据,注意ID Address(R) = 8’hB9(器件地址左移1位,低位补1)。上图中的NACK位由主机(这里指FPGA)产生,由于两线式接口总线不支持连续读写,因此NACK位必须为高电平。 MT9V034在上电后是存在默认寄存器的,即上电后就可以输出752x480分辨率的图像,如果大家需要其他的分辨率或模式就必须先对传感器进行初始化,可通过配置寄存器使其工作在预期的工作模式,以得到较好画质的图像。因为两线式接口总线的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。ON Semiconductor公司提供了MT9V034的数据手册(位于开发板所随附的资料“7_硬件资料/7_MT9V034资料/MT9V034数据手册.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。 表 43.1.2 MT9V034关键寄存器配置说明
MT9V034的寄存器较多,对于其它寄存器的描述可以参MT9V034的寄存器配置手册(位于开发板所随附的资料“7_硬件资料/7_MT9V034资料/MT9V034寄存器配置说明手册.pdf”)。 下图为MT9V034的一些特性。 从上图可以看出,MT9V034的输入时钟频率的范围是13Mhz~27Mhz;本次实验摄像头的输入时钟为24Mhz,是由外部晶振提供的;两线式接口总线的SCLK的时钟频率最大为400KHz。 图 43.1.10 PIXCLK和SYSCLK的关系 结合图 43.1.10和图 43.1.9可知,PIXCLK和SYSCLK是同频不同相的2个时钟,本次实验摄像头的输入时钟为24Mhz,所以摄像头的输出时钟也为24Mhz。 MT9V034在并行逻辑输出的模式下仅支持10bit的YUV(亮度参量和色度参量分开表示的像素格式),不支持其他格式。由于摄像头采集的图像最终要在LCD上显示,且ATK-DFPGL22G开发板上的数据接口为RGB888格式(详情请参考“LCD彩条显示实验”章节),因此必须将MT9V034摄像头输出的YUV格式的图像像素数据转换为RGB888格式。下图为摄像头输出的行时序图。 LINE_VALID:数据有效使能。当其为高时,输出的数据为有效数据。 PIXCLK:像素时钟。由MT9V034产生的对外输出的时钟信号。 DOUT:有效数据。摄像头采集得到的像素数据,本次实验取其高8位。 INE_VALID:数据有效使能。当其为高时,输出的数据为有效数据。 FRAME_VALID:帧(场)同步信号。当此信号有效的时候就表示开始显示新的一帧数据。
1.2 实验任务本节实验任务是使用ATK-DFPGL22G开发板及MT9V034摄像头实现图像采集,并通过RGB-LCD接口驱动RGB-LCD液晶屏(支持目前正点原子推出的所有RGB-LCD屏),并实时显示出图像。
1.3 硬件设计ATK-DFPGL22G开发板上有一个摄像头扩展接口,该接口可以用来连接MT9V034/OV5640等摄像头模块,摄像头扩展接口原理图如下图所示: ATK-MT9V034是正点原子推出的一款高性能36W像素高清摄像头模块。该模块通过2*9排针(2.54mm间距)同外部连接,我们将摄像头的排针直接插在开发板上的摄像头接口即可。 前面说过,MT9V034在YUV模式中有效数据是D[9:0],而我们的摄像头排针上数据引脚的个数是8位,而摄像头排针上的8位数据连接的就是MT9V034传感器的D[9:2],所以我们直接使用摄像头排针上的8位数据引脚即可。 需要注意的是,由图 43.3.1可知,摄像头扩展口的第18个引脚定义为CMOS_PWDN,而我们的MT9V034摄像头模块的STB(CMOS_PWDN)引脚固定为低电平,也就是一直处于正常工作模式。MT9V034摄像头接口的第18个引脚定义为EXP,这个引脚是摄像头外部触发脉冲的引脚,只在快照模式下启用它。MT9V034摄像头模块内部自带24M晶振的,所以不需要FPGA输出时钟给摄像头。 由于LCD接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出摄像头相关管脚分配,如下表所示: 表 43.3.1 MT9V034摄像头RGB-LCD显示实验管脚分配 MT9V034摄像头与OV7725摄像头的约束几乎相同,只是C2引脚的cam_sgm_ctrl信号改为cam_stb信号,除了信号名改变了,其余约束没有任何改变,所以这里不再赘述约束文件。
1.4 程序设计对比“OV7725摄像头RGB-LCD显示实验”的系统框图可以发现,本次实验只是把外设OV7725模块替换成了MT9V034模块,替换了图像采集模块和IIC配置模块,修改了IIC驱动模块,其余模块基本相同。 替换图像采集模块和修改图像采集顶层模块的原因在于OV7725摄像头输出的是RGB565格式16bit数据,而MT9V034输出的是YUV格式的8bit数据;替换IIC配置模块和修改IIC驱动模块的原因在于OV7725的一个寄存器地址只写8bit数据,而MT9V034的一个寄存器地址可以写16bit数据。 下图是根据本章实验任务画出的系统框图: 由上图可知,时钟模块(pll_clk)为LCD顶层模块、DDR3控制模块以及I2C驱动模块提供驱动时钟。I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入DDR3控制模块,LCD顶层模块从DDR3控制模块中读出数据,完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在DDR3和传感器都初始化完成之后才开始输出数据的,避免了在DDR3初始化过程中向里面写入数据。 MT9V034虽然在上电后不配置寄存器也能正常工作,但是此时输出的分辨率可能不是实验需要的分辨率,所以必须通过配置寄存器的值来达到实验所需要的分辨率。配置寄存器的协议和I2C协议在写操作时几乎一样,所以需要一个I2C驱动模块。为了使MT9V034在期望的模式下运行并且提高图像显示效果,需要配置较多的寄存器,这么多寄存器的地址与参数需要单独放在一个模块,因此还需要一个寄存配置信息的I2C配置模块。在摄像头配置完成后,开始输出图像数据,因此需要一个摄像头图像采集模块来采集图像;采集到的图像先进入DDR3存储器进行缓存,最后LCD顶层模块读取DDR3缓存的数据以达到最终实时显示的效果。 顶层模块的代码如下: - 1 module mt9v034_lcd(
- 2 input sys_clk ,
- 3 input sys_rst_n ,
- 4 //lcd接口
- 5 output lcd_hs , //LCD 行同步信号
- 6 output lcd_vs , //LCD 场同步信号
- 7 output lcd_de , //LCD 数据输入使能
- 8 inout [23:0 lcd_rgb , //LCD 颜色数据
- 9 output lcd_bl , //LCD 背光控制信号
- 10 output lcd_rst , //LCD 复位信号
- 11 output lcd_pclk , //LCD 采样时钟
- 12 //摄像头接口
- 13 input cam_pclk , //cmos 数据像素时钟
- 14 input cam_vsync , //cmos 场同步信号
- 15 input cam_href , //cmos 行同步信号
- 16 input [7:0 cam_data , //cmos 数据
- 17 output cam_rst_n , //cmos 复位信号,低电平有效
- 18 output cam_stb , //cmos pwer down
- 19 output cam_scl , //cmos SCCB_SCL线
- 20 inout cam_sda , //cmos SCCB_SDA线
- 21 //DDR3接口
- 22 input pad_loop_in , //低位温度补偿输入
- 23 input pad_loop_in_h , //高位温度补偿输入
- 24 output pad_rstn_ch0 , //Memory复位
- 25 output pad_ddr_clk_w , //Memory差分时钟正
- 26 output pad_ddr_clkn_w , //Memory差分时钟负
- 27 output pad_csn_ch0 , //Memory片选
- 28 output [15:0 pad_addr_ch0 , //Memory地址总线
- 29 inout [16-1:0 pad_dq_ch0 , //数据总线
- 30 inout [16/8-1:0 pad_dqs_ch0 , //数据时钟正端
- 31 inout [16/8-1:0 pad_dqsn_ch0 , //数据时钟负端
- 32 output [16/8-1:0 pad_dm_rdqs_ch0 , //数据Mask
- 33 output pad_cke_ch0 , //Memory差分时钟使
- 34 output pad_odt_ch0 , //On Die Terminati
- 35 output pad_rasn_ch0 , //行地址strobe
- 36 output pad_casn_ch0 , //列地址strobe
- 37 output pad_wen_ch0 , //写使能
- 38 output [2:0 pad_ba_ch0 , //Bank地址总线
- 39 output pad_loop_out , //低位温度补偿输出
- 40 output pad_loop_out_h //高位温度补偿输出
- 41 );
- 42
- 43 //parameter define
- 44 parameter SLAVE_ADDR = 7'b1001_000 ;//OV7725的器件地址7'h90
- 45 parameter BIT_CTRL =1'b0 ; //mt9v034的字节地址为8位 0:8位 1:16位
- 46 parameter DATA_CTRL = 1'b1 ; //mt9v034的数据为8位 0:8位 1:16位
- 47 parameter CLK_FREQ =27'd50_000_000; //i2c_dri模块的驱动时钟频率
- 48 parameter I2C_FREQ =18'd250_000 ;//I2C的SCL时钟频率,不超过400KHz
- 49 parameter APP_ADDR_MIN = 28'd0 ; //ddr3读写起始地址,以一个16bit的数据为一个单位
- 50 parameter BURST_LENGTH = 8'd64 ; //ddr3读写突发长度,64个128bit的数据
- 51
复制代码程序的第44行,修改了器件的地址。 程序的第46行,添加了参数DATA_CTRL,用以区分一个寄存器地址是写16位数据还是8位数据。 顶层模块中第47至第48行定义了两个变量: CLK_FREQ(i2c_dri模块的驱动时钟频率)和I2C_FREQ(I2C的SCL时钟频率),I2C_FREQ的时钟频率不能超过400KHz,否则有可能导致摄像头配置不成功 - 52 //wire define
- 53 wire sys_init_done ; //系统初始化完成(DDR3初始化+摄像头初始化)
- 54 wire rst_n ; //全局复位
- 55 //PLL
- 56 wire clk_50m ; //output 50M
- 57 wire clk_100m ; //output 100M
- 58 wire clk_locked ;
- 59 //MT9V034
- 60 wire i2c_dri_clk ; //I2C操作时钟
- 61 wire i2c_done ; //I2C寄存器配置完成信号
- 62 wire i2c_exec ; //I2C触发执行信号
- 63 wire [7:0 i2c_addr ; //I2C要配置的地址
- 64 wire [15:0 i2c_wr_data ; //I2C要配置的数据
- 65 wire cam_init_done ; //摄像头初始化完成
- 66 wire cmos_frame_vsync; //帧有效信号
- 67 wire cmos_frame_href ; //行有效信号
- 68 wire cmos_frame_valid; //数据有效使能信号
- 69 wire [15:0 wr_data ; //OV7725写入DDR3控制器模块的数据
- 70 wire [27:0 ddr3_addr_max ; //存入DDR3的最大读写地址
- 71 //LCD
- 72 wire lcd_clk ; //分频产生的LCD采样时钟
- 73 wire [10:0 h_disp ; //LCD屏水平分辨率
- 74 wire [10:0 v_disp ; //LCD屏垂直分辨率
- 75 wire [15:0 lcd_id ; //LCD屏的ID号
- 76 wire out_vsync ; //LCD场信号
- 77 wire rdata_req ; //读数据请求
- 78 //DDR3
- 79 wire [15:0 rd_data ; //DDR3控制器模块输出的数据
- 80 wire fram_done ; //DDR中已经存入一帧画面标志
- 81 wire ddr_init_done ; //ddr3初始化完成
- 82
- 83 //*****************************************************
- 84 //** main code
- 85 //*****************************************************
- 86
- 87 //待时钟锁定后产生结束复位信号
- 88 assign rst_n = sys_rst_n & clk_locked;
- 89
- 90 //系统初始化完成:DDR3和摄像头都初始化完成
- 91 //避免了在DDR3初始化过程中向里面写入数据
- 92 assign sys_init_done = ddr_init_done & cam_init_done;
- 93
- 94 //电源休眠模式选择 0:正常模式 1:电源休眠模式
- 95 assign cam_stb = 1'b0;
- 96
- 97 //不对摄像头硬件复位,固定高电平
- 98 assign cam_rst_n = 1'b1;
- 99
- 100 //例化PLL IP核
- 101 pll_clk u_pll_clk(
- 102 .pll_rst (~sys_rst_n ),
- 103 .clkin1 (sys_clk ),
- 104 .clkout0 (clk_50m ),
- 105 .clkout1 (clk_100m ),
- 106 .pll_lock (clk_locked )
- 107 );
- 108
- 109 //I2C配置模块
- 110 i2c_cfg u_i2c_cfg(
- 111 .clk (i2c_dri_clk ),
- 112 .rst_n (rst_n ),
- 113 .i2c_done (i2c_done ),
- 114 .i2c_exec (i2c_exec ),
- 115 .i2c_addr (i2c_addr ),
- 116 .i2c_wr_data (i2c_wr_data ),
- 117 .cfg_done (cam_init_done)
- 118 );
- 119
- 120 //I2C驱动模块
- 121 i2c_dri
- 122 #(
- 123 .SLAVE_ADDR (SLAVE_ADDR), //参数传递
- 124 .CLK_FREQ (CLK_FREQ ),
- 125 .I2C_FREQ (I2C_FREQ )
- 126 )
- 127 u_i2c_dr(
- 128 .clk (clk_50m ),
- 129 .rst_n (sys_rst_n ),
- 130 .i2c_exec (i2c_exec ),
- 131 .bit_ctrl (BIT_CTRL ),
- 132 .data_ctrl (DATA_CTRL ),
- 133 .i2c_rh_wl (0 ), //固定为0,只用到了IIC驱动的写操作
- 134 .i2c_addr ({8'b0,i2c_addr}),
- 135 .i2c_data_w (i2c_wr_data ),
- 136 .i2c_data_r ( ),
- 137 .i2c_done (i2c_done ),
- 138 .scl (cam_scl ),
- 139 .sda (cam_sda ),
- 140 .dri_clk (i2c_dri_clk ) //I2C操作时钟
- 141 );
- 142
- 143 //图像采集顶层模块
- 144 cmos_data_top u_cmos_data_top(
- 145 .rst_n (rst_n & sys_init_done), //系统初始化完成之后再开始采集数据
- 146 .cam_pclk (cam_pclk ),
- 147 .cam_vsync (cam_vsync ),
- 148 .cam_href (cam_href ),
- 149 .cam_data (cam_data ),
- 150 .lcd_id (lcd_id ),
- 151 .h_disp (h_disp ),
- 152 .v_disp (v_disp ),
- 153 .h_pixel ( ),
- 154 .v_pixel ( ),
- 155 .ddr3_addr_max (ddr3_addr_max ),
- 156 .cmos_frame_vsync (cmos_frame_vsync ),
- 157 .cmos_frame_href (cmos_frame_href ),
- 158 .cmos_frame_valid (cmos_frame_valid ), //数据有效使能信号
- 159 .cmos_frame_data (wr_data ) //有效数据
- 160 );
- 161
- 162 //ddr3
- 163 ddr3_top u_ddr3_top(
- 164 .refclk_in (clk_50m ),
- 165 .rst_n (rst_n ),
- 166 .app_addr_rd_min (APP_ADDR_MIN ),
- 167 .app_addr_rd_max (ddr3_addr_max ),
- 168 .rd_bust_len (BURST_LENGTH ),
- 169 .app_addr_wr_min (APP_ADDR_MIN ),
- 170 .app_addr_wr_max (ddr3_addr_max ),
- 171 .wr_bust_len (BURST_LENGTH ),
- 172 .ddr3_read_valid (1'b1 ),
- 173 .ddr3_pingpang_en (1'b1 ),
- 174 .wr_clk (cam_pclk ),
- 175 .rd_clk (lcd_clk ),
- 176 .datain_valid (cmos_frame_valid),
- 177 .datain (wr_data ),
- 178 .rdata_req (rdata_req ),
- 179 .rd_load (out_vsync ),
- 180 .wr_load (cmos_frame_vsync),
- 181 .fram_done (fram_done ),
- 182 .dataout (rd_data ),
- 183 .pll_lock (pll_lock ),
- 184 .ddr_init_done (ddr_init_done ),
- 185 .ddrphy_rst_done ( ),
- 186 .pad_loop_in (pad_loop_in ),
- 187 .pad_loop_in_h (pad_loop_in_h ),
- 188 .pad_rstn_ch0 (pad_rstn_ch0 ),
- 189 .pad_ddr_clk_w (pad_ddr_clk_w ),
- 190 .pad_ddr_clkn_w (pad_ddr_clkn_w ),
- 191 .pad_csn_ch0 (pad_csn_ch0 ),
- 192 .pad_addr_ch0 (pad_addr_ch0 ),
- 193 .pad_dq_ch0 (pad_dq_ch0 ),
- 194 .pad_dqs_ch0 (pad_dqs_ch0 ),
- 195 .pad_dqsn_ch0 (pad_dqsn_ch0 ),
- 196 .pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0 ),
- 197 .pad_cke_ch0 (pad_cke_ch0 ),
- 198 .pad_odt_ch0 (pad_odt_ch0 ),
- 199 .pad_rasn_ch0 (pad_rasn_ch0 ),
- 200 .pad_casn_ch0 (pad_casn_ch0 ),
- 201 .pad_wen_ch0 (pad_wen_ch0 ),
- 202 .pad_ba_ch0 (pad_ba_ch0 ),
- 203 .pad_loop_out (pad_loop_out ),
- 204 .pad_loop_out_h (pad_loop_out_h )
- 205
- 206 );
- 207
- 208 //LCD驱动显示模块
- 209 lcd_rgb_top u_lcd_rgb_top(
- 210 .sys_clk (clk_50m ),
- 211 .clk_100m (clk_100m ),
- 212 .sys_rst_n (rst_n && clk_locked ),
- 213 .sys_init_done (sys_init_done ),
- 214 //lcd接口
- 215 .lcd_id (lcd_id ), //LCD屏的ID号
- 216 .lcd_hs (lcd_hs ), //LCD 行同步信号
- 217 .lcd_vs (lcd_vs ), //LCD 场同步信号
- 218 .lcd_de (lcd_de ), //LCD 数据输入使能
- 219 .lcd_rgb (lcd_rgb ), //LCD 颜色数据
- 220 .lcd_bl (lcd_bl ), //LCD 背光控制信号
- 221 .lcd_rst (lcd_rst ), //LCD 复位信号
- 222 .lcd_pclk (lcd_pclk ), //LCD 采样时钟
- 223 .lcd_clk (lcd_clk ), //LCD 驱动时钟
- 224 //用户接口
- 225 .h_disp (h_disp ), //行分辨率
- 226 .v_disp (v_disp ), //场分辨率
- 227 .pixel_xpos ( ),
- 228 .pixel_ypos ( ),
- 229 .out_vsync (out_vsync ),
- 230 .data_in (rd_data ), //rfifo输出数据
- 231 .data_req (rdata_req ) //请求数据输入
- 232 );
- 233
- 234 endmodule
复制代码FPGA顶层模块(mt9v034_lcd)例化了以下六个模块: 时钟模块(pll_clk)、 I2C驱动模块(i2c_dri)、I2C配置模块(i2c_cfg)、图像采集顶层模块(cmos_data_top)、DDR3控制顶层模块(ddr3_top)和LCD顶层模块(lcd_rgb_top)。 时钟模块(pll_clk):时钟模块通过调用PLL IP核实现,共输出2个时钟,频率分别为50Mhz时钟和100Mhz时钟。100Mhz时钟作为LCD顶层模块输出时钟的源时钟;50Mhz时钟作为I2C驱动模块、DDR控制模块和LCD顶层模块的驱动时钟。 I2C驱动模块(i2c_dri):I2C驱动模块负责驱动MT9V034的两线式接口总线,用户根据该模块提供的用户接口可以很方便的对MT9V034的寄存器进行配置,该模块和“EEPROM 读写实验”章节中用到的I2C 驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM 读写实验”章节。 I2C配置模块(i2c_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出MT9V034的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对MT9V034传感器的初始化。 图像采集顶层模块(cmos_data_top):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成DDR3控制模块的写使能信号和16位写数据信号,完成对MT9V034传感器图像的采集。如果LCD屏的分辨率小于MT9V034的分辨率,还要对MT9V034采集的数据进行裁剪,以匹配LCD屏的分辨率。 DDR3控制顶层模块(ddr3_top):DDR3读写控制器模块负责驱动DDR3片外存储器,缓存图像传感器输出的图像数据。该模块将DDR3 IP核复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。有关DDR3控制顶层模块的详细介绍请大家参考“OV7725摄像头RGB-LCD显示实验”章节。 LCD顶层模块(lcd_rgb_top):LCD 顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。有关LCD驱动模块的详细介绍请大家参考“OV7725摄像头RGB-LCD显示实验”章节。 I2C配置模块代码如下所示: - 1 module i2c_cfg(
- 2 input clk ,
- 3 input rst_n ,
- 4 input i2c_done ,
- 5 output reg i2c_exec ,
- 6 output reg [7:0 i2c_addr ,
- 7 output reg [15:0 i2c_wr_data ,
- 8 output reg cfg_done //配置寄存器结束
- 9 );
- 10 //parameter define
- 11 parameter DELAY_MAX = 8'hff ;
- 12 parameter ROW_NUM = 16'd480; //行数
- 13 parameter COL_NUM = 16'd640; //列数
- 14
复制代码在程序的第11行,定义了一个DELAY_MAX参数,用以保证在上电的时候不会立即配置寄存器,而是等待一段时间后再配置。 在程序的第12和13行定义了2个参数,即本次实验所需要的摄像头的分辨率。 - 15 //reg define
- 16 reg [7:0 delay_cnt ;
- 17 reg delay_done ;
- 18 reg [3:0 cfg_cnt ;
- 19
- 20 //*****************************************************
- 21 //** main code
- 22 //*****************************************************
- 23
- 24 always @(posedge clk or negedge rst_n)
- 25 begin
- 26 if(rst_n==1'b0) begin
- 27 delay_cnt <= 1'b0 ;
- 28 delay_done <= 1'b0 ;
- 29 end
- 30 else begin
- 31 delay_done <= 1'b0 ;
- 32 if(i2c_done) begin
- 33 delay_cnt <= 1'b0 ;
- 34 end
- 35 else if(delay_cnt<DELAY_MAX) begin
- 36 delay_cnt <= delay_cnt +1'b1 ;
- 37 if(delay_cnt==DELAY_MAX-1'b1) begin
- 38 delay_done <= 1'b1 ;
- 39 end
- 40 end
- 41 end
- 42 end
- 43
- 44 always @(posedge clk or negedge rst_n)
- 45 begin
- 46 if(rst_n==1'b0) begin
- 47 i2c_exec <= 1'b0;
- 48 i2c_addr <= 1'b0;
- 49 i2c_wr_data <= 1'b0;
- 50 cfg_done <= 1'b0;
- 51 cfg_cnt <= 1'b0;
- 52 end
- 53 else begin
- 54 i2c_exec <= 1'b0;
- 55 if(cfg_done==1'b0) begin
- 56 if(delay_done) begin
- 57 cfg_cnt <= cfg_cnt + 1'b1;
- 58 if(cfg_cnt=='d2) begin
- 59 cfg_done <= 1'b1;
- 60 end
- 61 case(cfg_cnt)
- 62 4'd0 : begin
- 63 i2c_exec <= 1'b1;
- 64 i2c_addr <= 8'h03;//03
- 65 i2c_wr_data <= ROW_NUM;
- 66 end
- 67 4'd1 : begin
- 68 i2c_exec <= 1'b1;
- 69 i2c_addr <= 8'h04;
- 70 i2c_wr_data <= COL_NUM;
- 71 end
- 72 default : ;
- 73 endcase
- 74 end
- 75 end
- 76 end
- 77 end
- 78
- 79 endmodule
复制代码在程序的第24至第42行,是用来对延时的计数器进行计数和清零。 在程序的第44至第77行,是对摄像头的行场分辨率进行配置。在第58行是设置需要配置寄存器的个数。 I2C 驱动模块做了以下修改: - 116 st_addr8: begin //8位字地址
- 117 if(st_done) begin
- 118 if(wr_flag==1'b0) //读写判断
- 119 if(!data_ctrl)
- 120 next_state = st_data_wr_8;
- 121 else
- 122 next_state = st_data_wr_16;
- 123 else
- 124 next_state = st_addr_rd;
- 125 end
- 126 else begin
- 127 next_state = st_addr8;
- 128 end
- 129 end
- 130 st_data_wr_16: begin //写数据(8bit)
- 131 if(st_done)
- 132 next_state = st_data_wr_8;
- 133 else
- 134 next_state = st_data_wr_16;
- 135 end
- 136 st_data_wr_8: begin //写数据(8bit)
- 137 if(st_done)
- 138 next_state = st_stop;
- 139 else
- 140 next_state = st_data_wr_8;
- 141 end
复制代码代码116行至129行表示当状态发生跳转并且data_ctrl为1的时候,将状态跳转到st_data_wr_16,否则跳转到其他状态。 代码130行至135行表示当状态发生跳转时,将状态跳转到st_data_wr_8。 - 337 st_data_wr_16: begin //写高数据(8 bit)
- 338 case(cnt)
- 339 7'd0: begin
- 340 sda_out <= data_wr_t[15]; //I2C写高8位数据
- 341 sda_dir <= 1'b1;
- 342 end
- 343 7'd1 : scl <= 1'b1;
- 344 7'd3 : scl <= 1'b0;
- 345 7'd4 : sda_out <= data_wr_t[14];
- 346 7'd5 : scl <= 1'b1;
- 347 7'd7 : scl <= 1'b0;
- 348 7'd8 : sda_out <= data_wr_t[13];
- 349 7'd9 : scl <= 1'b1;
- 350 7'd11: scl <= 1'b0;
- 351 7'd12: sda_out <=data_wr_t[12];
- 352 7'd13: scl <= 1'b1;
- 353 7'd15: scl <= 1'b0;
- 354 7'd16: sda_out <= data_wr_t[11];
- 355 7'd17: scl <= 1'b1;
- 356 7'd19: scl <= 1'b0;
- 357 7'd20: sda_out <= data_wr_t[10];
- 358 7'd21: scl <= 1'b1;
- 359 7'd23: scl <= 1'b0;
- 360 7'd24: sda_out <= data_wr_t[9];
- 361 7'd25: scl <= 1'b1;
- 362 7'd27: scl <= 1'b0;
- 363 7'd28: sda_out <= data_wr_t[8];
- 364 7'd29: scl <= 1'b1;
- 365 7'd31: scl <= 1'b0;
- 366 7'd32: begin
- 367 sda_dir <= 1'b0;
- 368 sda_out <= 1'b1;
- 369 end
- 370 7'd33: scl <= 1'b1;
- 371 7'd34: begin //从机应答
- 372 st_done <= 1'b1;
- 373 if(sda_in == 1'b1) //高电平表示未应答
- 374 i2c_ack <= 1'b1; //拉高应答标志位
- 375 end
- 376 7'd35: begin
- 377 scl <= 1'b0;
- 378 cnt <= 1'b0;
- 379 end
- 380 default : ;
- 381 endcase
- 382 end
- 383 st_data_wr_8: begin //写数据(8 bit)
- 384 case(cnt)
- 385 7'd0: begin
- 386 sda_out <= data_wr_t[7]; //I2C写低8位数据
- 387 sda_dir <= 1'b1;
- 388 end
- 389 7'd1 : scl <= 1'b1;
- 390 7'd3 : scl <= 1'b0;
- 391 7'd4 : sda_out <= data_wr_t[6];
- 392 7'd5 : scl <= 1'b1;
- 393 7'd7 : scl <= 1'b0;
- 394 7'd8 : sda_out <= data_wr_t[5];
- 395 7'd9 : scl <= 1'b1;
- 396 7'd11: scl <= 1'b0;
- 397 7'd12: sda_out <= data_wr_t[4];
- 398 7'd13: scl <= 1'b1;
- 399 7'd15: scl <= 1'b0;
- 400 7'd16: sda_out <= data_wr_t[3];
- 401 7'd17: scl <= 1'b1;
- 402 7'd19: scl <= 1'b0;
- 403 7'd20: sda_out <= data_wr_t[2];
- 404 7'd21: scl <= 1'b1;
- 405 7'd23: scl <= 1'b0;
- 406 7'd24: sda_out <= data_wr_t[1];
- 407 7'd25: scl <= 1'b1;
- 408 7'd27: scl <= 1'b0;
- 409 7'd28: sda_out <= data_wr_t[0];
- 410 7'd29: scl <= 1'b1;
- 411 7'd31: scl <= 1'b0;
- 412 7'd32: begin
- 413 sda_dir <= 1'b0;
- 414 sda_out <= 1'b1;
- 415 end
- 416 7'd33: scl <= 1'b1;
- 417 7'd34: begin //从机应答
- 418 st_done <= 1'b1;
- 419 if(sda_in == 1'b1) //高电平表示未应答
- 420 i2c_ack <= 1'b1; //拉高应答标志位
- 421 end
- 422 7'd35: begin
- 423 scl <= 1'b0;
- 424 cnt <= 1'b0;
- 425 end
- 426 default : ;
- 427 endcase
- 428 end
复制代码代码337行至382行,表示写入数据的高8位。 代码383行至428行,表示写入数据的低8位。 图像采集顶层模块(cmos_data_top)例化了以下两个模块:图像采集模块(cmos_capture_raw_gray)和图像裁剪模块(cmos_tailor)。有关图像采集顶层模块的详细介绍请大家参考“OV7725摄像头RGB-LCD显示实验”章节。 图像采集模块(cmos_capture_raw_gray)为其他模块提供摄像头8bit输入数据和数据使能以及摄像头稳定后的行场信号。图像裁剪模块(cmos_tailor)只有在LCD的器件ID为16’h4342时起作用,即摄像头的分辨率大于LCD屏的分辨率,起到裁剪图像数据,使图像的有效数据达到匹配LCD屏的尺寸。有关图像裁剪模块的详细介绍请大家参考“OV7725摄像头RGB-LCD 显示实验”章节。 图像采集模块的代码如下: - 1 module cmos_capture_raw_gray
- 2 #(
- 3 parameter CMOS_FRAME_WAITCNT = 4'd10 //等待数据稳定所需要的帧数
- 4
- 5 )
- 6 (
- 7 //global clock
- 8 input clk_cmos , //锁相环分频时钟
- 9 input rst_n , //复位信号,低有效
- 10 //CMOS Sensor Interface
- 11 input cmos_pclk , //摄像头输入时钟
- 12 output cmos_xclk , //摄像头驱动时钟
- 13 input cmos_vsync , //摄像头场信号
- 14 input cmos_href , //摄像头行信号
- 15 input [7:0 cmos_data , //摄像头数据
- 16 //CMOS SYNC Data output
- 17 output cmos_frame_vsync, //摄像头场有效信号
- 18 output cmos_frame_href , //摄像头行有效信号
- 19 output[15:0wr_data , //摄像头有效数据
- 20 output cmos_frame_clken, //摄像头数据有效使能
- 21 //user interface
- 22 output[7:0 cmos_fps_rate //摄像头帧率
- 23 );
- 24
- 25 //parameter define
- 26 localparam DELAY_TOP = 2 * 24_000000; //2s delay
- 27
- 28 //reg define
- 29 reg [27:0 delay_cnt ;
- 30 reg frame_sync_flag;
- 31 reg [3:0 cmos_fps_cnt ;
- 32 reg [8:0 cmos_fps_cnt2 ;
- 33 reg [7:0 cmos_fps_rate ;
- 34 reg [1:0 cmos_vsync_r, cmos_href_r ;
- 35 reg [7:0 cmos_data_r0, cmos_data_r1;
- 36
- 37 //wire define
- 38 wire cmos_vsync_end ;
- 39 wire delay_2s ;
- 40 wire [7:0 cmos_frame_data;
- 41
- 42 //*****************************************************
- 43 //** main code
- 44 //*****************************************************
- 45
- 46 assign cmos_vsync_end = (cmos_vsync_r[1 & ~cmos_vsync_r[0]) ? 1'b1 : 1'b0;
- 47 assign cmos_xclk = clk_cmos; //24MHzCMOS XCLK output
- 48 assign cmos_frame_clken = frame_sync_flag ? cmos_href_r[1 : 1'b0;
- 49 assign cmos_frame_vsync = frame_sync_flag ? cmos_vsync_r[1 : 1'b0;//DFF 2clocks
- 50 assign cmos_frame_href = frame_sync_flag ? cmos_href_r[1 : 1'b0; //DFF 2 clocks
- 51 assign cmos_frame_data = frame_sync_flag ? cmos_data_r1 : 8'd0; //DFF 2 clocks
- 52 assign delay_2s = (delay_cnt == DELAY_TOP - 1'b1) ? 1'b1 : 1'b0;
- 53 assign wr_data = {cmos_frame_data[7:3],cmos_frame_data[7:2],cmos_frame_data[7:3]};
- 54
复制代码在程序的第53行实现了8位数据转16位数据的功能,这里将8位的灰度数据按照高位赋值的方式将数据按照RGB565的格式分别赋给各个颜色分量。需要注意的是摄像头的图像数据是在像素时钟(cam_pclk)下输出的,因此摄像头的图像数据必须使用像素钟来采集,否则会造成数据采集错误。 - 55 always@(posedge cmos_pclk or negedge rst_n)begin
- 56 if(!rst_n)
- 57 begin
- 58 cmos_vsync_r <= 0;
- 59 cmos_href_r <= 0;
- 60 {cmos_data_r1, cmos_data_r0} <= 0;
- 61 end
- 62 else
- 63 begin
- 64 cmos_vsync_r <= {cmos_vsync_r[0], cmos_vsync};
- 65 cmos_href_r <= {cmos_href_r[0], cmos_href};
- 66 {cmos_data_r1, cmos_data_r0} <= {cmos_data_r0, cmos_data};
- 67 end
- 68 end
- 69
- 70 //Wait for Sensor outputData valid 10 Frame of OmniVision
- 71 always@(posedge cmos_pclk or negedge rst_n)begin
- 72 if(!rst_n)
- 73 cmos_fps_cnt <= 0;
- 74 else //Wait until cmos init complete
- 75 begin
- 76 if(cmos_fps_cnt < CMOS_FRAME_WAITCNT)
- 77 cmos_fps_cnt <= cmos_vsync_end ? cmos_fps_cnt + 1'b1 : cmos_fps_cnt;
- 78 else
- 79 cmos_fps_cnt <= CMOS_FRAME_WAITCNT;
- 80 end
- 81 end
- 82
- 83 //Come ture framesynchronization to ignore error frame or has not capture when vsync begin
- 84 always@(posedge cmos_pclk or negedge rst_n)begin
- 85 if(!rst_n)
- 86 frame_sync_flag <= 0;
- 87 else if(cmos_fps_cnt == CMOS_FRAME_WAITCNT && cmos_vsync_end == 1)
- 88 frame_sync_flag <= 1;
- 89 else
- 90 frame_sync_flag <= frame_sync_flag;
- 91 end
- 92
- 93 //Delay 2s for cmos fpscounter
- 94 always@(posedge cmos_pclk or negedge rst_n)begin
- 95 if(!rst_n)
- 96 delay_cnt <= 0;
- 97 else if(delay_cnt < DELAY_TOP - 1'b1)
- 98 delay_cnt <= delay_cnt + 1'b1;
- 99 else
- 100 delay_cnt <= 0;
- 101 end
- 102
- 103 //cmos image output ratecounter
- 104 always@(posedge cmos_pclk or negedge rst_n)begin
- 105 if(!rst_n)
- 106 begin
- 107 cmos_fps_cnt2 <= 0;
- 108 cmos_fps_rate <= 0;
- 109 end
- 110 else if(delay_2s ==1'b0) //time is not reached
- 111 begin
- 112 cmos_fps_cnt2 <= cmos_vsync_end ? cmos_fps_cnt2 + 1'b1 : cmos_fps_cnt2;
- 113 cmos_fps_rate <= cmos_fps_rate;
- 114 end
- 115 else //time up
- 116 begin
- 117 cmos_fps_cnt2 <= 0;
- 118 cmos_fps_rate <= cmos_fps_cnt2[8:1]; //divide by 2
- 119 end
- 120 end
- 121
- 122 endmodule
复制代码CMOS 图像采集模块第3行定义了参数CMOS_FRAME_WAITCNT(寄存器数据稳定等待的帧个数),这里设置等待帧是为了保证摄像头输出稳定后才采样数据。这里采集场同步信号的上升沿来统计帧数,计数器计数超过10次之后产生数据有效的标志(frame_sync_flag),开始采集图像。 在程序的第94行至101行,是对2s的时间进行计数。 在程序的第103行至120行,是对摄像头的帧率进行计数。在110行至119行,当2s的时间未到且当场信号的下降沿到来时,对帧率计数器进行累加,当 2s 的时间到来时,对帧率计数器进行清零,把帧率计数器除以2的值赋给帧率寄存器。 我们在前面说过,本章实验程序设计和“OV7725摄像头RGB-LCD显示实验”相比,我们只是把外设OV7725模块替换成了MT9V034模块,替换了图像采集模块和I2C配置模块,修改了I2C驱动模块,其余模块基本相同。本章节主要介绍了I2C配置模块、图像采集模块和I2C驱动模块,而其它相同模块的程序设计详情请大家参考“OV7725摄像头RGB-LCD显示实验”章节。
1.5 下载验证首先将MT9V034摄像头插入开发板上的摄像头扩展接口(注意摄像头镜头朝外);将FPC排线一端与正点原子的7寸RGB模块上的J1接口连接,另一端与ATK-DFPGL22G开发板上的RGB_LCD接口连接;如图 43.5.1和图 43.5.2所示。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线。 连接实物图如下图所示: 图 43.5.1 ATK-7’ RGB-LCD模块FPC连接器 最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证MT9V034摄像头RGB-LCD实时显示功能。下载完成后观察显示器的显示图像如下图所示,说明MT9V034摄像头LCD显示程序下载验证成功。 |