一. 技术性能:
工作速率有100K和400K两种;
支持多机通讯;
支持多主控模块,但同一时刻只允许有一个主控;
由数据线SDA和时钟SCL构成的串行总线;
每个电路和模块都有唯一的地址;
每个器件可以使用独立电源
二. 基本工作原理:
以启动信号START来掌管总线,以停止信号STOP来释放总线;
每次通讯以START开始,以STOP结束;
启动信号START后紧接着发送一个地址字节,其中7位为被控器件的地址码,一位为读/写控制位R/W,R./W位为0表示由主控向被控器件写数据,R/W为1表示由主控向被控器件读数据;
当被控器件检测到收到的地址与自己的地址相同时,在第9个时钟期间反馈应答信号;
每个数据字节在传送时都是高位(MSB)在前;
写通讯过程:
1.主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2.发送一个地址字节(包括7位地址码和一位R/W);
3.当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
4.主控收到ACK后开始发送第一个数据字节;
5.被控器收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束;
6.主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;
读通讯过程:
1.主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2.发送一个地址字节(包括7位地址码和一位R/W);
3.当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
4.主控收到ACK后释放数据总线,开始接收第一个数据字节;
5.主控收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;
6.主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;
四. 总线信号时序分析
1.总线空闲状态
SDA和SCL两条信号线都处于高电平,即总线上所有的器件都释放总线,两条信号线各自的上拉电阻把电平拉高;
2.启动信号START
时钟信号SCL保持高电平,数据信号SDA的电平被拉低(即负跳变)。启动信号必须是跳变信号,而且在建立该信号前必修保证总线处于空闲状态;
3.停止信号STOP
时钟信号SCL保持高电平,数据线被释放,使得SDA返回高电平(即正跳变),停止信号也必须是跳变信号。
4.数据传送
SCL线呈现高电平期间,SDA线上的电平必须保持稳定,低电平表示0(此时的线电压为地电压),高电平表示1(此时的电压由元器件的VDD决定)。只有在SCL线为低电平期间,SDA上的电平允许变化。
5.应答信号ACK
I2C总线的数据都是以字节(8位)的方式传送的,发送器件每发送一个字节之后,在时钟的第9个脉冲期间释放数据总线,由接收器发送一个ACK(把数据总线的电平拉低)来表示数据成功接收。
6.无应答信号NACK
在时钟的第9个脉冲期间发送器释放数据总线,接收器不拉低数据总线表示一个NACK,NACK有两种用途:
a.一般表示接收器未成功接收数据字节;
b.当接收器是主控器时,它收到最后一个字节后,应发送一个NACK信号,以通知被控发送器结束数据发送,并释放总线,以便主控接收器发送一个停止信号STOP。
五. 寻址约定
地址的分配方法有两种:
1.含CPU的智能器件,地址由软件初始化时定义,但不能与其它的器件有冲突;
2.不含CPU的非智能器件,由厂家在器件内部固化,不可改变。
高7位为地址码,其分为两部分:
1.高4位属于固定地址不可改变,由厂家固化的统一地址;
2.低三位为引脚设定地址,可以由外部引脚来设定(并非所有器件都可以设定);
范例程序:
#include
#include
#define uchar unsignedchar //宏定义
#defineuint unsignedint
extern void Delay1us(unsigned char);
sbitSDA=P1^6;//模拟I2C数据传送位
sbitSCL=P1^7;//模拟I2C时钟控制位
bitack;//应答标志位
起动总线函数
函数原型:voidStart_I2c();
功能:启动I2C总线,即发送I2C起始条件.
void Start_I2c()
{
SDA=1;//发送起始条件的数据信号
Delay1us(1);
SCL=1;
Delay1us(5);//起始条件建立时间大于4.7us,延时
SDA=0;//发送起始信号
Delay1us(5);//起始条件锁定时间大于4μs
SCL=0;//钳住I2C总线,准备发送或接收数据
Delay1us(2);
}
结束总线函数
函数原型:voidStop_I2c();
功能:结束I2C总线,即发送I2C结束条件.
void Stop_I2c()
{
SDA=0;//发送结束条件的数据信号
Delay1us(1);//发送结束条件的时钟信号
SCL=1;//结束条件建立时间大于4us
Delay1us(5);
SDA=1;//发送I2C总线结束信号
Delay1us(4);
}
字节数据发送函数
函数原型:void SendByte(ucharc);
功能:将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
voidSendByte(uchar c)
{
uchar BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++)//要传送的数据长度为8位
{
if((c<<*判断发送位*>
elseSDA=0;
Delay1us(1);
SCL=1;//置时钟线为高,通知被控器开始接收数据位
Delay1us(5);//保证时钟高电平周期大于4μs
SCL=0;
}
Delay1us(2);
SDA=1;//8位发送完后释放数据线,准备接收应答位
Delay1us(2);
SCL=1;
Delay1us(3);
if(SDA==1)ack=0;
elseack=1;//判断是否接收到应答信号,由从机返回
SCL=0;
Delay1us(2);
}
字节数据接收函数
函数原型:ucharRcvByte();
功能:用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数应答从机。
ucharRcvByte()
{
uchar retc;
uchar BitCnt;
retc=0;
SDA=1;//置数据线为输入方式
for(BitCnt=0;BitCnt<8;BitCnt++)
{
Delay1us(1);
SCL=0;//置时钟线为低,准备接收数据位
Delay1us(5);//时钟低电平周期大于4.7μs
SCL=1;//置时 钟线为高使数据线上数据有效
Delay1us(3);
retc=retc<<1;
if(SDA==1)retc=retc+1;//读数据位,接收的数据位放入retc中
Delay1us(2);
}
SCL=0;
Delay1us(2);
return(retc);
}
应答子函数 (用于主机读取数据后,应答从机)
函数原型:void Ack_I2c(bit a);
功能:主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
void Ack_I2c(bit a)
{
if(a==0)SDA=0;//在此发出应答或非应答信号
else SDA=1;
Delay1us(3);
SCL=1;
Delay1us(5);//时钟低电平周期大于4μs
SCL=0;//清时钟线,钳住I2C总线以便继续接收
Delay1us(2);
}
向有子地址器件发送多字节数据函数
函数原型:bit ISendStr(uchar sla,ucharsuba,ucahr *s,uchar no);
功能:从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件
地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。
如果返回1表示操作成功,否则操作有误。
注意:使用前必须已结束总线。
bit ISendStr(uchar sla,uchar suba,uchar*s,uchar no)
{
uchar i;
Start_I2c();//启动总线
SendByte(sla);//发送器件地址
if(ack==0)return(0);
SendByte(suba);//发送器件子地址
if(ack==0)return(0);
for(i=0;i< FONT>
{
SendByte(*s);//发送数据
if(ack==0)return(0);// 检测从机发送过来的应答位,ack由sendbyte函数返回
s++;
}
Stop_I2c();//结束总线
return(1);
}
向有子地址器件读取多字节数据函数
函数原型:bit ISendStr(uchar sla,ucharsuba,ucahr *s,uchar no);
功能:从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件
地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。
如果返回1表示操作成功,否则操作有误。
注意:使用前必须已结束总线。
bit IRcvStr(uchar sla,uchar suba,uchar*s,uchar no)
{
uchar i;
Start_I2c();//启动总线
SendByte(sla);//发送器件地址
if(ack==0)return(0);
SendByte(suba);//发送器件子地址
if(ack==0)return(0);
Start_I2c();//重新启动总线
SendByte(sla+1);
if(ack==0)return(0);
for(i=0;i< FONT>
{
*s=RcvByte();//接收数据
Ack_I2c(0);//读完一个数据,就发送一个应答位
s++;
}
*s=RcvByte();
Ack_I2c(1);//全部读完,发送非应位
Stop_I2c();//结束总线
return(1);
}