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卡,那又是另一个故事了。