Menu Close

I2C 主设备启动及读写控制Verilog程序设计

I2C 主设备启动及读写控制Verilog程序设计

在I2C发起读/写过程之前,主设备会先发起I2C的启动(start), 从设备的地址匹配(从设备地址写),I2C的读/写控制以及从设备应答检测的等一系列过程。由于该部分内容,无论I2C读还是I2C写,该部分的操作过程都是必须的,因此把该部分内容单独作为一个模块进行设计,便于代码的重用。时序如图1:

%title插图%num

图1

  1. I2C启动部分主设备程序设计

    1. 接口要求:

      • 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)

input rst,

input sys_clk,

input clk_en,

      • 从设备地址、启动信号、读/写标志(即该段程序之后的I2C 总线行为

input [6:0] dev_addr,

input start,

input rd_wr, //rd_wr==0, write operation, 1–>read operation

      • I2C 总线的内部信号:由于inout信号一般只在端口使用,因此在FPGA的内部逻辑(内部模块)将会把inout(双向口)变换成input, output类型进行传递

input sda_in,

output reg scl_out,

output reg sda_out,

      • 握手信号:读就绪(rd_rdy)、写就绪(wr_rdy)、从设备应答。当获得从设备应答后,如果主设备是读控制,则rd_rdy输出高电平信号(单个系统时钟宽度),指示可以进行I2C读数据阶段了。同样如果得到从设备应答,而且主设备是写控制,则wr_rdy输出高电平(单个系统时钟宽度)。

output reg rd_rdy,

output reg wr_rdy,

output reg i2c_start_ack //从设备是否有应答

2. 总体接口如下:

module start_addr_rd_wr

#(parameter SIM=1)

(

input rst,

input sys_clk,

input clk_en,

input [6:0] dev_addr,

input start,

input rd_wr, //rd_wr==0, write operation, 1–>read operation

input sda_in,

output reg scl_out,

output reg sda_out,

output reg rd_rdy,

output reg wr_rdy,

output reg i2c_start_ack

);

3. 主体程序设计

该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(start),对从设备的地址与读写控制信号移位进行串行化,等待从设备应答。串行化期间对SCL,SDA的要求是,在SCL为高电平期间SDA保持不变(start除外),SDA的任何变化都在SCL为低电平的时段发生。程序的流程图如下:

%title插图%num

图2

  1. 程序代码如下:


  	module start_addr_rd_wr


  #(parameter SIM=1)


  (


  	input			rst,

  	input 			sys_clk,

  	input			clk_en,

  	input		[6:0]	dev_addr,

  	input			start,

  	input			rd_wr,    //rd_wr==0, write operation, 1-->read operation

  	input			sda_in,

        output	 reg	        scl_out,

  	output	 reg	       sda_out,


  	output	reg	rd_rdy,

  	output	reg	wr_rdy,

  	output	reg   i2c_start_ack


  );


  reg   [7:0]     addr_rd_wr;  //

  reg	[2:0]     sft_count;

  reg	[2:0]     i2c_st;

  always@(posedge sys_clk or posedge rst)


  if(rst) begin

  	i2c_st<=0;

  	scl_out<=1'b1;

  	sda_out<=1'b1;

  	i2c_start_ack<=0;

  	addr_rd_wr<=0;

  	wr_rdy<=0;

  	rd_rdy<=0;

  	sft_count<=7;


  end


  else case(i2c_st)

  	0:begin            //idle

  		scl_out<=1'b1;

  		sda_out<=1'b1;

  		i2c_start_ack<=0;

  		sft_count<=7;

  		rd_rdy<=0;

  		wr_rdy<=0;

  		addr_rd_wr<={dev_addr, rd_wr};

  		if(start)

  		i2c_st<=1;

  	end

  	1:begin

  		if(clk_en) begin

  		 sda_out<=1'b0;

  		 i2c_st<=2;

  		 end

  		end


  	2:begin

  		if(clk_en) begin

  		 scl_out<=1'b0;

  		 i2c_st<=3;

  		 end


  	  end


     3:begin

  		sda_out<=addr_rd_wr[sft_count];

  		if(clk_en) begin

  		   scl_out<=1'b1;

  		 if(sft_count==0)

  		   i2c_st<=4;

  		 else begin

  		  sft_count<=sft_count-1;

  		 i2c_st<=2;

  		 end

  		 end

  	  end

  	4:begin

  		if(clk_en) begin

  		 scl_out<=1'b0;

  		 i2c_st<=5;

  		 end
  	  end

  	5:begin

  		sda_out<=1'b1;

  		if(clk_en) begin

  		 scl_out<=1'b1;

  		 i2c_st<=6;

  		 end

  	  end

  	 6:begin

  			if(!sda_in) begin

  				i2c_st<=7;

  			end

  			else if(clk_en)  begin

  				if(SIM==1) 

  					i2c_st<=7;  	

  				else

  					i2c_st<=0;

  			end

  		end

  	 7:begin

  	  	 i2c_start_ack<=1'b1;

  		i2c_st=0;

  		if(addr_rd_wr[0])

  			rd_rdy<=1'b1;

  		else

  			wr_rdy<=1'b1;

  	   end

  	default:i2c_st<=0;

  	endcase

  endmodule

  
  1. 将设计的模块例化,并组装到 I2C_engine的模块中,这样就可以使用SCl_en模块,充分利用以设计的成果,逐步组装成程序代码及接口如下:

  		module I2C_engine

  #(

   parameter CLK_F=100000000 ,

   parameter SPEED=100000,

   parameter SIM=1

  )


  (


  input			rst,

  input			inclk,

  input		[6:0] dev_addr,

  input			rd_wr,

  input			start,

  input			wr_en,

  input		[31:0]div_addr,

  input		[31:0]div_in,

  output	   	clk_en,

  output		i2c_start_ack,

  inout                 SDA,

  inout			SCL


  );


  wire     scl_dev_out;

  wire     sda_dev_out;

  wire     rd_rdy;

  wire     wr_rdy;

  assign   SDA=sda_dev_out?1'bZ:1'b0;

  assign   SCL=scl_dev_out?1'bZ:1'b0;

   SCl_en

  #(

  .CLK_F(CLK_F) ,

  .SPEED(SPEED)

  )

  SCl_en_inst

  (

  .rst			(rst),

  .sys_clk		(inclk),

  .wr_en		(wr_en),

  .div_addr	        (div_addr),

  .div_in		(div_in),

  .clk_en		(clk_en)

  );

  start_addr_rd_wr

  #(.SIM(SIM))

  start_addr_rd_wr_inst

  (

  .rst				(rst),

  .sys_clk			(inclk),

  .clk_en			(clk_en),

  .dev_addr		        (dev_addr),

  .start			(start),

  .rd_wr			(rd_wr),    //rd_wr==0, write operation, 1--&gt;read operation

  .scl_out			(scl_dev_out),

  .sda_out			(sda_dev_out),

  .rd_rdy			(rd_rdy),

  .wr_rdy			(wr_rdy),

  .i2c_start_ack	        (i2c_start_ack)

  );


  endmodule


  	
  1. 仿真程序设计:

