OV7725图像传感器 OV7725摄像头是OmniVision公司的VGA分辨率的图像传感器,因为它成像比较清晰,开发简单,适合于在中低端产品开发中使用,它是一款集成1/4英寸单芯片VGA相机和图像处理器的高性能传感器,因为它性能优越,质量和可靠性方面也有不错表现,而且在低照度特性下,能在-20°~70°温度范围内稳定工作,这些特点使得该摄像头在PC相机、手机、照相机和笔记本等领域被广泛使用。 OV7725拥有640x480的感光阵列,最高支持640x480@60Hz图像的输出,并且其图像输出格式、分辨率和图像特性等均可参数化配置,所以可以灵活的使用在很多场合,如下图为OV7725。OV7725性能参数和特点如下: (1) 灵敏度高,适用于低照明场合; (2) 支持VGA、QVGA以及40*30到CIF(352*288)分辨率的图像输出; (3) 具有标准的SCCB配置接口,通过配置该接口,可输出RAW RGB、RGB(RGB565/555/444)、GRB4:2:2、YUV/YcbCr4:2:2等格式; (4) 自动调节噪声抑制和边缘增强; (5) 采用VarioPixel方式实现像素子采样; (6) 自动图像控制功能:自动曝光、自动带通滤波、自动白平衡、自动增益控制; (7) 镜头阴影矫正,饱和度自动调节。
OV7725图像传感器
配置与驱动OV7725图像传感器在工作之前需要进行初始化配置,其配置接口为标准的SCCB(Serial Camera Control Bus),通过该总线按照我们的需求对摄像头进行配置,从而达到预期的模式工作。下面两小节分别介绍SCCB总线和OV7725摄像头常用寄存器。 SCCB总线SCCB,即串行相机控制总线,其实质就是我们常用的IIC总线,由两根线组成,一根为时钟线SIO_C,另外一根为数据线SIO_D,SCCB总线必须一次性发送8位数据,下面介绍几种重要的信号。 (1) 开始信号和结束信号 开始信号:当SIO_C为高电平时候,SIO_D由高电平向低电平转换。 结束信号:当SIO_C为高电平时候,SIO_D由低电平向高电平转换。 (2) 应答信号和非应答信号 应答信号:当SIO_C为高电平时候,SIO_D低电平。 非应答信号:当SIO_C为高电平时候,SIO_D高电平。 (3) 数据传输 当SIO_C为低电平时,数据稳定传输;当SIO_C为高电平时,数据必须保持稳定,不能变更。 SCCB总线读写时序在了解了几个重要的信号以后,下面展开对寄存器读写的时序。
SCCB总线的写入寄存器时序:SCCB总线在写寄存器时,先写设备地址(0x42),然后再写寄存器地址,最后写将要写入寄存器的值,从而完成对一个寄存器的写入配置,流程如下图所示。
SCCB总线的读取寄存器时序:SCCB总线在读寄存器时,分两个阶段:第一阶段为先写设备地址(0x42),然后再写寄存器地址;第二阶段为写设备地址(0x43),然后读出寄存器的值,从而完成对一个寄存器值的读取,如下图所示为SCCB读取寄存器流程图。
OV7725寄存器及其配置OV7725总共有172个寄存器用于工作模式的配置,在传感器正常工作前,必须对寄存器进行初始化,当然这172个寄存器并非都需要配置,很多寄存器采用默认值即可工作。下面用Verilog语言配置一些重要的寄存器,其中前面值为寄存器地址,后面为寄存器的值。 1) 读取产品ID、制造商ID: // OV7725: VGA RGB565 Config //ReadData Index //0 : LUT_DATA = {8'h0A, 8'h77}; //Product ID Number MSB (Read only) //1 : LUT_DATA = {8'h0B, 8'h21}; //Product ID Number LSB (Read only) 0 : LUT_DATA = {8'h1C, 8'h7F}; //Manufacturer ID Byte - High (Read only) 1 : LUT_DATA = {8'h1D, 8'hA2}; //Manufacturer ID Byte - Low (Read only) 2) 分辨率、像素时钟和图像输出格式等配置: //Write Data Index 2 : LUT_DATA = {8'h12, 8'h80}; // BIT[7]-Reset all theRegisters 3 : LUT_DATA = {8'h3d,8'h03}; //DC offset for analog process 4 : LUT_DATA = {8'h15,8'h02}; //href/vsync/pclk/data 5 : LUT_DATA = {8'h17,8'h22}; //VGA: 8'h22; QVGA: 8'h3f; 6 : LUT_DATA = {8'h18,8'ha4}; //VGA: 8'ha4; QVGA: 8'h50; 7 : LUT_DATA = {8'h19,8'h07}; //VGA: 8'h07; QVGA: 8'h03; 8 : LUT_DATA = {8'h1a,8'hf0}; //VGA: 8'hf0; QVGA: 8'h78; 9 : LUT_DATA = {8'h32,8'h00}; //HREF /8'h80 10: LUT_DATA= {8'h29, 8'hA0}; //VGA: 8'hA0; QVGA: 8'hF0 11: LUT_DATA= {8'h2C, 8'hF0}; //VGA: 8'hF0; QVGA: 8'h78 12: LUT_DATA = {8'h0d,8'h41}; //Bypass PLL 13: LUT_DATA = {8'h11,8'h01}; //CLKRC 其中地址为0x15的寄存器,配置场行同步信号的电平标准;地址为0x11和0x0d寄存器,配置像素时钟频率;地址为0x12配置图像输出格式。 我们一般的处理系统有最小系统,那么我们摄像头的配置也有配置最少的寄存器来达到我们通用的需求,下面列出的便是我们必须配置的基本寄存器。 0 : LUT_DATA = {8'h1C, 8'h7F}; //Manufacturer ID Byte - High (Read only) 1 : LUT_DATA = {8'h1D, 8'hA2}; //Manufacturer ID Byte - Low (Read only) //Write Data Index 2: LUT_DATA = {8'h12, 8'h80}; // BIT[7]-Reset all the Reg 3 : LUT_DATA = {8'h3d, 8'h03}; //DC offset for analog process //COM10: href/vsync/pclk/datareverse(Vsync H,data is valid) 4 : LUT_DATA = {8'h15, 8'h02}; 5: LUT_DATA = {8'h11, 8'h01}; 6: LUT_DATA = {8'h12, 8'h06}; //BIT[6]: 7 : LUT_DATA = {8'h0C, 8'h10}; 8 : LUT_DATA = 16'h0c_d0; // vertical and horizontal iamge 我们标号为0和1的寄存器为通过SCCB总线读取的寄存器,那么我们对寄存器配置应该是写寄存器才对,为什么要对寄存器读呢?答案是因为我们为了保证我们的SCCB协议的正确性,我们对寄存器进行读操作,将读到的数据显示在LED上,从LED的结果上便可以看出我们协议的正确与否。 图像缓存模块
我们对摄像头初始化,使其输出图像格式为RGB565,但是SCCB总线输出的数据为8bits,我们输出的RGB565为16 bits,是由相邻两个像素拼接而成,其拼接如下图:
位拼接的Verilog代码实现如下: always@(posedge CMOS_PCLK or negedge RSTN) begin if (~RSTN ) begin address<= 0; address_next<=0; wr_hold<= 0; end else begin // R(5) G(6) B(5) CMOS_oDATA<= {d_latch[15: 11] , d_latch[10: 5] , d_latch[4: 0]}; address<= address_next; wr_hold<= { wr_hold[0] , (CMOS_HREF &(~wr_hold[0])) }; d_latch<= { d_latch[7:0] ,CMOS_iDATA[7:0]}; if(Frame_valid && wr_hold[1] == 1'b1) address_next <= address_next + 1; end end 我们拼接一个有效像素,就生成一个有效地址,为后面Block RAM做准备。 因为像素时钟为24MHz,VGA刷新频率为25MHz,而且因为进行了像素位拼接,所以像素需要在不同时钟域之间进行传输,为保证建立时间和保持时间得到满足,我们采用Block RAM 进行数据缓存,从而实现跨时钟域间数据传输。
Block RAM 在Xilinx的开发工具ISE和Vivado里都作为IP核存在,我们只需要配置相应的参数即可使用,我们数据位宽为16bits,图像分辨率为640x480,所以我们设置Block RAM的位宽为16bits,数据深度为一帧图像的有效像素个数,为307200,图3-6为Block RAM IP核,其中clka为写入时钟,addra为写入地址,dina为写入数据端,wea为写使能端,clkb为读出时钟,addrb为读地址端,doutb为读出数据端。
图像数据捕获 //*********************************Thispicture is perfect ************************************ `timescale 1ns /1ps moduleov7670_capture ( input iCLK, //24MHz input iRST, //I2C Initilize Done input Init_Done, //Init Done //Sensor Interface input CMOS_PCLK, //25MHz input [7:0] CMOS_iDATA, //CMOS Data input CMOS_VSYNC, input CMOS_HREF, output CMOS_XCLK, //24MHz //Ouput Sensor Data output reg CMOS_oCLK, // 12.5MHz(valid) output reg[11:0] CMOS_oDATA, // 位拼接后的数据 output reg[18:0] address // 一个数据位对应一个地址 ); assign CMOS_XCLK = iCLK; //24MHz XCLK //assign CMOS_XCLK = CMOS_PCLK; //25MHz XCLK ///===================上升沿==================== reg mCMOS_VSYNC; always@(posedgeCMOS_PCLK or posedge iRST) begin if(iRST) mCMOS_VSYNC <= 1; else mCMOS_VSYNC <= CMOS_VSYNC; //场同步:低电平有效 end wire CMOS_VSYNC_over = ({mCMOS_VSYNC,CMOS_VSYNC}== 2'b01) ? 1'b1 : 1'b0; //上升沿,一帧数据采样结束 ///====================采集数据================== reg[15:0] d_latch; reg[18:0] address_next; // 一个地址对应存储一个有效数据 (640*480 = 307200 =18'd307200) reg[1:0] wr_hold; always@(posedgeCMOS_PCLK ) begin if (CMOS_VSYNC == 1'b1 ) begin address <= 0; address_next <=0; wr_hold <= 0; end else begin // R(4) G(4) B(4) CMOS_oDATA <= {d_latch[15: 12] ,d_latch[10: 7] , d_latch[4: 1]}; address <= address_next; wr_hold <= { wr_hold[0] , (CMOS_HREF &(~wr_hold[0])) }; // 只采样1 3 5 7 .... 因为数据拼接的原因 d_latch <= { d_latch[7:0],CMOS_iDATA[7:0]}; if( Frame_valid &&wr_hold[1] == 1'b1) address_next <=($unsigned(address_next) + 1); //强制类型转换$signed(), 前面是错误的 end end //==================舍去前10帧================== reg [3:0] Frame_Cont; reg Frame_valid; always@(posedgeCMOS_PCLK or posedge iRST) begin if(iRST) begin Frame_Cont <= 0; Frame_valid <= 0; end else if(Init_Done) //CMOS I2C初始化完毕 begin if(CMOS_VSYNC_over == 1'b1) //VS上升沿,1帧写入完毕 begin if(Frame_Cont < 11) begin Frame_Cont <= Frame_Cont + 1'b1; Frame_valid <= 1'b0; end else begin Frame_Cont <= Frame_Cont; Frame_valid <= 1'b1; //数据输出有效 end end end end //=================CMOS_DATA数据同步输出使能时钟 (时钟2分频)================= always@(posedge CMOS_PCLK or posedge iRST) begin if(iRST) CMOS_oCLK <= 0; else if(Frame_valid == 1'b1 &&wr_hold[1]) CMOS_oCLK <= ~CMOS_oCLK; else CMOS_oCLK <= 0; end // always@(*) // begin // if(CMOS_oCLK) // CMOS_oDATA <= {d_latch[15: 12] , d_latch[10: 7] , d_latch[4: 1]}; // else // CMOS_oDATA <= 0; // end // endmodule
|