当前位置: 首页 > web >正文

EEPROM库详解

EEPROM

EEPROM 地址空间:

  • 每个字节有唯一地址(从 0 开始),例如 ATmega328P 的地址范围是 0~1023(共 1KB)。
  • 不同型号的 Arduino 板 EEPROM 大小不同(如 Mega2560 为 4KB,地址 0~4095)。

关键函数说明

EEPROM.read(address)

  • 功能:读取指定地址的字节数据。

  • 参数:

    • address:EEPROM 地址(int 类型,范围 0~EEPROM 大小 - 1)。
  • 返回值:

    • 成功:该地址存储的字节值(byte 类型)。
    • 失败:若地址超出范围,返回值未定义(可能为 0xFF)。
  • 示例:

    byte value = EEPROM.read(0);  // 读取地址 0 的值
    

EEPROM.write(address, value)

  • 功能:向指定地址写入字节数据。

  • 参数:

    • address:目标地址(int)。
    • value:要写入的字节值(byte)。 1 字节的取值范围是 0~255(无符号整数)或 -128~127(有符号整数)
  • 注意事项:

    • 写入耗时:约 3.3ms(ATmega328P),期间 Arduino 暂停执行。
    • 擦写次数限制:约 100,000 次,频繁写入会缩短寿命。
    • 仅当值不同时写入:若新值与当前值相同,不会执行写入操作,可减少磨损。
  • 示例:

    EEPROM.write(0, 65);  // 在地址 0 写入值 65(即 ASCII 'A')
    

EEPROM.update(address, value)

  • 功能:与 write() 类似,但仅在新值与当前值不同时执行写入,可减少不必要的擦写。

  • 参数:同 write()

  • 示例:

    EEPROM.update(0, 65);  // 仅当地址 0 的值不等于 65 时写入
    

EEPROM.get(address, variable)

  • 功能:读取指定地址的数据到变量,支持任意数据类型(自动处理多字节)。

  • 参数:

    • address:起始地址(int)。
    • variable:目标变量的引用(如 int num; EEPROM.get(0, num);)。
  • 支持的数据类型:

    • 基本类型:byteintfloatdouble 等。
    • 自定义类型:结构体、数组等(需确保地址空间足够)。
  • 示例:

    int number;
    EEPROM.get(0, number);  // 从地址 0 开始读取一个 int(占用 2 字节)struct Config {float tempOffset;  // 4 字节bool enabled;      // 1 字节
    };Config settings;
    EEPROM.get(2, settings);  // 从地址 2 开始读取结构体(共 5 字节)
    

EEPROM.put(address, variable)

  • 功能:将变量的值写入指定地址,支持任意数据类型。

  • 参数:同 get()

  • 示例:

    int number = 1234;
    EEPROM.put(0, number);  // 在地址 0 写入 int(占用 2 字节)Config settings = {25.5, true};
    EEPROM.put(2, settings);  // 在地址 2 写入结构体
    

EEPROM.length()

  • 功能:返回 EEPROM 的总大小(字节数)。

  • 返回值:

    • ATmega328P:1024
    • ATmega2560:4096
  • 示例:

    int size = EEPROM.length();  // 获取 EEPROM 总大小
    

注意

  1. 擦写寿命限制
    • 虽然 EEPROM 支持约 100,000 次擦写,但频繁写入(如在loop()中循环写入)仍可能导致磨损。建议:
      • 仅在必要时写入数据(如配置变更时)。
      • 对高频数据(如传感器读数)优先使用 RAM 缓存,定期批量写入 EEPROM。
  2. 数据类型与对齐
    • 使用 EEPROM.put()/get() 处理非字节数据(如 intfloat)时,需确保地址对齐(例如,int 占用 2 字节,应从偶数地址开始写入)。
  3. 原子性问题
    • EEPROM 写入操作需要约 3.3ms(ATmega328P),期间若发生中断(如复位),可能导致数据不完整。建议:
      • 写入关键数据前禁用中断(noInterrupts()),完成后恢复(interrupts())。

示例

#include <EEPROM.h>void setup() {// 写入数据到 EEPROM 地址 0EEPROM.write(0, 'A');         // 写入单个字节EEPROM.put(1, 12345);         // 写入整数(自动处理多字节)EEPROM.put(5, "Hello");       // 写入字符串// 读取数据char c = EEPROM.read(0);      // 读取单个字节int num;EEPROM.get(1, num);           // 读取整数char str[6];EEPROM.get(5, str);           // 读取字符串Serial.begin(9600);Serial.print("Char: "); Serial.println(c);Serial.print("Number: "); Serial.println(num);Serial.print("String: "); Serial.println(str);
}void loop() {// 主循环
}

