Fork me on GitHub

(verilog)IIC协议详讲与实现

相比与SPI与UART,IIC协议非常优雅,因为它在非常轻的基础设施上提供了非常高级的功能,例如自动多主机冲突处理和内置寻址管理。然而,由于其功能上的复杂安全,在传输性能上会有所缺失。

一、原理介绍

IIC是英文Inter-Integrated Circuit的缩写,直译为内部集成电路,I2C总线于1982年由飞利浦公司开发,它最初的目的是提供一种将CPU连接到电视机外围芯片的简单方法。只需要两根导线就可以将所有外围设备连接到微控制器。原始规范定义了100 kb/s的总线速度,后续引入了400 kb/s和 3.4 Mb/s。

IIC总线上只有两根信号线,串行数据线SDA和串行时钟线SCL,两根主线均为双向主线,主机和从机都可以控制主线,所有从机和主机都挂在总线上,同时总线还外接上拉电阻。

IIC总线物理拓扑图

IIC是半双工的通信协议,因为只有一根数据线SDA,所以只能进行读数据或是写数据。IIC是多主多从通信协议,主机和从机可以相互转换,先发起请求的作为主机,收到主机发出的请求后响应的机器作为从机。常见的其他串行通信协议中,UART是单主单从全双工通信协议,在读数据的同时也可以进行写数据。SPI是单主多从全双工通信协议。

IIC是同步通信协议,所有主机都挂在SCL时钟总线上,当主线空闲时SCL信号始终为高,当有主机占用主线时,SCL时钟信号开始翻转,为主机与从机之间的数据传输提供同步时钟。 时钟速率必须在100 kb/s、400 kb/s和3.4 Mb/s之间选择,分别称为标准模式、快速模式和高速模式。一些I2C变体也有10 kb/s(低速模式)和1 Mb/s(快速模式+)的时钟频率。常见的其他串行通信协议中,UART是异步通信协议,SPI是同步通信协议。

SDA控制

IIC协议规定,SDA只能在SCL为低时变化,SCL为高时保持不变。如果在SCL为高的时候,SDA发生变化,被认为是start 和 stop 标志的产生。如下图所示,没有主机占用主线的时候,SCL信号为高,当有主机需要占用主线时,主动拉低SDA信号,即视为产生 start 标志。当数据传输结束后,主机在SCL为高时拉高SDA信号,视为产生 stop标志。

start 和 stop 的产生

仲裁

由于IIC独特的物理结构,使得它不需要额外的仲裁电路模块,只通过总线本身就能完成多主机之间的仲裁,这也是IIC协议真正优雅的地方。在物理层上,SCL和SDA线路都是带上拉电阻的开漏I/O,将这样的线路拉到地上被解码为逻辑0,而释放线路让其浮动为逻辑1。

如果多个设备尝试在SDA线路上写入同样的逻辑,不会有任何冲突,SDA线路变为相应的逻辑,但是如果有设备试图写入逻辑0,有的设备试图写入逻辑1,则只有0被成功写入,也就是说,当出现多个主机同时向总线发送请求时,他们发送的前几位相同的信号都会成功写入总线,所有设备也都可以接收到数据,直到占用总线的主机们出现分歧时,一个要写入0,另一个要写入1,则主线变为0,写入0的主机继续发送数据,写入1的主机失去同步,转为从机接收主线上的数据。

由于前几位相同数据都被成功发送,不会发生数据丢失,而且写入0的主机甚至不会意识到自己发生过竞争的事情,只有失去同步的主机才会意识到自己参与过竞争,他们会在本次总线产生 stop 之后再次尝试发起请求。

数据位规定

IIC标准协议规定数据传输格式为下图所示,

数据位格式

第一个字节中的数据,前 7bit 为id寻址位,每个支持IIC协议的设备都会有一个id号,根据其出产厂家不同而不同,为了放置相同的设备id号一样,设备的id号低三位(A1,A2,A3)可以自定义,这样一个总线上可以挂8个相同的设备。

第一个字节的最低位为读/写识别位,为0表示写,为1表示读。

第二个字节中为要传输的数据,有的设备中有多个寄存器,所以还需要多1个或2个字节的address,同样的data也可以为两个字节。

当主机拉高start信号,所有从机收到start信号,开始接收SDA上的数据,第一个字节的id接收后与自己的id对比,如果相同,则返回一个ack信号,主机收到ack信号后继续发送数据,从机收到后再返回一个ack,主机收到后产生stop信号并释放总线。其他未参与通信的从机全程接收SDA数据直到收到stop信号。

