I2C总线是PHLIPS公司推出的一种串行总线,它只有两根双向信号线。一根是数据线SDA(serial data I/o),另一根是时钟总线SCL(serial clock)。
如下图所示,I2C总线上可以挂接多个器件,而每个器件都有唯一的地址,这样可以标识通信目标。数据的通信方式采用主从方式,主机负责主动联系从机,而从机则被动回应数据。
I2C总线的搭建
因为任何带有I2C硬件接口的器件都是开漏I型I/O口,不具备输出高电平的能力,所以2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连接到总线上的任一器件输出的低电平都将使总线的信号变低,即各器件的SDA及SCL都是“线与”关系,如下图所示。
I2C总线传输协议
在I2C总线通信的过程中,参与通信的双方互相之间所传输的信息种类归纳如下。
主机向从机发送的信息种类有:启动信号、停止信号、7位地址码、读/写控制位、10位地址码、数据字节、重启动信号、应答信号、时钟脉冲。
从机向主机发送的信息种类有:应答信号、数据字节、时钟低电平。
1、传送数据的有效性规定
SCL为高电平期间,数据线SDA上的数据必须保持稳定,只有当SCL信号为低电平期间,SDA状态才允许变化。在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。
时序图如下图所示
I2C的起始信号与终止信号
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号,它标志着一次数据传输的开始。SCL为高电平期间,SDA线由低电平向高电平的变化表示终止信号,它标志着传输数据结束。
时序图如下图所示
I2C的字节传送与应答
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一个应答位(即一帧共有9位)。应答位即主机向从机发送数据时,
时序图如下图所示
应答位(ACK)的作用
I2C总线上的所有数据都是以8位字节传送的,主机每发送一个字节,就在时钟脉冲9期间释放数据线,由从机反馈一个应答信号。
主机在发送数据时,每次发送一字节的数据,都需要读取从机的应答位,当从机空闲可以接收该字节数据时,从机会发出应答(一帧数据的第9位为“0”),当从机正忙于其它工作的处理来不及接收主机发送的数据时,从机会发出非应答(一帧数据的第9位为“1”),主机则应发出终止信号以结束数据的继续传送,主机通过从机发出的应答位来判断从机是否成功接收数据,对于反馈有效应答位ACK的要求是,从机在第9个时钟脉冲之前的低电平期间,将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。
当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号,这个信号是由对从机的“非应答”来实现的,然后,从机释放SDA线,以允许主机产生终止信号。
插入等待时间
如果从机需要延时下一个数据字节开始传送的时间,则可以通过把时钟总线SCL电平拉低并且保持,使主机进入等待状态,如果从机释放时钟总线,数据就可以被继续传输下去,这样就使得从机得到足够的时间转移已收到的数据,或者准备好即将发送的数据。
I2C写数据流程
如上图所示,S代表START ,首先主机先发送一个起始信号,然后接着发送7位器件地址+第八位数据传送方向位0/1(“0”代表主机发送数据,“1”代表主机接收数据),发送完地址后,等待从机应答(灰色为主机所发送的,白色为从机负责发送的),当从机应答后,主机会发送向从机写入数据的首地址,从机应答后,主机即向从机发送数据,每发送完一位,等待一从机一次应答,数据传输完成后,主机发送一个停止信号即完成了数据的传输。
时钟总线为高电平期间,数据可以被读取,当SCL为低电平期间,可以向数据总线SDA线上写数据。
I2C读数据流程
主机线发送一个起始信号,然后发送一个从机地址代表选择读取哪个从机的数据,读写方向为写(因为是给从机发送地址信号),然后等待从机应答,应答后,主机选择要从从机的的哪个内存地址开始读,然后不管从机是否应答,主机即开始再次发送一个起始信号,然后接着发送从机地址,读写方向为读,然后从机应答后开始向主机发送数据,发送完成后主机发送一个非应答信号和停止信号,即完成了I2C读数据的流程。
下面即为软件模拟I2C时序
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
#define slave_address 0x00 //假设从机地址位0x00
sbit SDA = P2^0; //假设P2.0和P2.1为I2C的数据总线与时钟总线
sbit SCL = P2^1;
/******************************
** 函数名称:
** 函数功能:软件延时
*******************************/
void delay(uchar i)
{
uchar j,k;
for(j=i;j>0;j--)
for(k=125;k>0;k--);
}
/******************************
** 函数名称:
** 函数功能:I2C初始化
*******************************/
void I2C_Init()//I2C初始化,使总线处于空闲状态
{
SCL = 1; //拉高SDA、SCL,使总线处于空闲状态
_nop_();
SDA = 1;
_nop_();
}
/******************************
** 函数名称:
** 函数功能:I2C起始信号
*******************************/
void I2C_Start() //SCL为高电平期间,SDA负跳变1->0
{
SCL = 1;
_nop_();
SDA = 1;
delay(5);
SDA = 0;
delay(5);
}
/******************************
** 函数名称:
** 函数功能:I2C终止信号
*******************************/
void I2C_Stop() //SCL为高电平期间,SDA正跳变 0->1
{
SDA = 0;
_nop_();
SCL = 1;
delay(5);
SDA = 1;
delay(5);
}
/******************************
** 函数名称:
** 函数功能:主机发送应答
*******************************/
void Master_ACK (bit i) //主机发送应答
{
SCL = 0; //拉低时钟总线允许SDA数据总线变化
_nop_(); //让总线稳定
if(i) //若果i等于1,则拉低时钟线,表示主机应答
{
SDA = 0; //表示主机应答
}
else
{
SDA = 1; //发送非应答
}
_nop_(); //让总线稳定
SCL = 1; //拉高时钟总线,让从机从SDA数据线上读走主机的应答信号
delay(5);
SCL = 0; //拉低时钟总线,占用总线,使双方继续通信
_nop_();
SDA = 1; //释放数据总线,交由从机控制
_nop_();
}
/******************************
** 函数名称:
** 函数功能:应答检测
*******************************/
bit Test_ACK()
{
SCL = 1; //SCL时钟总线为高电平时才能读到SDA数据线上的数据
delay(5);
if(SDA)
{
SCL = 0;
_nop_();
I2C_Stop();
return(0); //如果为非应答返回0
}
else
{
SCL = 0;
_nop_();
return(1);
}
}
/******************************
** 函数名称:
** 函数功能:发送一个字节
*******************************/
void I2C_send_byte(uchar byte)
{
uchar i;
for(i = 0 ; i < 8 ; i++)
{
SCL = 0;
_nop_();
if(byte & 0x80)
{
SDA = 1;
_nop_();
}
else
{
SDA = 0;
_nop_();
}
SCL = 1;
_nop_();
byte <<= 1;
}
SCL = 0; //占用时钟总线
_nop_();
SDA = 1; //释放数据总线
_nop_();
}
/******************************
** 函数名称:
** 函数功能:i2c读一个字节
*******************************/
uchar I2C_read_byte()
{
uchar I2C_data,i;
SCL = 0;
_nop_();
SDA = 0;
for(i = 0 ; i < 8 ; i++)
{
SCL = 1;
_nop_();
I2C_data <<= 1; //读完最后一位数据时不会再次移位
if(SDA)
{
I2C_data = I2C_data | 0x01;
}
else
{
I2C_data = I2C_data & 0xfe;
}
_nop_();
SCL = 0;
_nop_();
}
return(I2C_data);
}
/******************************
** 函数名称:
** 函数功能:i2c发送数据
*******************************/
bit I2C_transmit_data(uchar ADDR,DAT)
{
I2C_Start(); //SCL为高电平期间SDA负跳变
I2C_send_byte(slave_address + 0); //发送从机地址
if(!Test_ACK) //检测应答,如果为非应答Test_ACK则返回0,则条件成立
{
return(0);
}
I2C_send_byte(ADDR);//发送往从机的写地址
if(!Test_ACK) //如果为非应答
{
return(0);
}
I2C_send_byte(DAT);//传送8位数据,一位一位传送,最高位先开始
if(!Test_ACK) //检测应答,如果为非应答
{
return(0);
}
I2C_Stop(); //停止信号,SCL为高电平期间,SDA正跳变
return(1);
}
/******************************
** 函数名称:
** 函数功能:i2c读数据
*******************************/
uchar I2C_receive_data(uchar ADDR)
{
uchar DAT;
I2C_Start();
I2C_send_byte(slave_address + 0);
if(!Test_ACK) //如果为非应答
{
return 0;
}
I2C_send_byte(ADDR);
if(!Test_ACK) //如果为非应答
{
return 0;
}
Master_ACK(0);
I2C_send_byte(slave_address + 1);
if(!Test_ACK) //如果为非应答
{
return 0;
}
DAT = I2C_read_byte();
Master_ACK(0);
I2C_Stop();
return(DAT);
}
void main()
{
I2C_Init(); //初始化,使总线保持空闲状态
I2C_transmit_data(255,0xf0);
delay(5); //软件延时,为了使能够完整的读取出数据
I2C_receive_data(255);
while(1);
}