扩展EEPROM

1.I²C 接口 EEPROM 芯片

连接外部 EEPROM 芯片(如 AT24C32、AT24C256)

24C256为256Kbit,32KByte,具体容量看手册

使用 Wire.h 库通信

单个函数

byte读写


void writeEEPROM(int addr, byte data) {Wire.beginTransmission(EEPROM_ADDR);Wire.write((int)(addr >> 8));  // 发送高地址字节Wire.write((int)(addr & 0xFF)); // 发送低地址字节Wire.write(data);              // 发送数据Wire.endTransmission();delay(5);  // 等待写入完成
}byte readEEPROM(int addr) {byte data = 0xFF;Wire.beginTransmission(EEPROM_ADDR);Wire.write((int)(addr >> 8));  // 发送高地址字节Wire.write((int)(addr & 0xFF)); // 发送低地址字节Wire.endTransmission();Wire.requestFrom(EEPROM_ADDR, 1);if (Wire.available()) data = Wire.read();return data;
}

在 I2C 通信中,确实需要先指定地址,再写入数据。这正是代码中 Wire.write((int)(addr >> 8));Wire.write((int)(addr & 0xFF)); 的作用 —— 它们是在设置目标地址,而非直接写入数据。

一、I2C EEPROM 的通信流程

I2C EEPROM 的写入过程分为两个阶段:

  1. 设置地址阶段:告诉 EEPROM “我接下来要操作哪个内存地址”。
  2. 传输数据阶段:发送真正要写入的数据。

代码中的执行顺序完全符合这个流程:

Wire.beginTransmission(EEPROM_ADDR);  // 1. 开始与EEPROM通信
Wire.write((int)(addr >> 8));         // 2. 发送地址的高8位
Wire.write((int)(addr & 0xFF));       // 3. 发送地址的低8位
Wire.write(data);                     // 4. 发送要写入的数据
Wire.endTransmission();               // 5. 结束通信

二、为什么需要拆分地址?

EEPROM 的地址空间通常很大(例如:24LC256 芯片有 32,768 字节 = 2^15),需要 16 位(2 字节)才能完整表示。但 I2C 协议每次只能传输 8 位数据,因此必须将 16 位地址拆分成两个 8 位字节分别发送。

  • 高地址字节:表示地址的高位部分(范围:0-255)。
  • 低地址字节:表示地址的低位部分(范围:0-255)。