写模式时,两次ack均为从机发送给主机,读模式的时候,数据由从机发送给主机,所以第二个ack为主机发送给从机。ack信号为1代表有效,为0代表无效(也可以设置为低有效),当收到无效信号时,发送stop信号,结束本次通信,稍后再次重新发起。

伪握手机制

由于IIC独特的物理结构,还可以通过SCL进行主机与从机之间的伪握手,即从机收到id后,由于内部寄存器还没准备好,可以通过主动拉低SCL,使主机被迫等待,直到从机准备好后,拉高SCL时钟线,继续通信。

其他

IIC是一个规定很多的成熟的通信协议,因此还会有很多额外的功能模式,例如10bit寻址模式,快速模式,高速模式等。另外,第一个id寻址字节中的数据全为1,或全为0等特殊字节还有额外的功能,类似TCP-IP协议中的广播和ARP请求。

下面的链接是IIC的协议标准中文版,读者如有需求或想更详细的了解IIC协议,请自行下载。

IIC协议.pdf
869.5K
·
百度网盘

链接:

提取码:jgc2

连接有效期30天,失效后评论区补。

二、verilog实现

我们设计一个7bit 寻址模式的IIC设备模块,功能框图如下所示:

IIC模块功能框图

系统内部总线发来 command(寻址id),data(发送的数据),req(发送指示),模块接收到req后,判断总线状态,等待总线空闲状态时发送start信号开始通信。读过来的数据通过dout_reg缓存,再通过dout发送到内部总线。当模块作为slave时,写模式数据写进内部寄存器inner_data中,读模式把内部寄存器inner_data中的数据读出到SDA总线上。

SCL频率为 100K 的标准模式。

状态机跳转如下图

状态机跳转详情

在idle状态下,如果IIC总线处于空闲状态,且收到了内部发来的请求req信号,作为master进入send状态,根据读写指示位进入读或写状态。

在idle状态下,如果没有内部请求req信号,收到了start信号,则作为slave进入busy状态,如果id检测没有与自己的id匹配,就维持在busy状态,直到stop信号后,进入idle状态。

如果id检测与自己的id匹配,则根据读写指示位进入读写状态,需要注意的时,此时的读写是根据主机的角度来判断,如果读写指示位是0,是主机的写状态,则作为从机需要进入读状态,即sread状态。

需要注意的是,由于SDA和SCL是inout双向信号,在FPGA编译软件中会直接被综合成IOBUF模块,通过三态门来实现对IO口的控制。

I口为从FPGA输出的信号,O口为输入给FPGA的信号,IO口为与外部双向io信号连接的接口,T可以理解为选择信号,当T为1时,IO与I相连,IO口向外部输出FPGA想要输出的信号;当T为0时,I口变为高阻态,IO与O相连,IO向内部输入信号。

我们在FPGA内部使用的时候可以使用ip核,在top顶层例化的方式与SDA信号相连:

IOBUF	IOBUF_inst (
	.datain ( datain_sig ),
	.oe ( oe_sig ),
	.dataio ( dataio_sig ),
	.dataout ( dataout_sig )
	);

也可以用force 和 release语句来使IO口作为输入端口或者释放IO口作为输入端口。

或者用一个使能信号来做选择(IOBUF中的T口),用assign语句选择输入和输出,做输入的时候就令SDA为高阻态。

IIC模块中

assign iic_SDA = io_en ? iic_SDA_reg : 1'bz;

top顶层中

assign iic_SDA_top = (!io_en)? ack : 1'bz;

下面是我们本次实现的代码详情:

端口声明与内部信号

module IIC#(parameter id0 = 1'b0,id1 = 1'b0,id2 = 1'b1)(
input CLK_50M,
input rst_n,
input [7:0] data,
input [7:0] command,
input req,
inout iic_SLC,
inout iic_SDA,
output reg [7:0]data_out,
output reg finish,
output reg io_en   
);

