I2C总线是PHLIPS公司推出的一种串行总线,I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
每个接到I2C总线上的器件都有唯一的地址。主机与其它器件间的数据传送可以是由主机发送数据到其它器件,这时主机即为发送器。由总线上接收数据的器件则为接收器。
24C020的地址可以人为改变,只需改变A2,A1,A0的接线接到GND或VCC即可
由此原理图可以看出
此24C020的地址 读地址 0xaf,写地址0xae
I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。
IIC总线协议规定,没传送一个字节数据后,都要有一个应答信号以确定数据传送是否被对方收到。应答信号由接受设备产生,在SCL为高电平期间,接受设备将SDA拉低为低电平,表示数据传输正确,产生应答
数据位的有效性规定
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
要掌握IIC的通信协议,需要掌握以下6个通信信号:
1. 起始信号
voidIIC_start()
{
SDA = 1;
SCL = 1;
delay_us(1);
SDA = 0;
delay_us(1);
SCL =0; //方便下次使用
}
2. 终止信号
voidIIC_stop()
{
SDA = 0;
SCL = 1;
delay_us(1);
SDA = 1;
delay_us(1);
SCL = 0;
}
3.写数据
void IIC_senddat(unsigned char dat)
{
unsigned char i;
//SCL= 0;
for(i= 0;i<8;i++)
{
if((dat << i) & 0x80)
SDA = 1;
else
SDA = 0;
SCL= 1;
delay_us(1);
SCL= 0;
delay_us(1);
}
SDA= 1;
_nop_();
_nop_();
SCL= 1;
delay_us(1);
if(SDA== 1)
ack = 0; //表示无应答
else
ack = 1;
SCL= 0;
delay_us(10);
}
4.读数据
unsigned char IIC_revdat()
{
unsigned char i;
unsignedchar temp = 0;
SDA= 1;
for(i= 0;i < 8; i++)
{
SCL = 0;
delay_us(1);//通知被读器件变化数据
SCL= 1;
delay_us(1);
temp<<= 1;
if(1== SDA)
temp= temp + 1;
}
SCL= 0;
delay_us(10);
returntemp;
}
5.应答信号
void IICACK()
{
SDA=0;
SCL=1;
delay_us(1);
SCL=0;
}
6.非应答信号
void IICNOACK()
{
SDA=1;
SCL=1;
delay_us(1);
SCL=0;
}
//devaddr 表示芯片的地址 0xae写,0xaf读, wordaddr24c02内部地址 num写的个数
unsigned char IIC_SendStr(unsigned chardevaddr, unsigned char wordaddr,
unsigned char *s ,unsigned char num)
{
unsigned char i;
IIC_start(); //start()信号
IIC_senddat(devaddr); //发送器件地址
if(ack== 0) return 0; //发送无应答
IIC_senddat(wordaddr); //发送要写的器件内部存储地址(0-255个字节)
if(ack== 0) return 0; //发送无应答
for(i= 0; i < num; i++) //开始发送数据
{
IIC_senddat(*s);
if(ack== 0) return 0; //发送无应答
s++;
}
IIC_stop(); //停止信号
return1; //发送成功
}
unsigned char IIC_RevStr(unsigned char devaddr, unsignedchar wordaddr,
unsigned char *s ,unsigned char num)
{
unsigned char i;
IIC_start(); //开始信号
IIC_senddat(devaddr); //发送器件写地址
if(ack== 0) return 0; //发送无应答
IIC_senddat(wordaddr); //发送要开始读的器件内部存储地址(0-255个字节)
if(ack== 0) return 0; //发送无应答
IIC_start(); //开始信号
IIC_senddat(devaddr+1); //发送器件读地址
if(ack== 0) return 0; //发送无应答
for(i = 0; i < num-1; i++) //每读一次数据,要发送一次应答信号,向从器件表示
{ //接收到数据
*s = IIC_revdat(); //但最后一次读取数据不能返回应答信号,表示读取结束
IICACK(); //所以这里是num-1,最后一次在循环外,接受完返
s++; //回非应答信号
}
*s= IIC_revdat();
IICNOACK(); //读完最后一个数据,发送非应答信号
IIC_stop(); //发送停止信号
return1; //发送成功
}
注意事项
1.IIC时序的重要特点是在时钟SCK为高电平期间SDA作出各种变化来表示起始,终止,应答,非应答,发送数据时是在时钟为高电平期间让SDA稳定,读数据是在SCK为高电平期间采样从设备的数据,所以起始,终止,应答,非应答,发送数据都要先操作SDA,再操作SCK,如果先操作SCK,则会导致起始,终止,应答,非应答,发送数有干扰。
2.2.接收数据为读SDA线的数据,不会有这个问题。
3.3.IIC时序的数据传送从高位开始
4.很多人会遇到这样一个现象:连续从EEPROM读取数据,第一个读出来的是对的,往后读出来的都是0,原因是当读取第一个字节时,主机给从机的应答信号将SDA拉低,即SDA线被钳住,从从机读取数据时,SDA给出的高电平无法将SDA线拉高,导致读出来的数据都为0,所以在读取一个新的字节时,一定要将SDA拉高,才可以正常接收EEPROM的数据。
应用举例:
#include "bsp.h"
void main()
{
uchar write[10] = {0,1,2,3,4,5,6,7,8,9};
ucharread[10] = {0};
ucharlcd[10] = {0};
uchari = 0;
bsp_init(); //LCD1602显示屏初始化
IIC_SendStr(0xae,0,write,10); //把write数组里面的数据写到AT24C02里面第0个字节
delay_ms(100); //开始的地方去
IIC_RevStr(0xae,0,read,10); //把数据都出来
for(i = 0; i < 10; i++)
{
lcd[i] = read[i] + 0x30; // 把int型转化为char型,方便在LCD1602液晶屏}
} // 上显示出来
LCDwritestring(0,0,lcd); //送到液晶屏上显示
while(1);
PCF8591ADC芯片.c
--------------------
#include "bsp.h"
#define ADRESSWR 0x90
#define ADRESSRD 0x91
extern char ack;
uchar ReadAdc(uchar ch)
{
uchar val;
IIC_start();
IIC_senddat(ADRESSWR);
if(ack ==0) return 0;
IIC_senddat(0x40|ch); //control byte
if(ack ==0) return 0;
IIC_start();
IIC_senddat(ADRESSRD); //读取
if(ack ==0) return 0;
val=IIC_revdat();
IICNOACK();
IIC_stop();
return val;
}
uchar SetDac(uchar val)
{
IIC_start();
IIC_senddat(ADRESSWR);
if(ack ==0) return 0;
IIC_senddat(0x40); //control byte
if(ack ==0) return 0;
IIC_senddat(val);
if(ack ==0) return 0;
IIC_stop();
return 1;
}
#include "bsp.h"
void main()
{
uchar write[10] ={22,10,35};
uchar read[10] = {0};
uchar lcd[10] = {0};
uchar i = 0;
uchar val;
uchar valshow[16];
uchar brthled = 0;
bsp_init();
while(1)
{
val =ReadAdc(0); //读取通道0的AD值
sprintf(valshow,"v%2d",(int)val);
LCDwritestring(0,1,valshow);
SetDac(val);
sprintf(valshow,"b%2d",(int)brthled);
LCDwritestring(11,1,valshow);
}