第一次接触AT24C32这类串行EEPROM时,我完全被它的引脚排列搞懵了。这个看起来只有8个引脚的小芯片,居然能存储32Kbit数据(相当于4KB),而且断电后数据还能保存100年。在实际项目中,我经常用它来保存设备参数、用户设置和运行日志,比用单片机内部Flash方便多了。
AT24C32属于AT24CXX系列存储器,这个系列从1Kbit的AT24C01到512Kbit的AT24C512都有。它们引脚兼容但容量不同,就像亲兄弟一样。我特别喜欢它的I2C接口设计,只需要两根线(SCL和SDA)就能通信,特别适合引脚资源紧张的STM32这类单片机。不过要注意,不同容量的芯片在地址处理上有些差异,后面我会详细说明。
说到技术参数,有几点特别实用:工作电压范围2.7V-5.5V(3.3V和5V系统都能用),写周期寿命10万次(够普通应用折腾好几年),待机电流只有1μA(电池供电设备的最爱)。这些参数在选型时特别重要,我有次做低功耗设备就靠它省下了不少电量。
先来看看这个8脚小家伙的引脚定义(以DIP封装为例):
这是我验证过的稳定电路方案:
circuit复制VCC ----+---[4.7k]---+--- SDA
| |
[0.1μF] MCU
| |
GND ----+---+--------+--- GND
|
[4.7k]
|
SCL
特别注意:如果总线长度超过30cm,要减小上拉电阻值;多个设备并联时,总线上拉电阻要重新计算。我有次在工业现场因为线路太长导致通信失败,后来把电阻换成2.2kΩ就稳定了。
AT24C32的7位设备地址格式是:1010(A2)(A1)(A0)。比如A2-A0都接地时,写地址是0xA0,读地址是0xA1。这里有个坑:地址引脚悬空会被认为是高电平!我有块板子因为这个原因导致地址错误,后来全部加上下拉电阻才解决。
对于大容量芯片(如AT24C32),还需要发送内存地址的高字节。小容量芯片(如AT24C02)直接用地址低字节就行。驱动代码里要区分处理,不然会写入错误位置。
写周期最需要注意:每次写入后要等5ms(max)才能进行下次操作。我有次连续写入没加延迟,数据就丢失了。现在我的做法是:
c复制void EE_Delay(void) {
uint32_t timeout = 5000; // 5ms
while(timeout--);
}
读操作分两种:
时序波形要特别注意起止条件:
先实现最基本的I2C操作函数,以STM32 HAL库为例:
c复制void EE_IIC_Start(void) {
HAL_I2C_Master_Transmit(&hi2c1, devAddr, 0, 0, 100);
}
uint8_t EE_IIC_Wait_Ack(void) {
return HAL_I2C_IsDeviceReady(&hi2c1, devAddr, 1, 100) == HAL_OK;
}
void EE_IIC_Send_Byte(uint8_t dat) {
HAL_I2C_Master_Transmit(&hi2c1, devAddr, &dat, 1, 100);
}
uint8_t EE_IIC_Read_Byte(uint8_t ack) {
uint8_t data;
HAL_I2C_Master_Receive(&hi2c1, devAddr|1, &data, 1, 100);
if(!ack) EE_IIC_Stop();
return data;
}
单字节写入函数要特别注意页写入限制(32字节一页):
c复制void AT24CXX_WriteOneByte(uint16_t addr, uint8_t data) {
uint8_t buf[2];
if(EE_SIZE > 2048) { // AT24C32及以上
buf[0] = addr >> 8;
buf[1] = addr & 0xFF;
HAL_I2C_Mem_Write(&hi2c1, EE_DEV_ADDR, addr, I2C_MEMADD_SIZE_16BIT, &data, 1, 100);
} else { // AT24C16及以下
HAL_I2C_Mem_Write(&hi2c1, EE_DEV_ADDR, addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
}
HAL_Delay(5); // 必须的写入等待
}
多字节读取有个实用技巧:使用HAL库的连续读取模式能大幅提升速度:
c复制void AT24CXX_ReadBytes(uint16_t addr, uint8_t *buf, uint16_t len) {
if(EE_SIZE > 2048) {
HAL_I2C_Mem_Read(&hi2c1, EE_DEV_ADDR|1, addr, I2C_MEMADD_SIZE_16BIT, buf, len, 100);
} else {
HAL_I2C_Mem_Read(&hi2c1, EE_DEV_ADDR|1, addr, I2C_MEMADD_SIZE_8BIT, buf, len, 100);
}
}
EEPROM偶尔会出现位翻转,我有三个常用校验方案:
c复制uint8_t Calc_CRC8(uint8_t *data, uint8_t len) {
uint8_t crc = 0xFF;
while(len--) {
crc ^= *data++;
for(uint8_t i=0; i<8; i++)
crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : crc << 1;
}
return crc;
}
避免频繁写入同一位置的两个方法:
我的日志存储方案是采用环形缓冲区:
c复制#define LOG_SIZE 1024
typedef struct {
uint16_t head;
uint16_t tail;
uint8_t data[LOG_SIZE];
} LogBuffer;
void Save_Log(LogBuffer *log) {
AT24CXX_WriteBytes(0, (uint8_t*)log, sizeof(LogBuffer));
}
我的调试三件套:
这里分享一个实用的调试函数:
c复制void Print_EEPROM(uint16_t addr, uint16_t len) {
uint8_t buf[16];
while(len > 0) {
uint8_t rd_len = len > 16 ? 16 : len;
AT24CXX_ReadBytes(addr, buf, rd_len);
printf("%04X: ", addr);
for(uint8_t i=0; i<rd_len; i++)
printf("%02X ", buf[i]);
printf("\n");
addr += rd_len;
len -= rd_len;
}
}
在实际项目中,AT24C32的表现非常稳定。有次产品在-40℃到85℃的环境下运行三年,存储的数据依然完好。不过要注意,长期使用的EEPROM最好定期做数据校验,我一般会在系统启动时自动检查关键数据的CRC值。