1. I2C总线硬件结构和术语
---- I2C串行总线有两根信号线:一根双向的数据线SDA;另一根是时钟线SCL。所有接到I2C总线上的设备的串行数据都接到总线的SDA线,各设备的时钟线SCL接到总线的SCL。典型的I2C总线结构如图1。
图1:PC总线结构
---- 为了避免总线信号的混乱,要求各设备连接到总线的输出端必须是开漏输出或集电极开路输出的结构。设备与总线的接口电路如图2所示。设备上的串行数据线SDA接口电路应该是双向的,输出电路用于向总线上发数据,输入电路用于接收总线上的数据。串行时钟线也应是双向的,作为控制总线数据传送的主机要通过SCL输出电路发送时钟信号,同时要检测总线上SCL上的电平以决定什么时候发下一个时钟脉冲电平;作为接受主机命令的从机,要按总线上的SCL的信号发出或接收SDA上的信号,也可以向SCL线发出低电平信号以延长总线时钟信号周期。总线空闲时,因各设备都是开漏输出,上拉电阻RP使ADA和SCL线都保持高电平。任一设备输出的低电平都使相应的总线信号线变低,也就是说各设备的SDA是“与”关系,SCL也是“与”关系。
图2:设备和PC总线接口电路
---- 总线对设备接口电路的制造工艺和电平都没有特殊的要求(NMOS、CMOS都可以兼容)。数据传送率按I2C总线可高达每秒十万位,高速方式可高达每秒四十万位。总线上允许连接的设备数以总线上的电容量不超过400pF为限。
---- 总线的运行(数据传输)由主机控制。所谓主机即启动数据的传送(发出启动信号),发出时钟信号,传送结束时发出停止信号的设备,通常主机是微处理器。被主机寻访的设备都称为从机。为了进行通讯,每个接到I2C总线的设备都有一个唯一的地址,以便于主机寻访。主机和从机的数据传送,可以由主机发送数据到从机,也可以是从机发到主机。凡是发送数据到总线的设备称为发送器,从总线上接收数据的设备被称为接受器。
---- I2C总线上允许连接多个微处理器及各种外围设备,如存储器、LED及LCD驱动器、A/D及D/A转换器等。为了保证数据可靠地传送,任一时刻总线只能有由某一台主机控制一个微处理器应该在总线空闲时发启动数据,为了妥善解决多台微处理器同时发启数据传送(总线控制权)的冲突,并决定由哪一台微处理器控制总线。I2C总线允许连接不同传送速率的设备,多台设备之间时钟信号的同步过程称为同步化。
----2. I2C数据传输
---- 在I2C总线传输过程中,将两种特定的情况定义为开始和停止条件(如图3):当SCL保持“高”,SDA由“高”变为“低”时为开始条件;SCL保持“高”,SDA由“低”变为“高”是为停止条件。开始和停止条件由主控器产生。使用硬件接口可以很容易地检测开始和停止条件,没有这种接口的微机必须以每时钟周期至少两次对SDA取样以使检测这种变化。
图3:总线开始/停止
---- SDA线上的数据在时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,每次传输的字节不受限制,每个字节必须有一个应答为ACK。如果一接收器件在完成其他功能(如一内部中断)前不能接收另一数据的完整字节时,它可以保持时钟线SCL为低,以促使发送器进入等待状态,当接收器械准备好接受数据的其它字节并释放时钟SCL后,数据传输继续进行。I2C数据总线传送时序如图4。
图4:总线数据传送顺序
---- 数据传送具有应答是必须的。与应答对应的时钟脉冲由主控器产生,发送器在应答期间必须下拉SDA线。当寻址的被控器件不能应答时,数据保持为高,接着主控器产生停止条件终止传输。在传输的过程中,当用到主控接收器的情况下,主控接收器必须发出一数据结束信号给被控发送器,被控发送器必须释放数据线,以允许主控器产生停止条件。合法的数据传输格式如下:
超始位 被控接收器地址 R/W 应答位 数据 应答位 、、、、 停止位
---- I2C总线在开始条件后的首字节决定哪个被控器将被主控器选择,例外的是“通用访问”地址,它可以寻址所有期间。当主控器输出一地址时,系统中的每一器件都将开始条件后的前七位地址和自己地址比较。如果相同,该器件认为自己被主控器寻址,而作为被控接收器或被控发送器则取决于R/W位。
---- 二、I2C总线的应用
---- I2C总线是各种总线中使用信号线最少,并具有自动寻址,多主机时钟同步和仲裁等功能很强的总线。因此,使用I2C设计计算机系统十分方便、灵活、体积也小,在各类实际应用中得到广泛应用。下面举两个应用示例。
----1. 伺服控制系统用I2C扩展LCD显示
---- 图5是一个伺服系统的结构图。用8XC752单片机的PWM输出经放大后驱动电机,电机的转速有测速机测取并直接送到8XC752片内A/D电路。处理后的有关信息经I2C总线送到LCD驱动芯片PCF8577以驱动六十四段LCD显示板。
图5:伺服系统结构框图
----2. 通用I/O端口作为I2C总线接口
---- 目前,51、96系列的单片机应用很广,但是它们都没有I2C总线接口,限制了在这些系统中使用具有I2C总线接口的器件。但通过对I2C总线时序的分析知道可以用51单片机的两根I/O线来实现I2C总线的功能。I2C总线规定SCL线和SDA线是各设备对应输出状态相“与”的结果,任一设备都可以用输出低电平的方法延长SCL低电平时间,迫使高速设备进入等待状态,实现不同速度设备间的时钟同步。因此,即使时钟脉冲的高、低电平时间长短不一,也能实现数据的可靠传送,可以用软件控制I/O口做I2C接口。下面就是用GMS97C2051DE的通用I/O口作为I2C总线接口由软件控制实现数据传送的例子,图6为其连线图。
I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。
1 I2C总线特点
I2C总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。I2C总线的另一个优点是,它支持多主控(multimastering),其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
2 I2C总线工作原理
2.1 总线的构成及信号类型
I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,I2C总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。CPU发出的控制信号分为地址码和控制量两部分,地址码用来选址,即接通需要控制的电路,确定控制的种类;控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。
I2C总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
目前有很多半导体集成电路上都集成了I2C接口。带有I2C接口的单片机有:CYGNAL的 C8051F0XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外围器件如存储器、监控芯片等也提供I2C接口。
3 总线基本操作
I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。参见图1。
图1 串行总线上的数据传送顺序
3.1 控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。如图2所示。
图2 控制字节配置
3.2 写操作
写操作分为字节写和页面写两种操作,对于页面写根据芯片的一次装载的字节不同有所不同。关于页面写的地址、应答和数据传送的时序参见图3。
图3 页面写
3.3 读操作读操作有三种基本操作:当前地址读、随机读和顺序读。图4给出的是顺序读的时序图。应当注意的是:最后一个读操作的第9个时钟周期不是“不关心”。为了结束读操作,主机必须在第9个周期间发出停止条件或者在第9个时钟周期内保持SDA为高电平、然后发出停止条件。
图4 顺序读
4 实例:X24C04与MCS-51单片机软硬件的实现X24C04是XICOR公司的CMOS 4096位串行EEPROM,内部组织成512×8位。16字节页面写。与MCS-51单片机接口如图5所示。由于SDA是漏极开路输出,且可以与任何数目的漏极开路或集电极开路输出“线或”(wire-Ored)连接。上拉电阻的选择可参考X24C04的数据手册。下面是通过I2C接口对X24C04进行单字节写操作的例程。流程图及源程序如下:
图5 X24C04与51单片机接口
;名称:BSENT;描述:写字节
;功能:写一个字节
;调用程序:无
;输入参数:A
;输出参数:无
BSEND: MOV R2,#08H ;1字节8位
SENDA: CLR P3.2 ;
RLC A ;左移一位
MOV P3.3,C ;写一位
SETB P3.2
DJNZ R2,SENDA ;写完8个字节?
CLR P3.2 ;应答信号
SETB P3.3
SETB P3.2
RET
图6 流程图
5 结束语在I2C总线的应用中应注意的事项总结为以下几点 :
1) 严格按照时序图的要求进行操作,
2) 若与口线上带内部上拉电阻的单片机接口连接,可以不外加上拉电阻。
3) 程序中为配合相应的传输速率,在对口线操作的指令后可用NOP指令加一定的延时。
4)为了减少意外的干扰信号将EEPROM内的数据改写可用外部写保护引脚(如果有),或者在EEPROM内部没有用的空间写入标志字,每次上电时或复位时做一次检测,判断EEPROM是否被意外改写。
#include <intrins.h>
#include <reg52.h>
#ifndef uint8
#define uint8 unsigned char
#endif
#ifndef uint16
#define uint16 unsigned int
#endif
#include <IIC.h>
#define nop2() _nop_();_nop_()
#define nop3() _nop_();_nop_();_nop_()
#define nop4() _nop_();_nop_();_nop_();_nop_()
#define nop5() _nop_();_nop_();_nop_();_nop_();_nop_()
void StartIIC()
{
SDA=1; //发送起始条件资料信号
_nop_();
SCL=1; //发送起始条件的时钟信号
nop5(); //起始时间应大于4.7us
SDA=0; //发送起始信号
nop5();
SCL=0; //钳位
nop2();
}
void StopIIC()
{
SDA=0; //发送结束条件的资料信号
_nop_();
SCL=1; //发送结束条件的时钟信号
nop5(); //结束建立时间大于4.7us
SDA=1; //发送结束命令
nop5();
}
bit SendByteIIC(uint8 senddata)
{
bit check;
uint8 BitCnt; //辅助循环变量
for(BitCnt=0;BitCnt<8;BitCnt++)
{
if((senddata<<BitCnt)&0x80) //判断发送位,即最高位
SDA=1;
else
SDA=0;
_nop_();
SCL=1; //时钟为高通知被控制器接受数据
nop5();
SCL=0;
}
nop2();
SDA=1; //释放资料线,准备接受应答位
nop2();
SCL=1;
nop3();
check=~SDA;
SCL=0;
return check;
}
void SendStrIIC(uint8 *str)
{
uint8 i;
for(i=strlen(str);i!=0;--i)
{
while(!SendByteIIC(*str));
str++;
}
}
uint8 RecByteIIC()
{
uint8 rechar; //存放接收到的数据
uint8 BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++)
{
SCL=0; //时钟线为低,准备接收
nop4();
SCL=1; //时钟为高,使数据有效
rechar = rechar<<1;
if(SDA==1)
rechar++;
nop2();
}
SCL=0;
return rechar;
}
void AckIIC(bit flag)
{
SDA=flag;
nop3();
SCL=1;
nop5();
SCL=0;
}
bit NoAddSendByte(uint8 add,uint8 senddata)
{
StartIIC(); //启动
if(SendByteIIC(add)==0) //发送器件地址
return 0;
if(SendByteIIC(senddata)==0) //发送数据
return 0;
StopIIC(); //释放总线
return 1;
}
bit AddSendStr(uint8 add,uint8 devadd,uint8 *senddata,uint8 num)
{
uint8 i;
StartIIC(); //启动总线
if(SendByteIIC(add)==0) //发送器件地址
return 0;
if(SendByteIIC(devadd)==0) //发送器件内部地址
return 0;
for(i=0;i<num;i++)
{
if(SendByte(*senddata)==0)
return 0;
senddata++;
}
StopIIC();
return 1;
}