1.协议介绍
I2C即Inter-Integrated Circuit(集成电路总线),是由Philips半导体公司(现在的NXP半导体公司)在八十年代初设计出来的一种简单、双向、二线制总线标准。
多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。
主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。 I2C协议是一种简单、双向、二线制、同步、串行总线。占用引脚少,可扩展性强,支持多设备的总线。每个从设备都有一个独立的七位地址 标准模式100kb/s 快速模式400kb/s 高速模式3.4MB/
2.协议时序
1.时钟线和数据线均为高电平,总线空闲状态 2.时钟线为高电平,数据线由高变低,起始状态 3.数据读写过程,时钟下降沿数据变化,时钟上升沿保持,高位到低位传输 4.时钟线为高电平,数据线由高变低,停止信号 由图 28.1.3可知,我们在起始信号之后,主机开始发送传输的数据;在串行时钟线SCL为低电平状态时,SDA允许改变传输的数据位(1为高电平,0为低电平),在SCL为高电平状态时,SDA要求保持稳定,相当于一个时钟周期传输1bit数据,经过8个时钟周期后,传输了8bit数据,即一个字节。第8个时钟周期末,主机释放SDA以使从机应答,在第9个时钟周期,从机将SDA拉低以应答;如果第9个时钟周期,SCL为高电平时,SDA未被检测到为低电平,视为非应答,表明此次数据传输失败。第9个时钟周期末,从机释放SDA以使主机继续传输数据,如果主机发送停止信号,此次传输结束。我们要注意的是数据以8bit即一个字节为单位串行发出,其最先发送的是字节的最高位。 读写过程,绿色位主机发送信号,红色为从机发送信号,黄色有可能主机给从机也有可能从机给主机.
2.1写操作(w/r = 0)
写分为随机读写和页读写,一般设备只支持随机读写。 1.器件地址一般为固定地址或者低几位可以用户自定义,看不同从机设备。 2.地址位有八比特或者十六比特,如果是八比特就传输一个字节数据后地址位传输结束,如果是十六位则需要两个地址位,先传高位再传低位,不足十六位则高几位是无效位。 3.单字节写和页写的时序区别是发送完一个数据从机响应应答信号后,主机发送的是结束位还是继续传输数据。
2.2读模式(w/r = 1)
读模式有三种:当前位置读,随机读和连续读。
2.2.1 当前位置读
当前位置读是在一次读或者写操作之后发起的操作,由于I2C器件在读写操作后内部指针自动加1,因此当前地址读可以读出下一个地址的数据
2.2.2随机读操作
随机读操作可以任意指定地址进行读数据 可以看到随机读操作的过程为: 起始+器件地址加写+应答+读地址+应答+起始+器件地址加读+应答+接收到的数据+非应答+停止位 发送完器件地址和字地址后,竟然又发送起始信号和器件地址,而且第一次发送器件地址时后面的读写控制位为“0”,也就是写命令,第二次发送器件地址时后面的读写控制位为“1”,也就是读。 这是因为我们需要使从机内的存储单元地址指针指向我们想要读取的存储单元地址处,所以首先发送了一次Dummy Write也就是虚写操作,只所以称为虚写,是因为我们并不是真的要写数据,而是通过这种虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,就可以以当前地址读的方式 读数据了,
2.2.3连续读
连续读和随机读的区别就是将数据传输后的非应答改为应答,让从机继续传输数据
2.3总结
1.随机写:起始信号+器件地址写+应答+寄存器地址+应答+写数据+应答+停止位。 2.页写:起始信号+器件地址写+应答+寄存器地址+应答+(写数据+应答)*N+停止位。 3.当前读:起始信号+器件地址读+应答+读数据+非应答+停止信号。 4.随机读:起始信号+器件地址写+应答+寄存器地址+停止信号+起始信号+器件地址读+应答+读数据+非应答+停止信号。 5.连续读:起始信号+器件地址写+应答+寄存器地址+停止信号+起始信号+器件地址读+应答+(读数据+非应答)*N+停止信号。。 注意:读操作中第二个起始信号前可发停止位也可不发。
3正点原子的I2C驱动程序
3.1 编写思想
3.1.1 模块接口
3.1.2 状态机
3.1.3 读写数据时序
时序逻辑:cnt计数器1/3时刻更新scl,0时刻更新sda,2时刻采集数据
由时序图可知:写器件地址时期和读数据因为多了起始信号,比传输地址、写数据位多四个计数周期
3.2 代码
module i2c_dri
#(
parameter SLAVE_ADDR = 7'b1010000 ,
parameter CLK_FREQ = 26'd50_000_000,
parameter I2C_FREQ = 18'd250_000
)
(
input clk ,
input rst_n ,
input i2c_exec ,
input bit_ctrl ,
input i2c_rh_wl ,
input [15:0] i2c_addr ,
input [ 7:0] i2c_data_w ,
output reg [ 7:0] i2c_data_r ,
output reg i2c_done ,
output reg i2c_ack ,
output reg scl ,
inout sda ,
output reg dri_clk
);
localparam st_idle = 8'b0000_0001;
localparam st_sladdr = 8'b0000_0010;
localparam st_addr16 = 8'b0000_0100;
localparam st_addr8 = 8'b0000_1000;
localparam st_data_wr = 8'b0001_0000;
localparam st_addr_rd = 8'b0010_0000;
localparam st_data_rd = 8'b0100_0000;
localparam st_stop = 8'b1000_0000;
reg sda_dir ;
reg sda_out ;
reg st_done ;
reg wr_flag ;
reg [ 6:0] cnt ;
reg [ 7:0] cur_state ;
reg [ 7:0] next_state;
reg [15:0] addr_t ;
reg [ 7:0] data_r ;
reg [ 7:0] data_wr_t ;
reg [ 9:0] clk_cnt ;
wire sda_in ;
wire [8:0] clk_divide ;
assign sda = sda_dir ? sda_out : 1'bz;
assign sda_in = sda ;
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle: begin
if(i2c_exec) begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done) begin
if(bit_ctrl)
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin
if(st_done) begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8: begin
if(st_done) begin
if(wr_flag==1'b0)
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr: begin
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd: begin
if(st_done) begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd: begin
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default: next_state= st_idle;
endcase
end
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r<= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin
wr_flag <= i2c_rh_wl ;
addr_t <= i2c_addr ;
data_wr_t <= i2c_data_w;
i2c_ack <= 1'b0;
end
end
st_sladdr: begin
case(cnt)
7'd1 : sda_out <= 1'b0;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0;
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15];
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7];
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin
case(cnt)
7'd0: begin
sda_out <= data_wr_t[7];
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr_rd: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1;
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
st_stop: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1;
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1;
end
default : ;
endcase
end
endcase
end
end
endmodule
|