1.I2C介绍
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线。
两根通信线:SCL(Serial Clock)SDA(Serial Data)
通信特点:同步、半双工,带数据应答Ack
2.常见通信接口比较
2.1相关术语介绍
全双工:通信双方在同一时刻互相传送数据。
半双工:通信双方互相双送信息,但是不能同时进行而是分时复用同一根线。
单工:通信从一方发送到另一方,不能反向。
异步:通信双方各自约定通信速率。
同步:通信双方靠一根时钟线约定速率。
总线:连接各个设备的数据传输路线。
3.I2C电路规范
(1)I2C总线使用连接设备的SCL(串行时钟总线)和SDA(串行数据总线)来控制发送数据和读数据。
(2)设备的SCL和SDA均要配置成开漏输出模式。
(3)SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右。
(4)开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
4.I2C通信发送数据时序结构
发送一帧数据步骤:
- 开始条件:S
- 发送地址:SLAVE ADDRESS+W/R 写地址或者读地址(发送完地址要加应答位)
- 发送数据:BYTE1.BYTE2........BYTEn(发送完一字节数据要加应答位)
- 停止条件: P
4.1I2C标准时序图
主机在 SCL 线上输出串行时钟信号,数据在 SDA 线上进行传输,每传输一个字节(最高位 MSB 开始传输)后面跟随一个应答位Ack,一个 SCL 时钟脉冲传输一个数据位。
4.2开始和终止条件
当总线上的主机都不驱动总线,总线进入空闲状态, SCL 和 SDA 都为高电平。
起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
//I2C开始
//参数:无
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
//I2C停止
//参数:无
void I2C_Stop(void)
{
I2C_SDA=0;//停止之前是有数据的
I2C_SCL=1;
I2C_SDA=1;
}
4.3发送地址
开始条件后面的帧是地址帧(一个字节),用于指定主机通信的对象地址SLAVE ADDRESS。
=
由上图可以看出地址帧的前七位是从机的地址,最后一位决定往从机里面R写数据还是W读数据。
第八位0,写数据。第八位1读数据。
S:代表开始
SLAVE ADDRESS:代表着发送从机地址
RA:为0,代表从机应答。
4.4.发送数据
4.4.1发送一个字节(接受一个字节)
//I2C发送一个字节
//参数:Byte
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}
//I2C接受一个字节
//参数:无
//接收到的字节数据
unsigned char I2C_ReceiveByte()
{
unsigned char i;
unsigned char Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if( I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
4.4.2发送应答位(接受应答位)
消息中的每一帧后面都跟随一个应答⁄不应答位。如果成功接收到一个地址帧或数据帧,则从机会向主机返回一个ACK位
发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。应答是什么?是接收方向发送方传送的握手信号,表示我已经接受到您发的消息了!!
接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
//I2C发送应答
//参数Ackbit:0为应答,1为非应答
void I2C_SendAck(unsigned char Ackbit )
{
I2C_SDA=Ackbit;
I2C_SCL=1;
I2C_SCL=0;
}
//I2C接受应答
//无参数
//返回值接收到的应答位
unsigned char I2C_RecieveAck(void)
{
unsigned char Ackbit;
I2C_SDA=1;
I2C_SCL=1;
Ackbit=I2C_SDA;
I2C_SCL=0;
return Ackbit;
}
5.AT24C02存储数据
5.1AT24C02介绍
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
存储介质:E2PROM
通讯接口:I2C总线
容量:256字节
5.2AT24C02引脚及应用电路
5.3AT24C02内部结构框图
简单来说,就是通过SCL和SDA接口获得数据,经过一定的逻辑,数据存储到EEPROM(通过X和Y来控制数据的存储位置),并且可以通过一定的逻辑,将数据输出出来
字节写:在“字地址”处写入“数据”DATA WORD ADDR/COUNTER-用来设置地址,内部设有存储地址的寄存器,每写入或读出一个地址,这个寄存器会自动加1,如果读出不指定地址的话,它默认把寄存器的数据拿出来。
5.4AT24C02数据帧发送和读写
AT24C02的固定地址为1010,可配置地址本开发板上为000 所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1
字节写:主机向从机的“字地址”处写入“数据”
随机读:主机写入从机地址--从机应答后--再写入字地址--读从机地址--读出在“字地址”处的“数据” --接受完以后不应答--停止。
5.3AT24C0存储数据
实验结果:将存储的数据再LCD1602上读出来
5.3.1子程序
包含AT24C02.c,AT24C02.h,I2C.c,I2C.h,Delay.cDelay.h,Key.c和Key.h以及main.c LCD1602.c和LCD1602.h
5.3.2I2C.c子程序
#include <REGX52.H>
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
//I2C开始
//参数:无
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
//I2C停止
//参数:无
void I2C_Stop(void)
{
I2C_SDA=0;//停止之前是有数据的
I2C_SCL=1;
I2C_SDA=1;
}
//I2C发送一个字节
//参数:Byte
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}
//I2C接受一个字节
//参数:无
//接收到的字节数据
unsigned char I2C_ReceiveByte()
{
unsigned char i;
unsigned char Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if( I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
//I2C发送应答
//参数Ackbit:0为应答,1为非应答
void I2C_SendAck(unsigned char Ackbit )
{
I2C_SDA=Ackbit;
I2C_SCL=1;
I2C_SCL=0;
}
//I2C接受应答
//无参数
//返回值接收到的应答位
unsigned char I2C_RecieveAck(void)
{
unsigned char Ackbit;
I2C_SDA=1;
I2C_SCL=1;
Ackbit=I2C_SDA;
I2C_SCL=0;
return Ackbit;
}
5.3.3I2C.h子程序
#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte();
void I2C_SendAck(unsigned char Ackbit );
unsigned char I2C_RecieveAck(void);
#endif
5.3.4AT24C02.c子程序
#include <REGX52.H>
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0
//主机向AT24C02写入一个字节
//参数:worldAddress要写入字节的地址
//参数:Data要写入的数据
//返回值无
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
I2C_Start();//开始
I2C_SendByte(AT24C02_ADDRESS);//发送从机地址
I2C_RecieveAck();//从机接受应答
I2C_SendByte(WordAddress);//发送字地址
I2C_RecieveAck();//从机接受应答
I2C_SendByte(Data);//发送数据
I2C_RecieveAck();//从机接受应答
I2C_Stop();//停止
}
//AT24C02读出一个字节
//参数:worldAddress要读出字节的地址
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();//开始
I2C_SendByte(AT24C02_ADDRESS);//发送从机地址
I2C_RecieveAck();//从机接收应答
I2C_SendByte(WordAddress);//发送字地址
I2C_RecieveAck();//从机接收应答
// I2C_Start();//开始
I2C_SendByte(AT24C02_ADDRESS|0x01);//主机发送从机读地址
I2C_RecieveAck();//从机接受应答
Data=I2C_ReceiveByte();//主机接收数据
I2C_SendAck(1);//主机发送给从机停止应答
I2C_Stop();//停止
return Data;
}
5.3.5AT24C02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
5.4main.c主程序
#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"
unsigned char KeyNum;
unsigned int Num;
void main()
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);
while(1)
{
KeyNum=Key();
if(KeyNum==1) //K1按键,Num自增
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2) //K2按键,Num自减
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3) //K3按键,向AT24C02写入数据
{
AT24C02_WriteByte(0,Num%256);
Delay(5);
AT24C02_WriteByte(1,Num/256);
Delay(5);
LCD_ShowString(2,1,"Write OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
if(KeyNum==4) //K4按键,从AT24C02读取数据
{
Num=AT24C02_ReadByte(0);
Num|=AT24C02_ReadByte(1)<<8;
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read OK ");
Delay(1000);
LCD_ShowString(2,1," ");
}
}
}