IIC总线EEPROM
IIC总线是PHL IPS公司推出的一种串行总线,只有两根双向信号线:
数据线SDA(serial data I/O)和时钟线SCL(serial clock)
IIC总线上可以挂多个器件,每个器件都有唯一的地址,这样可以识别通信目标。数据的通信的方式采用主从方式,主机负责主动联系从机,从机被动回应数据。
在多主机系统,可以同时有几个主机企图启动总线发送数据,为了避免混乱,IIC总线需要通过仲裁,以决定哪台主机控制总线。
IIC总线通过上拉电阻接正电源,总线空闲时,两根线均为高电平,连接到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA,SCL都是线“与”关系。
IIC总线传输协议
SCL为高,SDA上的数据必须保持稳定;SCL位低,SDA状态才允许变化
SCL为高电平期间,SDA由高到低表示起始信号,SDA由低到高为终止信号。
起始信号,终止信号都是由主机发送的,在起始信号产生后,总线处于被占用状态,终止信号产生,总线处于空闲状态。
IIC字节的传送与应答
每个字节必须保证8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面必须跟一个应答位(即一帧共9位)
应答位的作用:当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号,这个信号是由对从机的“非应答”来实现的,然后,从机释放SDA线,以允许主机产生终止信号。
数据帧的传送格式
IIC总线上传送的数据信号是广义的,即包括地址信号,有包括真正的数据信号。在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T)0:表示发送数据(R),1:表示接收数据(T),每次数据传送总是由主机产生的终止信号结束,若主机希望继续占用总线进行新数据的传送,则可以不产生终止信号,马上再发出起始信号对另一从机进行寻址。
在总线的一次数据传送过程中,可以有一下几种组合方式
1.主机向从机发送数据,数据传送方向在整个传送过程中不变:
注:有阴影部分表示数据有主机向从机发送,无阴影表示数据由从机向主机传送。
A:应答 S:起始信号 P:终止信号
2.主机在第一个字节后,立即从从机读数据
3.在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反。
总线的寻址
IIC总线协议有明确的规定:采用7位的寻址字节(寻址字节是起始信号后的第一个字节)。
寻址字节的位定义
D7~D1位组成从机的地址。D0位是数据传送方向位。
主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址码进行比较,如果相同,则认为自己被主机寻址,根据R/T位确定自己为发送器或接收器。
从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该器件的最大数目。
如果一个从机的7位寻址位有4位是固定的,3位是可编程的,这时能寻址8个同样的器件,即可以有8个相同的器件接入到该IIC总线系统中。
AT20C02芯片
芯片手册查询网站:www.alldatasheet.com
软件模拟IIC通信时序
为保证数据的可靠性,IIC总线的数据传送有严格的时序要求。
iic.c
#include <iic.h>
/*
SCL为高,SDA上的数据必须保持稳定
SCL为低,SDA状态才允许变化
*/
void IIC_Start()//起始信号
{
//时钟总线为高电平期间数据总线由高变低产生起始信号
SCL = 1;
SDA = 1;
somenop;
SDA = 0;
somenop;
SCL = 0;
}
void IIC_Stop()//终止信号
{
//时钟总线为高电平期间数据总线由低变高产生终止信号
SDA = 0;
SCL = 1;
somenop;
SDA = 1;
}
void IIC_Ack(bit i)//主机发送应答
{
if(i)//发送非应答
SDA = 0;
else//发送应答
SDA = 1;
somenop;
SCL = 1;//拉高时钟总线,让从机读SDA
somenop;//保持4.7us以上
SCL = 0;//拉低时钟总线,允许SDA释放
SDA = 1;//释放数据总线
somenop;
}
bit IIC_WaitAck()//主机读从机应答
{
SDA = 1;//拉高,读SDA
somenop;
SCL = 1;
somenop;
if(SDA) //非应答
{
SCL = 0;
IIC_Stop();
return 0;
}
else //应答
{
SCL = 0;
return 1;
}
}
void IIC_SendByte(uchar dat)//用过IIC总线发送一个字节数据
{
uchar i;
for(i = 0; i < 8; i++)
{
if(dat & 0x80)//IIC总线传送从最高位开始
SDA = 1;
else
SDA = 0;
somenop;
SCL = 1;//拉高时钟总线,让从机读SDA
dat <<= 1;
somenop;
SCL = 0;
}
}
uchar IIC_RecByte()//从IIC总线上接收数据
{
uchar i,dat;
for(i = 0; i < 8; i++)
{
SCL = 1;//拉高时钟总线,读取SDA上的数据
somenop;
dat <<= 1;
if(SDA)
dat |= 0x01;
SCL = 0;//允许SDA发生变化
somenop;
}
return dat;
}
at24c02.c
#include <iic.h>
void Write_AT24C02(uchar addr,dat)
{
IIC_Start();
IIC_SendByte(0XA0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_Stop();
}
uchar Read_AT24C02(uchar addr)
{
uchar dat;
IIC_Start();
IIC_SendByte(0XA0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0XA1);
IIC_WaitAck();
dat = IIC_RecByte();
IIC_Ack(0);
IIC_Stop();
return dat;
}
iic.h
#ifndef _IIC_H_
#define _IIC_H_
#include <STC15F2K60S2.H>
#include <intrins.h>
sbit SDA = P2^1;
sbit SCL = P2^0;
#define somenop {_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();};
#define uchar unsigned char
//iic.c
void IIC_Start();
void IIC_Stop();
void IIC_Ack(bit i);
bit IIC_WaitAck();
void IIC_SendByte(unsigned char dat);
unsigned char IIC_RecByte();
//at24c02.c
void Write_AT24C02(unsigned char addr,dat);
unsigned char Read_AT24C02(unsigned char addr);
#endif