zzuxzt

基于FPGA的OV摄像头初始化之SCCB协议的实现

0
阅读(5545)

关于OV系列摄像头的初始化,最主要的还是SCCB协议的编写调试,其实也就是众所周知的IIC协议。下面介绍一下用得最多的SCCB协议2线工作模式。

一.SCCB协议时序

SCCB协议2线工作模式只能工作在只有1个从机挂载下,具有接口SIO_C,SIC_D,也就是IIC的i2c_clk和i2c_sdat。SCCB协议的时序有以下几点需要注意和掌握的:

(1)时钟频率:和业界标准一致,只能工作在400KHz以内。

(2)数据读/写:数据是从高位D7开始到低位D0进行传输的。数据线SIO_D在时钟SIO_C高电平期间读取,在时钟SIO_C低电平期间写入,最佳采样点就是高、低电平期间的中点。换句话说,也就是数据只能在时钟低电平期间变化,在高电平期间不能发生变化。

(3)开始信号:在时钟SIO_C高电平期间,数据线SIO_D由高电平跳变为低电平即表示开始传输,如下图1-1所示:

wps_clip_image-29277

图1-1

(4)结束信号:在时钟SIO_C高电平期间,数据线SIO_D由低电平跳变为高电平即表示开始传输,如下图1-2所示:

wps_clip_image-5884

图1-2

(5)应答信号:在主机(FPGA)发送完8bit数据后,等待从机(OV系列摄像头)应答ACK信号,如正常应答,则主机(FPGA)在第九个时钟SIO_C高电平期间能读回SIO_D低电平信号,即从机(OV系列摄像头)在第九个时钟低电平期间拉低数据线SIO_D。

图1-3就是SCCB传输8bit数据的完整时序图:

wps_clip_image-19739

图1-3

二.OV摄像头的数据传输

首先要说的是,OV的datasheet里规定8bit数据的完整传输过程称为一相,一相包含9bit数据。对OV摄像头寄存器进行配置的写操作需要三相,对其进行读取的读操作需要四相。

先介绍一下配置寄存器的写操作:(最重要也是最有用的)

图2-1就是写操作的三相数据,第一相为OV摄像头设备ID地址0x42,第二相为所要配置的寄存器地址,第三相为所要写入的数据。

wps_clip_image-13307

图2-1

尤其需要重视的是,设备ID地址0x42只占7位,从D7到D1,第一相写设备ID地址的第8位数据是读写控制位R/W,0表示写,1表示读。具体可由下图2-2传输时序图可知:

wps_clip_image-17427

图2-2

第二、第三相数据传输皆和图2-2相似,只是第8位数据为正常数据位。

下面再介绍一下主机读回寄存器数据的读操作:(老实说,此操作对摄像头的初始化没有多大用处,可不毕操作,只是用来测试摄像头是否挂掉)

读操作由二相写操作和二相读操作构成,图2-3为二相写操作,其数据传输时序与三相写操作相同;图2-4为二相读操作,所要注意的是,在读取数据后,主机要在第9个时钟低电平期间拉高数据线SIO_D,发送不应答信号NA,其传输时序图如图2-5所示。

wps_clip_image-25684

图2-3

wps_clip_image-30184 图2-4

wps_clip_image-32343

图2-5

三.Verilog程序的编写

其实SCCB协议的编写还是比较简单的,前提是你十分熟悉它的时序,明白时钟怎么产生,每个时钟需要干什么,数据线什么时候是输入,什么时候是输出。我把像SPI,IIC这样协议的编写划分为2个部分:一个是是时钟及使能信号产生,一个是数据的传输。

(1)SCCB协议时钟及使能信号的产生:时钟信号由计数器分频产生,对主时钟clk = 25MHz进行200分频,时钟i2c_sclk初始为高电平。其中SCCB协议传输启动信号i2c_config_start非常重要,它的开启与闭合能够让每一次的寄存器配置时序都能精准控制,而不至于处于失控造成时钟偏移的状态。由于数据是在高、低电平中点采样最合适,故写使能信号i2c_wen在时钟低电平中点时刻置1使能进行写数据,读使能信号i2c_ren在时钟高电平中点时刻置1使能进行读数据。

此部分代码如下:

