开发板:普中51-单核-A2;
I2C器件-EEPROM:AT24C02、FM24C02(仿真EEPROM);
仿真软件:proteus;
开发环境:Keil4;
参考资料:开发板所附视频;
如有错误,感谢指正。如有侵权请联系博主。
首先需要了解I2C是什么。
I2C
是 Inter-Integrated circuit
的简称,飞利浦公司于1980年代提出,为了让主板、嵌入式系统或手机用以连接低速周边外部设备而发展。
I2C具备多主机系统需要的总线裁决、高低速器件同步等功能,是一种高性能的串行总线。
串行总线用于串行通信,串行通信速率低,在数据通信吞吐量不是很大的微处理器中比较简易、方便、灵活;
并行总线用于并行通信,并行通信速度快,实时性好,但是占用的口线多,不适于小型化产品;
更多关于总线的内容可以参考:总线的定义, 并行总线和串行总线。
I2C允许相当大的工作电压,典型的电压基准为+3.3V或+5V。
I2C总线有两根双向信号线:SDA
和SCL
,SDA是数据线,用来传送数据;SCL是时钟线,用来传输CLK时钟信号,主设备向从设备发送。
如下所示,在I2C总线上可以连接多个器件,通常这些器件中有一个是主器件,其它的是从器件。
每个接到I2C总线上的器件都有唯一的地址
,可以通过该地址识别主机想要通信的是哪个器件。
在多主机系统中,可能同时有几个主机想要主动传送数据,为了避免混乱,I2C总线通过总线仲裁,决定由哪一台主机控制总线。
80C51单片机应用系统中,通常是以80C51为主机,其它接口器件为从机的单主机情况。
常用到的I2C总线以传输速率不同分为:
I2C有如下特征:
I2C总线有两种状态:空闲态和忙态。
I2C总线通过上拉电阻接正电源,这样可以保证当总线空闲时,两根线均为高电平
。连接到总线上的任一器件输出低电平,都将使总线信号变低,各器件的SDA和SCL是线与
的关系。
起始信号和终止信号(由主机发出):
接收端将SCL拉低,可以使主机处于等待状态;接收端将SCL拉高,可以使主机工作。
起始信号产生后,总线就处于忙态(被占用);终止信号产生后,总线处于空闲态。
I2C总线上传送的数据信号既包括地址信号,又包括数据信号。
注:如下图中数据传送方向,阴影表示数据由主机向从机传送,无阴影部分表示数据由从机向主机发送,A表示应答(低电平), A ‾ \overline{A} A表示非应答(高电平),S表示起始信号,P表示终止信号。
起始信号后必须传送一个7位的从机地址+1位数据传送方向,0表示主机发送数据,1表示主机接收数据。
然后可以传送一个字节的数据,每字节数据后会收到1位应答信号,数据传送结束会收到非应答信号。
每次数据传送都是由主机产生终止信号结束。但是如果主机希望继续占用总线进行新的数据传送,可以不产生终止信号,马上再次发出起始信号对另一个从机寻址。
总线的一次数据传送中,可以有以下几种组合方式:
I2C协议规定,采用7位的寻址字节,寻址字节的位定义:
D7~D1是从机地址,D0表示数据传送方向,0表示主机向从机写数据,1表示主机由从机读数据。
从机的地址由固定部分和可编程部分组成。
主机可以采用不带I2C总线接口的单片机,使用软件实现I2C总线的数据传送,即软件和硬件结合的信号模拟。
为保证数据传送的可靠性,标准的I2C总线的数据传送有严格的时序要求。如下。
起始信号需要SCL&SDA保持高电平>4.7us后,SDA由高电平到低电平>4us;
终止信号需要TSCL=H&SCL=L >4us,TSCL=H&SDA=L->H>4/7us;
应答信号需要TSCL=H&SDA=0>4us;
非应答信号需要TSCL=H&SDA=H > 4us;
单片机写
操作时,首先发送该器件的7位地址码和1位写方向位0
(共1个字节,8位),发送完后释放SDA线(拉高SDA),并在SCL线上产生第9个时钟信号,从器件产生应答信号作为确认是自己的地址,单片机收到应答后就可以传送数据了。
传送数据时,单片机首先发送一个字节的被写入器件的存储区的首地址,收到应答后,主机逐个发送各数据字节,每发送一个字节都要等待应答。
当写入的数据传送完后,主机发出终止信号以结束写入操作.
写入n个字节的数据格式如下:
单片机读
操作时,首先发送该器件的7位地址码和1位写方向位0
(共1个字节,8位),发送完后释放SDA线(拉高SDA),并在SCL线上产生第9个时钟信号,从器件产生应答信号作为确认是自己的地址。
然后再发送要读出器件的存储区的首地址,收到应答后,主机要重复一次起始信号并发出器件地址和读方向位,收到器件应答后就可以读出数据字节。
没读出一个字节,主机都要回复应答信号,当最后一个字节数据读完,主机返回非应答信号,并发出终止信号以结束读数据操作。
板子上的EEPROM是 AT24C02,其原理图如下:
实现功能:
原理图如下:
main.c
:
#include "reg52.h"
#include "i2c.h"
typedef unsigned char u8;
typedef unsigned int u16;
sbit K1 = P3^1; // K1 保存显示的数据
sbit K2 = P3^0; // K2 读取保存的数据
sbit K3 = P3^2; // K3 累加读取的数据
sbit K4 = P3^3; // K4 清零
// 38 译码器
sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;
// 数码管段选数据
u8 code smgduan[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // 显示0~9的值
char num=0,disp[4];
// 延时函数
void delay(u16 i)
{
while(i--);
}
// 按键处理函数
void Keypros()
{
if(K1==0) // 按键按下
{
delay(1000);
if(K1==0) // 按键按下
{
AT24C02Write(1,num); // 写入数据
}
while(!K1); // 独立按键是否松开
}
if(K2==0) // 按键按下
{
delay(1000);
if(K2==0) // 按键按下
{
num = (char)AT24C02Read(1); // 读取数据
}
while(!K2); // 独立按键是否松开
}
if(K3==0) // 按键按下
{
delay(1000);
if(K3==0) // 按键按下
{
num++; // 累加
if(num > 255)
num=0;
}
while(!K3); // 独立按键是否松开
}
if(K4==0) // 按键按下
{
delay(1000);
if(K4==0) // 按键按下
{
num=0; // 清零
}
while(!K4); // 独立按键是否松开
}
}
void datapros()
{
// 4~0分别存储个、十、百、千位
disp[0] = smgduan[num/1000];
disp[1] = smgduan[num%1000/100];
disp[2] = smgduan[num%1000%100/10];
disp[3] = smgduan[num%1000%100%10];
}
// 数码管显示
void DigDisplay()
{
u8 i = 0;
for(i=0; i<4; i++){
switch(i){
case 0:
LSA = 1;LSB = 1; LSC = 1; break; //显示第0位
case 1:
LSA = 0;LSB = 1; LSC = 1; break; //显示第1位
case 2:
LSA = 1;LSB = 0; LSC = 1; break; //显示第2位
case 3:
LSA = 0;LSB = 0; LSC = 1; break; //显示第3位
}
P0 = disp[i]; // 发送段码
delay(100); // 间隔一段时间扫描
P0 = 0x00; //消隐
}
}
void main()
{
while(1)
{
Keypros();
datapros();
DigDisplay();
}
}
i2c.h
:
#ifndef _I2C_H
#define _I2C_H
#include
// 定义时钟线和数据线
sbit SCL = P2^1;
sbit SDA = P2^0;
void I2CStart();
void I2CStop();
unsigned char I2CWriteByte(unsigned char dat);
unsigned char I2CReadByte();
void AT24C02Write(unsigned char addr,unsigned char dat);
unsigned char AT24C02Read(unsigned char addr);
#endif
i2c.c
:
#include "i2c.h"
// 单片机管脚模拟时许
// 延时10us
void Delay10us(void)
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=2;a>0;a--);
}
// 模拟起始信号
void I2CStart()
{
// SCL=H,SCL=H->L,delay4.7us
SDA = 1;
Delay10us();
SCL=1; // SDA/SCL = H
Delay10us();
SDA=0; // SDA H->L
Delay10us();
SCL=0; // SCL=L时,数据可以改变
Delay10us();
}
// 模拟终止信号
void I2CStop()
{
// SCL=H,SCL=L->H,delay4.7us
SDA = 0;
Delay10us();
SCL=1;
Delay10us();
SDA=1; // SDA L->H
Delay10us();
}
// 模拟发送数据
unsigned char I2CWriteByte(unsigned char dat)
{
unsigned char a=0, b=0;
for(a=0;a<8;a++)
{
SDA = (dat>>7); // 传送最高位
dat = (dat<<1); // 替换最高位
Delay10us();
SCL = 1; // SCL=H, 数据保持稳定
Delay10us();
SCL = 0; // SCL=L, 数据可以改变
Delay10us();
}
SDA=1; // 释放时钟线和数据线
Delay10us();
SCL=1;
// Delay10us();
while(SDA) // 应答信号SDA=0 ,非应答SDA=1
{
b++;
if(b>200) // 非应答超过200次,至少200us
{
SCL=0; // 拉低时钟线
Delay10us();
return 0; // 表示发送信号失败
}
}
SCL=0;
Delay10us();
return 1;
}
// 模拟接收数据
unsigned char I2CReadByte()
{
unsigned char a=0, dat=0;
SDA=1; // SDA=1拉高使其处于空闲状态
Delay10us();
for(a=0;a<8;a++)
{
SCL=1; // 保持数据稳定
Delay10us();
dat <<= 1; // data=0 <<1 -> 0
dat |= SDA;
Delay10us();
SCL=0; // SDA数据可以改变
Delay10us();
}
return dat;
}
// AT24C02 EEPROM 读写函数
void AT24C02Write(unsigned char addr, unsigned char dat)
{
// 1. 起始信号
I2CStart();
// 2. 器件地址
I2CWriteByte(0xa0);
// 3. 写入的首地址
I2CWriteByte(addr);
// 4. 写入数据
I2CWriteByte(dat);
// 5. 停止信号
I2CStop();
}
unsigned char AT24C02Read(unsigned char addr)
{
unsigned char dat=0;
// 1. 起始信号
I2CStart();
// 2. 器件地址
I2CWriteByte(0xa0);
// 3. 写入的首地址
I2CWriteByte(addr);
// 再次发送器件地址时,需要重新发送起始信号
// 4. 起始信号
I2CStart();
// 5. 器件地址
I2CWriteByte(0xa1);
// 6. 读取数据
dat = I2CReadByte();
// 5. 停止信号
I2CStop();
return dat;
}
结果:初始读取为7是因为之前有进行过I2C写入,默认没有对I2C操作前读取到的数据是0。
出现的问题:
I2C.H(13): error C141: syntax error near ')'
;原因是不能声明变量名为data,data是C51中的关键字。C51中的关键字data,idata,xdata,pdata,bdata。