例如,地址0x1234需要拆分为:

  • 高地址字节:0x12(二进制0001 0010
  • 低地址字节:0x34(二进制0011 0100

三、代码详解

  1. Wire.write((int)(addr >> 8));
  • addr >> 8:将地址右移 8 位,丢弃低 8 位,只保留高 8 位。
    例如:addr = 0x12340x1234 >> 8 = 0x12
  • (int):强制类型转换为int(实际转换为uint8_t更合适,但 Arduino 的 Wire 库会自动截断为 8 位)。
  • 作用:发送地址的高 8 位字节。
  1. Wire.write((int)(addr & 0xFF));
  • addr & 0xFF:将地址与0xFF(二进制1111 1111)按位与,只保留低 8 位。
    例如:addr = 0x12340x1234 & 0xFF = 0x34
  • (int):同样为强制类型转换(实际发送时会截断为 8 位)。
  • 作用:发送地址的低 8 位字节。

四、为什么要这样设计?

这是由 I2C 协议和 EEPROM 的通信机制决定的:

  1. I2C 协议限制:每次只能发送 1 个字节的数据,因此 16 位地址必须分两次发送。
  2. EEPROM 寻址方式:大多数 I2C EEPROM 要求先发送高地址字节,再发送低地址字节,最后发送数据。

五、常见问题

1. 为什么不直接发送addr

  • addr是 16 位整数(2 字节),而Wire.write()只能发送 8 位数据。直接发送会导致数据截断,丢失高 8 位信息。

2. 能否交换高低字节的发送顺序?

  • 不能。EEPROM 严格要求先接收高地址字节,再接收低地址字节。顺序错误会导致寻址错误。

3. 为什么需要(int)强制类型转换?

  • 严格来说,这里应转换为(uint8_t)以明确指定 8 位类型。但 Arduino 的 Wire 库会自动处理类型转换,因此(int)也能正常工作(但可能引发编译器警告)。

六、优化建议

为提高代码可读性和安全性,建议修改为:

Wire.write((uint8_t)(addr >> 8));   // 高地址字节(明确转换为8位)
Wire.write((uint8_t)(addr & 0xFF)); // 低地址字节(明确转换为8位)

string读写


void writeString(int addr, const char* str) {int i = 0;while (str[i] != '\0') {  // 循环直到字符串结束符writeEEPROM(addr + i, str[i]);  // 逐个字节写入i++;}writeEEPROM(addr + i, '\0');  // 写入字符串结束符
}void readString(int addr, char* buffer, int maxLength) {int i = 0;char c;while (i < maxLength - 1) {  // 防止缓冲区溢出c = readEEPROM(addr + i);if (c == '\0') break;  // 遇到结束符则停止buffer[i] = c;i++;}buffer[i] = '\0';  // 确保字符串以 '\0' 结尾
}-----------------// 写入字符串
writeString(10, "Hello");  // 从地址 10 开始写入 "Hello"// 读取字符串
char buffer[20];
readString(10, buffer, 20);  // 从地址 10 读取字符串到 buffer
Serial.println(buffer);  // 输出: Hello

int读写

void writeInt(int addr, int value) {writeEEPROM(addr, (value >> 0) & 0xFF);  // 低字节writeEEPROM(addr + 1, (value >> 8) & 0xFF);  // 高字节
}int readInt(int addr) {int low = readEEPROM(addr);int high = readEEPROM(addr + 1);return (high << 8) | low;
}

float读写

void writeFloat(int addr, float value) {byte* ptr = (byte*)&value;for (int i = 0; i < 4; i++) {writeEEPROM(addr + i, ptr[i]);  // 逐个字节写入}
}float readFloat(int addr) {float value;byte* ptr = (byte*)&value;for (int i = 0; i < 4; i++) {ptr[i] = readEEPROM(addr + i);  // 逐个字节读取}return value;
}
注意事项

地址空间规划

  • 写入多字节数据时,需确保地址空间足够且不重叠。例如:

    writeString(0, "Hello");  // 占用地址 0~5(包含 '\0')
    writeInt(10, 1234);      // 从地址 10 开始写入,避免冲突
    

字符串长度限制

  • 读取字符串时需提供缓冲区大小,防止溢出:

    char buffer[10];
    readString(0, buffer, 10);  // 最多读取 9 个字符 + '\0'

字节序(Endianness)

  • 写入多字节数据(如 intfloat)时,需注意字节序(大端 / 小端)。上述示例为 小端序(低字节在前)。

在计算机中,多字节数据(如 intfloat 等占 2 字节以上的数据类型)在内存或存储设备中的字节排列顺序称为 字节序(Endianness)。字节序主要分为两种:大端序(Big-Endian)和小端序(Little-Endian)。以下结合示例详细说明:

字节序的核心区别

假设要存储一个 16 位整数 0x1234(二进制 00010010 00110100):

字节序类型内存 / 存储中的字节顺序(低地址 → 高地址)直观表示
大端序先存高位字节(0x12),再存低位字节(0x34[12][34]
小端序先存低位字节(0x34),再存高位字节(0x12[34][12]

示例代码中的小端序问题

在你提供的示例代码中,当写入 intfloat 等多字节数据时(例如通过 (uint8_t*)&data 强制类型转换),默认使用的是小端序,原因如下:

1. 小端序的本质:低字节在前

以 32 位整数 0x12345678 为例:

  • 内存中的二进制表示(小端序):
    低地址 → 高地址:0x78(最低位字节)→ 0x560x340x12(最高位字节)
    存储顺序[78][56][34][12]
  • 代码中的体现
    当通过 writePage 写入 int 类型变量时,会将其强制转换为 uint8_t 数组,按字节顺序逐个写入 EEPROM。由于 Arduino 开发板(如 AVR、ESP32 等)通常使用 小端序架构,转换后的字节数组会按 低字节在前 的顺序存储。

2. 大端序与小端序的兼容性问题

  • 如果接收方(如其他单片机、上位机软件)使用 大端序 解析数据,直接读取小端序存储的字节会导致错误。
    :存储 int a = 0x1234(小端序存储为 [34][12]),若按大端序解析会被误读为 0x3412

如何处理字节序问题?

为确保多字节数据在不同设备间正确解析,需显式指定字节序。以下是两种常见方案:

1. 统一使用大端序(网络序)

适用场景:跨设备通信(如通过网络传输数据)。
实现方法:手动将数据转换为大端序后再写入。

// 将 16 位整数转换为大端序字节数组
void int16ToBigEndian(uint16_t value, uint8_t* buffer) {buffer[0] = (value >> 8) & 0xFF;  // 高位字节(大端序优先)buffer[1] = value & 0xFF;          // 低位字节
}// 示例:写入大端序的 int16 数据
uint16_t number = 0x1234;
uint8_t buffer[2];
int16ToBigEndian(number, buffer);
eeprom.writePage(0, buffer, 2);  // 存储为 [12][34](大端序)

2. 读写时保持一致的字节序

适用场景:同一设备(或同架构设备)的读写操作。
原则:写入时按小端序存储,读取时也按小端序解析。

// 写入小端序的 int32 数据
uint32_t value = 0x12345678;
eeprom.writePage(0, (uint8_t*)&value, 4);  // 存储为 [78][56][34][12]// 读取时按小端序解析
uint32_t readValue = 0;
for (int i = 0; i < 4; i++) {((uint8_t*)&readValue)[i] = eeprom.readByte(i);  // 按顺序填充低到高字节
}
// 解析结果:readValue = 0x12345678(正确)

为什么 Arduino 默认使用小端序?

  • 硬件架构:Arduino 常用的 AVR 芯片(如 ATmega328P)和 ESP32 均为 小端序架构,内存中多字节数据按小端序存储。
  • 强制类型转换的本质:通过 (uint8_t*)&data 直接读取变量的字节时,会按硬件架构的字节序获取数据。

总结:关键操作建议

  1. 明确需求
    • 若数据仅在同一设备读写,可直接使用小端序(与 Arduino 架构一致)。
    • 若涉及跨设备通信或大端序系统(如某些嵌入式处理器、网络协议),需手动转换字节序。
  2. 避免隐式转换
    多字节数据写入 EEPROM 时,尽量通过自定义函数显式处理字节序,避免因架构差异导致的兼容性问题。
  3. 测试验证
    写入后通过串口打印或其他设备读取字节,确认存储顺序是否符合预期。

通过显式处理字节序,可确保多字节数据在存储和传输过程中保持一致性。

EEPROM_I2C.H库封装

通过封装函数,可实现字符串和其他数据类型的读写,但本质仍是通过多次调用单字节读写函数完成。使用时需注意地址规划、字符串结束符和缓冲区大小。

#ifndef EEPROM_I2C_H
#define EEPROM_I2C_H#include <Wire.h>class EEPROM_I2C {
private:uint8_t deviceAddress;  // EEPROM 设备地址uint32_t pageSize;      // 页大小(用于页写入优化)uint32_t maxAddress;    // 最大地址public:// 构造函数EEPROM_I2C(uint8_t addr, uint32_t pageSize = 64, uint32_t maxAddr = 0xFFFF): deviceAddress(addr), pageSize(pageSize), maxAddress(maxAddr) {}// 初始化 I2C 总线void begin() {Wire.begin();}// 写入单个字节void writeByte(uint16_t address, uint8_t value) {if (address > maxAddress) return;Wire.beginTransmission(deviceAddress);Wire.write((uint8_t)(address >> 8));  // 高地址字节Wire.write((uint8_t)(address & 0xFF)); // 低地址字节Wire.write(value);Wire.endTransmission();delay(5);  // EEPROM 写入需要时间}// 读取单个字节uint8_t readByte(uint16_t address) {if (address > maxAddress) return 0xFF;Wire.beginTransmission(deviceAddress);Wire.write((uint8_t)(address >> 8));  // 高地址字节Wire.write((uint8_t)(address & 0xFF)); // 低地址字节Wire.endTransmission(false);  // 保持连接Wire.requestFrom(deviceAddress, (uint8_t)1);if (Wire.available()) {return Wire.read();}return 0xFF;}// 写入字符串void writeString(uint16_t address, const char* str) {uint16_t i = 0;while (str[i] != '\0' && (address + i) <= maxAddress) {writeByte(address + i, str[i]);i++;}if ((address + i) <= maxAddress) {writeByte(address + i, '\0');  // 写入字符串结束符}}// 读取字符串uint16_t readString(uint16_t address, char* buffer, uint16_t maxLength) {uint16_t i = 0;char c;while (i < maxLength - 1 && (address + i) <= maxAddress) {c = readByte(address + i);if (c == '\0') break;  // 遇到字符串结束符buffer[i] = c;i++;}buffer[i] = '\0';  // 确保字符串以 '\0' 结尾return i;  // 返回实际读取的字符数}// 写入整数void writeInt(uint16_t address, int value) {if (address + 1 > maxAddress) return;writeByte(address, (uint8_t)(value & 0xFF));        // 低字节writeByte(address + 1, (uint8_t)((value >> 8) & 0xFF));  // 高字节}// 读取整数int readInt(uint16_t address) {if (address + 1 > maxAddress) return 0;uint8_t low = readByte(address);uint8_t high = readByte(address + 1);return (high << 8) | low;}// 写入浮点数void writeFloat(uint16_t address, float value) {if (address + 3 > maxAddress) return;uint8_t* ptr = (uint8_t*)&value;for (int i = 0; i < 4; i++) {writeByte(address + i, ptr[i]);}}// 读取浮点数float readFloat(uint16_t address) {if (address + 3 > maxAddress) return 0.0f;float value;uint8_t* ptr = (uint8_t*)&value;for (int i = 0; i < 4; i++) {ptr[i] = readByte(address + i);}return value;}// 页写入(优化批量写入)void writePage(uint16_t address, const uint8_t* data, uint16_t length) {if (address + length > maxAddress) return;uint16_t bytesWritten = 0;while (bytesWritten < length) {// 计算当前页可写入的字节数uint16_t pageOffset = address % pageSize;uint16_t bytesThisPage = min(length - bytesWritten, pageSize - pageOffset);Wire.beginTransmission(deviceAddress);Wire.write((uint8_t)(address >> 8));  // 高地址字节Wire.write((uint8_t)(address & 0xFF)); // 低地址字节for (uint16_t i = 0; i < bytesThisPage; i++) {Wire.write(data[bytesWritten + i]);}Wire.endTransmission();delay(5);  // 等待页写入完成address += bytesThisPage;bytesWritten += bytesThisPage;}}
};#endif // EEPROM_I2C_H

调用

#include <Wire.h>
#include "EEPROM_I2C.h"// 创建 EEPROM 对象 (地址 0x50, 页大小 64 字节, 最大地址 0x7FFF = 32767)
EEPROM_I2C eeprom(0x50, 64, 0x7FFF);void setup() {Serial.begin(115200);eeprom.begin();// 写入数据eeprom.writeByte(0, 0xAA);                     // 写入单个字节eeprom.writeString(10, "Hello, EEPROM!");      // 写入字符串eeprom.writeInt(50, 12345);                    // 写入整数eeprom.writeFloat(100, 3.14159f);              // 写入浮点数// 读取数据uint8_t byteValue = eeprom.readByte(0);char stringBuffer[20];eeprom.readString(10, stringBuffer, 20);int intValue = eeprom.readInt(50);float floatValue = eeprom.readFloat(100);// 输出结果Serial.print("Byte value: 0x");Serial.println(byteValue, HEX);Serial.print("String value: ");Serial.println(stringBuffer);Serial.print("Int value: ");Serial.println(intValue);Serial.print("Float value: ");Serial.println(floatValue, 5);
}void loop() {// 主循环可以留空
}

writePage()

#include <Wire.h>
#include "EEPROM_I2C.h"// 创建 EEPROM 对象 (地址 0x50, 页大小 64 字节, 最大地址 32767)
EEPROM_I2C eeprom(0x50, 64, 0x7FFF);void setup() {Serial.begin(115200);eeprom.begin();// 示例1:写入一个小数组uint8_t data1[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};eeprom.writePage(0, data1, 10);  // 从地址0开始写入10字节// 示例2:写入一个大数据块(跨页写入)uint8_t data2[100];for (int i = 0; i < 100; i++) {data2[i] = i;  // 填充0~99的数据}eeprom.writePage(100, data2, 100);  // 从地址100开始写入100字节(跨多个页)// 验证读取Serial.println("Reading back data...");// 读取第一个数组Serial.print("Data1: ");for (int i = 0; i < 10; i++) {Serial.print(eeprom.readByte(i));Serial.print(" ");}Serial.println();// 读取第二个数组Serial.print("Data2: ");for (int i = 0; i < 100; i++) {if (i % 20 == 0 && i > 0) Serial.println();  // 每20个字节换行Serial.print(eeprom.readByte(100 + i));Serial.print(" ");}Serial.println();
}void loop() {// 无需循环操作
}

结构体数据读写

struct SensorData {float temperature;int humidity;uint8_t status;
};void setup() {// ...// 写入结构体SensorData data = {25.5f, 60, 0x01};eeprom.writePage(200, (uint8_t*)&data, sizeof(SensorData));// 读取结构体SensorData readData;for (int i = 0; i < sizeof(SensorData); i++) {((uint8_t*)&readData)[i] = eeprom.readByte(200 + i);}Serial.print("Temperature: ");Serial.println(readData.temperature);// ...
}

关键特性

  1. 页写入优化
    • 自动处理跨页写入,避免数据被截断
    • 例如:对于 64 字节页大小的 EEPROM,写入 100 字节数据时会自动分 2 次传输
  2. 性能提升
    • 相比逐字节写入,页写入可减少 I2C 传输次数
    • 例如:写入 64 字节数据时,页写入只需 1 次传输(而非 64 次)
  3. 安全机制
    • 自动检查地址范围,防止越界写入
    • 每次页写入后等待 5ms,确保 EEPROM 完成内部写入操作

使用注意事项

  1. 页大小限制
    • 不同 EEPROM 型号的页大小不同(常见:16/32/64/128 字节)
    • 初始化时需正确设置页大小(如 eeprom(0x50, 64, ...)
  2. 写入延时
    • 每次页写入后必须等待 5ms(EEPROM 写入周期)
    • 频繁写入会影响系统响应速度,建议批量操作
  3. 数据类型
    • writePage 仅支持 uint8_t 数组
    • 如需写入其他类型(如字符串、结构体),需先转换为字节数组
  4. 地址对齐
    • 尽量从页边界开始写入(如地址 0、64、128…)
    • 非对齐地址会导致页内剩余空间浪费

I2C_EEPROM库

https://github.com/RobTillaart/I2C_EEPROM

1. I2C_eeprom

构造函数

I2C_eeprom(uint8_t deviceAddress, TwoWire *wire = &Wire)
  • 功能:创建一个 I2C_eeprom 对象,指定 EEPROM 设备地址和 I2C 总线。
  • 参数:
    • deviceAddress:EEPROM 设备的 I2C 地址。
    • wire:可选参数,指定 I2C 总线对象,默认为 Wire
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50); // 创建一个地址为 0x50 的 EEPROM 对象
I2C_eeprom(uint8_t deviceAddress, uint32_t deviceSize, TwoWire *wire = &Wire)
  • 功能:创建一个 I2C_eeprom 对象,指定 EEPROM 设备地址、设备大小和 I2C 总线。
  • 参数:
    • deviceAddress:EEPROM 设备的 I2C 地址。
    • deviceSize:EEPROM 设备的大小。
    • wire:可选参数,指定 I2C 总线对象,默认为 Wire
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50, 0x8000); // 创建一个地址为 0x50,大小为 0x8000 的 EEPROM 对象

初始化函数

bool begin(uint8_t writeProtectPin = -1)
  • 功能:初始化 I2C 总线,检查设备地址是否可用。
  • 参数:
    • writeProtectPin:可选参数,指定写保护引脚,默认为 -1 表示不使用写保护。
  • 返回值:如果初始化成功返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50);
if (eeprom.begin()) {Serial.println("EEPROM initialized successfully");
} else {Serial.println("Failed to initialize EEPROM");
}
bool isConnected()
  • 功能:测试设备地址是否在总线上可用。
  • 返回值:如果设备地址可用返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
if (eeprom.isConnected()) {Serial.println("EEPROM is connected");
} else {Serial.println("EEPROM is not connected");
}

读写函数

int writeByte(uint16_t memoryAddress, uint8_t value)
  • 功能:向指定内存地址写入单个字节。
  • 参数:
    • memoryAddress:要写入的内存地址。
    • value:要写入的字节值。
  • 返回值:如果写入成功返回 0,否则返回错误码。
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
eeprom.writeByte(0x10, 0xAA); // 在地址 0x10 写入 0xAA
bool read(T &buffer)bool read(T *buffer)
  • 功能:从 EEPROM 读取数据到缓冲区。
  • 参数:
    • buffer:要读取数据的缓冲区。
  • 返回值:如果读取成功返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
uint8_t data;
eeprom.read(data); // 读取数据到 data 变量
bool write(T &buffer)bool write(T *buffer)
  • 功能:将缓冲区数据写入 EEPROM。
  • 参数:
    • buffer:要写入的数据缓冲区。
  • 返回值:如果写入成功返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
uint8_t data = 0xBB;
eeprom.write(data); // 将 data 变量的值写入 EEPROM

更新函数

int updateByte(uint16_t memoryAddress, uint8_t value)
  • 功能:写入单个字节,但仅当值发生变化时才写入。
  • 参数:
    • memoryAddress:要写入的内存地址。
    • value:要写入的字节值。
  • 返回值:如果值相同或写入成功返回 0。
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
eeprom.updateByte(0x20, 0xCC); // 在地址 0x20 更新字节值
uint16_t updateBlock(uint16_t memoryAddress, uint8_t * buffer, uint16_t length)
  • 功能:从指定内存地址开始写入缓冲区,但仅当数据发生变化时才写入。
  • 参数:
    • memoryAddress:要写入的起始内存地址。
    • buffer:要写入的数据缓冲区。
    • length:要写入的数据长度。
  • 返回值:实际写入的字节数,小于等于 length
  • 示例
#include "I2C_eeprom.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
uint8_t buffer[] = {0x11, 0x22, 0x33};
eeprom.updateBlock(0x30, buffer, sizeof(buffer)); // 在地址 0x30 开始更新数据块
2. I2C_eeprom_cyclic_store
  1. 延长 EEPROM 寿命:EEPROM 有写入次数的限制,频繁在同一位置写入数据会导致该位置的寿命提前耗尽。I2C_eeprom_cyclic_store 类通过将写入操作分散到 EEPROM 的不同页面,减少了对单个页面的写入次数,从而延长了整个 EEPROM 的使用寿命。
  2. 数据版本管理:该类会为每次写入的数据添加一个版本号,在初始化时会扫描 EEPROM 中所有存储槽,找到版本号最高的槽位,将其作为当前数据进行读取。这确保了每次读取的数据都是最新写入的版本。
  3. 循环写入机制:当数据写入到 EEPROM 分配区域的末尾时,会自动回到起始位置继续写入,实现循环写入。这种机制使得 EEPROM 可以持续使用,而不需要手动管理写入位置。

初始化函数

bool begin(I2C_eeprom &eeprom, uint8_t pageSize, uint16_t totalPages)
  • 功能:初始化循环存储实例,搜索 EEPROM 中最新写入的版本并设置当前槽位。
  • 参数:
    • eeprom:要使用的 I2C_eeprom 实例。
    • pageSize:每个写入页的字节数。
    • totalPages:要使用的总页数。
  • 返回值:如果初始化成功返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
#include "I2C_eeprom_cyclic_store.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
I2C_eeprom_cyclic_store<uint8_t> cyclicStore;
if (cyclicStore.begin(eeprom, 32, 4)) {Serial.println("Cyclic store initialized successfully");
} else {Serial.println("Failed to initialize cyclic store");
}

格式化函数

bool format()
  • 功能:格式化 EEPROM,清除数据。
  • 返回值:如果成功返回 true,如果无法写入 EEPROM 则返回 false
  • 示例
#include "I2C_eeprom.h"
#include "I2C_eeprom_cyclic_store.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
I2C_eeprom_cyclic_store<uint8_t> cyclicStore;
cyclicStore.begin(eeprom, 32, 4);
if (cyclicStore.format()) {Serial.println("EEPROM formatted successfully");
} else {Serial.println("Failed to format EEPROM");
}

读写函数

bool read(T &buffer)bool read(T *buffer)
  • 功能:从 EEPROM 的当前槽位读取数据到缓冲区。
  • 参数:
    • buffer:要读取数据的缓冲区。
  • 返回值:如果数据读取成功返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
#include "I2C_eeprom_cyclic_store.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
I2C_eeprom_cyclic_store<uint8_t> cyclicStore;
cyclicStore.begin(eeprom, 32, 4);
uint8_t data;
if (cyclicStore.read(data)) {Serial.println("Data read successfully");
} else {Serial.println("Failed to read data");
}
bool write(T &buffer)bool write(T *buffer)
  • 功能:将缓冲区数据写入 EEPROM 的下一个槽位。
  • 参数:
    • buffer:要写入的数据缓冲区。
  • 返回值:如果数据写入成功返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
#include "I2C_eeprom_cyclic_store.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
I2C_eeprom_cyclic_store<uint8_t> cyclicStore;
cyclicStore.begin(eeprom, 32, 4);
uint8_t data = 0xDD;
if (cyclicStore.write(data)) {Serial.println("Data written successfully");
} else {Serial.println("Failed to write data");
}

指标函数

bool getMetrics(uint16_t &slots, uint32_t &writeCounter)
  • 功能:返回 EEPROM 使用指标。
  • 参数:
    • slots:用于存储写入数据缓冲区的槽位数。
    • writeCounter:用于存储自上次格式化(或首次使用)以来的总写入次数。
  • 返回值:如果实例已初始化返回 true,否则返回 false
  • 示例
#include "I2C_eeprom.h"
#include "I2C_eeprom_cyclic_store.h"
I2C_eeprom eeprom(0x50);
eeprom.begin();
I2C_eeprom_cyclic_store<uint8_t> cyclicStore;
cyclicStore.begin(eeprom, 32, 4);
uint16_t slots;
uint32_t writeCounter;
if (cyclicStore.getMetrics(slots, writeCounter)) {Serial.print("Slots: ");Serial.println(slots);Serial.print("Write counter: ");Serial.println(writeCounter);
} else {Serial.println("Failed to get metrics");
}
示例
#include "I2C_eeprom.h"
#include "I2C_eeprom_cyclic_store.h"#define I2C_EEPROM_ADDR 0x50
#define I2C_EEPROM_SIZE 0x1000// 定义要存储的数据结构
struct DummyTestData {uint8_t padding;
};void setup() {Serial.begin(9600);// 创建 I2C_eeprom 实例I2C_eeprom eeprom(I2C_EEPROM_ADDR, I2C_EEPROM_SIZE);eeprom.begin();// 创建 I2C_eeprom_cyclic_store 实例I2C_eeprom_cyclic_store<DummyTestData> cyclicStore;// 初始化循环存储实例if (cyclicStore.begin(eeprom, 32, 4)) {Serial.println("Cyclic store initialized successfully");// 准备要写入的数据DummyTestData data;data.padding = 0xAA;// 写入数据if (cyclicStore.write(data)) {Serial.println("Data written successfully");} else {Serial.println("Failed to write data");}// 读取数据DummyTestData readData;if (cyclicStore.read(readData)) {Serial.print("Read data: ");Serial.println(readData.padding, HEX);} else {Serial.println("Failed to read data");}} else {Serial.println("Failed to initialize cyclic store");}
}void loop() {// 主循环中可以添加其他操作
}

2.SPI 接口 EEPROM 芯片

  • 适用于高速读写场景,使用 SPI.h 库通信(如 Microchip 25LC512(64KB))。
http://www.xdnf.cn/news/10281.html

相关文章:

  • 颠覆传统!单样本熵最小化如何重塑大语言模型训练范式?
  • Linux 网络流量监控实战:使用 iftop 精准定位高带宽连接
  • 跟我学c++中级篇——隐式转换的意义
  • PostgreSQL的扩展 dblink
  • MySQL--day10--数据处理之增删改
  • 【Java实战】低侵入的线程池值传递
  • Jinja2 模板继承机制
  • 【Linux】mmap文件内存映射
  • LeetCode[257]二叉树的所有路径
  • 4.2.4 Spark SQL 数据写入模式
  • 67.实现AI流式回答的后端实现(2)
  • Vue-Router简版手写实现
  • 2025年5月个人工作生活总结
  • lstm 长短期记忆 视频截图 kaggle示例
  • Rock9.x(Linux)安装Redis7
  • 寒假学习笔记【匠心制作,图文并茂】——1.20拓扑、强连通分量、缩点
  • CppCon 2014 学习: The Implementation of Value Types
  • Compose原理 - 整体架构与主流程
  • JDK21深度解密 Day 8:Spring Boot 3与虚拟线程整合
  • 【清晰教程】查看和修改Git配置情况
  • SCSS 全面深度解析
  • neo4j 5.19.0安装、apoc csv导入导出 及相关问题处理
  • Windows最快速打开各项系统设置大全
  • RAID磁盘阵列配置
  • 鸿蒙编译ffmpeg库
  • M4Pro安装ELK(ElasticSearch+LogStash+Kibana)踩坑记录
  • 性能优化 - 理论篇:性能优化的七类技术手段
  • SMT贴片机工艺优化与效率提升策略
  • WEB3——为什么做NFT铸造平台?
  • 配置远程无密登陆ubuntu服务器时无法连接问题排查