I2C即Inter-Integrated Circuit(集成电路总线),它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代设计出来的一种简单、双向、二线制总线标准。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。
I2C是指一个能够支持多个设备的总线,其中包含了一条双向串行数据线SDA,一条串行时钟线SCL。每个连接到主线的设备都有独立的地址,主机通过相应的地址来访问相应的设备。主机通过发送设备(器件)地址来寻找到从机,然后通过相应指令对从机进行读操作或写操作。
I2C数据传输的速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s,各种被控器件均并联在总线上,通过器件地址(SLAVE_ADDR,具体可查看器件手册)识别。
在I2C器件与主机通信之前,串行时钟线SCL和串行数据线SDA线由于上拉而处于高电平状态,这表示I2C总线目前处于空闲状态。
如果主机想要开始传输数据时,就会产生一个起始位信号,而从机检测到起始信号后,将准备接收数据,待数据传输结束后主机又要产生一个停止位信号,通知从机数据传输结束了。在起始位信号发送之前,总线是处于空闲状态的;而在起始位信号之后到停止位信号发送之前的这段时间,主机可以向从机写数据也可以读取其输出的数据;最后产生停止位信号,总线回归至空闲状态。
起始位
: SCL为高电平
时,SDA电平从高到低
变化
停止位
: SCL为高电平
时,SDA电平从低到高
变化
整个I2C通信过程中,前8个时钟周期为数据传输过程,第8个周期末到第9个周期末是应答期。
在整个过程中,SDA在数据传输过程中受传送器件控制,而在应答期中受接收器件控制,因此SDA总线是三态门
(inout
)。
当SCL为低电平
时,SDA进行变化
。
当SCL为高电平
时,SDA数据锁存
。
应答期是第9个时钟周期,指第8个周期末到第9个周期末:
1.第8个周期末,主机(传送器件)释放SDA以让从机(接收器件)应答。
2.第9个周期中,从机(接收器件)控制SDA拉低以相应主机(传送器件),其中SCL为高电平时,如果SDA没有被检测到低电平,则视为非应答,表明此次数据传输失败。
3.第9个周期末,从机(接收器件)释放SDA以让主机(传送器件)继续传输数据,如果主机发送停止位信号,则传输结束。
每个I2C器件都有一个器件地址,有的2C器件的器件地址是固定的(出厂时设置好了),例如OV7670
的器件地址为固定的0x42
,而还有的I2C器件的器件地址是由一个固定部分和一个可编程的部分构成,例如常见的E2PROM
存储器,其中就留有3个控制的引脚,这三个引脚可以联接到GND
或VCC
来设置不同的编程地址,达到了I2C总线上挂载多个E2PROM器件而增加了系统E2PROM容量
的效果。
因此,主机第一步不是向从机发送地址,而是向总线上发送地址,所有的从机接收到主机发送的地址进行比较,若从机匹配上就会向总线发送一个响应信号;主机收到响应信号后,才开始向总线发送数据,这样就达成了与从机的通信。如果主机没收到响应信号,也就代表此次寻址失败。
进行数据传输时,主机发送起始位信号后,按照从高到低的位序发送器件地址,一般为7bit。
第8bit位为读写控制位R/W,该位为0时表示主机对从机进行写操作,当该位为1时表示主机对从机进行读操作。
一般器件地址格式如下图所示:
一般来说,每个支持I2C协议的器件,内部总会有一些可供读写的寄存器或者存储器,例如E2PROM存储器,内部是一系列顺序编址的内存单元。因此,我们还要向I2C器件内的存储单元(寄存器或者存储器)进行读写操作时,就需要先制定存储单元的地址,然后才能够向该地址写入数据或者读取其内容。
存储单元地址的字节大小一般为一个或两个字节,其受I2C器件的影响,例如同为E2PROM存储器,AT24C02的地址长度仅用了一位字节,而AT24C04的地址长度为9位bit(其中他占用了器件地址的A0位),而AT24C64的地址长度为两个字节(器件地址中的A2,A1,A0位不够用了,才增加一个字节),这是因为AT24C64的存储单元数目超过了一个字节和器件地址中的三位bit(A2,A1,A0位)所表示的最大值(2^8 * 2^3 == 2K),所以需要用两个字节来表示。
以下为AT24C64的字地址示意图:
主机在发送存储单元地址后,从机会在应答后将内部的存储单元地址指向主机所发的单元。如果读写控制R/W位为0
即写命令,从机就处于等待接收数据的状态。此时,主机就开始写数据了。写数据分为单字节写
(对于EEPROM而言,称为字节写)和连续写
(对于EEPROM而言,称为页写)。
不管单字节写和连续写,都可概括为: start+[器件地址,写命令]+字地址+数据+stop
发送完一字节数据后发送结束信号。
发送完一字节数据后继续发送下一字节数据,最后发送的是结束信号。
主机发送完字地址,从机正确应答后就把内部的存储单元地址指针指向该单元。如果读写控制位R/W位为“1”即读命令,主机就处于接收数据的状态,从机从该地址单元输出数据。读数据分为当前地址读
、单字节读
和连续读
。
不管是单字节读还是连续读,都可以概括为:start +[器件地址,写命令(0)] + 字地址 + start + [器件地址,读命令(1)] + 接收从机的数据 + 主机非应答(1) + stop
为什么会先进行没有发送数据的单次写操作
,然后再进行读操作呢?这是因为我们需要使从机内的存储单元地址指针
指向我们想要读取的存储单元地址处,所以首先发送了一次Dummy Write也就是虚写操作,之所以称为虚写,是因为我们并不是真的要写数据,而是通过这种虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,就可以按当前地址读的方式读数据了,所以读数据可以理解为:没有发送数据的单次写操作 + 当前地址的读操作
。
读取完一字节数据后,主机发送非应答信号。
读取完一字节数据后主机发送应答信号,继续读取下一字节数据,最后主机发送非应答信号。
源码:
module i2c_test(
input clk,
input rst_n,
output reg i2c_scl,
inout i2c_sda
);
//sda三态门
module iic_test(
//System Clock
input clk,
input rst_n,
//Wr/Rd signal
input [1:0] Rd_Wr_Sig,
input [7:0] Word_Addr,
input [6:0] Device_Addr,
//Wr/Rd Data
input [7:0] Wr_Data,
output [7:0] Rd_Data,
output Done_Sig,
//I2c Two Line
output Scl,
inout Sda
);
assign Rd_Data = Rd_Data_r;
assign Done_Sig = Done;
//F100K时钟产生
parameter F100K = 9'd500;
//I2C双总线
reg Scl_r;
reg Sda_Dir;
reg Sda_r;
assign Scl = Scl_r;
assign Sda = (Sda_Dir)? Sda_r : 1'bz;
//I2C数据传输过程
reg [7:0] Wr_Data_r;
reg [7:0] Rd_Data_r;
reg [4:0] Step;
reg [4:0] Step_Next;
reg [9:0] Count_SCL;
reg Ack_r;
reg Done;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
Wr_Data_r <= 8'd0;
Rd_Data_r <= 8'd0;
Step <= 5'd0;
Step_Next <= 5'd0;
Ack_r <= 1'b1;
Done <= 1'b0;
Count_SCL <= 9'd0;
Scl_r <= 1'b1;
Sda_r <= 1'b1;
Sda_Dir <= 1'b1;
end
else if(Rd_Wr_Sig[0])begin //写数据
case(Step)
0:begin //Start
Sda_Dir <= 1; //Sda Output
if(Count_SCL == 0) Scl_r <= 1'b1;
else if(Count_SCL == 400) Scl_r <= 1'b0;
if(Count_SCL == 0) Sda_r <= 1'b1;
else if(Count_SCL == 200) Sda_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
1:begin //Device_Wr
Wr_Data_r <= {Device_Addr , 1'b0}; //Device Write
Step <= 5'd7; //Turn to Send Bit
Step_Next <= Step + 1'b1;
end
2:begin //Word
Wr_Data_r <= Word_Addr; //Word Addr
Step <= 5'd7; //Turn to Send Bit
Step_Next <= Step + 1'b1;
end
3:begin //Data
Wr_Data_r <= Wr_Data; //Write Data
Step <= 5'd7; //Turn to Send Bit
Step_Next <= Step + 1'b1;
end
4:begin //Stop
Sda_Dir <= 1; //Sda Output
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
if(Count_SCL == 0) Sda_r <= 1'b0;
else if(Count_SCL == 300) Sda_r <= 1'b1;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
5:begin //Done
Done <= 1'b1;
Step <= Step + 1'b1;
end
6:begin //return
Done <= 1'b0;
Step <= 5'd0;
end
//Send Bit
7,8,9,10,11,12,13,14:begin
Sda_Dir <= 1; //Sda Output
Sda_r <= Wr_Data_r[5'd14 - Step]; //High Bit First
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
else if(Count_SCL == 300) Scl_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
15:begin //Receive Ack
Sda_Dir <= 0; //Sda Input
if(Count_SCL == 100) Ack_r <= Sda;
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
else if(Count_SCL == 300) Scl_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
16:begin //Ack Check
if(Ack_r != 1'b0) Step <= 5'd0;
else Step <= Step_Next;
end
endcase
end
else if(Rd_Wr_Sig[1])begin //读数据
case(Step)
0:begin //Start
Sda_Dir <= 1; //Sda Output
if(Count_SCL == 0) Scl_r <= 1'b1;
else if(Count_SCL == 400) Scl_r <= 1'b0;
if(Count_SCL == 0) Sda_r <= 1'b1;
else if(Count_SCL == 200) Sda_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
1:begin //Device_Wr
Wr_Data_r <= {Device_Addr, 1'b0}; //Device Addr(Write)
Step <= 5'd9;
Step_Next <= Step + 1'b1;
end
2:begin //Word
Wr_Data_r <= Word_Addr; //Words Addr
Step <= 5'd9;
Step_Next <= Step + 1'b1;
end
3:begin //Start again
Sda_Dir <= 1'b1; //Sda Output
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
else if(Count_SCL == 500) Scl_r <= 1'b0;
if(Count_SCL == 0) Sda_r <= 1'b0;
else if(Count_SCL == 100) Sda_r <= 1'b1;
else if(Count_SCL == 300) Sda_r <= 1'b0;
if(Count_SCL == 600 - 1)begin
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else
Count_SCL <= Count_SCL + 1'b1;
end
4:begin //Device_Rd
Wr_Data_r <= {Device_Addr, 1'b1}; //Device Addr(Read)
Step <= 5'd9;
Step_Next <= Step + 1'b1;
end
5:begin //Read Data
Rd_Data_r <= 8'd0;
Step <= 5'd19;
Step_Next <= Step + 1'b1;
end
6:begin //Stop
Sda_Dir <= 1; //Sda Output
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
if(Count_SCL == 0) Sda_r <= 1'b0;
else if(Count_SCL == 300) Sda_r <= 1'b1;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
7:begin //Done
Done <= 1'b1;
Step <= Step + 1'b1;
end
8:begin //return
Done <= 1'b0;
Step <= 5'b0;
end
//Send Bit
9,10,11,12,13,14,15,16:begin
Sda_Dir <= 1; //Sda Output
Sda_r <= Wr_Data_r[5'd16 - Step]; //High Bit First
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
else if(Count_SCL == 300) Scl_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
17:begin //Receive Ack
Sda_Dir <= 0; //Sda Input
if(Count_SCL == 200) Ack_r <= Sda;
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
else if(Count_SCL == 300) Scl_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
18:begin //Ack Check
if(Ack_r != 1'b0) Step <= 5'd0;
else Step <= Step_Next;
end
//Read Data
19,20,21,22,23,24,25,26:begin
Sda_Dir <= 0; //Sda Input
if(Count_SCL == 200) Rd_Data_r[5'd26 - Step] <= Sda; //High Bit First
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
else if(Count_SCL == 300) Scl_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step + 1'b1;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
27:begin //Send NoAck
Sda_Dir <= 1; //Sda Output
if(Count_SCL == 0) Scl_r <= 1'b0;
else if(Count_SCL == 100) Scl_r <= 1'b1;
else if(Count_SCL == 300) Scl_r <= 1'b0;
if(Count_SCL == F100K-1)begin //100K
Count_SCL <= 9'd0;
Step <= Step_Next;
end
else //++
Count_SCL <= Count_SCL + 1'b1;
end
endcase
end
end
endmodule
endmodule
1.维基百科-I²C
2.协议——IIC - 咸鱼FPGA - 博客园
3.黑金Altera开发板Verilog实例教程
4.I²C_NXP数据手册