16位片内地址的I2C SLAVE接口设计

8位片内地址的I2C SLAVE在OPENCORS.org上面有,但是我没有找到16位的,我打算用B210的接EEPROM的I2C总线实现跟FPGA通讯就对照24C256的数据手册写了一个。

以下代码2022-6-6更新已经实际运行通过。

 
 
 
  
 
 
 /*
i2c_slave # ( .I2C_ADR( 7'b1010_000 ) )
i2c_slave (
clk,
rst,
scl_i,
sda_i,
sda_oen,  
addr ,
wr,
rd,
dout ,
din ,
st  ,
inr
);
*/ 
 
 
module i2c_slave # ( parameter I2C_ADR = 7'b1010_000 ) (
input clk,rst,
 
input scl_i,sda_i,
output reg sda_oen, 
 
output reg  [15:0] addr ,
output reg wr,rd,
output reg [7:0] dout ,
input [7:0] din ,
output  reg [7:0 ] st  ,
output reg [6:0 ] inr
 
);
 
reg [7:0] dinr, doutr ;
reg [2:0] sdar; always @ (posedge clk) sdar<= { sdar[1:0] , sda_i };
reg [2:0] sclr; always @ (posedge clk) sclr<= { sclr[1:0] , scl_i };
 
 
wire  sda_rise =  sdar[2:1]==2'b01 ;
wire  sda_fall =  sdar[2:1]==2'b10 ;
 
wire  scl_rise =  sclr[2:1]==2'b01 ;
wire  scl_fall =  sclr[2:1]==2'b10 ;
 
wire  start_con_w =  sclr[1] & sda_fall ;
wire  stop_con_w  =  sclr[1] & sda_rise ;  
reg rd1wr0; 

 /*
wire[35:0] CONTROL0 ;
reg  [31:0]  bus32 ; 
 
always @ (posedge clk)  bus32[7:0] <= st   ;
always @ (posedge clk)  bus32[9:8] <= {wr,rd} ;
always @ (posedge clk)  bus32[31:16] <= addr;
always @ (posedge clk)  bus32[15:14] <= {sdar,sclr} ;//dout[5:0];


   chipscope_ila_32 chipscope_ila_32_0 (
				      .CONTROL(CONTROL0), // INOUT BUS [35:0]
				      .CLK(clk), // IN
				      .TRIG0({bus32 }) // IN BUS [31:0]
				      );
						
   chipscope_icon chipscope_icon_i0
     (
      .CONTROL0(CONTROL0) // INOUT BUS [35:0]
      );
		

reg [7:0]d ;always @(posedge clk) case (st) 112,80,168:d<=d+1;default d<=0; endcase 
 
always @ (posedge clk)  bus32[7:0] <= st   ;
always @ (posedge clk)  bus32[9:8] <= {wr,rd} ;
always @ (posedge clk)  bus32[31:16] <= addr;
always @ (posedge clk)  bus32[15:14] <= {sdar,sclr} ;//dout[5:0];
		*/
	 
	 
always @ (posedge clk) if (rst) st<=0;else if (start_con_w)st<=20; else case (st)
0  : st <= 10 ;
10 : begin sda_oen<=1; wr<=0; if (start_con_w) st <= 20 ;end 
20 : begin inr[6]<=sdar[1];if (scl_rise)st<=st+1;end
21 : begin inr[5]<=sdar[1];if (scl_rise)st<=st+1;end
22 : begin inr[4]<=sdar[1];if (scl_rise)st<=st+1;end
23 : begin inr[3]<=sdar[1];if (scl_rise)st<=st+1;end
24 : begin inr[2]<=sdar[1];if (scl_rise)st<=st+1;end
25 : begin inr[1]<=sdar[1];if (scl_rise)st<=st+1;end
26 : begin inr[0]<=sdar[1];if (scl_rise)st<=st+1;end
27 : begin rd1wr0<=sdar[1];if (scl_rise)st<=st+1;end   
28 : if ( inr[6:0] == I2C_ADR ) st<=30;else st<=10;

