目录
一、常见的串行通信协议
UART 异步通信(一条数据线输入,一条数据线输出)
IIC 同步串行方式通信(一条时钟线SCL,一条数据线SDA)
SPI 同步串行方式通信(一条时钟线,一条数据输入线,一条数据输出线)
二、IIC通信工作原理
IIC总线上可以挂多个器件,每个器件有唯一的地址,从而标识通信目标(从机通过对比自己的地址与主机发送的是否相符,以确定自己是否为发射的对象)
数据通信方式 :主从方式 主机主动取得联系从机,从机被动回应数据
三、相关规定
- 数据位的有效性规定 :SCL高电平时,数据线上数据必须保持稳定,只有SCL为低电平,SDA状态才能发生变化(数据在时钟线SCL的上升沿到来之前就需要准备好,并在下降沿到来之前必须保持稳定)
- IIC的起始与终止信号:SCL为高电平时,SDA由高->低 为起始信号 (总线被占用) SDA由低->高 为终止信号 (总线空闲)
- IIC字节的传送与应答:每个字节8位数 数据传送先传送最高位 每个字节后必须跟随一位应答位(即8+1 9位)
- 应答位为0 成功接收数据 应答位为1 表示来不及接收数据 注意:数据有效性与IIC的起始终止信号不冲突,正是由于SCL为高时SDA电平发生变化,才会被视作为起始与终止信号而不是有效的数据。即为了数据有效,SDA不能发生变化 (即起始与终止信号不受数据有效性约束)
注意要点:SDA使用完后要将其拉高释放,否则低电平时总线被占用,不可继续使用,无论是否设置为高电平都会被拉低成低电平。(SDA与SCL两条线均为线与的关系【即这两条线上诸多器件中任一器件拉低电平,该条线将会保持低电平】)
四、应答位
1、从机发送应答位给主机,来不及接收主机数据时发出非应答,主机则终止数据传输;而从机空闲时,从机发出应答(数据第九位为0),即主机根据从机发出的应答位判断从机是否成功接收数据(主机发送数据时)(检测应答)
2、主机收到最后一个数据后,向从机发送结束传送的信号(对从机发送非应答,过程中会将SDA拉低),然后从机释放SDA线(防止SDA拉低以后主机无法将其拉高),从而允许主机产生终止信号 (主线接收数据时)(发送应答)
其次,主机在发送应答后,也要释放SDA线,防止将其拉低后影响从机后续的数据传送
五、数据帧格式
IIC总线上传送的数据是广义的,即包括地址信号,又包括真正的数据信号。
规定:在起始信号后必须传送一个从机的地址,从机地址占7位,第八位是数据的传送方向位(0表示主机发送数据,1为主机接收数据)
六、具体组合形式
1、主机发送数据(且整个过程都只发送数据)
2、主机接收数据(且整个过程中都只接收数据)见下图
3、主机先发送数据再接收数据 见下图
注意看清主体是从机还是主机
整体思路
注意:主机发送数据时,如果是正常发送数据,无需等待从机非应答信号,直接产生终止信号即可。
七、总线寻址(上述所说的七位地址)
IIC总线协议规定7位寻址
1、寻址字节的位定义:D7~D1为组成从机的地址 D0为数据传送方向位(“0"为主机向从机写数据 ”1“为主机向从机读数据)
2、从机地址由固定部分与可编程部分组成,可编程部分决定了系统中能够接入的最大从机个数 (例如3位可编程位,表示最多8个从机接入)
3、主机发送地址时,总线上每个从机都将其与自己的地址相比较,以确定自己是否为发送器或接收器
八、软件模拟IIC通信时序
器件地址 A0 (由于固定位1010 且E0 E1 E2均接地为0 —-> 1010000) [从机地址]
串行EEPROM典型产品:AT24C系列器件 AT24C02(256字节) 读写图示意如下
九、代码实现
#include <reg52.h>
#include <intrins.h>
#define AT24c02ADDR 0xA0
#define I2cRead 1
#define I2cWrite 0
#define uchar unsigned char
sbit SCL = P2^1;
sbit SDA = P2^0;
sbit LA=P2^2;
sbit LB=P2^3;
sbit LC=P2^4; //数码管段选
unsigned char smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
bit ACKFlag; //由于只有0和1两种状态,设为bite型
void delay(unsigned int z)
{
unsigned int x,y;
for(x=z;x>0;x--)
for(y=120;y>0;y--);
}
//延时5us
void delay5us()
{
_nop_();
}
void timefrist()
{
EA = 1; //打开总开关
ET0 = 1; //中断函数0的开关
TR0 = 1; //打开定时器0开关
TMOD = 0x01; //模式一,定时模式
TH0 = 0xED; //这里改值了,到上限就5ms(改初值)
TL0 = 0xFF;
//开关、模式、赋初值
}
unsigned char miao=0;
void DigDisplay(unsigned char h)
{
unsigned char a=h%10; //个位
unsigned char b=h/10; //十位
static unsigned char wei=0; //静态变量,就是函数执行完也不会抹去这个变量的值,在次使用函数值可以用(只有初值时才会初始的,后来不改变)
switch(wei) //这里重新布局,我们这里不断交换显示,用一次函数就显示一个,当快速多显示就动态显示
{
case 0: LA=1;LB=1;LC=1;P0 = smgduan[b];break; //wei值0与1不断交换,就动态显示
case 1: LA=0;LB=1;LC=1;P0 = smgduan[a];break;
}
wei++; //用过之后++
if(wei==2) //我们只要1与0不断交换,为2时就重新回0
{
wei = 0;
}
}
//起始信号
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) //非应答
{
SCL=0; //拉低时钟线,后续数据才能允许变化
return (1);
} else { //应答
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(uchar DAT)
{
uchar i;
for(i=0;i<8;i++)
{
SCL=0; //时钟线为0时才允许数据发生变化
if(DAT & 0x80) //通过与运算可以将最高位取出来赋值给SDA
SDA=1;
else
SDA=0;
SCL=1; //拉高以后ATC02开始从SDA读数据
DAT<<=1; //DAT不断左移可以将数字逐渐移到最高位取出
}
SCL=0;
SDA=1; //释放数据总线
}
//接收字节函数
uchar I2cReadByte()
{
uchar i,DAT=0x00; //从SDA中读出数据并将其存入DAT中
for(i=0;i<8;i++)
{
DAT<<=1; //由于我们最开始得到的是最右边一位的数,但是我们先得到为最高位,所以不断左移到最高位
SCL = 0; //SCL拉低才允许SDA变化(从机将数据置于SDA线)
SCL = 1; //SCL置高以后从机已经将数据置于SDA线上
if(SDA)
DAT |= 0x01;
}
return(DAT);
}
//写数据函数(主机发送数据)
void AT24C02Write(uchar ADDR,DAT)
{
I2cStart();
I2cSendByte(0xA0 + 0); //I2C发送一个字节
if(ReadACK())
ACKFlag=1; //非应答
else
ACKFlag=0; //应答
I2cSendByte(ADDR); //I2C发送写入的地址
if(ReadACK())
ACKFlag=1; //非应答
else
ACKFlag=0; //应答
I2cSendByte(DAT); //I2C发送发送数据
if(ReadACK())
ACKFlag=1; //非应答
else
ACKFlag=0; //应答
I2cStop(); //终止信号
}
//读数据函数(主机接收数据)
uchar At24c02Read(uchar ADDR)
{
uchar DAT;
I2cStart();
I2cSendByte(0xA0 + 0); //发送从机地址 (发送)
if(ReadACK()) //主机检测应答(从机发送应答)
ACKFlag=1; //非应答
else
ACKFlag=0; //应答
I2cSendByte(ADDR);
ReadACK(); //无论应答还是非应答 下面均会主机发起始信号(无所谓应答或非应答)
I2cStart();
I2cSendByte(0xA0 + 1); //主机发送从机地址 (接收)
if(ReadACK()) //主机检测应答(从机发送应答)
ACKFlag=1; //非应答
else
ACKFlag=0; //应答
DAT = I2cReadByte(); //DAT用于存放从从机读到的数据
SendACK(1); //主机发送非应答
I2cStop();
return(DAT);
}
void main()
{
timefrist(); //定时器0初始化
EA=0; //屏蔽中断(关总开关)
AT24C02Write(2,9); //主机写数据至第二个单元
delay(1); //由于主机正处理上述事件,会产生非应答,所以让其延时一下
miao = At24c02Read(2); //主机从第二个单元读数据
EA=1; //打开中断开关
while(1);
}
//定时器中断函数
void timer0() interrupt 1
{
TH0 = 0xED; //重新定义5ms初始值
TL0 = 0xFF;
DigDisplay(miao); //显示,不断5ms显示就是动态显示
}