在做图像处理的时候,最必要的一个环节就是摄像头的驱动了
本文对摄像头的工作原理不再详细概述,有问题的小伙伴可以看正点原子的公开课,或跟着设计思虑自行理解。
做过SOC设计的同学都清楚,摄像头的驱动有两种方法,一种是在内核里编程,通过软件将IO口模拟为IIC接口,向sensor中的指定寄存器进行写值,完成初始化。另外一种,也就是没有内核时常会用到的,在FPGA中编写对应IIC模块,寄存需要写入sensor的寄存器的地址和数值。
在这里我们两种方式都会进行讲解和实现。
本次驱动我们选择的摄像头模块是OmniVision系类的OV7725,其他OV系列产品可以自行查询产品的数据手册,改变寄存器的值即可,整体架构是一样的。
首先,我们先完成可以生成正确IIC时序的时序控制模块
//首先我们先完成IIC的时序的实现
module i2c_timing_ctrl
#(
parameter CLK_FREQ = 100_000000, //100 MHz
parameter I2C_FREQ = 10_000 //10 KHz(< 400KHz)
)
(
//global clock
input clk, //100MHz
input rst_n, //system reset
//i2c interface
output i2c_sclk, //i2c clock
inout i2c_sdat, //i2c data for bidirection
//user interface
input [7:0] i2c_config_size, //i2c config data counte
output reg [7:0] i2c_config_index, //i2c config reg index, read 2 reg and write xx reg
input [23:0] i2c_config_data, //i2c config data
output i2c_config_done, //i2c config timing complete
output reg [7:0] i2c_rdata //i2c register data while read i2c slave
);
首先在初始化的时候,需要等待一段时间
//----------------------------------------
//Delay XXXus until i2c slave is steady
reg [16:0] delay_cnt;
localparam DELAY_TOP = CLK_FREQ/1000; //1ms Setting time after software/hardware reset
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
delay_cnt <= 0;
else if(delay_cnt < DELAY_TOP - 1'b1)
delay_cnt <= delay_cnt + 1'b1;
else
delay_cnt <= delay_cnt;
end
wire delay_done = (delay_cnt == DELAY_TOP - 1'b1) ? 1'b1 : 1'b0;
//----------------------------------------
//----------------------------------------
//I2C Control Clock generate
reg [15:0] clk_cnt; //divide for i2c clock
reg i2c_ctrl_clk; //i2c control clock, H: valid; L: valid
reg i2c_transfer_en; //send i2c data before, make sure that sdat is steady when i2c_sclk is valid
reg i2c_capture_en; //capture i2c data while sdat is steady from cmos
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
clk_cnt <= 0;
i2c_ctrl_clk <= 0;
i2c_transfer_en <= 0;
i2c_capture_en <= 0;
end
else if(delay_done)
begin
if(clk_cnt < (CLK_FREQ/I2C_FREQ) - 1'b1)
clk_cnt <= clk_cnt + 1'd1;
else
clk_cnt <= 0;
//i2c control clock, H: valid; L: valid(divide the freuquency)
i2c_ctrl_clk <= ((clk_cnt >= (CLK_FREQ/I2C_FREQ)/4 + 1'b1) &&
(clk_cnt < (3*CLK_FREQ/I2C_FREQ)/4 + 1'b1)) ? 1'b1 : 1'b0;
//send i2c data before, make sure that sdat is steady when i2c_sclk is valid
i2c_transfer_en <= (clk_cnt == 16'd0) ? 1'b1 : 1'b0;
//capture i2c data while sdat is steady from cmos
i2c_capture_en <= (clk_cnt == (2*CLK_FREQ/I2C_FREQ)/4 - 1'b1) ? 1'b1 : 1'b0;
end
else
begin
clk_cnt <= 0;
i2c_ctrl_clk <= 0;
i2c_transfer_en <= 0;
i2c_capture_en <= 0;
end
end
下面对状态机进行设计,首先设计各个状态,其中的ACKL等过程都是SCCB传输中必要的部分。SCCB的具体规则和IIC类似,可以自行查找,这里我们不过多解释。
//-----------------------------------------
//I2C Timing state Parameter
localparam I2C_IDLE = 5'd0;
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
localparam I2C_WR_START = 5'd1;
localparam I2C_WR_IDADDR = 5'd2;
localparam I2C_WR_ACK1 = 5'd3;
localparam I2C_WR_REGADDR = 5'd4;
localparam I2C_WR_ACK2 = 5'd5;
localparam I2C_WR_REGDATA = 5'd6;
localparam I2C_WR_ACK3 = 5'd7;
localparam I2C_WR_STOP = 5'd8;
//I2C Read: {ID_Address + REG_Address} + {ID_Address + R_REG_Data}
localparam I2C_RD_START1 = 5'd9;
localparam I2C_RD_IDADDR1 = 5'd10;
localparam I2C_RD_ACK1 = 5'd11;
localparam I2C_RD_REGADDR = 5'd12;
localparam I2C_RD_ACK2 = 5'd13;
localparam I2C_RD_STOP1 = 5'd14;
localparam I2C_RD_IDLE = 5'd15;
localparam I2C_RD_START2 = 5'd16;
localparam I2C_RD_IDADDR2 = 5'd17;
localparam I2C_RD_ACK3 = 5'd18;
localparam I2C_RD_REGDATA = 5'd19;
localparam I2C_RD_NACK = 5'd20;
localparam I2C_RD_STOP2 = 5'd21;
了解了通信协议后,我们需要设置每次传输时通过IIC总线传输到摄像头中的数据,这里我们提前初始化OV7725时需要写的寄存器的值和地址都写进去,这里我们采取的方案是开辟一块空间存储将这些有效值和地址,通过索引的方式来读取。
//-----------------------------------------
wire i2c_transfer_end = (current_state == I2C_WR_STOP || current_state == I2C_RD_STOP2) ? 1'b1 : 1'b0;
reg i2c_ack; //i2c slave renpose successed
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
i2c_config_index <= 0;
else if(i2c_transfer_en)
begin
if(i2c_transfer_end & ~i2c_ack)
begin
if(i2c_config_index < i2c_config_size)
i2c_config_index <= i2c_config_index + 1'b1;
else
i2c_config_index <= i2c_config_size;
end
else
i2c_config_index <= i2c_config_index;
end
else
i2c_config_index <= i2c_config_index;
end
assign i2c_config_done = (i2c_config_index == i2c_config_size) ? 1'b1 : 1'b0;
三段式状态机的实现
1.首先是第一段,用来控制现态到次态的跳变
//-----------------------------------------
// 三段式状态机,现态到次态的跳变
reg [4:0] current_state, next_state; //i2c write and read state
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
current_state <= I2C_IDLE;
else if(i2c_transfer_en)
current_state <= next_state;
end
2.接着是第二段,根据状态跳变图,设计次态的变化
reg [3:0] i2c_stream_cnt; //i2c data bit stream count
always@( *)
begin
next_state = I2C_IDLE; //state initialization
case(current_state)
I2C_IDLE: //5'd0
begin
if(delay_done) //1ms Setting time after software/hardware reset
begin
if(i2c_transfer_en)
begin
if(i2c_config_index < 8'd2)
next_state = I2C_RD_START1; //Read I2C Slave ID
else if(i2c_config_index < i2c_config_size)
next_state = I2C_WR_START; //Write Data to I2C
else// if(i2c_config_index >= i2c_config_size)
next_state = I2C_IDLE; //Config I2C Complete
end
else
next_state = next_state;
end
else
next_state = I2C_IDLE; //Wait I2C Bus is steady
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_START: //5'd1
begin
if(i2c_transfer_en) next_state = I2C_WR_IDADDR;
else next_state = I2C_WR_START;
end
I2C_WR_IDADDR: //5'd2
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK1;
else next_state = I2C_WR_IDADDR;
I2C_WR_ACK1: //5'd3
if(i2c_transfer_en) next_state = I2C_WR_REGADDR;
else next_state = I2C_WR_ACK1;
I2C_WR_REGADDR: //5'd4
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK2;
else next_state = I2C_WR_REGADDR;
I2C_WR_ACK2: //5'd5
if(i2c_transfer_en) next_state = I2C_WR_REGDATA;
else next_state = I2C_WR_ACK2;
I2C_WR_REGDATA: //5'd6
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK3;
else next_state = I2C_WR_REGDATA;
I2C_WR_ACK3: //5'd7
if(i2c_transfer_en) next_state = I2C_WR_STOP;
else next_state = I2C_WR_ACK3;
I2C_WR_STOP: //5'd8
if(i2c_transfer_en) next_state = I2C_IDLE;
else next_state = I2C_WR_STOP;
//I2C Read: {ID_Address + REG_Address} + {ID_Address + R_REG_Data}
I2C_RD_START1: //5'd9
if(i2c_transfer_en) next_state = I2C_RD_IDADDR1;
else next_state = I2C_RD_START1;
I2C_RD_IDADDR1: //5'd10
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_RD_ACK1;
else next_state = I2C_RD_IDADDR1;
I2C_RD_ACK1: //5'd11
if(i2c_transfer_en) next_state = I2C_RD_REGADDR;
else next_state = I2C_RD_ACK1;
I2C_RD_REGADDR: //5'd12
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_RD_ACK2;
else next_state = I2C_RD_REGADDR;
I2C_RD_ACK2: //5'd13
if(i2c_transfer_en) next_state = I2C_RD_STOP1;
else next_state = I2C_RD_ACK2;
I2C_RD_STOP1: //5'd14
if(i2c_transfer_en) next_state = I2C_RD_IDLE;
else next_state = I2C_RD_STOP1;
I2C_RD_IDLE: //5'd15
if(i2c_transfer_en) next_state = I2C_RD_START2;
else next_state = I2C_RD_IDLE;
I2C_RD_START2: //5'd16
if(i2c_transfer_en) next_state = I2C_RD_IDADDR2;
else next_state = I2C_RD_START2;
I2C_RD_IDADDR2: //5'd17
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_RD_ACK3;
else next_state = I2C_RD_IDADDR2;
I2C_RD_ACK3: //5'd18
if(i2c_transfer_en) next_state = I2C_RD_REGDATA;
else next_state = I2C_RD_ACK3;
I2C_RD_REGDATA: //5'd19
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_RD_NACK;
else next_state = I2C_RD_REGDATA;
I2C_RD_NACK: //5'd20
if(i2c_transfer_en) next_state = I2C_RD_STOP2;
else next_state = I2C_RD_NACK;
I2C_RD_STOP2: //5'd21
if(i2c_transfer_en) next_state = I2C_IDLE;
else next_state = I2C_RD_STOP2;
default:; //default vaule
endcase
end
3.接下来是第三段,不同的状态对应不同的操作
reg i2c_sdat_out; //i2c data output
reg [7:0] i2c_wdata; //i2c data prepared to transfer
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
i2c_sdat_out <= 1'b1;
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
end
else if(i2c_transfer_en)
begin
case(next_state)
I2C_IDLE: //5'd0
begin
i2c_sdat_out <= 1'b1; //idle state
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_START: //5'd1
begin
i2c_sdat_out <= 1'b0;
i2c_stream_cnt <= 0;
i2c_wdata <= i2c_config_data[23:16]; //ID_Address
end
I2C_WR_IDADDR: //5'd2
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
end
I2C_WR_ACK1: //5'd3
begin
i2c_stream_cnt <= 0;
i2c_wdata <= i2c_config_data[15:8]; //REG_Address
end
I2C_WR_REGADDR: //5'd4
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
end
I2C_WR_ACK2: //5'd5
begin
i2c_stream_cnt <= 0;
i2c_wdata <= i2c_config_data[7:0]; //W_REG_Data
end
I2C_WR_REGDATA: //5'd6
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
end
I2C_WR_ACK3: //5'd7
i2c_stream_cnt <= 0;
I2C_WR_STOP: //5'd8
i2c_sdat_out <= 1'b0;
//I2C Read: {ID_Address + REG_Address} + {ID_Address + R_REG_Data}
I2C_RD_START1: //5'd10
begin
i2c_sdat_out <= 1'b0;
i2c_stream_cnt <= 0;
i2c_wdata <= i2c_config_data[23:16];
end
I2C_RD_IDADDR1: //5'd11
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
end
I2C_RD_ACK1: //5'd11
begin
i2c_stream_cnt <= 0;
i2c_wdata <= i2c_config_data[15:8];
end
I2C_RD_REGADDR: //5'd12
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
end
I2C_RD_ACK2: //5'd13
i2c_stream_cnt <= 0;
I2C_RD_STOP1: //5'd14
i2c_sdat_out <= 1'b0;
I2C_RD_IDLE: //5'd15
i2c_sdat_out <= 1'b1; //idle state
//-------------------------
I2C_RD_START2: //5'd16
begin
i2c_sdat_out <= 1'b0;
i2c_stream_cnt <= 0;
i2c_wdata <= i2c_config_data[23:16];
end
I2C_RD_IDADDR2: //5'd17
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
if(i2c_stream_cnt < 5'd7)
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
else
i2c_sdat_out <= 1'b1; //Read flag for I2C Timing
end
I2C_RD_ACK3: //5'd18
i2c_stream_cnt <= 0;
I2C_RD_REGDATA: //5'd19
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
I2C_RD_NACK: //5'd20
i2c_sdat_out <= 1'b1; //NACK
I2C_RD_STOP2: //5'd21
i2c_sdat_out <= 1'b0;
endcase
end
else
begin
i2c_stream_cnt <= i2c_stream_cnt;
i2c_sdat_out <= i2c_sdat_out;
end
end
下面一段是为了满足IIC协议的要求而设计的,和SCCB的关系不大。
reg i2c_ack1, i2c_ack2, i2c_ack3;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
{i2c_ack1, i2c_ack2, i2c_ack3} <= 3'b111;
i2c_ack <= 1'b1;
i2c_rdata <= 0;
end
else if(i2c_capture_en)
begin
case(next_state)
I2C_IDLE:
begin
{i2c_ack1, i2c_ack2, i2c_ack3} <= 3'b111;
i2c_ack <= 1'b1;
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_ACK1: i2c_ack1 <= i2c_sdat;
I2C_WR_ACK2: i2c_ack2 <= i2c_sdat;
I2C_WR_ACK3: i2c_ack3 <= i2c_sdat;
I2C_WR_STOP: i2c_ack <= (i2c_ack1 | i2c_ack2 | i2c_ack3);
//I2C Read: {ID_Address + REG_Address} + {ID_Address + R_REG_Data}
I2C_RD_ACK1: i2c_ack1 <= i2c_sdat;
I2C_RD_ACK2: i2c_ack2 <= i2c_sdat;
I2C_RD_ACK3: i2c_ack3 <= i2c_sdat;
I2C_RD_STOP2: i2c_ack <= (i2c_ack1 | i2c_ack2 | i2c_ack3);
I2C_RD_REGDATA: i2c_rdata <= {i2c_rdata[6:0], i2c_sdat};
endcase
end
else
begin
{i2c_ack1, i2c_ack2, i2c_ack3} <= {i2c_ack1, i2c_ack2, i2c_ack3};
i2c_ack <= i2c_ack;
end
end
最后将输出连接上
wire out_en =( current_state == I2C_WR_ACK1 || current_state == I2C_WR_ACK2 || current_state == I2C_WR_ACK3 ||
current_state == I2C_RD_ACK1 || current_state == I2C_RD_ACK2 || current_state == I2C_RD_ACK3 ||
current_state == I2C_RD_REGDATA) ? 1'b0 : 1'b1;
assign i2c_sclk = (current_state >= I2C_WR_IDADDR && current_state <= I2C_WR_ACK3 ||
current_state >= I2C_RD_IDADDR1 && current_state <= I2C_RD_ACK2 ||
current_state >= I2C_RD_IDADDR2 && current_state <= I2C_RD_NACK) ?
i2c_ctrl_clk : 1'b1;
assign i2c_sdat = (out_en) ? i2c_sdat_out : 1'bz;
整体设计结束
endmodule
上面说了,我们另外单独开辟了一块空间用来存储寄存器的地址和对应值。这个部分和两个模块的调用在下个章节出现。