STC89C52不支持I2C总线,因此这里是使用软件模拟IIC总线
I2C串行总线的组成和工作原理
I2C的组成
I2C总线可以挂载多个器件,每个器件有唯一的地址。
采用主从方式,主机(单片机)负责主动联系从机,从机(其他外设)被动发送数据
通常是单主机系统。如果存在不止一个单片机芯片,就是多主机系统,这种情况下可能会发生冲突,需要I2C仲裁
I2C总线通过上拉电阻连接电源。
当总线空闲时,两根线均为高电平。连到总线上的任一器件输出低电平都会使总线信号为低电平,即各器件的SDA(数据线)和SCL(时钟线)都是线与关系。
SCL始终由单片机控制,SDA则不是
I2C的工作原理
数据位的有效性规定
SCL高电平期间,SDA信号不能发生改变;SCL低电平期间,SDA信号可以变化
I2C的起始和终止信号
SCL高电平期间,SDA由高变低表示起始信号
SCL高电平期间,SDA由低变高表示终止信号
起始和终止信号均有主机发出。起始信号产生后,总线处于占用状态,直至终止信号出现
I2C字节的传送与应答
每个字节必须是8位。先传送最高位,每个被传送的字节后面必须跟随一位应答位(即一帧有9位)
主机在发送数据时,每发送一字节数据,都要读取从机应答位。当从机空闲时,从机会发出应答位(第9位为0),否则发出非应答(应答位为1),此时主机应发出终止信号、停止发送数据。
主机在接收数据时,收到最后一个字节后,必须向从机发出一个非应答信号。之后从机释放SDA,允许主机产生终止信号。
数据帧格式
I2C总线传送的信号既有数据信号,又有地址信号。
在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向(R/T),0表示主机发送数据(T),1表示主机接收数据(R)。
每次数据传送均以主机产生的终止信号结束。若主机希望继续进行数据传送,可不产生终止信号,再次发出起始信号和地址信号。
主机发送数据
- 阴影部分是主机发送,非阴影部分是从机发送
- S表示起始信号
- A表示应答
- $\overline{A}$表示非应答
- P表示终止信号
主机接收数据
主机发送转接收
总线寻址
D1~D7为从机地址,D0表示传送方向,0表示主机发送数据,1表示主机接收数据
主机发送地址时,每个从机都会将地址码和自己的地址进行比较,若相同则认为自己被选中,并根据最后一位确定自己是否为发送方或接收方。
从机的地址由固定部分和可编程部分组成。固定部分由芯片厂家给出,见于datasheet。可编程部分的位数决定了该I2C总线最多可挂载的相同从机数量,如3位可编程部分则最多挂载$2^3=8$个相同从机
软件模拟I2C通信时序
I2C总线的数据传输有严格的时序要求。
初始信号程序:
void I2cStart(){
SCL = 1;
SDA = 1;
delay5us();
SDA = 0;
delay5us();
}
终止信号程序:
void I2cStop(){
SCL = 0;
SDA = 0;
SCL = 1;
delay5us();
SDA = 1;
delay5us();
}
主机读从机应答程序:
bit ReadACK()
{
SCL = 0;
SCL = 1;
delay5us();
if(SDA)//NOACK
{
SCL = 0;
return(1);
}else{//ACK
SCL = 0;
return(0);
}
}
主机发送应答:
void SendACK(bit i){
SCL = 0;
if(i)
SDA = 1;
else
SDA = 0;
SCL = 1;
delay5us();
SCL = 0;
SDA = 1;//释放数据线
}
与AT24C02进行通信
下面通过软件模拟IIC、与AT24C02 EEPROM模块进行通信。
以下是原理图
#include "Header/IIC.h"
#define At24c02ADDR 0xA0
#define I2cRead 1
#define I2cWrite 0
sbit dula = P2^6;//段选控制
sbit wela = P2^7;//位选控制
sbit SCL = P2^1;//IIC时钟线
sbit SDA = P2^0;//IIC数据线
unsigned char num = 0;//动态显示的数字,限定为3位
bit At24c02AckFlag;//At24c02应答标志位
//段选编码表
unsigned char code table_dula[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
//毫秒级延时函数定义
void delay(unsigned int z)
{
unsigned int x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
//位选编码表
unsigned char code table_wela[]={
0xfe,0xfd,0xfb
};
//动态显示3位数字
void display(unsigned char num){
static unsigned char i;//位选控制位
if(i > 2)//重置位选控制位
i = 0;
//位选
wela = 1;
P0 = table_wela[i];
wela = 0;
//段选
switch(i){
case 0:dula = 1; P0 = table_dula[num/100]; dula = 0; break;//百位
case 1:dula = 1; P0 = table_dula[(num % 100)/10]; dula = 0; break;//十位
case 2:dula = 1; P0 = table_dula[num%10]; dula = 0; break;//个位
}
i++;
}
//初始化定时器T0,以进行动态显示
void timing0_Init(){
EA = 1;//开总中断
ET0 = 1;//允许计时器中断
TR0 = 1;//启动计时
TMOD = 0x01;//设置定时器0为工作方式1:16位
//设置计数初值
TH0 = 0xED;
TL0 = 0xff;//定时5ms
}
//T0中断函数
void timing0() interrupt 1
{
//重置计数初值
TH0 = 0xED;
TL0 = 0xff;
display(num);
}
//延时5us
void delay5us()
{
_nop_();//执行一次_nop_可延时1us,进入该函数需要3点几机器周期,因此整体延时5us左右
}
//IIC起始信号
void I2cStart()
{
SCL = 1;
SDA = 1;
delay5us();
SDA = 0;
delay5us();
}
//IIC终止信号
void I2cStop()
{
SCL = 0;
SDA = 0;
SCL = 1;
delay5us();
SDA = 1;
delay5us();
}
//主机读从机应答
bit ReadACK()
{
SCL = 0;
SCL = 1;
delay5us();
if(SDA)//NOACK
{
SCL = 0;
return(1);
}else{//ACK
SCL = 0;
return(0);
}
}
//主机发送应答
void SendACK(bit i){
SCL = 0;
if(i)
SDA = 1;
else
SDA = 0;
SCL = 1;
delay5us();
SCL = 0;
SDA = 1;//释放数据线
}
//发送一个字节
void I2cSendByte(unsigned char dat)
{
unsigned char i;
for(i = 0;i < 8;i++)
{
SCL = 0;
if(dat & 0x80)//判断最高位是否为1
SDA = 1;
else
SDA = 0;
SCL = 1;
dat <<= 1;//左移一位
}
SCL = 0;
SDA = 1;//释放数据总线
}
//接收一个字节
unsigned char I2cReadByte()
{
unsigned char i,dat;
for(i = 0;i < 8;i++)
{
dat <<= 1;//左移一位
SCL = 0;//从机将一位数据发到SDA上
SCL = 1;//保持
if(SDA)
dat |= 0x01;
}
return dat;
}
//向AT24C02写入数据
void At24c02Write(unsigned char addr,unsigned char dat)
{
I2cStart();//起始信号
I2cSendByte(At24c02ADDR + I2cWrite);//找到At24c02
if(ReadACK())//At24c02是否应答
At24c02AckFlag = 1;//应答
else
At24c02AckFlag = 0;//无应答
I2cSendByte(addr);//找到存储单元
if(ReadACK())//At24c02是否应答
At24c02AckFlag = 1;//应答
else
At24c02AckFlag = 0;//无应答
I2cSendByte(dat);//发送数据
if(ReadACK())//At24c02是否应答
At24c02AckFlag = 1;//应答
else
At24c02AckFlag = 0;//无应答
I2cStop();
}
//读AT24C02中的数据
unsigned char At24c02Read(unsigned char addr)
{
unsigned char dat;//读出的数据
I2cStart();//起始信号
I2cSendByte(At24c02ADDR + I2cWrite);//找到At24c02
if(ReadACK())//At24c02是否应答
At24c02AckFlag = 1;//应答
else
At24c02AckFlag = 0;//无应答
I2cSendByte(addr);//找到存储单元
ReadACK();
I2cStart();
I2cSendByte(At24c02ADDR + I2cRead);//改变方向
if(ReadACK())//At24c02是否应答
At24c02AckFlag = 1;//应答
else
At24c02AckFlag = 0;//无应答
dat = I2cReadByte();
SendACK(1);//发送非应答
I2cStop();
return dat;
}
//测试
void test()
{
timing0_Init();
EA = 0;//屏蔽中断
At24c02Write(2,9);//向第二个存储单元写入9
delay(1);//延时1ms,等待At24c02处理完成
num = At24c02Read(2);//读出数据并在数码管上显示
EA = 1;
while(1);
}