//iic控制
reg [8:0] SLC_cnt;
reg cnt_flag;
reg [4:0] n_cnt;
reg n_cnt_flag;
reg iic_SDA_reg;
reg iic_SLC_reg;
//外部输入缓存
reg [7:0] data_reg;
reg [7:0] command_reg;
reg req_reg;
//输出数据缓存
reg [7:0] data_out_reg;
//内部需要被访问的寄存器
reg [7:0] inner_data;
//状态信号
reg busy;
wire send;
wire start;
wire stop;
//id寻找
wire match_flag;
//ack错误
reg error;
//状态机
reg [6:0]cur_state;
reg [6:0]next_state;
localparam   s_idle   =  7'b000_0001; 
localparam   s_send   =  7'b000_0010;
localparam   s_mwrite =  7'b000_0100;
localparam   s_mread  =  7'b000_1000;
localparam   s_busy   =  7'b001_0000;
localparam   s_swrite =  7'b010_0000;
localparam   s_sread  =  7'b100_0000;

//id序列检测
reg [7:0]id_state;
localparam   id_idle      = 8'b0000_0001; 
localparam   id_1         = 8'b0000_0010;
localparam   id_10        = 8'b0000_0100;
localparam   id_101       = 8'b0000_1000;
localparam   id_1010      = 8'b0001_0000;
localparam   id_1010_0    = 8'b0010_0000;
localparam   id_1010_00   = 8'b0100_0000;
localparam   id_1010_001  = 8'b1000_0000;

我们把这个IIC设备的id低三位设置成001,前四位假设为1010,则此IIC设备的id码为1010 001。

