4. EEPROM与FRAM:非易失性存储的两种选择
大家好,我是老张。今天我们来聊聊嵌入式系统里两个重要的存储器件——EEPROM和FRAM。说实话,这两个东西我用了十几年,踩过的坑还真不少。EEPROM大家可能比较熟悉,FRAM相对新一些,但各有各的脾气。
4.1 EEPROM工作原理
EEPROM,全称是电可擦除可编程只读存储器。名字挺长,说白了就是:掉电不丢数据,还能用电擦写。
它的核心结构是浮栅晶体管。每个存储单元里有个「浮置栅极」,被绝缘层包裹着。写入数据时,通过施加高电压,让电子穿过绝缘层注入浮栅。电子一旦进去,就被「关」在里面了——断电也不会跑掉。这就是非易失性的秘密。
关键点:EEPROM的擦写是以字节为单位的。这一点和Flash不一样,Flash通常要按扇区擦除。所以EEPROM适合存那些需要频繁修改的小数据,比如设备配置参数、校准值等。
我在项目中遇到过一个问题:某款工业传感器,每次上电都要读取上次的校准参数。用的就是EEPROM。但后来发现,写入次数多了,某些字节就「卡死」了——怎么擦都擦不掉。嗯,这就是EEPROM的寿命问题。
注意:EEPROM的擦写寿命通常在10万到100万次之间。别小看这个数字,如果你每秒写一次,几天就废了。我建议:非必要不写入,写入前先判断数据是否真的变了。
4.2 I2C接口EEPROM驱动
I2C接口的EEPROM,最常见的就是AT24C系列。比如AT24C02,容量256字节,I2C地址0x50。驱动起来其实不复杂,但有几个细节要注意。
先看一个基本的写操作流程:
// I2C EEPROM 单字节写入
uint8_t eeprom_write_byte(uint16_t addr, uint8_t data) {
uint8_t buf[2];
buf[0] = (uint8_t)(addr >> 8); // 高地址
buf[1] = (uint8_t)(addr & 0xFF); // 低地址
// 发送设备地址 + 写命令
i2c_start();
i2c_write(0xA0); // 设备地址,最后一位为0表示写
i2c_wait_ack();
// 发送存储地址
i2c_write(buf[0]);
i2c_wait_ack();
i2c_write(buf[1]);
i2c_wait_ack();
// 发送数据
i2c_write(data);
i2c_wait_ack();
i2c_stop();
// 等待内部写完成(关键!)
delay_ms(5); // AT24C02典型写周期5ms
return 0;
}
这里有个坑:写周期等待。EEPROM写完数据后,内部需要时间把数据真正「固化」到存储单元里。这段时间内,芯片不响应任何命令。我曾经犯过一个错误——连续写入时没加延时,结果数据全丢了。后来我改用查询ACK的方式:写完一字节后,不断发送设备地址,直到收到ACK为止。这样效率更高。
我的习惯:写EEPROM驱动时,我会封装一个「页写入」函数。AT24C02支持一次写8字节(一页),这样能大幅提升写入速度。但要注意:页内地址不能跨页,否则会回卷覆盖。
读操作就简单多了:
// I2C EEPROM 随机读取
uint8_t eeprom_read_byte(uint16_t addr) {
uint8_t data;
// 先发送要读的地址
i2c_start();
i2c_write(0xA0);
i2c_wait_ack();
i2c_write((uint8_t)(addr >> 8));
i2c_wait_ack();
i2c_write((uint8_t)(addr & 0xFF));
i2c_wait_ack();
// 重新发送起始条件,改为读模式
i2c_start();
i2c_write(0xA1); // 最后一位为1表示读
i2c_wait_ack();
// 读取数据,发送NACK表示结束
data = i2c_read_nack();
i2c_stop();
return data;
}
4.3 SPI接口EEPROM驱动
SPI接口的EEPROM,比如25AA系列,速度比I2C快不少。但接线多了两根——MISO、MOSI、SCK、CS,一共4根线。
SPI EEPROM的指令集很简洁:
| 指令 | 操作码 | 说明 |
|---|---|---|
| WREN | 0x06 | 写使能 |
| WRDI | 0x04 | 写禁止 |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 读数据 |
| WRITE | 0x02 | 写数据 |
写数据前,必须先发WREN指令使能写操作。这个设计是为了防止误写。我见过有人忘了这一步,折腾了半天才发现——嗯,我也干过这种事。
// SPI EEPROM 页写入示例
void spi_eeprom_page_write(uint16_t addr, uint8_t *data, uint8_t len) {
// 写使能
cs_low();
spi_transfer(0x06); // WREN
cs_high();
// 开始写入
cs_low();
spi_transfer(0x02); // WRITE
spi_transfer((uint8_t)(addr >> 8));
spi_transfer((uint8_t)(addr & 0xFF));
for(uint8_t i = 0; i < len; i++) {
spi_transfer(data[i]);
}
cs_high();
// 等待写完成
while(1) {
cs_low();
spi_transfer(0x05); // RDSR
uint8_t status = spi_transfer(0x00);
cs_high();
if(!(status & 0x01)) break; // WIP位为0表示完成
}
}
速度对比:SPI EEPROM的时钟可以跑到20MHz甚至更高,而I2C通常只有400kHz。如果你需要频繁读写大量数据,SPI是更好的选择。但I2C胜在接线少,只需要两根线。
4.4 FRAM的优势与应用场景
FRAM,铁电存储器,这玩意儿我第一次接触是在一个医疗设备项目里。当时客户要求数据写入次数要超过100亿次,EEPROM根本扛不住。后来用了FRAM,问题迎刃而解。
FRAM的核心技术是铁电晶体。它利用铁电材料的极化方向来存储数据,而不是像EEPROM那样存储电荷。这就带来了几个巨大的优势:
- 写入速度快:FRAM的写入时间只有几十纳秒,而EEPROM需要几毫秒。差距是十万倍级别。
- 几乎无限的寿命:FRAM的擦写次数可达10^12次以上,也就是一万亿次。你想想看,就算每秒写一次,也能用三万年。
- 低功耗:FRAM的写入功耗远低于EEPROM,特别适合电池供电的设备。
- 无需写周期等待:写入即完成,不像EEPROM那样要等内部固化。
| 特性 | EEPROM | FRAM |
|---|---|---|
| 写入速度 | ~5ms | ~50ns |
| 擦写寿命 | 10万~100万次 | 10^12次以上 |
| 写入功耗 | 较高 | 极低 |
| 写周期等待 | 需要 | 不需要 |
| 成本 | 低 | 较高 |
| 容量 | 可达2Mb | 通常64Kb~4Mb |
FRAM最适合的场景:
- 数据采集系统:需要频繁记录传感器数据,比如工业数据记录仪。我用FRAM做过一个振动监测设备,每秒记录100组数据,跑了两年没出问题。
- 智能电表:需要频繁存储电量数据,而且要求数据不丢失。FRAM的快速写入和无限寿命简直是绝配。
- 医疗设备:比如胰岛素泵,需要频繁记录用药历史。EEPROM的寿命根本不够用。
- 汽车电子:黑匣子、事件记录器等,要求高可靠性和快速写入。
我的建议:如果你的产品只需要存一些配置参数,一年改不了几次,用EEPROM就够了,便宜又成熟。但如果你的设备需要频繁记录数据,或者对写入速度有要求,别犹豫,上FRAM。虽然贵一点,但省心太多了。
4.5 避坑指南
最后分享几个我踩过的坑:
- 写保护引脚:很多EEPROM和FRAM都有WP(写保护)引脚。我曾经因为WP引脚悬空,导致数据写不进去。查了两天才发现——嗯,低级错误。
- 电源去耦:存储芯片对电源噪声很敏感。我建议在VCC和GND之间放一个0.1μF的陶瓷电容,尽量靠近芯片引脚。
- 地址冲突:I2C总线上多个设备时,注意地址不要冲突。AT24C系列可以通过A0/A1/A2引脚配置地址,最多可以挂8片。
- 数据校验:重要数据建议加CRC校验。EEPROM和FRAM虽然可靠性高,但也不能完全信任。我在一个项目里就遇到过FRAM的位翻转问题,加了校验后才放心。
好了,关于EEPROM和FRAM就聊到这里。这两种存储器件各有千秋,选型时一定要根据实际需求来。下一章我们聊聊NAND Flash和SD卡,那又是另一个故事了。