你真的理解IIC吗?
程序员文章站
2024-02-23 23:22:30
...
1.IIC总线结构
2.IIC时序
3.IIC概念
1、开始条件(start condition):
为了标识传输正式启动,
master
设备会将
SCL
置为高电平(当总线空闲时,
SDA
和
SCL
都处于高电平状态),然后
将
SDA
拉低,这样,所有
slave
设备就会知道传输即将开始。如果两个
master
设备在同一时刻都希望获得总线的所
有权,那么谁先将
SDA
拉低,谁就赢得了总线的控制权。在整个通信期间,可以存在多个
start
来开启每一次新的
通信序列(
communication sequence
),而无需先放弃总线的控制权,后面会讲到这种机制。
注意,在正常传输数据过程中,当 SCL 处于高电平时,SDA 上的值不应该变化。
2、地址帧(address frame):
地址帧总是在一次通信的最开始出现。一个
7-bit
的地址是从最高位(
MSB
)开始发送的,这个地址后面会紧跟
1-bit
的操作符,
1
表示读操作,
0
表示写操作。
3、应答 AC K/NACK
接下来的一个
bit
是
NACK/ACK
,当这个帧中前面
8bits
发送完后,接收端的设备获得
SDA
控制权,此时接收设备
应该在第
9
个时钟脉冲之前回复一个
ACK
(将
SDA
拉低)以表示接收正常,如果接收设备没有将
SDA
拉低,则
说明接收设备可能没有收到数据(如寻址的设备不存在或设备忙)或无法解析收到的消息,如果是这样,则由
master
来决定如何处理(
stop
或
repeated start condition
)。
4、数据帧(data frames):
在地址帧发送之后,就可以开始传输数据了。
Master
继续产生时钟脉冲,而数据则由
master
(写操作)或
slave
(读
操作)放到
SDA
上。每个数据帧
8bits
,数据帧的数量可以是任意的,直到产生停止条件。每一帧数据传输(即每
8-bit
)之后,接收方就需要回复一个
ACK
或
NACK
(写数据时由
slave
发送
ACK
,读数据时由
master
发送
ACK
。
当
master
知道自己读完最后一个
byte
数据时,可发送
NACK
然后接
stop condition
)。
5、停止条件(stop condition):
当所有数据都发送完成时,
master
将产生一个停止条件。停止条件定义为:在
SDA
置于低电平时,将
SCL
拉高并
保持高电平,然后将
SDA
拉高。
注意,在正常传输数据过程中,当 SCL 处于高电平时,SDA 上的值不应该变化,防止意外产生一个停止条件。
6、重复开始条件(repeated start condition):
有时
master
需要在一次通信中进行多次消息交换(例如与不同的
slave
传输消息,或切换读写操作),并且期间不希
望被其他
master
干扰,这时可以使用“重复开始条件” —— 在一次通信中,
master
可以产生多次
start condition
,
来完成多次消息交换,最后再产生一个
stop condition
结束整个通信过程。由于期间没有
stop condition
,因此
master
一直占用总线,其他
master
无法切入。
为了产生一个重复的开始条件,
SDA
在
SCL
低电平时拉高,然后
SCL
拉高。接着
master
就可以产生一个开始条件
继续新的消息传输(按照正常的
7-bit/10-bit
地址传输时序)。重复开始条件的传输时序如下图所示:
7、时钟拉伸(clock stretching)(了解即可):
有时候,低速
slave
可能由于上一个请求还没处理完,尚无法继续接收
master
的后续请求,即
master
的数据传输速
率超过了
slave
的处理能力。这种情况下,
slave
可以进行时钟拉伸来要求
master
暂停传输数据 —— 通常时钟都是
由
master
提供的,
slave
只是在
SDA
上放数据或读数据。而时钟拉伸则是
slave
在
master
释放
SCL
后,将
SCL
主
动拉低并保持,此时要求
master
停止在
SCL
上产生脉冲以及在
SDA
上发送数据,直到
slave
释放
SCL
(
SCL
为高
电平)。之后,
master
便可以继续正常的数据传输了。可见时钟拉伸实际上是利用了时钟同步的机制(见下文),只
是时钟由
slave
产生。
如果系统中存在这种低速
slave
并且
slave
实现了
clock stretching
,则
master
必须实现为能够处理这种情况,实际上
大部分
slave
设备中不包含
SCL
驱动器的,因此无法拉伸时钟。
所以更完整的
I2C
数据传输时序图为:
8、10-bit 地址空间(了解即可)
在
10-bit
地址的
I2C
系统中,需要两个帧来传输
slave
的地址。第一个帧的前
5
个
bit
固定为
b11110
,后接
slave
地
址的高
2
位,第
8
位仍然是
R/W
位,接着是一个
ACK
位,由于系统中可能有多个
10-bit slave
设备地址的高
2bit
相同,因此这个
ACK
可能由多有
slave
设备设置。第二个帧紧接着第一帧发送,包含
slave
地址的低
8
位(
7:0
),
接着该地址的
slave
回复一个
ACK
(或
NACK
)。
注意,
10-bit
地址的设备和
7-bit
地址的设备在一个系统中是可以并存的,因为
7-bit
地址的高
5
位不可能是
b11110
。
实际上对于
7-bit
的从设备地址,合法范围为
b0001XXX-b1110XXX
,’
X
’表示任意值,因此该类型地址最多有
112
个(其他为保留地址
[1]
)。
两个地址帧传输完成后,就开始数据帧的传输了,这和
7-bit
地址中的数据帧传输过程相同。
9、时钟同步和仲裁(了解即可):
如果两个
master
都想在同一条空闲总线上传输,此时必须能够使用某种机制来选择将总线控制权交给哪个
master
,
这是通过时钟同步和仲裁来完成的,而*让出控制权的
master
则需要等待总线空闲后再继续传输。在单一
master
的系统上无需实现时钟同步和仲裁。
10、时钟同步(了解即可):
时钟同步是通过
I2C
接口和
SCL
之间的线“与”(
wired-AND
)来完成的,即如果有多个
master
同时产生时钟,那
么只有所有
master
都发送高电平时,
SCL
上才表现为高电平,否则
SCL
都表现为低电平。
11、总线仲裁(了解即可):
总线仲裁和时钟同步类似,当所有
master
在
SDA
上都写
1
时,
SDA
的数据才是
1
,只要有一个
master
写
0
,那此
时
SDA
上的数据就是
0
。一个
master
每发送一个
bit
数据,在
SCL
处于高电平时,就检查看
SDA
的电平是否和发
送的数据一致,如果不一致,这个
master
便知道自己输掉仲裁,然后停止向
SDA
写数据。也就是说,如果
master
一直检查到总线上数据和自己发送的数据一致,则继续传输,这样在仲裁过程中就保证了赢得仲裁的
master
不会丢
失数据。
输掉仲裁的
master
在检测到自己输了之后也不再产生时钟脉冲,并且要在总线空闲时才能重新传输。
仲裁的过程可能要经过多个
bit
的发送和检查,实际上两个
master
如果发送的时序和数据完全一样,则两个
master
都能正常完成整个的数据传输。
4.IIC注意项
在正常传输数据过程中,当 SCL 处于高电平时,SDA 上的值不应该变化,因此利用 SCL处于高电平时,数据变化来区分起始和停止信号,和数据传输得以区分。
SCL处于高电平时:SDA拉低为起始信号,SDA拉高为停止信号。
5.IIC FPGA代码
module i2c#
(
parameter WMEN_LEN = 8'd0,
parameter RMEN_LEN = 8'd0,
parameter CLK_DIV = 16'd499
)
(
input clk_i,
output iic_scl,
inout iic_sda,
input [WMEN_LEN*8-1'b1:0]wr_data,//write data
input [7:0]wr_cnt,//write data lenth include device address
output [RMEN_LEN*8-1'b1:0]rd_data,//read data
input [7:0]rd_cnt,//read data lenth
input iic_en,//iic_en == 1 enable iic transmit
input iic_mode,//iic_mode = 1 random read iic_mode = 0 current read or page read
output iic_busy,//iic controller busy
output sda_dg//for ila debug
);
parameter IDLE = 4'd0;
parameter START = 4'd1;
parameter W_WAIT = 4'd2;
parameter W_ACK = 4'd3;
parameter R_WAIT = 4'd4;
parameter R_ACK = 4'd5;
parameter STOP1 = 4'd6;
parameter STOP2 = 4'd7;
reg [2:0] IIC_S = 4'd0;
//generate scl
reg [15:0] clkdiv = 16'd0;
reg scl_clk = 1'b0;
aaa@qq.com(posedge clk_i)
if(clkdiv < CLK_DIV)
clkdiv <= clkdiv + 1'b1;
else begin
clkdiv <= 16'd0;
scl_clk <= !scl_clk;
end
parameter OFFSET = CLK_DIV - CLK_DIV/4;
wire scl_offset = (clkdiv == OFFSET);//scl delay output to fit timing
reg iic_busy = 1'b0;
reg iic_scl = 1'b0;
reg scl_r = 1'b1;
reg sda_o = 1'b0;
reg [7:0] sda_r = 8'd0;
reg [7:0] sda_i_r = 8'd0;
reg [7:0] wcnt = 8'd0;
reg [7:0] rcnt = 8'd0;
reg [2:0] bcnt = 3'd0;
reg rd_en = 1'b0;
reg [RMEN_LEN*8-1'b1:0] rd_data = 0;
wire sda_i;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst (
.O(sda_i), // Buffer output
.IO(iic_sda), // Buffer inout port (connect directly to top-level port)
.I(sda_o), // Buffer input
.T(sda_o) // 3-state enable input, high=input, low=output
);
always @(posedge clk_i) iic_scl <= scl_offset ? scl_r : iic_scl;
// Pullup output (connect directly to top-level port)
PULLUP PULLUP_inst (.O(iic_sda));
//scl output
always @(*) begin
if(IIC_S == IDLE || IIC_S == STOP1 || IIC_S == STOP2)
scl_r <= 1'b1;
else
scl_r <= ~scl_clk;
end
//sda output
always @(*) begin
if(IIC_S == START || IIC_S == STOP1 || (IIC_S == R_ACK && (rcnt != rd_cnt)))
sda_o <= 1'b0;
else if(IIC_S == W_WAIT)
sda_o <= sda_r[7];
else sda_o <= 1'b1;
end
//sda output shift
always @(negedge scl_clk) begin
if(IIC_S == W_ACK || IIC_S == START)begin
sda_r <= wr_data[(wcnt*8) +: 8];
if( rd_en ) sda_r <= {wr_data[7:1],1'b1};
end
else if(IIC_S == W_WAIT)
sda_r <= {sda_r[6:0],1'b1};
else
sda_r <= sda_r;
end
//sda input shift
always @(posedge scl_clk) begin
if(IIC_S == R_WAIT ||IIC_S == W_ACK ) begin
sda_i_r <= {sda_i_r[6:0],sda_i};
end
else if(IIC_S == R_ACK)
rd_data[((rcnt-1'b1)*8) +: 8] <= sda_i_r[7:0];
else if(IIC_S == IDLE)begin
sda_i_r <= 8'd0;
end
end
reg iic_sda_r = 1'b1;
reg sda_dg = 1'b1;
always @(posedge scl_clk) iic_sda_r <= sda_i;
always @(posedge clk_i) sda_dg <= sda_i;
//iic state machine
always @(negedge scl_clk)begin
case(IIC_S) //sda = 1 scl =1
IDLE://idle wait iic_en == 1'b1 start trasmit rd_en == 1'b1 restart
if(iic_en == 1'b1 || rd_en == 1'b1)begin
iic_busy <= 1'b1;
IIC_S <= START;
end
else begin
iic_busy <= 1'b0;
wcnt <= 8'd0;
rcnt <= 8'd0;
rd_en <= 1'b0;
end
START:begin //sda = 0 then scl_clk =0 scl =0 generate start
bcnt <= 3'd7;
IIC_S <= W_WAIT;
end
W_WAIT://write data
begin
if(bcnt > 3'd0)
bcnt <= bcnt - 1'b1;
else begin
wcnt <= wcnt + 1'b1;
IIC_S <= W_ACK;
end
end
W_ACK://write data ack
begin
if(wcnt < wr_cnt)begin
bcnt <= 3'd7;
IIC_S <= W_WAIT;
end
else if(rd_cnt > 3'd0)begin// read data
if(rd_en == 1'b0 && iic_mode == 1'b1)begin
rd_en <= 1'b1;
IIC_S <= IDLE;
end
else
IIC_S <= R_WAIT;
bcnt <= 3'd7;
end
else
IIC_S <= STOP1;
if(iic_sda_r == 1'b1)
IIC_S <= STOP1;
end
R_WAIT://read data
begin
rd_en <= 1'b0;
bcnt <= bcnt - 1'b1;
if(bcnt == 3'd0)begin
rcnt <= (rcnt < rd_cnt) ? (rcnt + 1'b1) : rcnt;
IIC_S <= R_ACK;
end
end
R_ACK://read date ack
begin
bcnt <= 3'd7;
IIC_S <= (rcnt < rd_cnt) ? R_WAIT : STOP1;
end
STOP1://sda = 0 scl = 1
IIC_S <= STOP2;
STOP2://sda = 1 scl = 1
IIC_S <= IDLE;
default:
IIC_S <= IDLE;
endcase
end