目录
- 硬知识
- AT24Cxx 介绍
- 引脚排列
- 引脚说明
- 存储结构
- 器件寻址
- 器件操作
- 待机模式
- 存储复位
- 写操作
- 字节写
- 页写
- 应答查询
- 读操作
- 当前地址读
- 随机读
- 顺序读
- 示例程序
- 24C02.c
- 24C02.h
- 测试程序
- main.c
- 实验现象
- 通讯波形
- 写入部分
- 读取部分
普中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具有低功耗待机的特点,条件为:
- 电源上电;
- 接收停止条件及完成任何内部操作后。
存储复位
当协议中产生中断、掉电或系统复位后,I2C总线可通过以下步骤复位:
- 产生9个时钟周期。
- 当SCL为高时,SDA也为高。
- 产生一个起始条件。
写操作
字节写
写操作要求在接收器件地址和ACK应答后,接收8位的字地址。接收到这个地址后EEPROM应答"0",然后是一个8位数据。在接收8位数据后,EEPROM应答"0",接着必须由主器件发送停止条件来终止写序列。
此时EEPROM进入内部写周期tWRt_{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位对齐部分为随机单字节读取
对齐后进行顺序读取,并自动换页
末尾未对齐部分也进行顺序读取