【51单片机快速入门指南】4.1: I2C 与 AT24C02 (EEPROM) 的跨页连续读写

普中51-单核-A2
STC89C52
Keil uVision V5.29.0.0
PK51 Prof.Developers Kit Version:9.60.0.0


硬知识

       摘自《普中 51 单片机开发攻略》、《24C02/24C04/24C08/24C16/24C32/24C64芯片手册》

AT24Cxx 介绍

       AT24C01/02/04/08/16…是一个1K/2K/4K/8K/16K 位串行 CMOS,内部含有 128/256/512/1024/2048 个 8 位字节,AT24C01 有一个 8 字节页写缓冲器, AT24C02/04/08/16 有一个 16 字节页写缓冲器。该器件通过 I2C 总线接口进行操作,它有一个专门的写保护功能。开发板上使用的是 AT24C02(EEPROM)芯片,此芯片具有 I2C 通信接口,芯片内保存的数据在掉电情况下都不丢失, 所以通常用于存放一些比较重要的数据等。

引脚排列

AT24Cxx 芯片管脚如下图所示:
在这里插入图片描述

引脚说明

在这里插入图片描述

存储结构

在这里插入图片描述

器件寻址

       起始条件使能芯片读写操作后,EEPROM都要求有8位的器件地址信息。
       器件地址信息由"1"、“0"序列组成,前4位如图中所示,对于所有串行EEPROM都是一样的。对于24C02/32/64,随后3位A2,A1和A0为器件地址位,必须与硬件输入引脚保持一致。
       对于24C04,随后2位A2和A1为器件地址位,另1位为页地址位。A2和A1必须与硬件输入引脚保持一致,而A0是空脚。
       对于24C08,随后1位A2为器件地址位,另2位为页地址位。A2必须与硬件输入引脚保持一致,而A1和A0是空脚。
       对于24C16,无器件地址位,3位都为页地址位,而A2,A1和A0是空脚。
       器件地址信息的LSB为读/写操作选择位,高为读操作,低为写操作。
       若比较器件地址一致,EEPROM将输出应答"0”,如果不一致,则返回到待机状态。
在这里插入图片描述

器件操作

待机模式

EEPROM具有低功耗待机的特点,条件为:

  1. 电源上电;
  2. 接收停止条件及完成任何内部操作后。

存储复位

当协议中产生中断、掉电或系统复位后,I2C总线可通过以下步骤复位:

  1. 产生9个时钟周期。
  2. 当SCL为高时,SDA也为高。
  3. 产生一个起始条件。

写操作

字节写

       写操作要求在接收器件地址和ACK应答后,接收8位的字地址。接收到这个地址后EEPROM应答"0",然后是一个8位数据。在接收8位数据后,EEPROM应答"0",接着必须由主器件发送停止条件来终止写序列。
       此时EEPROM进入内部写周期 t W R t_{WR} tWR,数据写入非易失性存储器中,在此期间所有输入都无效。
       直到写周期完成,EEPROM才会有应答。
在这里插入图片描述

页写

       24C02器件按8字节/页执行页写,24C04/08/16器件按16字节/页执行页写,24C32/64器件按32字节/页执行页写。
       页写初始化与字节写相同,只是主器件不会在第一个数据后发送停止条件,而是在EEPROM的ACK以后,接着发送7个(24C02)或15个(24C04/08/16)或31个(24C32/64)数据。
       EEPROM收到每个数据后都应答"0",最后仍需由主器件发送停止条件,终止写序列接收到每个数据后,字地址的低3位(24C02)或4位(24C04/08/16)或5位(24C32/64)内部自动加1,高位地址位不变,维持在当前页内。当内部产生的字地址达到该页边界地址时,随后的数据将写入该页的页首。如果超过8个(24C02)或16个(24C04/08/16)或32个24C32/64)数据传送给了EEPROM,字地址将回转到该页的首字节,先前的字节将会被覆盖。
在这里插入图片描述

应答查询

       一旦内部写周期启动,EEPROM输入无效,此时即可启动应答查询:发送起始条件和器件地址(读/写位为期望的操作)。只有内部写周期完成,EEPROM才应答"0",之后可继续读/写操作。
在这里插入图片描述

读操作

       读操作与写操作初始化相同,只是器件地址中的读/写选择位应为"1",有三种不同的读操作方式:当前地址读,随机读和顺序读。

当前地址读

       内部地址计数器保存着上次访问时最后一个地址加1的值。只要芯片有电,该地址就一直保存。当读到最后页的最后字节,地址会回转到0;当写到某页尾的最后一个字节,地址会回转到该页的首字节。
       接收器件地址(读/写选择位为"1")、EEPROM应答ACK后,当前地址的数据就随时钟送出。
       主器件无需应答"0",但需发送停止条件。
