AT24C02的读写操作

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); 			//发送读器件地址

理由是我发现当只发送读取地址的首地址时单片机内读取到的数据只有一个,如下图:
**主意时钟频率设置为6MHZ**
单片机内存中的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();
}
 

可能代码有些冗余,不过通过以上代码实现读写是没问题的。

以上是我个人的一点体会,希望对大家有所帮助。

  • 10
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
FPGAEEPROM芯片AT24C02实验Verilog逻辑源码Quartus11.0工程文件, FPGA型号为CYCLONE4E系列中的EP4CE6E22C8,可以做为你的学习设计参考。 module iic_com( clk,rst_n, sw1,sw2, scl,sda, dis_data ); input clk; // 50MHz input rst_n; //复位信号,低有效 input sw1,sw2; //按键1、2,(1按下执行操作,2按下执行操作) output scl; // 24C02的时钟端口 inout sda; // 24C02的数据端口 output[7:0] dis_data; //数码管显示的数据 //按键检测 reg sw1_r,sw2_r; //键值锁存寄存器,每20ms检测一次键值 reg[19:0] cnt_20ms; //20ms计数寄存器 always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt_20ms <= 20'd0; else cnt_20ms <= cnt_20ms+1'b1; //不断计数 end always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin sw1_r <= 1'b1; //键值寄存器复位,没有键盘按下时键值都为1 sw2_r <= 1'b1; end else if(cnt_20ms == 20'hfffff) begin sw1_r <= sw1; //按键1值锁存 sw2_r <= sw2; //按键2值锁存 end end //--------------------------------------------- //分频部分 reg[2:0] cnt; // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间 reg[8:0] cnt_delay; //500循环计数,产生iic所需要的时钟 reg scl_r; //时钟脉冲寄存器 always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt_delay <= 9'd0; else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //计数到10us为scl的周期,即100KHz else cnt_delay <= cnt_delay+1'b1; //时钟计数 end always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 3'd5; else begin case (cnt_delay) 9'd124: cnt <= 3'd1; //cnt=1:scl高电平中间,用于数据采样 9'd249: cnt <= 3'd2; //cnt=2:scl下降沿 9'd374: cnt <= 3'd3; //cnt=3:scl低电平中间,用于数据变化 9'd499: cnt <= 3'd0; //cnt=0:scl上升沿 default: cnt <= 3'd5; endcase end end `define SCL_POS (cnt==3'd0) //cnt=0:scl上升沿 `define SCL_HIG (cnt==3'd1) //cnt=1:scl高电平中间,用于数据采样 `define SCL_NEG (cnt==3'd2) //cnt=2:scl下降沿 `define SCL_LOW (cnt==3'd3) //cnt=3:scl低电平中间,用于数据变化 always @ (posedge clk or negedge rst_n) begin if(!rst_n) scl_r <= 1'b0; else if(cnt==3'd0) scl_r <= 1'b1; //scl信号上升沿
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值