仿真程序主要设置好时钟,I2C 的读写速度,从设备地址等内容,做好接口和握手信号(如,start, i2c_start_ack等)


  	
  `timescale   1 ns / 1ps

  module tb_dev_addr

  (

  );

  parameter PERIOD =10 ;

  parameter FREQUANCY=100000000;//100M

  parameter SPEED    =100000;// 100K

  parameter SPEED1   =400000;  //400K

  parameter DEV_ADDR=7'b101_0001;

  parameter SIM=1;

  reg inclk;

  always #(PERIOD/2)  inclk=~inclk;  //133M

  reg  			rst;  //

  wire        clk_en;

  reg  			wr_en;   //write enable signal ,synchronized with sys_clk;

  reg   [31:0]div_in;  //for baudrate register

  wire  [31:0]div_addr=32'hFFFF_0001;

  reg			start;

  reg			rd_wr;

  wire        SDA;

  wire        SCL;

  reg    [2:0] st;   

  wire 	sys_clk=inclk;

  wire        i2c_start_ack;						

  initial  begin

   rst=1'b1;

   inclk=0;

   #50 rst=1'b0;

  end


  always@(posedge sys_clk or posedge rst)

  if(rst) begin

  	st<=0;

  	div_in<=0;

  	rd_wr<=1'b0;    //read;

  	wr_en<=1'b0;

  	start<=0; 

  end


  else begin

  	case(st)

  	0:begin
  		start<=0; 

  		rd_wr<=1'b1;    //read;
  		st<=1;

  	end


  	1:begin

  		 start<=1; 

  		 st<=2;

  	  end


     2:begin

  	   start<=0; 

  		if(i2c_start_ack)

  		 st<=3;

  		end

  	3:begin

  		  start<=1; 

  		  rd_wr<=1'b0;    //read;

  		  st<=4;	

  	   end

  	 4:begin

  	   start<=0; 

  		if(i2c_start_ack)

  		 st<=5;

  		end

  	 5:begin

  		div_in<=(FREQUANCY/SPEED1*2)-1;   //切换到400K进行测试

  		wr_en<=1'b1;

  		st<=6;

  		end

  	 6:begin
  			wr_en<=1'b0;

  			st<=7;

  		end

  	 7:begin
  		st<=0;

  		end

  	default: st<=0;

  	endcase

  end


  I2C_engine


  #(


  .CLK_F(FREQUANCY) ,  //default with 100Mhz


  .SPEED(SPEED) ,       //default speed 100k


  .SIM	(SIM)


  )


  I2C_engine_inst


  (


  .rst				(rst),


  .inclk			(sys_clk),


  .dev_addr		        (DEV_ADDR),


  .rd_wr			(rd_wr),


  .start			(start),


  .wr_en			(wr_en),


  .div_addr		       (div_addr),


  .div_in			(div_in),


  .clk_en			(clk_en),


  .i2c_start_ack (i2c_start_ack),


  .SDA				(SDA),


  .SCL				(SCL)


  );


  endmodule


  	
  1. 仿真结果

仿真波形如图3

%title插图%num

图3

从图3可以看出在读阶段串行了1010001(地址)及1(读),写阶段串行了1010001(从设备地址)及0(写控制)。图中没有得到设备应答,主要时仿真时没有提供从设备的仿真程序。后续课程中会有从设备的verilog程序开发,到时会采样联合仿真的方式进行验证, 为了程序能在仿真时顺利进行,在仿真程序中提供了一个参数SIM用于控制是否需要等待从设备应答才能进行后面的读写。如在start_addr_rd_wr.v 有如下代码,可用于灵活选择。

6:begin

if(!sda_in) begin

i2c_st<=7;

end

else if(clk_en) begin

if(SIM==1)

i2c_st<=7;

else

i2c_st<=0;

end

end

从程序中可以看出,在SIM==1时状态机跳转到7,可以假设已经得到了从设备的应答,使后面的读写程序可以进行下去。

 

Posted in FPGA, FPGA, Quartus II, 开发工具, 教材与教案, 文章

发表评论

相关链接