//---------------------------i2c_sclk generator--------------------------
reg [11:0] sclk_cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
sclk_cnt <= 1'b0;
i2c_sclk <= 1'b1;   //i2c_clk high level start
end
else if(sclk_cnt==12'd124)
begin
sclk_cnt <= 1'b0;
i2c_sclk <= ~i2c_sclk;    //200 frequency division
end
else if(i2c_config_start)  //controll the i2c_sclk work
begin
sclk_cnt <= sclk_cnt + 1'b1;
i2c_sclk <= i2c_sclk;
end
else
begin
sclk_cnt <= 1'b0;
i2c_sclk <= 1'b1;
end  
end

//------------------i2c_wen and i2c_ren generator-------------------
wire i2c_wen,i2c_ren;
reg [11:0] en_cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
en_cnt <= 1'b0;
else if(en_cnt==12'd249)
en_cnt <= 1'b0;
else if(i2c_config_start)   //controll the en_cnt work
en_cnt <= en_cnt + 1'b1;
else
en_cnt <= 1'b0;
end

assign i2c_ren = (en_cnt==12'd62) ? 1'b1:1'b0;  //sclk's high level neutral point: write i2c_config_data
assign i2c_wen = (en_cnt==12'd187) ? 1'b1:1'b0; //sclk's low level neutral point: read i2c_rdata

(2)SCCB协议数据的传输:在时钟i2c_sclk的驱动下,配置数据的传输可用状态机实现,重点在于开始信号,结束信号和等待应答/发出应答这几个状态的实现。其中三态数据线i2c_sdat的控制尤为重要,通过使能信号i2c_en来控制,一定要明白i2c_sdat在主机发送数据给从机的时钟期间是输出,在主机等待从机应答的时钟期间是输入高阻态。

下面给出一相传输的主要代码,其他相的都类似了:

    开始信号的产生:

//-------------start transmisson--------------------
6'd0: begin         //start signal
if(i2c_config_start & i2c_ren)
begin
w_sdat <= 1'b0;
w_state <= w_state + 1'b1;
end
else
begin
i2c_config_done <= 1'b0;
w_sdat <= 1'b1;
i2c_en <= 1'b0;   //write data
end
end

    写入8bit数据:

//-------------------write ID_Address----------------------
6'd1: begin
if(i2c_wen)
begin
w_sdat <= ID_Add[7];
w_state <= w_state + 1'b1;
end
end
6'd2: begin
if(i2c_wen)
begin
w_sdat <= ID_Add[6];
w_state <= w_state + 1'b1;
end
end
6'd3: begin
if(i2c_wen)
begin
w_sdat <= ID_Add[5];
w_state <= w_state + 1'b1;
end
end  
6'd4: begin
if(i2c_wen)
begin
w_sdat <= ID_Add[4];
w_state <= w_state + 1'b1;
end
end
6'd5: begin
if(i2c_wen)
begin
w_sdat <= ID_Add[3];
w_state <= w_state + 1'b1;
end
end  
6'd6: begin
if(i2c_wen)
begin
w_sdat <= ID_Add[2];
w_state <= w_state + 1'b1;
end
end 
6'd7: begin
if(i2c_wen)
begin
w_sdat <= ID_Add[1];
w_state <= w_state + 1'b1;
end
end
6'd8: begin
if(i2c_wen)
begin
w_sdat <= 1'b0;   //write 
w_state <= w_state + 1'b1;
end
end

    写操作等待应答:由于写操作都是写使能信号i2c_wen控制状态跳转的,在等待应答时,需要两个状态来实现,第一个状态等待第九个时钟的低电平到来,释放总线;第二个状态才是等待第九个时钟的高电平到来,三态控制信号i2c_en置1读取应答。我开始调试的时候就是因为没有写第一个状态而直接到了第二个状态,实际上就变成了第八个时钟状态去读取了,导致时序错误,老是读不回ACK

//-------------waiting for ACK1-------------
6'd9: begin
if(i2c_wen)         //the ninth clock L
begin
w_sdat <= 1'b1;  //release the bus
i2c_en <= 1'b0;  //write data
w_state <= w_state + 1'b1;
end
end
6'd10: begin         //the ninth clock H
if(i2c_ren)
begin
i2c_en <= 1'b1;  //read data
ackw1 <= i2c_sdat;   //receive ack 
w_state <= w_state + 1'b1;
end
else
begin
i2c_en <= 1'b1;  //read data
ackw1 <= 1'b1;
end
end

    结束信号的产生:

//----------------stop transmisson------------ 
6'd11: begin
if(i2c_ren)        //stop signal
begin
w_sdat <= 1'b1;
i2c_config_done <= 1'b1;
w_state <= w_state + 1'b1;
end
else if(i2c_wen)
begin
w_sdat <= 1'b0;
i2c_en <= 1'b0;   //write data
end
end

    三态信号的控制:

assign i2c_sdat = (i2c_en == 1'b0) ? w_sdat : 1'bz;

四.Modelsim仿真图

最后来一张SCCB协议的modelsim仿真图:

wps_clip_image-12539