I2C(Intel-Integrated Circuit)总线是由Phliphs公司开发的一种两线式串行总线,用于连接外围设备和微控制器。应用非常广泛,可以用在连接E2ROM来保存各种接口卡、显示器上的ROM信息 ,也可以用在BMC上来连接各种温度、电压、电流等传感器,还可以用来连接内存条DIMM上,用来获取内存条的信息。Smbus兼容i2c,虽然信号引脚定义上有出入,但从软件的角度上基本上可以视作I2C,I2C的应用几乎不要任何修改就可以运行在Smbus上。


硬件上I2C只有两个信号线,数据线SDA和时钟SCL,通过两者不同的相位组合表示不同的帧,起始帧、地址数据帧、结束帧构成一次I2C传输,具体的时序图请见下面的示意图:
 I2C从驱动到应用(上篇)_第1张图片

由上面的图可以看到,当SCL电平为高时,SDA的下降沿表示一次I2C传输的开始S,SDA的上升沿表示一次I2C传输的结束P。在SCL每个时钟周期的高电平时,按序采样SDA的值,依次得到B1/B2..../B3,作为链路上传输的原始数据。链路上传输的原始数据可以分为三大类:1.逻辑地址;2逻辑数据;3.确认字符。其中逻辑地址是由发起I2C传输的设备指定的,逻辑地址的最低一位,指定了数据的传输方向:为1表示数据从I2C设备上读回到CPU,为0表示数据从CPU写到i2C设备。逻辑数据可以是CPU发送到I2c设备上的数据,也可能是从I2C设备读回的数据。确认字符表示I2C从设备返回给主设备的确认帧。 一次I2C传输可以完成读写一个或多个byte,这就省去了每传输一个byte都需要重新确认的时间,连续读写多个byte的时序图如下:
 I2C从驱动到应用(上篇)_第2张图片除了上述的这中简单读写的方式外,还有下面的组合传输访问方式:
 S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P
上面各个符号的定义如下:
S     (1 bit) : Start bit
P     (1 bit) : Stop bit
Rd/Wr (1 bit) : Read/Write bit. Rd equals 1, Wr equals 0.
A, NA (1 bit) : Accept and reverse accept bit.
Addr  (7 bits): I2C 7 bit address. Note that this can be expanded as usual to
                get a 10 bit I2C address.
Comm  (8 bits): Command byte, a data byte which often selects a register on
                the device.
Data  (8 bits): A plain data byte. Sometimes, I write DataLow, DataHigh
                for 16 bit data.
Count (8 bits): A data byte containing the length of a block operation.
[..]: Data sent by I2C device, as opposed to data sent by the host adapter.

理解了i2c基本时序后,我们就把握了i2c控制器的工作原理。基于这点,在需要连接i2c设备但主机上又没有i2c控制器的情况下,可以用两根GPIO线分别来模拟SDA和SCL,只要遵从上面的i2c时序,就能模拟出i2c控制器的行为,进而控制i2c设备。


根据上面的时序图,我们还可以看到i2c的地址位有7个bit(bit7~bit1),最低一位(bit0)是读写位。前面7个bit 来自i2c设备的物理地址。物理地址的bit7~bit4代表设备类型,由设备厂家固定好,不同类型的编码对应不同的group,统一由http://dlnware.com/theory/I2C-Address-Allocation-Table约定, 例如下面的Group9 和 Group10:
 I2C从驱动到应用(上篇)_第3张图片

物理地址的bit3~bit1是地址线字段,对应i2c的SAD,由硬件通过上下拉电阻固定,用来区别连接到同一个i2c控制器上的不同设备。由于地址线字段只有3个bit,所以同一个 i2c控制器上最多只能连接8个i2c设备。以BIOS内存自检时读取SPD的i2c控制器为例,每个DIMM都连到了CPU的i2c控制器上去了,如下图所示,不同的DIMM上的SA0/SA1/SA2信号上下拉不一样:

I2C从驱动到应用(上篇)_第4张图片 
因为DIMM属于group10,因此上面的两个DIMM对应的i2c地址分别是:0xa0,0xa2。
如果需要连接更多的设备,要用到i2c switch。比如在需要检测多个温度传感器、电压传感器的BMC里面,就经常用到i2c switch。如下面图所示,需要用i2c switch拓展i2c上挂载的设备:

 

I2C从驱动到应用(上篇)_第5张图片和i2c控制器直接访问i2c设备相比,跨过i2c桥访问i2c设备多的一个步骤就是:在start condition之后,发送桥后面的一个从设备的地址和待写到从设备控制寄存器的内容到i2c bus上去。从设备的地址需要根据实际使用的i2c switch芯片来确定,如果swtich芯片是PCA9543A,它后面可以扩展出两路i2c通道,从设备的地址格式如下:
 I2C从驱动到应用(上篇)_第6张图片

其中A0/A1就是来选择使能哪一路通道的。而控制寄存器中的内容需要参考芯片手册来确定。比如对PCA9543A,实现的定义如下:

I2C从驱动到应用(上篇)_第7张图片

一旦完成上述两步操作,桥片后的设备和i2c控制器之间的链路就建立起来了,用户可以像直接访问i2c设备一样访问桥片后的设备。当然,不同的i2c设备的访问方式由于设备类型、厂家实现会略有不同。有的i2c设备要求把读写的起始地址发送到i2c总线上去,而且这个偏移地址只能占用一个byte,此时如果它的存储容量超过了2**8=256byte,那么这个设备可能有多个i2c 从设备地址。当然,如果偏移地址允许两个byte,只要访问的地址偏移不超过2**16byte即可。此外,由于芯片实现的差异,有的i2c设备对连续读写的起始地址限制在芯片内页或扇区的起始地址。但总的说来,只要掌握了i2c的基本原理,同时能够注意到具体板卡和芯片的差异,我们就能够实现对i2c设备的控制和管理。