在这里插入图片描述

随机读

       随机读需先写一个目标字地址,一旦EEPROM接收器件地址和字地址并应答了ACK,主器件就产生一个重复的起始条件。
       然后,主器件发送器件地址(读/写选择位为"1"),EEPROM应答ACK,并随时钟送出数据。主器件无需应答"0",但需发送停止条件。
在这里插入图片描述

顺序读

       顺序读可以通过“当前地址读”或"随机读”启动。主器件接收到一个数据后,应答ACK,只要EEPROM接收到ACK,将自动增加字地址并继续随时钟发送后面的数据。若达到存储器地址末尾,地址自动回转到0,仍可继续顺序读取数据。
       主器件不应答"0",而发送停止条件,即可结束顺序读操作。
在这里插入图片描述

示例程序

       stdint.h【51单片机快速入门指南】1:基础知识和工程创建
       软件I2C程序见【51单片机快速入门指南】4: 软件 I2C

       由原理图,此24C02的地址为1010 000,即 0x50
在这里插入图片描述

24C02.c

#include "24C02.h"

/*******************************************************************************
* 函 数 名         : at24c02_delay_1ms 移植时需修改
* 函数功能		   : 延时1ms
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void at24c02_delay_1ms()	//@11.0592MHz
{
    
    
	unsigned char i, j;

	_nop_();
	i = 2;
	j = 199;
	do
	{
    
    
		while (--j);
	} while (--i);
}

void at24c02_delay_ms(int i)
{
    
    
	while(i--)
		at24c02_delay_1ms();
}

/*******************************************************************************
* 函 数 名         : at24c02_write_one_byte
* 函数功能		   : 在AT24CXX指定地址写入一个数据
* 输    入         : addr:写入数据的目的地址 
					 dat:要写入的数据
* 输    出         : 无
*******************************************************************************/
void at24c02_write_one_byte(uint8_t addr,uint8_t dat)
{
    
    			
	i2c_mem_write(ADDR_24C02, addr, &dat, 1);
	at24c02_delay_ms(10);
}

/*******************************************************************************
* 函 数 名         : at24c02_read_one_byte
* 函数功能		   : 在AT24CXX指定地址读出一个数据
* 输    入         : addr:开始读数的地址 
* 输    出         : 读到的数据
*******************************************************************************/
uint8_t at24c02_read_one_byte(uint8_t addr)
{
    
    				  
	uint8_t temp = 0;		  	    																 
	i2c_mem_read(ADDR_24C02, addr, &temp, 1);
	return temp;							//返回读取的数据
}

/*******************************************************************************
* 函 数 名         : at24c02_read_one_page
* 函数功能		   : 在AT24CXX指定地址读出一页数据
* 输    入         : addr:写入数据的目的地址  
					 pbuffer:要写入的缓冲区首地址
					 Len:数据长度
* 输    出         : 无
*******************************************************************************/
void at24c02_read_one_page(uint8_t addr, uint8_t *pbuffer)
{
    
    				  
	i2c_mem_read(ADDR_24C02, addr, pbuffer, 8);
}

/*******************************************************************************
* 函 数 名         : at24c02_write_one_page
* 函数功能		   : 在AT24CXX指定页写入一页数据
* 输    入         : addr:写入数据的目的地址 
					 dat:要写入的数据
* 输    出         : 无
*******************************************************************************/
void at24c02_write_one_page(uint8_t addr, uint8_t *dat)
{
    
    		
	i2c_mem_write(ADDR_24C02, addr, dat, 8);
	at24c02_delay_ms(10);
}

/*******************************************************************************
* 函 数 名         : at24c02_read_bytes
* 函数功能		   : 在AT24CXX指定地址读出一段数据
* 输    入         : addr:写入数据的目的地址  
					 pbuffer:要写入的缓冲区首地址
					 Len:数据长度
* 输    出         : 无
*******************************************************************************/
void at24c02_read_bytes(uint8_t addr, uint8_t* pbuffer, uint8_t Len)
{
    
    				  
	uint8_t pdat_id_S = addr % 8;
	uint8_t i, pages;
	if(pdat_id_S)
	{
    
    
		for(i = pdat_id_S; i < 8; ++i)
		{
    
    
			*pbuffer++ = at24c02_read_one_byte(addr++);
			--Len;
			if(!Len)
				return;
		}
	}
	pages = Len / 8;
	for (i = 0; i < pages; ++i)
	{
    
    
		at24c02_read_one_page(addr, pbuffer);
		addr += 8;
		pbuffer += 8;
		Len  -= 8;
	}
	if(!Len)
		return;
	i2c_mem_read(ADDR_24C02, addr, pbuffer, Len);
	pbuffer += Len;
	*pbuffer = '\0';
}

