EEPROM芯片读写程序相关索引
1.AT24C01/AT24C02系列EEPROM芯片单片机读写驱动程序
2.AT24C04、AT24C08、AT24C16系列EEPROM芯片单片机读写驱动程序
3.AT24C32、AT24C64、AT24C128、AT24C256、AT24C512系列EEPROM芯片单片机读写驱动程序
4.x24Cxx系列EEPROM芯片C语言通用读写程序
一、概述
在之前的3篇文章中,介绍了x24C01~x24C512的读写程序,相关文章如下:
1.IIC驱动:4位数码管显示模块TM1637芯片C语言驱动程序
2.AT24C01、AT24C02读写:AT24C01/AT24C02系列EEPROM芯片单片机读写驱动程序
3.AT24C04、AT24C08、AT24C16读写:AT24C04、AT24C08、AT24C16系列EEPROM芯片单片机读写驱动程序
4.AT24C32、AT24C64、AT24C128、AT24C256、AT24C512读写:AT24C32、AT24C64、AT24C128、AT24C256、AT24C512系列EEPROM芯片单片机读写驱动程序
本篇博文将综合前述几篇文章,将x24C01-x24C512写成可以通用的程序,以方便不同容量EEPROM芯片移植。为此,笔者斥巨资购入了x24C01-x24C512共10个EEPROM芯片以及读写模块,全家福如下图:
以下的所有程序均在所有型号芯片上经过验证。
二、芯片对比
型号 | 容量bit | 容量byte | 页数 | 字节/页 | 器件寻址位 | 可寻址器件数 | WordAddress位数/字节数 | 备注 |
---|---|---|---|---|---|---|---|---|
AT24C01 | 1k | 128 | 16 | 8 | A2A1A0 | 8 | 7/1 | WordAddress1个字节 |
AT24C02 | 2k | 256 | 32 | 8 | A2A1A0 | 8 | 8/1 | WordAddress1个字节 |
AT24C04 | 4k | 512 | 32 | 16 | A2A1 | 4 | 9/1 | WordAddress1个字节+P0位 |
AT24C08 | 8k | 1024 | 64 | 16 | A2 | 2 | 10/1 | WordAddress 1个字节+P0、P1位 |
AT24C16 | 16k | 2048 | 128 | 16 | - | 1 | 11/1 | WordAddress1个字节+P0、P1、P2位 |
AT24C32 | 32k | 4k | 128 | 32 | A2A1A0 | 8 | 12/2 | WordAddress2个字节 |
AT24C64 | 64k | 8k | 256 | 32 | A2A1A0 | 8 | 13/2 | WordAddress2个字节 |
AT24C128 | 128k | 16k | 256 | 64 | A1A0 | 4 | 14/2 | WordAddress2个字节 |
AT24C256 | 256k | 32k | 512 | 64 | A1A0 | 4 | 15/2 | WordAddress2个字节 |
AT24C512 | 512k | 64k | 512 | 128 | A2A1A0 | 8 | 16/2 | WordAddress2个字节 |
通过上表,结合前3篇文章的介绍,我们可以总结出,x24C01/x24C02的读写最基本,在这个基础上,由于x24C04/x24C08/x24C16的存储地址的增多,需要在对存储地址寻址的时候,多出几位,这几位加在P0/P1/P2即“页选择位”上;还是在这个基础上,x24C32/x24C64/x24C128/x24C256/x24X512的存储地址更多,1个字节+页选择位也无法满足,因此干脆将WordAddress搞成2个字节的,不再需要页选择位。
三、主要程序代码
经过上述的分析,我们还是通过宏定义的方式,先定义器件型号,再以条件编译的方式,对不同的芯片编译不同的代码。
/*******************************************************************************
型号 Byte容量 页数 页内字节数 WORD_ADDR位数 WORD_ADDR字节数
x24C01 128Byte 16页 8Byte 7bit 1Byte
x24C02 256Byte 32页 8Byte 8bit 1Byte
x24C04 512Byte 32页 16Byte 9bit 1Byte
x24C08 1024Byte 64页 16Byte 10bit 1Byte
x24C16 2048Byte 128页 16Byte 11bit 1Byte
x24C32 4096Byte 128页 32Byte 12bit 2Byte
x24C64 8192Byte 256页 32Byte 13bit 2Byte
x24C128 16384Byte 256页 64Byte 14bit 2Byte
x24C256 32768Byte 512页 64Byte 15bit 2Byte
x24C512 65536Byte 512页 128Byte 16bit 2Byte
*******************************************************************************/
#define READ_CMD 1
#define WRITE_CMD 0
#define x24C01//器件名称,x24C01~x24C512
#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
#ifdef x24C04
#define PAGE_NUM 32 //页数
#define PAGE_SIZE 16 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 1 //地址字节个数
#endif
#ifdef x24C08
#define PAGE_NUM 64 //页数
#define PAGE_SIZE 16 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 1 //地址字节个数
#endif
#ifdef x24C16
#define PAGE_NUM 128 //页数
#define PAGE_SIZE 16 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 1 //地址字节个数
#endif
#ifdef x24C32
#define PAGE_NUM 128 //页数
#define PAGE_SIZE 32 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 2 //地址字节个数
#endif
#ifdef x24C64
#define PAGE_NUM 256 //页数
#define PAGE_SIZE 32 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 2 //地址字节个数
#endif
#ifdef x24C128
#define PAGE_NUM 256 //页数
#define PAGE_SIZE 64 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 2 //地址字节个数
#endif
#ifdef x24C256
#define PAGE_NUM 512 //页数
#define PAGE_SIZE 64 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 2 //地址字节个数
#endif
#ifdef x24C512
#define PAGE_NUM 512 //页数
#define PAGE_SIZE 128 //页面大小(字节)
#define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) //总容量(字节)
#define ADDR_BYTE_NUM 2 //地址字节个数
#endif
/*******************************************************************************
* 函数名:x24Cxx_WriteByte
* 功 能:写一个字节
* 参 数:u16Addr要写入的地址
u8Data要写入的数据
* 返回值:无
* 说 明:器件地址(包含写入命令) -> 1或2个字节WORD ADDR -> 数据
*******************************************************************************/
void x24Cxx_WriteByte(uint16_t u16Addr, uint8_t u8Data)
{
x24Cxx_WriteEnable();//使能写入
IIC_Start();//起始信号
#if (ADDR_BYTE_NUM == 1)//地址只有1个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD | (((uint8_t)((u16Addr >> 8) & 0x07)) << 1));//器件寻址+写+页选择位
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
}
#endif
#if (ADDR_BYTE_NUM == 2)//地址有2个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));//地址高字节
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//地址低字节
IIC_WaitAck();//等待应答
}
#endif
IIC_WriteByte(u8Data);
IIC_WaitAck();//等待应答
IIC_Stop();
x24Cxx_WriteDisble();//禁止写入
}
/*******************************************************************************
* 函数名:x24Cxx_ReadByte
* 功 能:读一个字节
* 参 数:u16Addr要读取的地址
* 返回值:u8Data读出的数据
* 说 明:无
*******************************************************************************/
uint8_t x24Cxx_ReadByte(uint16_t u16Addr)
{
uint8_t u8Data = 0;
IIC_Start();//起始信号
#if (ADDR_BYTE_NUM == 1)//地址只有1个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD | (((uint8_t)((u16Addr >> 8) & 0x07)) << 1));//器件寻址+写+页选择位
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
}
#endif
#if (ADDR_BYTE_NUM == 2)//地址有2个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));//地址高字节
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//地址低字节
IIC_WaitAck();//等待应答
}
#endif
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | READ_CMD);//器件寻址+读
IIC_WaitAck();//等待应答
u8Data = IIC_ReadByte();
IIC_NoAck();
IIC_Stop();
return u8Data;
}
/*******************************************************************************
* 函数名:x24Cxx_WritePage
* 功 能:页写
* 参 数:u16Addr要写入的首地址;
u8Len写入数据字节数,最大为PAGE_SIZE
pData要写入的数据首地址
* 返回值:无
* 说 明:最多写入1页,防止翻卷,如果地址跨页则去掉跨页的部分
*******************************************************************************/
void x24Cxx_WritePage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pData)
{
uint8_t i;
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);//跨页,截掉跨页的部分
}
x24Cxx_WriteEnable();//使能写入
IIC_Start();//起始信号
#if (ADDR_BYTE_NUM == 1)//地址只有1个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD | (((uint8_t)((u16Addr >> 8) & 0x07)) << 1));//器件寻址+写+页选择位
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
}
#endif
#if (ADDR_BYTE_NUM == 2)//地址有2个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));//地址高字节
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//地址低字节
IIC_WaitAck();//等待应答
}
#endif
for (i = 0; i < u8Len; i++)
{
IIC_WriteByte(*(pData + i));
IIC_WaitAck();//等待应答
}
IIC_Stop();
x24Cxx_WriteDisble();//禁止写入
}
/*******************************************************************************
* 函数名:x24Cxx_ReadPage
* 功 能:页读
* 参 数:u16Addr要读取的首地址;
u8Len读取数据字节数,最大为PAGE_SIZE
pBuff读取数据存入的缓存
* 返回值:无
* 说 明:无
*******************************************************************************/
void x24Cxx_ReadPage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pBuff)
{
uint8_t i;
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);//跨页,截掉跨页的部分
}
IIC_Start();//起始信号
#if (ADDR_BYTE_NUM == 1)//地址只有1个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD | (((uint8_t)((u16Addr >> 8) & 0x07)) << 1));//器件寻址+写+页选择位
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//只取地址的低字节
IIC_WaitAck();//等待应答
}
#endif
#if (ADDR_BYTE_NUM == 2)//地址有2个字节
{
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+写
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));//地址高字节
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));//地址低字节
IIC_WaitAck();//等待应答
}
#endif
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | READ_CMD);//器件寻址+读
IIC_WaitAck();//等待应答
for (i = 0; i < (u8Len - 1); i++)
{
*(pBuff + i) = IIC_ReadByte();
IIC_Ack();
}
*(pBuff + u8Len - 1) = IIC_ReadByte();
IIC_NoAck();//最后一个不应答
IIC_Stop();
}
四、总结
1.器件地址必须与A2/A1/A0引脚的硬件连接对应;
2.该程序,写入或读取的地址,不要超过芯片的容量,否则结果会异常,读者可以自行修改程序,做功能更全面的地址是否合规的检查;
3.x24Cxx_WriteEnable()函数为WP写保护功能使能,根据单片机的引脚配置自行定义;
4.调用写入程序(无论是单字节写入还是页写),需要延时10ms(即twr,有的芯片手册说是5ms)后再对器件进行操作,否则这段时间内器件不响应命令;