30 : if (sclr[1]==0) begin  st<=st+1 ;end //you are visiting my device ,ack it now .
31 : begin sda_oen <= 0; if (scl_rise) st<=st+1; end // master read back this ack .
32 : if (sclr[1]==0) begin st<=st+1 ;end 
33 : begin sda_oen  <= 1  ;if (rd1wr0==0) st <= 50 ; 
else st <= 150 ; // read out 
end  
50 : begin addr[15]<=sdar[1];if (scl_rise)st<=st+1;end
51 : begin addr[14]<=sdar[1];if (scl_rise)st<=st+1;end
52 : begin addr[13]<=sdar[1];if (scl_rise)st<=st+1;end
53 : begin addr[12]<=sdar[1];if (scl_rise)st<=st+1;end
54 : begin addr[11]<=sdar[1];if (scl_rise)st<=st+1;end
55 : begin addr[10]<=sdar[1];if (scl_rise)st<=st+1;end
56 : begin addr[9 ]<=sdar[1];if (scl_rise)st<=st+1;end
57 : begin addr[8 ]<=sdar[1];if (scl_rise)st<=st+1;end    
58 : if (sclr[1]==0) begin sda_oen  <= 0 ;st<=st+1;end 
59 : if (scl_rise) st<=st+1;
60 : if (sclr[1]==0) begin sda_oen  <=1  ;st<=70 ;end

70 : begin addr[7]<=sdar[1];if (scl_rise)st<=st+1;end
71 : begin addr[6]<=sdar[1];if (scl_rise)st<=st+1;end
72 : begin addr[5]<=sdar[1];if (scl_rise)st<=st+1;end
73 : begin addr[4]<=sdar[1];if (scl_rise)st<=st+1;end
74 : begin addr[3]<=sdar[1];if (scl_rise)st<=st+1;end
75 : begin addr[2]<=sdar[1];if (scl_rise)st<=st+1;end
76 : begin addr[1]<=sdar[1];if (scl_rise)st<=st+1;end
77 : begin addr[0]<=sdar[1];if (scl_rise)st<=st+1;end    
78 : if (sclr[1]==0) begin sda_oen <= 0 ;st<=st+1 ;end 
79 : if (scl_rise) begin  st<=st+1;end 
80 : if (sclr[1]==0)begin  sda_oen <=1 ;st<=st+1;end //ack this address 
81 : begin doutr[7]<=sdar[1]; if (scl_rise) st<=82;end  
82 :  if (start_con_w)  st<=20;
else if (sclr[1]==0)   st<=101  ; // master write me data  

150 :  begin st <= st+1;end  // set read current addr 
151 :  begin st <= st+1; dinr<= din ; addr <= addr+1;end  //latch in dinr 
152 :  begin sda_oen<= ~dinr[7]; if (scl_rise)st<=st+1;end  153 :  if(sclr[1]==0) st<=st+1;
154 :  begin sda_oen<= ~dinr[6]; if (scl_rise)st<=st+1;end  155 :  if(sclr[1]==0) st<=st+1;
156 :  begin sda_oen<= ~dinr[5]; if (scl_rise)st<=st+1;end  157 :  if(sclr[1]==0) st<=st+1;
158 :  begin sda_oen<= ~dinr[4]; if (scl_rise)st<=st+1;end  159 :  if(sclr[1]==0) st<=st+1;
160 :  begin sda_oen<= ~dinr[3]; if (scl_rise)st<=st+1;end  161 :  if(sclr[1]==0) st<=st+1;
162 :  begin sda_oen<= ~dinr[2]; if (scl_rise)st<=st+1;end  163 :  if(sclr[1]==0) st<=st+1;
164 :  begin sda_oen<= ~dinr[1]; if (scl_rise)st<=st+1;end  165 :  if(sclr[1]==0) st<=st+1;
166 :  begin sda_oen<= ~dinr[0]; if (scl_rise)st<=st+1;end  167 :  if(sclr[1]==0) st<=st+1;
168 :  st<=169;
169 :  begin sda_oen<= 1;if (scl_rise)st<=st+1 ;end  
170 :  if (sdar[1]==0)  st<=180;//ACK : burst read;
       else st<=st+1;//NACK		
	   180: if (sclr[1] == 0 ) st<=150;//wait next reading 
