原子的OV7670模块Verilog实现

原子的OV7670模块Verilog实现

该模块是由原子公司自己开发设计的,主要面向对象为STM32(所以用Verlog实现总感觉有点别扭)。通过SCCB 总线控制,可以输出整帧、子采样、取窗口等方式的各种分辨率8位影像数据。该产品VGA图像最高达到30帧/秒。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、度、色度等都可以通过SCCB接口编程。OmmiVision 图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、托尾、浮散等,提高图像质量,得到清晰的稳定的彩色图像。
该模块会输出VGA(分别率为640480)、QVGA(分别率为320240)、QQVGA(分别率为160120)。时序控制主要由像素时钟PCLK、帧同步信号VSYNC、行同步信号HSYNC/HREF控制,图像数据输出由D[7:0]输出。输出时序图如图1所示。
图1 输出时序图
如图所示,当HREF为高时,每个PCLK时钟输出一个字节的数据,在平时使用时,大多采用RGB565格式的数据,共16位的数据,及2个字节,所以在数据输出时,每2个字节组成一个像素的数据(高字节在前,低字节在后)。帧时序如图2所示。
图2 帧时序
SYNC主要作为帧的同步信号,在帧同步后,后面数据才会有效。
该模块自带了一块FIFO,正如我开始说的,它的主要面向对象为STM32,因为OV7670的PCLK时钟最高可达到24MHz,STM32抓取这个信号的确有点困难,所以通过FIFO先对采集到的像素数据进行缓存。模块原理图如图3所示。
图3 原理图
如图所示,OV7670的XCLK连接12MHz的有源晶振,作为OV7670的工作时钟,FIFO芯片采用的是AL422B,该芯片的容量是384K的字节,这点需要注意一下,640
480的一帧为614400字节,即614.4K的字节;320240的一帧为153600字节,即153.6K字节;160120的一帧为38400字节,即38.4K字节。所以该FIFO芯片能存储2帧的320240的图片数据,8帧的160120的图片数据,而640*480是无法存储一帧数据的,在使用时,需要提前读取FIFO的数据。AL422B的引脚如图4所示。
AL422B引脚图
该原理图中,AL422B的数据输入接OV7670的数据输出,写时钟WCK接OV7670的像素时钟PCLK。
接下来,就直接放代码。代码主要有3个.v文件组成:SCCB_sender、cfg_reg、ov7670_data。它们对应的功能如下:

module SCCB_sender(
        input       clk,
        inout       siod,
        output  reg   sioc,
        output  reg  ready = 0,
        input        valid,
        input [7:0] sub_addr,value

    );
    parameter id ='h42;
            reg error;

    reg [20:0] cntr  = 0 ;

    always @ (posedge clk) // valid有效时开始
        if (0==cntr)
        cntr <= valid ;
        else
        begin
            if ( 31 == cntr[20:11] ) cntr <= 0 ;  else cntr <= cntr + 1 ;
        end
        
        

    always @ (posedge clk) ready <=  (cntr == 0) && (valid ==1) ; //告诉上位机数据已经取走

    reg [7:0]idr,addr,val;//锁存要发送的数据
    always @(posedge clk)if ( ( cntr == 0 ) && ( valid == 1 ) ) idr   <=  id ;
    always @(posedge clk)if ( ( cntr == 0 ) && ( valid == 1 ) ) addr  <=  sub_addr ;
    always @(posedge clk)if ( ( cntr == 0 ) && ( valid == 1 ) ) val   <=  value ;

    reg SIOD_EN ; // 发送使能 为0则是输入

    always @ (posedge clk)

    case  ( cntr[20:11] )

        0: sioc <= 1 ;

        1: case (cntr[10:9])
            2'b00:  sioc <= 1 ;
            2'b01:  sioc <= 1 ;
            2'b10:  sioc <= 1 ;
            2'b11:  sioc <= 0 ;
        endcase

		

        29:
        case (cntr[10:9])
            2'b00:  sioc <= 0 ;
            2'b01:  sioc <= 1 ;
            2'b10:  sioc <= 1 ;
            2'b11:  sioc <= 1 ;
        endcase
        30,31: sioc <= 1 ;
        
	    default 
		   case (cntr[10:9])
            2'b00:  sioc <= 0 ;
            2'b01:  sioc <= 1 ;
            2'b10:  sioc <= 1 ;
            2'b11:  sioc <= 0 ;
        endcase
		
    endcase

    //SIOD_EN
    always @ (posedge clk) //在三个DONOT-CARE 状态设置为输入状态
    case  (cntr[20:11])
        10,19,28 :SIOD_EN <=0 ;
        default SIOD_EN<=1;
    endcase
    
    reg SIOD_DAT ;//SIOD_DAT
     
    always @(posedge clk) //对应的SIOD的输出
    case (cntr[20:11])

        0:SIOD_DAT<=1;
        1:SIOD_DAT<=0;

        2:SIOD_DAT<=idr[7] ;
        3:SIOD_DAT<=idr[6] ;
        4:SIOD_DAT<=idr[5] ;
        5:SIOD_DAT<=idr[4] ;
        6:SIOD_DAT<=idr[3] ;
        7:SIOD_DAT<=idr[2] ;
        8:SIOD_DAT<=idr[1] ;
        9:SIOD_DAT<=idr[0] ;

        11:SIOD_DAT<=addr[7] ;
        12:SIOD_DAT<=addr[6] ;
        13:SIOD_DAT<=addr[5];
        14:SIOD_DAT<=addr[4];
        15:SIOD_DAT<=addr[3];
        16:SIOD_DAT<=addr[2];
        17:SIOD_DAT<=addr[1];
        18:SIOD_DAT<=addr[0];

        20:SIOD_DAT<=val[7] ;
        21:SIOD_DAT<=val[6] ;
        22:SIOD_DAT<=val[5] ;
        23:SIOD_DAT<=val[4] ;
        24:SIOD_DAT<=val[3] ;
        25:SIOD_DAT<=val[2] ;
        26:SIOD_DAT<=val[1] ;
        27:SIOD_DAT<=val[0] ;

        29:SIOD_DAT<=0;
        30:SIOD_DAT<=1;

        default SIOD_DAT<=1;

    endcase

    reg [31:0] dr ; //采用串行移位方式。

    always @ (posedge clk)
        if ( (cntr == 0)&& (valid ==1 ) )
            dr <= {   2'b10,   id,1'bx, 			sub_addr,1'bx,     value, 1'bx,       3'b011    };
        else if ( cntr[10:0] == 0 ) dr <={dr[30:0],1'b1};

 assign siod = ( SIOD_EN ) ? dr[31] : 'BZ ;


    always @ (posedge clk)
        if (cntr[10:9] == 2'b01  )
        case(cntr[20:11])
            1: error<=0;
            10,19,28 :error <= error | siod ;
        endcase

endmodule

上面是SCCB_sender文件,clk作为工作时钟,在这说明一下,clk工作时钟15MHz。其次是siod和sioc,这2个为SCCB的外接接口,ready和valid是与cfg_reg文件的“握手”,sub_addr和value分别为寄存器和对应的值,模块的地址id为0x42,已经在SCCB_sender加入。

module cfg_reg #(
        parameter LEN = 'h7b
    )(
        input clk,  rst,
        output [7:0] sub_addr,value,       
        output reg  m_valid,
        input m_ready,
        output data_start
    );

    reg [7:0] cntr = 0 ;
    reg  [15:0] dout;
    assign  sub_addr = dout[15:8];
    assign  value = dout[7:0];

    always @ (posedge clk)
        if (rst) cntr<=0;
        else if ( m_ready & m_valid )
            cntr<=cntr + 1 ;

    always @ (posedge clk)m_valid <= ( cntr != LEN )   ;
    
    assign  data_start =( cntr == LEN )?1'b1:1'b0;

    always @ (posedge clk)
    case (cntr)
        0:
            dout <= 16'h1280;
        8'h01 :
            dout <= 16'h1280;
        8'h02 :
            dout <= 16'h3a04;
        8'h03 :
            dout <= 16'h40d0;
        8'h04 :
            dout <= 16'h1214;
        8'h05 :
            dout <= 16'h3280;
        8'h06 :
            dout <= 16'h1716;
        8'h07 :
            dout <= 16'h1804;
        8'h08 :
            dout <= 16'h1902;
        8'h09 :
            dout <= 16'h1a7b;
        8'h0A :
            dout <= 16'h0306;
        8'h0B :
            dout <= 16'h0c00;
        8'h0C :
            dout <= 16'h1500;
        8'h0D :
            dout <= 16'h3e00;
        8'h0E :
            dout <= 16'h703a;
        8'h0F :
            dout <= 16'h7135;
        8'h10 :
            dout <= 16'h7211;
        8'h11 :
            dout <= 16'h7300;
        8'h12 :
            dout <= 16'ha202;
        8'h13 :
            dout <= 16'h1181;
        8'h14 :
            dout <= 16'h7a20;
        8'h15 :
            dout <= 16'h7b1c;
        8'h16 :
            dout <= 16'h7c28;
        8'h17 :
            dout <= 16'h7d3c;
        8'h18 :
            dout <= 16'h7e55;
        8'h19 :
            dout <= 16'h7f68;
        8'h1A :
            dout <= 16'h8076;
        8'h1B :
            dout <= 16'h8180;
        8'h1C :
            dout <= 16'h8288;
        8'h1D :
            dout <= 16'h838f;
        8'h1E :
            dout <= 16'h8496;
        8'h1F :
            dout <= 16'h85a3;
        8'h20 :
            dout <= 16'h86af;
        8'h21 :
            dout <= 16'h87c4;
        8'h22 :
            dout <= 16'h88d7;
        8'h23 :
            dout <= 16'h89e8;
        8'h24 :
            dout <= 16'h13e0;
        8'h25 :
            dout <= 16'h0000;
        8'h26 :
            dout <= 16'h1000;
        8'h27 :
            dout <= 16'h0d00;
        8'h28 :
            dout <= 16'h1428;
        8'h29 :
            dout <= 16'ha505;
        8'h2A :
            dout <= 16'hab07;
        8'h2B :
            dout <= 16'h2475;
        8'h2C :
            dout <= 16'h2563;
        8'h2D :
            dout <= 16'h26a5;
        8'h2E :
            dout <= 16'h9f78;
        8'h2F :
            dout <= 16'ha068;
        8'h30 :
            dout <= 16'ha103;
        8'h31 :
            dout <= 16'ha6df;
        8'h32 :
            dout <= 16'ha7df;
        8'h33 :
            dout <= 16'ha8f0;
        8'h34 :
            dout <= 16'ha990;
        8'h35 :
            dout <= 16'haa94;
        8'h36 :
            dout <= 16'h13e5;
        8'h37 :
            dout <= 16'h0e61;
        8'h38 :
            dout <= 16'h0f4b;
        8'h39 :
            dout <= 16'h1602;
        8'h40 :
            dout <= 16'h1e27;
        8'h41 :
            dout <= 16'h2102;
        8'h42 :
            dout <= 16'h2291;
        8'h43 :
            dout <= 16'h2907;
        8'h44 :
            dout <= 16'h330b;
        8'h45 :
            dout <= 16'h350b;
        8'h46 :
            dout <= 16'h371b;
        8'h47 :
            dout <= 16'h3871;
        8'h48 :
            dout <= 16'h392a;
        8'h49 :
            dout <= 16'h3c78;
        8'h4A :
            dout <= 16'h4d40;
        8'h4B :
            dout <= 16'h4e20;
        8'h4C :
            dout <= 16'h6900;
        8'h4D :
            dout <= 16'h6b40;
        8'h4E :
            dout <= 16'h7419;
        8'h4F :
            dout <= 16'h8d4f;
        8'h50 :
            dout <= 16'h8e00;
        8'h51 :
            dout <= 16'h8f00;
        8'h52 :
            dout <= 16'h9000;
        8'h53 :
            dout <= 16'h9100;
        8'h54 :
            dout <= 16'h9200;
        8'h55 :
            dout <= 16'h9600;
        8'h56 :
            dout <= 16'h9a80;
        8'h57 :
            dout <= 16'hb084;
        8'h58 :
            dout <= 16'hb10c;
        8'h59 :
            dout <= 16'hb20e;
        8'h5A :
            dout <= 16'hb382;
        8'h5B :
            dout <= 16'hb80a;
        8'h5C :
            dout <= 16'h4314;
        8'h5D :
            dout <= 16'h44f0;
        8'h5E :
            dout <= 16'h4534;
        8'h5F :
            dout <= 16'h4658;
        8'h60 :
            dout <= 16'h4728;
        8'h61 :
            dout <= 16'h483a;
        8'h62 :
            dout <= 16'h5988;
        8'h63 :
            dout <= 16'h5a88;
        8'h64 :
            dout <= 16'h5b44;
        8'h65 :
            dout <= 16'h5c67;
        8'h66 :
            dout <= 16'h5d49;
        8'h67 :
            dout <= 16'h5e0e;
        8'h68 :
            dout <= 16'h6404;
        8'h69 :
            dout <= 16'h6520;
        8'h6A :
            dout <= 16'h6605;
        8'h6B :
            dout <= 16'h9404;
        8'h6C :
            dout <= 16'h9508;
        8'h6D :
            dout <= 16'h6c0a;
        8'h6E :
            dout <= 16'h6d55;
        8'h6F :
            dout <= 16'h4f80;
        8'h70 :
            dout <= 16'h5080;
        8'h71 :
            dout <= 16'h5100;
        8'h72 :
            dout <= 16'h5222;
        8'h73 :
            dout <= 16'h535e;
        8'h74 :
            dout <= 16'h5480;  
        8'h75 :
            dout <= 16'h0903;
        8'h76 :
            dout <= 16'h6e11;
        8'h77 :
            dout <= 16'h6f9f;
        8'h78 :
            dout <= 16'h5500;
        8'h79 :
            dout <= 16'h5640;
        8'h7A :
            dout <= 16'h5740;
                           
        default 	  dout <= 16'hb80a;

    endcase
endmodule

上面为cfg_reg文件,该文件主要的部分为OV7670的寄存器对应值,作为初始化使用,在实际使用时,也可以将这些数据存入至ROM中。具体寄存去的使用可以参考这OV7670寄存器这个文件中注意一下data_start,它的主要作用是表明初始化完成,在初始化完成后,ov7670_data文件才会真正开始采集数据。

module ov7670_data(
    input         clk,
	input         rst_n,
	output        fifo_oe,
	output        fifo_wen,
	output   reg  fifo_wrst,
	output   reg  fifo_rrst,
	output   reg  fifo_rclk,
	input         vsync,
	input   [7:0] in_data,
	input         data_start
    );
	
assign  fifo_oe = 1'b0;
assign  fifo_wen  = 1'b1;

reg     vsync_reg;

reg              wrst_state;
reg  [2:0] 		 rrst_state;
reg  [2:0] 		 wrst_cnt;
reg  [2:0] 		 rrst_cnt;

always@(posedge clk or negedge rst_n)begin
 if(!rst_n || data_start==0) vsync_reg <=1'b0;
 else       vsync_reg <=vsync;
end

wire  	vsync_flag = (vsync_reg == 1'b1)?0:(vsync==1'b1)?1:0;

always@(posedge clk or negedge rst_n)begin
 if(!rst_n) begin
  fifo_wrst <= 1'b1;
  wrst_state <= 1'b1;end
 else begin
   case(wrst_state)
    1'b0:begin
	       if(vsync_flag)begin
		     fifo_wrst  <= 1'b0;
			 wrst_state <= 1'b1;end 
		   else begin
		     fifo_wrst  <= 1'b1;
			 wrst_state <= 1'b0;end
		  end
	1'b1:begin
	       if(wrst_cnt[2]==1'b1)begin
		      wrst_cnt  <= 3'b000;
			  wrst_state<= 1'b0;end
		   else begin
		      wrst_cnt  <= wrst_cnt+1'b1;
			  wrst_state<= 1'b0;end
		 end
	endcase
	end
end

wire  ov_sta =(wrst_cnt[2]==1'b1)?1:0;
reg [16:0] color_cnt;
reg [15:0] color;

always@(posedge clk or negedge rst_n)begin
 if(!rst_n) begin
   fifo_rrst <= 1'b1;
   rrst_state<= 3'b000;
   fifo_rclk <= 1'b1;
   color     <= 16'd0;
   color_cnt <= 17'd0;end
  else begin
   case(rrst_state)
    3'd0:begin
	      if(ov_sta)begin
		     fifo_rrst <= 1'b0;
			 fifo_rclk <= 1'b0;
			 rrst_state<= 3'd1;end
		  else begin
		     fifo_rrst <= 1'b1;
			 fifo_rclk <= 1'b1;
			 rrst_state<= 3'd0;end
		 end
	3'd1:begin
	      fifo_rclk <= 1'b1;
		  if(rrst_cnt[2]==1'b1)begin
		    fifo_rclk <= 1'b0;
			rrst_cnt  <= 3'b000;
			rrst_state<= 3'd2;end
		  else  rrst_cnt<= rrst_cnt+1'b1;
		 end
	3'd2:begin
	      fifo_rclk  <= 1'b0;
		  color[7:0] <= in_data;
		  color_cnt  <= color_cnt+1'b1;
		  rrst_state <= 3'd3;end
	3'd3:begin
	      fifo_rclk <= 1'b1;
		  color     <= {color[7:0],8'd0};
		  rrst_state<=3'd4;end
    3'd4:begin
	      fifo_rclk <= 1'b0;
		  color[7:0]<= in_data;
		  rrst_state<= 3'd5;end
	3'd5:begin
	      fifo_rclk <= 1'b1;
		  if(color_cnt[16:10]==7'b1001011)rrst_state<=3'd0;//这是表明一帧数据采集结束,输出为QVGA的数据
		  else                            rrst_state<=3'd2;
		 end
	endcase
  end       	
end

endmodule

上面为ov7670_data文件,fifo_oe为FIFO芯片的片选,低电平有效;fifo_wen为FIFO的写使能,高电平有效,这里需要说明一下,FIFO芯片是低电平有效,但输出的信号和OV7670的HREF信号经过一个与非门作为FIFO芯片的写使能,前面已经说过,HREF为高时,输出数据;fifo_wrst为FIFO芯片的写复位,低电平有效;fifo_rrst为FIFO芯片的读复位,也是低电平有效;fifo_rclk为FIFO芯片数据读取的读时钟;vsync为OV7670的帧同步信号;in_data为FIFO芯片输出的图像像素数据。
上面的代码只是提供一个参考,具体使用时,有些部分还是需要改动的!

猜你喜欢

转载自blog.csdn.net/qq_37989552/article/details/83047703