参考文章:
这里附上一篇看到写得很好的大佬的文章:
STM32F407单片机通用24CXXX读写程序(KEIL),兼容24C系列存储器(24C01到24C512),支持存储器任意地址跨页连续读写多个页
AT24C32/64官方手册:AT24C32/64
一、AT24CXX容量表
|----------|---------|----------|-----|-------------|
| 型号 | 容量(bit) | 容量(byte) | 页数 | 每页字节数(byte) |
| AT24C01 | 1K | 128 | 16 | 8 |
| AT24C02 | 2K | 256 | 32 | 8 |
| AT24C04 | 4K | 512 | 32 | 16 |
| AT24C08 | 8K | 1024 | 64 | 16 |
| AT24C16 | 16K | 2048 | 128 | 16 |
| AT24C32 | 32K | 4096 | 128 | 32 |
| AT24C64 | 64K | 8192 | 256 | 32 |
| AT24C128 | 128K | 16384 | 256 | 64 |
| AT24C256 | 256K | 32768 | 512 | 64 |
| AT24C512 | 512K | 65536 | 512 | 128 |
二、AT24CXX寻址方式
|---------|-------------------|----------|-------------------|
| 型号 | WORD ADDRESS(bit) | 型号 | WORD ADDRESS(bit) |
| AT24C01 | 7 | AT24C32 | 12 |
| AT24C02 | 8 | AT24C64 | 13 |
| AT24C04 | 9 | AT24C128 | 14 |
| AT24C08 | 10 | AT24C256 | 15 |
| AT24C16 | 11 | AT24C512 | 16 |
三、AT24CXX时序图
1.字节写
2.页写
AT24CXX内部是有分页的,根据型号不同,页数不同,每页字节数不同。连续写入数据的时候,内部指针会+1,当内部指针移动到当前页末的时候,就会自动移动到当前页头部,再往里写数据的时候就会覆盖掉之前的数据。
如果想要连续写多页数据,那就需要去判断是否需要翻页,如果地址是在另一页,就需要重新发送字节写的时序。
3.当前地址读
4.顺序读
5.随机读
顺序读是从当前地址开始读,那么随机读搭配顺序读即可以读取任意地址。随机读就是先发送写命令,让EEPROM将指针移动到要读取的位置,然后主机发送起始条件,发送从机地址(读写位为读),即开始顺序读。
四、代码
这里附上的代码是基于STM32 HAL库,模拟IIC读写EEPROM,对AT24CXX系列通用。
bsp_at24cxx.c
cpp
/** BSP_AT24CXX.C EEPROM AT24CXX/FM24CXX驱动
*
* @author Dai Zu<zhangruilin@163.com>
* @date 2024/4/10
*
*/
/* BSP头文件 */
#include "BSP_AT24CXX.h"
/* 宏定义 */
#define AT24CXX_ADDR 0xA0 // 从机地址
// #define AT24C01 {128, 8, AT24CXX_ADDR}
// #define AT24C02 {256, 8, AT24CXX_ADDR}
// #define AT24C04 {512, 16, AT24CXX_ADDR}
// #define AT24C08 {1024, 16, AT24CXX_ADDR}
// #define AT24C16 {2048, 16, AT24CXX_ADDR}
// #define AT24C32 {4096, 32, AT24CXX_ADDR}
#define AT24C64 {8192, 32, AT24CXX_ADDR}
// #define AT24C128 {16384, 64, AT24CXX_ADDR}
// #define AT24C256 {32768, 64, AT24CXX_ADDR}
// #define AT24C512 {65536, 128, AT24CXX_ADDR}
/* EEPROM结构体 */
struct AT24CXX_TYPE
{
uint32_t size; // 容量,单位(字节)
uint8_t pageSize; // 每页字节数
uint8_t addr; // 从机地址
};
struct AT24CXX_TYPE EEPROM_TYPE= AT24C64;
#define IIC_Soft 1 // 软件IIC
#if IIC_Soft
/* 使用STM32Hal库,以下是IIC接口,移植IIC时只需要实现以下接口即可 */
#define IIC_SCL_PORT EEP_SCL_GPIO_Port
#define IIC_SDA_PORT EEP_SDA_GPIO_Port
#define IIC_SCL_PIN EEP_SCL_Pin
#define IIC_SDA_PIN EEP_SDA_Pin
#define IIC_SCL_SET HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_SET)
#define IIC_SCL_RESET HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_RESET)
#define IIC_SDA_SET HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_SET)
#define IIC_SDA_RESET HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_RESET)
#define READ_SDA HAL_GPIO_ReadPin(IIC_SDA_PORT, IIC_SDA_PIN)
void IIC_SDA_Dir(uint8_t dir);
/* IO方向 */
enum IIC_SDA_DIR
{
IIC_SDA_OUTPUT = 0,
IIC_SDA_INPUT
};
enum IIC_ACK
{
ACK = 0,
NACK = 1
};
/**
* @brief 初始化IIC相关的外设
*/
void IIC_MSP_Init(void)
{
}
/**
* @brief 初始化IIC
*/
void IIC_Init(void)
{
IIC_MSP_Init();
}
/**
* @brief 设置SDA的方向
* @param dir IIC_SDA_DIR
* IIC_SDA_OUTPUT or IIC_SDA_INPUT
*/
void IIC_SDA_Dir(uint8_t dir)
{
if (dir == IIC_SDA_INPUT)
{
IIC_SDA_SET;
}
}
void Delay_us(uint32_t us)
{
__IO uint32_t Delay = us * 48 / 8;//(SystemCoreClock / 8U / 1000000U)
//见stm32f1xx_hal_rcc.c -- static void RCC_Delay(uint32_t mdelay)
do
{
__NOP();
}
while (Delay --);
}
/**
* @brief 产生IIC起始信号
* SCL高电平期间,SDA产生下降沿
*/
void IIC_Start(void)
{
IIC_SDA_Dir(IIC_SDA_OUTPUT);
IIC_SDA_SET;
IIC_SCL_SET;
Delay_us(4);
IIC_SDA_RESET;
Delay_us(4);
IIC_SCL_RESET;
}
/**
* @brief 产生IIC停止信号
* SCL高电平期间,SDA产生上升沿
*/
void IIC_Stop(void)
{
IIC_SDA_Dir(IIC_SDA_OUTPUT);
IIC_SCL_RESET;
IIC_SDA_RESET;
Delay_us(4);
IIC_SCL_SET;
Delay_us(4);
IIC_SDA_SET;
}
/**
* @brief 等待应答信号
* @return ACK or NACK
*/
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
IIC_SDA_Dir(IIC_SDA_INPUT);
IIC_SDA_SET;Delay_us(1);
IIC_SCL_SET;Delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return NACK;
}
}
IIC_SCL_RESET;
return ACK;
}
/**
* @brief 产生ACK应答
*/
void IIC_Ack(void)
{
IIC_SCL_RESET;
IIC_SDA_Dir(IIC_SDA_OUTPUT);
IIC_SDA_RESET;
Delay_us(2);
IIC_SCL_SET;
Delay_us(2);
IIC_SCL_RESET;
}
/**
* @brief 不应答
*/
void IIC_NAck(void)
{
IIC_SCL_RESET;
IIC_SDA_Dir(IIC_SDA_OUTPUT);
IIC_SDA_SET;
Delay_us(2);
IIC_SCL_SET;
Delay_us(2);
IIC_SCL_RESET;
}
/**
* @brief IIC发送一个字节
*/
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
IIC_SDA_Dir(IIC_SDA_OUTPUT);
IIC_SCL_RESET;
for(t=0;t<8;t++)
{
if ((txd&0x80)>>7)
{
IIC_SDA_SET;
}
else
{
IIC_SDA_RESET;
}
txd<<=1;
Delay_us(2);
IIC_SCL_SET;
Delay_us(2);
IIC_SCL_RESET;
Delay_us(2);
}
}
/**
* @brief 读一个字节
* @param ack 是否发送应答
* 0-发送应答,1-不发送应答
*/
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
IIC_SDA_Dir(IIC_SDA_INPUT);
for(i=0;i<8;i++ )
{
IIC_SCL_RESET;
Delay_us(2);
IIC_SCL_SET;
receive<<=1;
if(READ_SDA)receive++;
Delay_us(1);
}
if (ack)
{
IIC_NAck();
}
else
{
IIC_Ack();
}
return receive;
}
/**
* @brief 初始化
*/
void AT24CXX_Init(void)
{
IIC_Init();
}
/**
* @brief 从指定地址读出一个数据
* @param ReadAddr 数据地址
* @retval 读取到的数据
*/
uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr)
{
uint8_t temp=0;
IIC_Start();
if(EEPROM_TYPE.size>2048)
{
IIC_Send_Byte(EEPROM_TYPE.addr); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8);//发送高地址
}else
{
IIC_Send_Byte(EEPROM_TYPE.addr+((ReadAddr/256)<<1)); //发送器件地址,写数据
}
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(EEPROM_TYPE.addr+1);
IIC_Wait_Ack();
temp=IIC_Read_Byte(1);
IIC_Stop();
return temp;
}
/**
* @brief 向指定地址写入一个字节
* @param WriteAddr 数据地址
* @param DataToWrite 要写入的数据
*/
void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite)
{
IIC_Start();
if(EEPROM_TYPE.size>2048)
{
IIC_Send_Byte(EEPROM_TYPE.addr); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr>>8);//发送高地址
}else
{
IIC_Send_Byte(EEPROM_TYPE.addr+((WriteAddr/256)<<1)); //发送器件地址,写数据
}
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Send_Byte(DataToWrite);
IIC_Wait_Ack();
IIC_Stop();
HAL_Delay(10);
}
/**
* @brief 向指定地址写入16位或32位数据
* @param WriteAddr 数据地址
* @param DataToWrite 要写入的数据
* @param Len 2字节或4字节
*/
void AT24CXX_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len)
{
uint8_t t;
for(t=0;t<Len;t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}
/**
* @brief 从指定地址读取16位或32位的数据
* @param WriteAddr 数据地址
* @param Len 2字节或4字节
* @retval 读出的数据
*/
uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len)
{
uint8_t t;
uint32_t temp=0;
for(t=0;t<Len;t++)
{
temp<<=8;
temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
}
return temp;
}
/**
* @brief 检查EEPROM是否正常
* @retval 1-检测失败 0-成功
*/
uint8_t AT24CXX_Check(void)
{
uint8_t temp;
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
else//排除第一次初始化的情况
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}
/**
* @brief 连续读
* @param addr 数据地址
* @param data 存储位置
* @param length 读取长度
*
*/
void AT24CXX_SequentialRead(uint16_t addr,uint8_t *data,uint16_t length)
{
if (length == 0)
{
return;
}
IIC_Start();
if(EEPROM_TYPE.size>2048)
{
IIC_Send_Byte(EEPROM_TYPE.addr); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(addr>>8);//发送高地址
}else
{
IIC_Send_Byte(EEPROM_TYPE.addr+((addr/256)<<1)); //发送器件地址,写数据
}
IIC_Wait_Ack();
IIC_Send_Byte(addr%256); //发送低地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(EEPROM_TYPE.addr+1);
IIC_Wait_Ack();
*data++=IIC_Read_Byte(0);
while(--length)
{
*data++=IIC_Read_Byte(0);
}
IIC_Stop();
}
/**
* @brief 页写
* @param addr 数据地址
* @param data 数据指针
* @param length 要写入数据的个数
*/
void AT24CXX_PageWrite(uint16_t addr,uint8_t *data,uint16_t length)
{
if (length==0 || addr>=EEPROM_TYPE.size)
{
return;
}
IIC_Start();
if(EEPROM_TYPE.size>2048)
{
IIC_Send_Byte(EEPROM_TYPE.addr); // 发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(addr>>8); // 发送高地址
}else
{
IIC_Send_Byte(EEPROM_TYPE.addr+((addr/256)<<1)); //发送器件地址,写数据
}
IIC_Wait_Ack();
IIC_Send_Byte(addr%256); // 发送低地址
IIC_Wait_Ack();
for (uint16_t i = 0; i < length; i++)
{
IIC_Send_Byte(data[i]);
IIC_Wait_Ack();
addr++;
if (addr >= EEPROM_TYPE.size) // 内存已满
{
break;
}
if ((addr)%EEPROM_TYPE.pageSize == 0) // 满页
{
IIC_Stop();
HAL_Delay(10);
IIC_Start();
if(EEPROM_TYPE.size>2048)
{
IIC_Send_Byte(EEPROM_TYPE.addr); // 发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(addr>>8); // 发送高地址
}else
{
IIC_Send_Byte(EEPROM_TYPE.addr+((addr/256)<<1)); //发送器件地址,写数据
}
IIC_Wait_Ack();
IIC_Send_Byte(addr%256); // 发送低地址
IIC_Wait_Ack();
}
}
IIC_Stop();
HAL_Delay(10);
}
#elif
#endif
BSP_AT24CXX.h
cpp
/** BSP_AT24CXX.h EEPROM AT24CXX/FM24CXX驱动
*
* @author Dai Zu<zhangruilin@163.com>
* @date 2024/4/10
*
*/
#ifndef __BSP_AT24Cxx_H
#define __BSP_AT24Cxx_H
#include "main.h"
uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr);
void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite);
void AT24CXX_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len);
uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len);
void AT24CXX_SequentialRead(uint16_t addr,uint8_t *data,uint16_t length);
void AT24CXX_PageWrite(uint16_t addr,uint8_t *data,uint16_t length);
uint8_t AT24CXX_Check(void);
void AT24CXX_Init(void);
#endif