171 :  if (stop_con_w) st<=10 ; //stop now  
//now in write state 
100 : begin doutr[7] <= sdar[1] ; if (scl_rise)st<=st+1;end
101 : begin doutr[6] <= sdar[1] ; if (scl_rise)st<=st+1;end //checked ok
102 : begin doutr[5] <= sdar[1] ; if (scl_rise)st<=st+1;end
103 : begin doutr[4] <= sdar[1] ; if (scl_rise)st<=st+1;end
104 : begin doutr[3] <= sdar[1] ; if (scl_rise)st<=st+1;end
105 : begin doutr[2] <= sdar[1] ; if (scl_rise)st<=st+1;end
106 : begin doutr[1] <= sdar[1] ; if (scl_rise)st<=st+1;end
107 : begin doutr[0] <= sdar[1] ; if (scl_rise)st<=st+1;end  
108 : begin dout<=doutr ; wr<=1 ; st<=st+1; end
109 : begin addr<=addr+1; wr<=0; st<=st+1; end
110 : if (sclr[1]==0) st<=st+1; 
111 : begin sda_oen<=0;if (scl_rise)st<=st+1;end 
112 : if (sclr[1]==0) st<=st+1; //send ACK
113 : begin sda_oen <=1;doutr[7] <= sdar[1];if (scl_rise)st<=st+1;end
114 : if (stop_con_w) st<=20; else if (sclr[1]==0) st<=101;   
 
default st<=0;
endcase
always @ (*) rd = st == 150 ;
endmodule 















代码风格还是我用的本办法。

看图说话:

这里要注意以下几点:

1,SCL高电平期间SDA的输出保持不变。

2,SCL==0 时候,并且满足建立保持时间后,SDA允许数据变化。

3,SCL==1S时候如果SDA由高变低(下降沿)定义为START信号。

4,SCL==1S时候如果SDA由低变高(上升沿)定义为STOP信号。

5,当SLAVE(本代码)发送ACK后,在SCL的上升边缘被MASTER所采集到,并且MASTER要发送STOP信号,或者另外一个START信号,这时候需要考究一下SLAVE何时释放SDA输出是能信号SDA_OEN。数据手册里面描述的此时间最小是100ns。我们可以认为MASTER用SCL的上升边沿采集SLAVE发出的SDA,其保持时间只要不小于100ns就可以满足。(MASTER实际需要的保持时间应该更短),我们这里设置100ns多一些即便是和master的STOP或者START发生两输出冲突,及短也不会有什么损坏器件的影响。

6,I2C总线建立时间是说SCL上升边沿到来之前,SDA要保持的最短时间。

7,I2C总线的建立时间是说SCL下降边沿到来之后,SDA要保持的最短时间。

上述代码简单写好了,框架和思路没有问题,但是还没有调试,细节方面应该是有点问题,明天继续看看。

===========================以下内容2022-6-6添加============================

1,ACK或者NACK是都是独立的一个BIT,没有和STOP和START混合在一起。

2,读的过程首先是设置地址,这个过程伪写,就是将地址写入内部寄存器。之后不STO而立即START,开始读出数据。每个数据之后MASTER给一个ACK,最后一个字节MASTER给一个NACK之后下个BIT发送STOP命令结束总线对话。最后一个字节虽然发送的是NACK实际上MASTER也收到了正确数据,只是为了告诉SLAVE接下来不要传输了,就发送了NACK。

3,PAGE是连续操作的字节个数,的在24C256数据手册里面看到写的是16字节,实际进行读操作时候发现读操作不受PAGE字节数限制,可以一次读出所有的字节。

4,地址是一个默认参数,每次读写操作都会使得地址寄存器加1。

5,写的操作都必须携带地址,而读的操作实际是包含了设置地址和真正读操作。这两个操作序列可以在一次I2C会话中实现,也可以分开来。

