目录
普中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进入内部写周期
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位对齐部分为随机单字节读取
对齐后进行顺序读取,并自动换页
末尾未对齐部分也进行顺序读取