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