Verilog语言编写 摄像头接口模块

在做图像处理的时候,最必要的一个环节就是摄像头的驱动了

本文对摄像头的工作原理不再详细概述,有问题的小伙伴可以看正点原子的公开课,或跟着设计思虑自行理解。

做过SOC设计的同学都清楚,摄像头的驱动有两种方法,一种是在内核里编程,通过软件将IO口模拟为IIC接口,向sensor中的指定寄存器进行写值,完成初始化。另外一种,也就是没有内核时常会用到的,在FPGA中编写对应IIC模块,寄存需要写入sensor的寄存器的地址和数值。

在这里我们两种方式都会进行讲解和实现。

本次驱动我们选择的摄像头模块是OmniVision系类的OV7725,其他OV系列产品可以自行查询产品的数据手册,改变寄存器的值即可,整体架构是一样的。

首先我们看下OV7725  CMOS Sensor 内部功能模块的框架

 首先,我们先完成可以生成正确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

上面说了,我们另外单独开辟了一块空间用来存储寄存器的地址和对应值。这个部分和两个模块的调用在下个章节出现。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA设计实现OV5640 摄像头采集数据VGA显示输出Verilog设计逻辑Quartus工程源码文件,FPGA型号Cyclone4E系列中的EP4CE10F17C8,Quartus版本18.0。 module ov5640_rgb565_1024x768_vga( input sys_clk , //系统时钟 input sys_rst_n , //系统复位,低电平有效 //摄像头接口 input cam_pclk , //cmos 数据像素时钟 input cam_vsync , //cmos 场同步信号 input cam_href , //cmos 行同步信号 input [7:0] cam_data , //cmos 数据 output cam_rst_n , //cmos 复位信号,低电平有效 output cam_pwdn , //cmos 电源休眠模式选择信号 output cam_scl , //cmos SCCB_SCL线 inout cam_sda , //cmos SCCB_SDA线 //SDRAM接口 output sdram_clk , //SDRAM 时钟 output sdram_cke , //SDRAM 时钟有效 output sdram_cs_n , //SDRAM 片选 output sdram_ras_n , //SDRAM 行有效 output sdram_cas_n , //SDRAM 列有效 output sdram_we_n , //SDRAM 写有效 output [1:0] sdram_ba , //SDRAM Bank地址 output [1:0] sdram_dqm , //SDRAM 数据掩码 output [12:0] sdram_addr , //SDRAM 地址 inout [15:0] sdram_data , //SDRAM 数据 //VGA接口 output vga_hs , //行同步信号 output vga_vs , //场同步信号 output [15:0] vga_rgb //红绿蓝三原色输出 ); //parameter define parameter SLAVE_ADDR = 7'h3c ; //OV5640的器件地址7'h3c parameter BIT_CTRL = 1'b1 ; //OV5640的字节地址为16位 0:8位 1:16位 parameter CLK_FREQ = 26'd65_000_000; //i2c_dri模块的驱动时钟频率 65MHz parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率,不超过400KHz parameter CMOS_H_PIXEL = 24'd1024 ; //CMOS水平方向像素个数,用于设置SDRAM缓存大小 parameter CMOS_V_PIXEL = 24'd768 ; //CMOS垂直方向像素个数,用于设置SDRAM缓存大小 //wire define wire clk_100m ; //100mhz时钟,SDRAM操作时钟 wire clk_100m_shift ; //100mhz时
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值