AT24C02的读写操作
**说明:**最近我在做基于EEPROM存储器24c02存储的电子锁时遇到了一些问题:每次向24C02写入数据的时候没出现什么问题,可以正常写入。但是每次读取24C02却总是出现错误。
通过查找网上的各种资料,发现这个问题很多小伙伴都有遇到,但是楼下的评论不是说时序有问题就是说函数有问题,却没有具体的指出来到底哪里有问题。
下面我就以我的I2C驱动24C02为例来说说我是如何解决24C02数据读取问题的。
关于24c02的资料我觉得这篇博客写的比较详细,我这里就不花时间了(而且感觉我写的也没这篇博客写的好)
https://blog.csdn.net/snyanglq/article/details/50408489?
不懂的小伙伴可以康康。
我主要讲讲我如何实现读取24c02的问题
请看代码:
# 原始代码
void At24c02Read(unsigned char *b, unsigned char addr)
{
unsigned char i;
I2C_Start_signal();
I2C_SendByte(0xa0, 1); //发送写器件地址 1010 0000
I2C_SendByte(addr, 1); //发送要读取的地址
I2C_Start_signal();
I2C_SendByte(0xa1, 1); //发送读器件地址
for(i = 0; i < 8; i++)
{
*b = I2cReadByte(); //读取数据
b++;
}
I2C_Stop_signal();
}
# 改进代码
void At24c02Read(unsigned char b[], unsigned char addr)
{
unsigned char i;
for(i = 0; i < 7; i++)
{
I2C_Start_signal(); //开始信号
I2C_SendByte(0xa0, 1); //发送写器件地址1010 0000
I2C_SendByte(addr + i, 1); //发送要读取的地址
I2C_Start_signal();
I2C_SendByte(0xa1, 1); //发送读器件地址
b[i] = I2cReadByte(); //读取数据
ACK();
}
I2C_Start_signal();
I2C_SendByte(0xa0, 1); //发送写器件地址1010 0000
I2C_SendByte(addr + i, 1); //发送要读取的地址
I2C_Start_signal();
I2C_SendByte(0xa1, 1); //发送读器件地址
b[i] = I2cReadByte();
NACK();
I2C_Stop_signal();
Delay10us();
}
与其他人的代码有些差异,我将这几行代码同时放入循环体内
I2C_Start_signal(); //开始信号
I2C_SendByte(0xa0, 1); //发送写器件地址 1010 0000
I2C_SendByte(addr + i, 1); //发送要读取的地址
I2C_Start_signal();
I2C_SendByte(0xa1, 1); //发送读器件地址
理由是我发现当只发送读取地址的首地址时单片机内读取到的数据只有一个,如下图:
单片机内存中的FF数据为24c02中其他地址的,并不是连续读取。所以我用一个循环,每次读取都给定一个不同的地址,通过软件操作使得单片机从24C02中连续读取数据。按照改进的代码可以得到如下情况:
需要进行说明的是,这里我设置的系统时钟是6M左右,如果偏高会出现不能读取数据的情况。
下面附上我的整个24c02的读写函数以及主函数代码:
# 主函数
#include <reg51.h>
#include "I2C.h"
void main()
{
unsigned char a[8]={1,3,1,4,5,2,0,0};
unsigned char b[8];
At24c02Write(0x50, a);
Delay10us();
At24c02Read(b,0x50);
while(1);
}
I2C.H
#ifndef __I2C_H
#define __I2C_H
#include <reg51.h>
sbit SCL=P1^0; //时钟线
sbit SDA=P1^1; //地址线
/**********************************
** 函数名称 :ACK
** 函数功能 : 应答信号
**********************************/
void ACK(); //发送应答A子函数ACK
/**********************************
** 函数名称 :NACK
** 函数功能 : 非应答信号
**********************************/
void NACK();
/**
* 函数: void At24c02Write(unsigned char addr,unsigned char dat);
* 函数功能:AT24C02写数据.
* 使用方法:用户想要往里写数据的话就调用本函数,传入地址和数据即可
**/
void At24c02Write(unsigned char addr,unsigned char dat[]);
/**
* 函数: unsigned char At24c02Read(unsigned char addr);
* 函数功能:AT24C02读数据.
* 使用方法:用户想要读数据的话就调用本函数,传入地址即可
**/
void At24c02Read(unsigned char b[], unsigned char addr);
//unsigned char At24c02Read(unsigned char addr);
/**
* 函数: void I2C_Start_signal(void);
* 函数功能:I2C总线的起始信号
**/
void I2C_Start_signal(void);
/**
* 函数: void I2C_Stop_signal(void);
* 函数功能:I2C总线的结束信号
**/
void I2C_Stop_signal(void);
/**
* 函数: unsigned char I2C_SendByte(unsigned char dat, unsigned char ack);
* 函数功能:I2C总线的写数据
**/
unsigned char I2C_SendByte(unsigned char dat, unsigned char ack);
/**
* 函数: unsigned char I2cReadByte();
* 函数功能:I2C总线的读数据
**/
unsigned char I2cReadByte();
/**
* 函数: void Delay10us();
* 函数功能:10us延时
**/
void Delay10us();
#endif
# I2C.c
#include "i2c.h"
/**********************************
** 函数名称 :ACK
** 函数功能 : 应答信号
**********************************/
void ACK() //发送应答A子函数ACK
{
unsigned char i;
SCL = 1; //时钟线发出时钟脉冲
Delay10us();
while((SDA == 1) && (i < 200))i++ ;
SCL = 0; //与SCL=1组成时钟脉冲
Delay10us();
}
/**********************************
** 函数名称 :NACK
** 函数功能 : 非应答信号
**********************************/
void NACK() //发送应答A子函数NACK,见实例354
{
SDA = 1; //数据线高电平(发送数据1)
SCL = 0;
Delay10us();
SCL = 1; //时钟线发出时钟脉冲
Delay10us();
SCL = 0; //与SCL=1组成时钟脉冲
SDA = 0; //数据线低电平复位
}
/*******************************************************************************
* 函数名 : Start_signal(void)
* 函数功能 : I2C总线起始信号
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void I2C_Start_signal(void)
{
SDA = 1 ;
Delay10us();
SCL = 1 ;
Delay10us();
SDA = 0 ;
Delay10us();
SCL = 0 ;
Delay10us();
}
/*******************************************************************************
* 函数名 : Start_signal(void)
* 函数功能 : I2C总线终止信号
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void I2C_Stop_signal(void)
{
SDA = 0 ;
Delay10us();
SCL = 1 ;
Delay10us();
Delay10us();
SDA=1;
Delay10us();
}
/*******************************************************************************
* 函数名 : I2C_SendByte(unsigned char dat, unsigned char ack)
* 函数功能 : I2C总线发送数据
* 输入 : dat,一个字节的数据
* 输出 : 发送成功返回1,发送失败返回0
* 备 注 : 发送完一个字节I2C_SCL=0, 需要应答则应答设置为1,否则为0
*******************************************************************************/
unsigned char I2C_SendByte(unsigned char dat, unsigned char ack)
{
unsigned char a = 0,b = 0;//最大255,一个机器周期为1us,最大延时255us。
Replay:
b = 0 ;
for(a=0; a<8; a++)//要发送8位,从最高位开始
{
SDA = dat >> 7; //起始信号之后I2C_SCL=0,所以可以直接改变I2C_SDA信号
dat = dat << 1;
Delay10us();
SCL = 1;
Delay10us();//建立时间>4.7us
SCL = 0;
Delay10us();//时间大于4us
}
SDA = 1;
Delay10us();
SCL = 1;
while(SDA && (ack == 1))//等待应答,也就是等待从设备把I2C_SDA拉低
{
b++;
if(b > 200) //如果超过200us没有应答发送失败,或者为非应答,表示接收结束
{
SCL = 0;
Delay10us();
// return 0;
goto Replay ; //如果超过200us没有应答则发送失败,或者为非应答,这时候系统启动重发机制
//使用goto语句返回到上面接着发
}
}
SCL = 0;
Delay10us();
return 1;
}
/*******************************************************************************
* 函数名 : I2cReadByte()
* 函数功能 : I2C总线接收数据
* 输入 : 无
* 输出 : dat,数据
*******************************************************************************/
unsigned char I2cReadByte()
{
unsigned char a=0,dat=0;
SDA=1;
Delay10us();
for(a=0;a<8;a++)//接收8个字节
{
SCL=1;
Delay10us();
dat<<=1;
dat|=SDA;
Delay10us();
SCL=0;
Delay10us();
}
return dat;
}
/*******************************************************************************
* 函数名 : Delay10us()
* 函数功能 : 延时
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Delay10us()
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=2;a>0;a--);
}
/*******************************************************************************
* 函 数 名 : void At24c02Write(unsigned char addr,unsigned char dat)
* 函数功能 : 往24c02的8个地址写入8个数据
* 输 入 : 地址和数据
* 输 出 : 无
*******************************************************************************/
void At24c02Write(unsigned char addr,unsigned char dat[])
{
unsigned char i;
I2C_Start_signal();
I2C_SendByte(0xa0, 1);//发送写器件地址
I2C_SendByte(addr, 1);//发送要写入内存地址
for(i = 0; i < 8; i++)
I2C_SendByte(dat[i], 0); //发送数据
I2C_Stop_signal();
}
/*******************************************************************************
* 函 数 名 : unsigned char At24c02Read(unsigned char addr)
* 函数功能 : 读取24c02的一个地址的一个数据
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void At24c02Read(unsigned char b[], unsigned char addr)
{
unsigned char i;
for(i = 0; i < 7; i++)
{
I2C_Start_signal();
I2C_SendByte(0xa0, 1); //发送写器件地址 1010 0000
I2C_SendByte(addr + i, 1); //发送要读取的地址
I2C_Start_signal();
I2C_SendByte(0xa1, 1); //发送读器件地址
b[i] = I2cReadByte(); //读取数据
ACK();
}
I2C_Start_signal();
I2C_SendByte(0xa0, 1); //发送写器件地址 1010 0000
I2C_SendByte(addr + i, 1); //发送要读取的地址
I2C_Start_signal();
I2C_SendByte(0xa1, 1); //发送读器件地址
b[i] = I2cReadByte();
NACK();
I2C_Stop_signal();
Delay10us();
}
可能代码有些冗余,不过通过以上代码实现读写是没问题的。
以上是我个人的一点体会,希望对大家有所帮助。