EEPROM是嵌入式开发中比较常用的芯片,用来保存参数及掉电记忆的数据等,最常用的是ATMEL的AT24Cxx系列的IIC接口,也有其他厂家的如罗姆Rohm的BR24Gxx系列、ST的M24Cxx系列等。该系列芯片容量不等,如AT24C01即为1kbit,AT24C02为2kbit…
本文记录AT24C01/AT24C02的读写驱动。用到的IIC总线读写驱动可参考本人的另一篇博文4位数码管显示模块TM1637芯片C语言驱动程序。
型号 | 容量bit | 容量byte | 页数 | 字节/页 | 器件寻址位 | 可寻址器件数 | WordAddress位数/字节数 |
---|---|---|---|---|---|---|---|
AT24C01 | 1k | 128 | 16 | 8 | A2A1A0 | 8 | 7/1 |
AT24C02 | 2k | 256 | 32 | 8 | A2A1A0 | 8 | 8/1 |
上表为根据芯片手册总结的两种规格芯片的参数,这两种芯片除了容量的区别外,读写程序可以通用,下面会详细介绍手册中的相关内容。
A0、A1、A2是硬件连接的设备地址输入引脚,在1个总线上最多可寻址8个1Kbit或2Kbit器件。
AT24C01内有16页,每页8个字节,需要7bit的 word address寻址;AT24C02内有32页,每页8个字节,需要8bit的 word address寻址。
1kbit和2kbit的EEPROM器件,需要在起始条件后,跟着一个8bit的器件地址来使能读或写操作。
器件的地址字,高4位强制为1010即0xA,如图7-1,这一点对所有的串行EEPROM器件都通用。
后面3个bit是A2,A1,A0,即1k或2k EEPROM的器件地址,必须与硬件连接的响应输入引脚对应,以使器件应答。
器件地址的第8个bit是读/写操作选择位,1为读,0为写。
器件地址与A2,A1,A0输入引脚硬件连接比对有效后,EEPROM会输出一个0(应答);如果比较不成功,芯片会返回待机状态。
写单字节,需要在发送器件地址,收到应答信号之后,发送一个8位的word address(需要写入数据的地址),收到EEPROM收到这个地址后,会再次发送一个0(应答),然后接收8位的数据(需要写入的数据),再输出一个0(应答),寻址设备,如单片机,必须发送停止信号来结束写操作。同时,EEPROM进入了内部定时写周期(twr,即写入时的延时,一般为10ms),在写入周期内,禁止所有输入,在写结束前,EEPROM不会响应。
1kb/2kb的EEPROM支持8字节页写。
页写的开始于字节写一样,但是单片机发送在第一个数据后不发送停止信号,而是在收到EEPROM对第一个数据的应答后,可继续发送多达7个字节的数据。EEPROM在收到每个字节数据后均会应答。单片机必须发送停止信号终止页写操作。
在收到每个数据字节Data后,Word Address的低3位会在内部自动增加,高5位不增加,保存内存页的行地址(因为每页8个字节,所以一次页写最多只能写8个字节,所以发送Word Address实际就是需要写入的首地址,然后每发一个byte的数据,EEPROM内部会将Word Address自动递增,最多再递增7次,只需要3最低的bit即可,注意这个递增是自动的,主机不需要发送;而高5位实际就是“页数”)。当内部递增生成的Word Address到达页的边界时,下一个数据字节将会写入同一页的开始位置,所以如果发送8个以上数据,会发送“翻转”现象,前面的字节会被覆盖。
读操作的开始于写操作相同,除了设备地址字节里的读/写选择位不同,0为写,1为读。有3种写操作:读当前地址、读任意地址、顺序读。
内部地址计数器会保持上次读或写操作的地址,增加1。只要芯片有电,这个地址都会保持有效,当读到最后一页的最后一个字节时,也会“翻转”到第1页的第1个字节。
EEPROM接收到读写选择位置为1的器件地址且应答后,即开始发送当前地址的数据。单片机不应答,而是发送一个停止信号。
(笔者注:此处random如果直接翻译为“随机”,则无法表达出该操作的含义,这里random是指“任意”。)任意读需要发送一个伪字节的写时序,来载入数据字节的地址(就是读指定单个地址的数据)。EEPROM接收到器件地址和数据地址且应答后,单片机必须产生新的起始信号,发送器件起始,读写选择位置1,开始“读当前地址”操作。EEPROM应答器件地址,并输出数据字。单片机不应答,而是发送一个停止信号。
顺序读,既可以由“读当前地址”开始,也可以由“任意读”开始(由“读当前地址”开始,则读取的地址是地址计数器里的地址+1,由“任意读”开始,则需要先发送一个地址)。
单片机收到一个数据,回复一个应答信号,只要EEPROM收到应答,就继续自动递增数据的地址,并输出数据。当到达存储地址的极限时,也会发生“翻转”现象,继续“顺序读”。顺序读直到单片机不应答而是产生停止信号才停止。
在实际使用中,一般只会用到读取某个地址的数据,或者向某个地址写入数据,不会用到“读当前地址”,因此只编写“读/写任意地址”、“读/写一页的功能”的程序。
先对器件地址等信息进行宏定义:
#define READ_CMD 1
#define WRITE_CMD 0
#define x24C02//器件名称,x24C01或x24C02
#define DEV_ADDR 0xA0 //设备硬件地址
#ifdef x24C01
#define PAGE_NUM 16 //页数
#define PAGE_SIZE 8 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 1 //地址字节个数
#endif
#ifdef x24C02
#define PAGE_NUM 32 //页数
#define PAGE_SIZE 8 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 1 //地址字节个数
#endif
通过总结3.1.1节的描述,可以得出,对任意地址写入单个字节的时序为:
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要写入数据的地址(8bit)–>收到应答–>发送需要写入的数据–>收到应答–>发送停止信号
/*******************************************************************************
* 函数名:x24Cxx_WriteByte
* 功 能:写一个字节
* 参 数:u16Addr要写入的地址
u8Data要写入的数据
* 返回值:无
* 说 明:无
*******************************************************************************/
void x24Cxx_WriteByte(uint16_t u16Addr, uint8_t u8Data)
{
x24Cxx_WriteEnable();//使能写入
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写+页选择位
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
IIC_WriteByte(u8Data);
IIC_WaitAck();//等待应答
IIC_Stop();
x24Cxx_WriteDisble();//禁止写入
}
时序:
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要写入数据的首地址(8bit)–>收到应答–>发送需要写入的第1个数据–>收到应答–>发送需要写入的第2个数据–>收到应答…–>发送需要写入的第n个数据–>收到应答–>发送停止信号
/*******************************************************************************
* 函数名:x24Cxx_WritePage
* 功 能:页写
* 参 数:u16Addr要写入的首地址;
u8Len写入数据字节数,最大为PAGE_SIZE
pData要写入的数据首地址
* 返回值:无
* 说 明:最多写入1页,防止翻卷,如果地址跨页则去掉跨页的部分
*******************************************************************************/
void x24Cxx_WritePage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pData)
{
uint8_t i;
x24Cxx_WriteEnable();//使能写入
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
if (u8Len > PAGE_SIZE)//长度大于页的长度
{
u8Len = PAGE_SIZE;
}
if ((u16Addr + (uint16_t)u8Len) > CAPACITY_SIZE)//超过容量
{
u8Len = (uint8_t)(CAPACITY_SIZE - u16Addr);
}
if (((u16Addr % PAGE_SIZE) + (uint16_t)u8Len) > PAGE_SIZE)//判断是否跨页
{
u8Len -= (uint8_t)((u16Addr + (uint16_t)u8Len) % PAGE_SIZE);//跨页,截掉跨页的部分
}
for (i = 0; i < u8Len; i++)
{
IIC_WriteByte(*(pData + i));
IIC_WaitAck();//等待应答
}
IIC_Stop();
x24Cxx_WriteDisble();//禁止写入
}
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要读取数据的地址–>收到应答–>发送起始信号–>发送器件地址(包含读取命令)–>收到应答–>读取数据–>不应答–>发送停止信号
/*******************************************************************************
* 函数名:x24Cxx_ReadByte
* 功 能:读一个字节
* 参 数:u16Addr要读取的地址
* 返回值:u8Data读出的数据
* 说 明:无
*******************************************************************************/
uint8_t x24Cxx_ReadByte(uint16_t u16Addr)
{
uint8_t u8Data = 0;
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | READ_CMD);//器件寻址+读
IIC_WaitAck();//等待应答
u8Data = IIC_ReadByte();
IIC_NoAck();
IIC_Stop();
return u8Data;
}
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要读取数据的首地址–>收到应答–>发送起始信号–>发送器件地址(包含读取命令)–>收到应答–>读取第1个数据–>发送应答–>读取第2个数据–>发送应答…–>读取第n个数据–>不应答–>发送停止信号
/*******************************************************************************
* 函数名:x24Cxx_ReadPage
* 功 能:页读
* 参 数:u16Addr要读取的首地址;
u8Len读取数据字节数,最大为PAGE_SIZE
pBuff读取数据存入的缓存
* 返回值:无
* 说 明:无
*******************************************************************************/
void x24Cxx_ReadPage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pBuff)
{
uint8_t i;
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | READ_CMD);//器件寻址+读
IIC_WaitAck();//等待应答
if (u8Len > PAGE_SIZE)//长度大于页的长度
{
u8Len = PAGE_SIZE;
}
if ((u16Addr + (uint16_t)u8Len) > CAPACITY_SIZE)//超过容量
{
u8Len = (uint8_t)(CAPACITY_SIZE - u16Addr);
}
if (((u16Addr % PAGE_SIZE) + (uint16_t)u8Len) > PAGE_SIZE)//判断是否跨页
{
u8Len -= (uint8_t)((u16Addr + (uint16_t)u8Len) % PAGE_SIZE);//跨页,截掉跨页的部分
}
for (i = 0; i < (u8Len - 1); i++)
{
*(pBuff + i) = IIC_ReadByte();
IIC_Ack();
}
*(pBuff + u8Len - 1) = IIC_ReadByte();
IIC_NoAck();//最后一个不应答
IIC_Stop();
}
1.仅适用于x24C01、x24C02系列EEPROM芯片;
2.器件地址必须与A2/A1/A0引脚的硬件连接对应;
3.调用写入程序(无论是单字节写入还是页写),需要延时10ms(即twr,有的芯片手册说是5ms)后再对器件进行操作,否则这段时间内器件不响应命令;