SCL时钟生成

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    cnt_flag <= 1'b0;
  else if(start)
    cnt_flag <= 1'b1;
  else if(stop)
    cnt_flag <= 1'b0;

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    SCL_cnt <= 'd0;
  else if(SCL_cnt == 'd499)
    SCL_cnt <= 'd0;
  else if(cnt_flag)
    SCL_cnt <= SCL_cnt + 1'b1;
  else 
    SCL_cnt <= 'd0;
	
always@(posedge CLK_50M or negedge rst_n)	
  if(~rst_n)
    iic_SCL_reg <= 1'b1;
  else if(SCL_cnt == 'd249 || SCL_cnt == 'd499)
    iic_SCL_reg <= ~iic_SCL_reg;
	 
assign iic_SCL = iic_SCL_reg;

50M时钟计数 250 翻转一次 iic_SCL ,生成100K频率的 iic_SCL 同步时钟 。

由于SCL形成伪握手机制需要配合IIC的物理结构实现,只用verilog实现不了,我们就没有给 iic_SCL 添加IOBUF,使得 iic_SCL 一直输出有效。

同时由于 inout 信号需要为 wire 型,所以用reg型的 iic_SCL_reg 放置在 always 块中,并用 assign 赋值给iic_SCL。

状态信号及状态机

assign start = ((cur_state == s_send && n_cnt == 'd0) || 
((cur_state == s_idle || cur_state == s_busy) && n_cnt == 'd0))? (iic_SCL && ~iic_SDA) : 1'b0;
assign stop  = (n_cnt == 'd19 && SCL_cnt > 'd125)? (iic_SCL && iic_SDA) : 1'b0;

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)begin
    command_reg <= 'd0;
	 data_reg <= 'd0;
	 req_reg <= 1'b0;end
  else if(req)begin
    command_reg <= command;
    data_reg <= data;
	 req_reg <= req;end
  else if(next_state == s_send && SCL_cnt == 'd249)
    req_reg <= 1'b0;
  else if(error)	 
    req_reg <= 1'b1;
	
assign send = req_reg && ~finish && ~busy;

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    busy <= 1'b0;
  else if(stop)
    busy <= 1'b0;  
  else if(~req_reg && start)
    busy <= 1'b1;  

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    next_state <= s_idle;
  else
    next_state <= cur_state;

always@(*)
  case(next_state)
    s_idle : begin
      if(busy) 
        cur_state = s_busy;
      else if(send)
	     cur_state = s_send;
	   else
	     cur_state = s_idle;end
	 
	 s_send : begin 
	   if(n_cnt == 'd8 && iic_SDA == 1'b0 && SCL_cnt == 'd499)
	     cur_state = s_mwrite;
		else if(n_cnt == 'd8 && iic_SDA == 1'b1 && SCL_cnt == 'd499)
		  cur_state = s_mread;
		else
		  cur_state = s_send;end
   
    s_busy : begin   
    	if(match_flag && iic_SDA == 1'b0 && SCL_cnt == 'd499)//这时候作为从机,主机的写是从机的读
        cur_state = s_sread;
      else if(match_flag && iic_SDA == 1'b1 && SCL_cnt == 'd499)//这时候作为从机,主机的读是从机的写		
        cur_state = s_swrite;
		else if(stop)
		  cur_state = s_idle;
      else
        cur_state = s_busy;end

    s_mwrite : begin 
	   if(error)
		  cur_state = s_idle;
		else if(stop)
	     cur_state = s_idle;
		else
		  cur_state = s_mwrite;end

    s_mread : begin
	   if(error)
		  cur_state = s_idle;      
		else if(stop)
	     cur_state = s_idle;
		else
		  cur_state = s_mread; end

    s_swrite : begin
	   if(error)
		  cur_state = s_idle;      
		else if(stop)
	     cur_state = s_idle;
		else
		  cur_state = s_swrite;end

    s_sread : begin
      if(stop)
	     cur_state = s_idle;
		else
		  cur_state = s_sread;end 			  
    default : cur_state = s_idle;		  
endcase

id序列检测

always@(posedge iic_SCL or negedge rst_n)
  if(~rst_n)
    id_state <= id_idle;
  else if(busy && n_cnt <= 'd8) begin
    case(id_state)
	 id_idle:begin  
	   if(iic_SDA == 1'b1)
	     id_state <= id_1;
		else
		  id_state <= id_idle; end	 
    id_1:begin	 
      if(iic_SDA == 1'b0)
	     id_state <= id_10;
		else
		  id_state <= id_idle; end	 
    id_10:begin	 
      if(iic_SDA == 1'b1)
	     id_state <= id_101;
		else
		  id_state <= id_1; end	     
    id_101:begin	 
      if(iic_SDA == 1'b0)
	     id_state <= id_1010;
		else
		  id_state <= id_1; end
    id_1010:begin	 
      if(iic_SDA == 1'b0)
	     id_state <= id_1010_0;
		else
		  id_state <= id_101; end
    id_1010_0:begin	 
      if(iic_SDA == 1'b0)
	     id_state <= id_1010_00;
		else
		  id_state <= id_1; end
    id_1010_00:begin	 
      if(iic_SDA == 1'b1)
	     id_state <= id_1010_001;
		else
		  id_state <= id_idle; end
    id_1010_001:	 
		  id_state <= id_idle; 	  
	 default:
	     id_state <= id_idle;
	 endcase
	 end	 
    else		  
      id_state <= id_idle;

assign match_flag = (id_state == id_1010_001);

线性序列机

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    n_cnt <= 'd0;
  else if(~n_cnt_flag)
    n_cnt <= 'd0;	
  else if(n_cnt_flag && SCL_cnt == 'd499)
    n_cnt <= n_cnt + 1'b1;

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    n_cnt_flag <= 1'b0;
  else if(cur_state == s_idle)
    n_cnt_flag <= 1'b0;
  else if(stop)	
    n_cnt_flag <= 1'b0;
  else if(start)
    n_cnt_flag <= 1'b1; 

always@(*)
begin
  if(next_state == s_idle) begin
      iic_SDA_reg = 1'bz;
      io_en = 1'b0;	end
  else if(next_state == s_send) begin
  case(n_cnt)
    0:begin iic_SDA_reg = 1'b0;io_en = 1'b1;end
    1:begin iic_SDA_reg = command_reg[7];io_en = 1'b1;end
    2:begin iic_SDA_reg = command_reg[6];io_en = 1'b1;end
    3:begin iic_SDA_reg = command_reg[5];io_en = 1'b1;end
    4:begin iic_SDA_reg = command_reg[4];io_en = 1'b1;end
    5:begin iic_SDA_reg = command_reg[3];io_en = 1'b1;end
    6:begin iic_SDA_reg = command_reg[2];io_en = 1'b1;end
    7:begin iic_SDA_reg = command_reg[1];io_en = 1'b1;end
    8:begin iic_SDA_reg = command_reg[0];io_en = 1'b1;end
  default:begin iic_SDA_reg =  1'b1;io_en = 1'b1;end
  endcase
  end
  
  else if(next_state == s_mwrite) begin
  case(n_cnt)  
    9: begin iic_SDA_reg = 1'bz;io_en = 1'b0;end//ack
    10:begin iic_SDA_reg = data_reg[7];io_en = 1'b1;end
    11:begin iic_SDA_reg = data_reg[6];io_en = 1'b1;end
    12:begin iic_SDA_reg = data_reg[5];io_en = 1'b1;end	
    13:begin iic_SDA_reg = data_reg[4];io_en = 1'b1;end
    14:begin iic_SDA_reg = data_reg[3];io_en = 1'b1;end
    15:begin iic_SDA_reg = data_reg[2];io_en = 1'b1;end
    16:begin iic_SDA_reg = data_reg[1];io_en = 1'b1;end
    17:begin iic_SDA_reg = data_reg[0];io_en = 1'b1;end
    18:begin iic_SDA_reg = 1'bz;io_en = 1'b0;end//ack
    19:begin
      io_en = 1'b1;
      if(SCL_cnt > 'd125)
        iic_SDA_reg = 1'b1;
      else if(SCL_cnt <= 'd124)
        iic_SDA_reg = 1'b0; end
    default:begin iic_SDA_reg =  1'b1;io_en = 1'b1;end 
  endcase
  end 
  
  else if(next_state == s_mread) begin
  case(n_cnt)  
    9,10,11,12,13,14,15,16,17:begin 
      iic_SDA_reg = 1'bz;io_en = 1'b0;end
    18:begin iic_SDA_reg = 1'b1;io_en = 1'b1;end//ack	
19:begin
      io_en = 1'b1;
      if(SCL_cnt > 'd125)begin
        iic_SDA_reg = 1'b1;end
      else if(SCL_cnt <= 'd124)
        iic_SDA_reg = 1'b0; end	 
    default:begin iic_SDA_reg =  1'b1;io_en = 1'b1;end 	  
  endcase
  end

  else if(next_state == s_sread) begin
  case(n_cnt)
    9:begin iic_SDA_reg = 1'b1;io_en = 1'b1;end//ack
10,11,12,13,14,15,16,17:begin 
      iic_SDA_reg = 1'bz;io_en = 1'b0;end	
    18:begin iic_SDA_reg = 1'b1;io_en = 1'b1;end
    19:begin iic_SDA_reg = 1'bz;io_en = 1'b0;end//等待主机发送stop
default:begin iic_SDA_reg =  1'b1;io_en = 1'b1;end  
  endcase
  end 

  else if(next_state == s_swrite) begin
  case(n_cnt)
    9 :begin iic_SDA_reg = 1'b1;io_en = 1'b1;end//ack
10:begin iic_SDA_reg = inner_data[7];io_en = 1'b1;end
    11:begin iic_SDA_reg = inner_data[6];io_en = 1'b1;end
    12:begin iic_SDA_reg = inner_data[5];io_en = 1'b1;end
    13:begin iic_SDA_reg = inner_data[4];io_en = 1'b1;end
    14:begin iic_SDA_reg = inner_data[3];io_en = 1'b1;end
    15:begin iic_SDA_reg = inner_data[2];io_en = 1'b1;end
    16:begin iic_SDA_reg = inner_data[1];io_en = 1'b1;end
    17:begin iic_SDA_reg = inner_data[0];io_en = 1'b1;end
    18:begin iic_SDA_reg = 1'bz;io_en = 1'b0;end//ack
19:begin iic_SDA_reg = 1'bz;io_en = 1'b0;end//等待主机发送stop
default:begin iic_SDA_reg =  1'b1;io_en = 1'b1;end	  
  endcase
  end	
	
  else begin
    iic_SDA_reg = 1'bz;io_en = 1'b0;end
end  

assign iic_SDA = io_en ? iic_SDA_reg : 1'bz;

inner_data,dout,finish控制

always@(posedge iic_SCL or negedge rst_n)
  if(~rst_n)
    inner_data <= 'd0;
  else if(next_state == s_sread && n_cnt >= 'd11 && n_cnt <= 'd18)
    inner_data <= {inner_data[6:0],iic_SDA};  

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    finish <= 1'b0;
  else if(next_state == s_mread && n_cnt == 'd19)	 
	 finish <= 1'b1;
  else if(next_state == s_mwrite && n_cnt == 'd19)	 
	 finish <= 1'b1;
  else 
    finish <= 1'b0;  

always@(posedge CLK_50M or negedge rst_n)
  if(~rst_n)
    error <= 1'b0;
  else if(next_state == s_mwrite)begin	
    if(n_cnt == 'd9 ||n_cnt == 'd18) begin
      if(iic_SDA == 1'b0)
        error <= 1'b1;
      else 
        error <= 1'b0; end
	 else
      error <= 1'b0;	end 
  else if(next_state == s_mread) begin
    if(n_cnt == 'd9) begin
      if(iic_SDA == 1'b0)
        error <= 1'b1;
      else 
        error <= 1'b0; end
	 else
      error <= 1'b0;	 end	 
  else if(next_state == s_swrite)begin
    if(n_cnt == 'd18) begin
      if(iic_SDA == 1'b0)
        error <= 1'b1;
      else 
        error <= 1'b0; end
	 else 
      error <= 1'b0; 	end 
  else
    error <= 1'b0;		 

always@(posedge iic_SCL or negedge rst_n)
  if(~rst_n)
    data_out_reg <= 'd0;
  else if(next_state == s_mread && n_cnt < 'd18)
    data_out_reg <= {data_out_reg[6:0],iic_SDA};

always@(posedge CLK_50M or negedge rst_n)	 
  if(~rst_n)
    data_out <= 'd0;
  else if(next_state == s_mread && n_cnt == 'd19)	
    data_out <= data_out_reg;
  else 
    data_out <= 'd0; 

endmodule

top顶层

module IIC_top(
input ack,
input CLK_50M,
input rst_n,
input [7:0] data,
input [7:0] command,
input req,
inout iic_SCL,
output  finish,
output [7:0] data_out   
);

wire iic_SDA_top;
wire io_en;

assign iic_SDA_top = (!io_en)? ack : 1'bz;


IIC#(.id0(1'b0),
    .id1 (1'b0),
    .id2 (1'b1)
	 )master1(
 .CLK_50M (CLK_50M) ,
 .rst_n   (rst_n)   ,
 .data    (data)    ,
 .command (command) ,
 .req     (req  )   ,
 .iic_SCL (iic_SCL) ,
 .iic_SDA (iic_SDA_top) ,
 .finish  (finish ) ,
 .io_en   (io_en  ) ,
 .data_out (data_out)
);

endmodule

三、仿真波形分析

测试作为master时的写中断和写模式

主线空闲时收到req,产生start标志,进入send状态

当总线空闲时收到req请求时,使 SDA 拉低 ,产生start标志,进入send状态

send状态过程

n_cnt 等于1-8时,发送id寻址字节,1010 011 0(写)。

ack=0时的error处理

n_cnt 等于9时,接收ack信号,ack = 0 ,代表无效,发生错误,回到idle状态,此时总线还是处于空闲状态,重新产生start信号,再次进入send状态。

send状态与mwrite状态的完整过程

重新进入send状态,这次 n_cnt=8 时的ack信号为1,有效,进入 mwrite 状态,发送数据1100 1010。n_cnt = 18时,ack 信号为1,有效,顺利结束 mwrite 状态,产生stop标志,finish信号拉高,iic_SDA_reg 变为高阻态,释放总线。

stop标志产生

stop标志产生:在SCL为低时先拉低SDA信号,在SCL为高时,拉高SDA,即产生stop标志。

测试作为master时的读模式

send状态与mread状态的完整过程

send模式发送id寻址字节 1010 011 1(读),进入mread状态,n_cnt 等于9时,接收ack信号,ack = 1,继续读取数据,iic_SDA_reg 为高阻态,iic_SDA 数据总线作为输入,输入数据 1010 1010。在n_cnt 等于18时,发送ack信号,iic_SDA_reg= 1,代表接收成功,finish信号拉高,产生stop信号,释放总线,data_out 传输数据出去。

data_out信号放大

测试作为slave时的写模式

busy状态与swrite状态的完整过程

没有收到req请求时,总线SDA拉低,即收到start标志,进入busy状态,检测到id寻址字节为 1010 001 0(写),与自己的 id号一致,进入swrite状态,n_cnt 等于9和18时,发送ack信号,iic_SDA_reg= 1,代表接收成功,其余iic_SDA_reg为高阻态,接收数据,SDA 以移位的方式写进内部寄存器inner_data。等待主机传来stop标志,结束swrite过程。

测试作为slave时的读模式

busy状态与sread状态的完整过程

没有收到req请求时,总线SDA拉低,即收到start标志,进入busy状态,检测到id寻址字节为 1010 001 1(读),与自己的 id号一致,进入sread状态,n_cnt 等于9时,发送ack信号,iic_SDA_reg= 1,代表接收成功。接着发送内部寄存器inner_data中的数据 1100 1100 给数据总线 iic_SDA。当 n_cnt = 18时,接收ack 信号,ack=1,有效,等待主机传来stop标志,结束sread过程。

转自:https://zhuanlan.zhihu.com/p/549003671

posted @ 2024-01-17 18:38  _浮尘  阅读(1555)  评论(0)    收藏  举报