6,SCL=0期间允许数据表换,SCL上升边沿SDA的数据被采集,

SCL=1期间如果SDA由高变低被认为是I2C总线会话的开始,

SCL=1期间如果SDA由低变高认为是I2C总线会话的结束。

 

===========================以下内容2022-6-7添加============================

1,在case之前加一个对起始位的判断,只要有起始位到来,就认为是新的会会话开始。这样的目的是为了防止状态机进入未知状态而死机。

2,一个传输开始之后,首先是固定的发送dev_addr+读写请求,如果这时候器件允许读,则就发送ACK,否则不发送任何内容会被认为是NACK。

3,发送ACK相应了器件寻址之后,就会进入都或者写的模式。

4,进入写的模式,首先必须是写操作地址,写完操作地址后,实际又有两种情况,一种是直接继续传输写入内容,另外一种MASTER从新发送START开始新的会话(这种情况下,之前的写操作实际就是之写了地址寄存器)。

5,对4的分析,我们在处理写命令的时候,首先将前两个数据作为地址保存好,之后继续进入收取I2C写来的内容并进行实际写的状态,如果I2C主控要实际进行写,会继续往下进行写操作,如果I2C主控只是为了读而设置地址寄存器,主控会从新发出START命令,被我们检测到以后从新进入判断读写状态。

6,上述4,5说的都是写的情况,读的情况只有一种:在7位的器件地址后面紧接一位是1,说明是读,之后I2C的从设备从之前已经写好的地址寄存器里面获取地址,取数并每个字节发送到I2C的MASTER,并将地址寄存器递增加1。主机收到一个字节后就ACK一下,让I2C从设备再次发送下一个字节,如果主机不打算再接受就发送NACK,之后接着下位发送一个STOP,终止这次传输。

===================2022-6-7添加的关于I2C总线硬件接口的分析==============

1,首先有一个很重要的概念叫做“线与”,这里的“与”就是对应逻辑and,我们知道逻辑and,多个说如进行and逻辑操作,只要有一个是0,则结果是0。

2,我们看下面的总线:

 我们看到红色框内只能将I2C的两个线进行拉低,这样在I2C总线上电平是0,而红色框内那个MOS管的接入方式叫做OPEN DRAIN 或者说“开漏”方式,只能吸收电流拉低为0,而无法实现输出1。I2C总线要输出1则要依靠外部的上拉电阻RP。

3,在2的图中,我们只看SDA,同种任意一个外设的拉低总线都会导致总线上电平表现为0.这就是实现了1中所讲到的“线与”。

4,我们进一步分析图中MOS管的控制,当MOS管处于导通状态,实际输出0,当MOS管处理截至状态,是没有输出的,可以认为是输入状态,由于上拉电阻拉到高电平也可以认为是输出1。

5,既然输出1是上拉,因此不存在多个I2C设备同时输出不一样的电平造成的电流冲突,这方面真是精髓。

6,由于上述5这条实现了多个输出发生碰撞不会冲突,所以I2C可以允许总线上不但可以有多个从设备外还可以有多个主设备。

7,还有一个几乎是俗成的约定,就是对器件进行寻址的时候,如果从设备不此时不具备响应条件(比如正在执行上一个写操作发下来的命令对数据正在烧写到实际存储区域),直到具备相应条件才进行相应。这实际就是要求在读写前确保器件不忙,可以在

A,每次操作后反复进行写操作寻址的寻址,如果得到ACK后就退出,这就保证实际的操作起作用,并且了下次操作直接进行;

B,或者是在每次操作之前进行器件寻址等到回应ACK后才继续下一步。

我实际推荐A方式,确保生效后再退出。

8,上述7.A提到每次设置后要等待生效,下面的逻辑分析仪的抓图就是例证:

 由于图片大小限制只能抓部分图片,大家可以看到执行完写操作后,反复寻址器件,而器件一直NACK,知道器件回复ACK,I2C的主设备才停止询问,认为一次写操作完成。

9,I2C接口要用到三态输入,最好将三态门实例化在TOP层。这写实现细节可能在之后FPGA具体实现中讲到。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值