/*******************************************************************************
* 函 数 名         : at24c02_write_bytes
* 函数功能		   : 在AT24CXX指定地址写入一段数据
* 输    入         : addr:写入数据的目的地址  
					 pdat:要写入的数据首地址
					 Len:数据长度
* 输    出         : 无
*******************************************************************************/
void at24c02_write_bytes(uint8_t addr, uint8_t* pdat, uint8_t Len)
{
    
    		
	uint8_t Temp[8];
	uint8_t pdat_id_S = addr % 8;
	uint8_t i, pages;
	if(pdat_id_S)
	{
    
    
		for(i = 0; i < pdat_id_S; ++i)
			Temp[i] = at24c02_read_one_byte(addr - pdat_id_S + i);
		for (; i < 8; ++i)
		{
    
    
			Temp[i] = *pdat;
			++pdat;
			--Len;
			if(!Len)
			{
    
    
				at24c02_write_one_page(addr - pdat_id_S, Temp);
				return;
			}
		}
		at24c02_write_one_page(addr - pdat_id_S, Temp);
		addr = addr + 8 - pdat_id_S;
	}
	pages = Len / 8;
	for (i = 0; i < pages; ++i)
	{
    
    
		at24c02_write_one_page(addr, pdat);
		addr += 8;
		pdat += 8;
		Len  -= 8;
	}
	if(!Len)
		return;
	for (i = 0; i < Len; ++i)
	{
    
    
		Temp[i] = *pdat;
		++pdat;
	}
	for(; i < 8; ++i)
	{
    
    
		Temp[i] = at24c02_read_one_byte(addr + i);
	}
	at24c02_write_one_page(addr, Temp);
}

24C02.h

#ifndef _24C02_H_
#define _24C02_H_

#include "stdint.h"
#include "intrins.h"
#include "Software_I2C.h"

#define ADDR_24C02 0x50	//24C02的7位地址

void at24c02_write_one_byte(uint8_t addr,uint8_t dat);
uint8_t at24c02_read_one_byte(uint8_t addr);
void at24c02_write_one_page(uint8_t addr,uint8_t *dat);
void at24c02_read_one_page(uint8_t addr, uint8_t *pbuffer);

void at24c02_write_bytes(uint8_t addr, uint8_t *pdat, uint8_t Len);
void at24c02_read_bytes(uint8_t addr, uint8_t *pbuffer, uint8_t Len);

#endif

测试程序

串口程序见【51单片机快速入门指南】3.3:USART 串口通信

main.c

       在地址0x06处连续写入
       “123456789098765432123456789012345678909876543212345678901234567890987654321234567890”
       读取后通过串口返回,波特率为57600,晶振频率为11.0592MHz。

#include <STC89C5xRC.H>
#include "intrins.h"
#include "stdint.h"
#include "USART.h"
#include "24C02.h"

void Delay1ms()		//@11.0592MHz
{
    
    
	unsigned char i, j;

	_nop_();
	i = 2;
	j = 199;
	do
	{
    
    
		while (--j);
	} while (--i);
}

void Delay_ms(int i)
{
    
    
	while(i--)
		Delay1ms();
}

void main(void)
{
    
    
	char Input[] = "123456789098765432123456789012345678909876543212345678901234567890987654321234567890";
	char Str[sizeof(Input)];
	USART_Init(USART_MODE_1, Rx_ENABLE, STC_USART_Priority_Lowest, 11059200, 57600, DOUBLE_BAUD_ENABLE, USART_TIMER_1);
	at24c02_write_bytes(6, Input, sizeof(Input)-1);
	while(1)
	{
    
    
		Delay_ms(500);
		at24c02_read_bytes(6, Str, sizeof(Input)-1);
		printf("%s\r\n", Str);
	}
}

实验现象

如图,成功读取
在这里插入图片描述

通讯波形

写入部分

未对齐部分先读取之前的字节,在对齐顺序写入,并自动换页
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
末尾未对齐部分先读取后面的字节,再连同要写入的数据顺序写入
在这里插入图片描述

读取部分

未8位对齐部分为随机单字节读取
在这里插入图片描述
对齐后进行顺序读取,并自动换页
在这里插入图片描述
末尾未对齐部分也进行顺序读取
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44457994/article/details/121453106