EEPROM Management

Maybe my search skills are lacking but I haven't found much info on managing the EEPROM memory space. I am developing code to manage the storage of several instances of a struct containing data on rolls of wire used for my wire cutter project. Wire cutter and stripper - YouTube
The current code appears to work as intended though more testing is needed. The goal of this post is to ask if I'm going at this the right way.

The Theory:

• Divide the EEPROM space into data blocks based on the size of my struct.

• An ID number will be assigned consecutively to be marked on the physical roll of wire and will also be stored as the first member variable of each instance. ( code to assign this number is on the TODO list )

• Data is located by iterating through the data blocks looking for the ID number.

• Data for each instance remains at that location for the duration of its use.

• New data is placed by finding the location of the highest ID number and then placing it in the next empty data block. This provides a "round robin" usage of the EEPROM skipping over data block's still in use.

• A method to ensure removal of old data to keep the EEPROM from becoming overfilled is on the TODO list. Atm I am thinking of simply limiting the number of data sets that can be stored.

Code would not fit with this message so I will post it in the next post.

The code:

// **************************************************************************************************
//  Developing code to manage EEPROM while storing a struct for my Wirebot project
//
//  MUST initialize EEPROM to all zero's prior to first use
// **************************************************************************************************

// Debug code Thanks to LarryD post #8 https://forum.arduino.cc/index.php?topic=215334.0
#define DEBUG   //If you comment this line, the DPRINT & DPRINTLN lines are defined as blank.
#ifdef  DEBUG    //Macros are usually in all capital letters.
#define DSERIAL_BEGIN(...)   Serial.begin(__VA_ARGS__) // DSERIAL is a macro, debug Serial.begin
#define DPRINT(...)    Serial.print(__VA_ARGS__)       // DPRINT is a macro, debug print
#define DPRINTLN(...)  Serial.println(__VA_ARGS__)     // DPRINTLN is a macro, debug print with new line
#else
#define DSERIAL_BEGIN(...)     // now defines a blank line
#define DPRINT(...)
#define DPRINTLN(...)
#endif

#include <avr/eeprom.h>

// ================= Data Structs ================

typedef struct {
  uint16_t Id;
  uint8_t Gauge;
  uint8_t Color;
  uint8_t Diameter;
  uint8_t Offset;
  uint16_t InchesUsed = 0;
} WireRollData;

WireRollData wireData;

// ================= Constants ================

constexpr uint16_t DATA_BLOCK_SIZE       = sizeof(wireData);
constexpr uint16_t NUM_AVAILABLE_ADDRESS = 1024;
constexpr uint16_t FIRST_DATA_ADDRESS    = 0;
constexpr uint16_t LAST_DATA_ADDRESS     = NUM_AVAILABLE_ADDRESS - DATA_BLOCK_SIZE;
constexpr uint16_t NUM_DATA_BLOCKS       = LAST_DATA_ADDRESS / DATA_BLOCK_SIZE;
constexpr uint16_t ERASE_DATA            = 0;

// ================= Function Prototypes ================

void writeNewData(WireRollData &thisData);
bool retrieveWireData(uint16_t ID, WireRollData &thisData);
bool deleteWireData(uint16_t wireId);
uint16_t findEmptyDataSlot(uint16_t address);
uint16_t maxIdAddress(void);

// ============================================
//                 setup()
// ============================================
void setup() {
  DSERIAL_BEGIN(115200);
  DPRINTLN(DATA_BLOCK_SIZE);
  DPRINTLN(NUM_DATA_BLOCKS);
  DPRINTLN(LAST_DATA_ADDRESS);

  wireData.Id       = 1;
  wireData.Gauge    = 18;
  wireData.Color    = 1;
  wireData.Diameter = 115;
  wireData.Offset   = 0;

  writeNewData(wireData);               // Save 1st data set to EEPROM

  wireData.Id       = 2;
  wireData.Gauge    = 16;
  wireData.Color    = 2;
  wireData.Diameter = 117;
  wireData.Offset   = 1;

  writeNewData(wireData);               // Save 2nd data set to EEPROM

  if (retrieveWireData(1, wireData)) {   // retrieve 1st data set and print it
    DPRINT(F("Wire1 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire1 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire1 Diameter = "));
    DPRINTLN(wireData.Diameter);
    DPRINT(F("Wire1 Offset = "));
    DPRINTLN(wireData.Offset);
    DPRINTLN("");
  }
  else {
    DPRINTLN(F("ID#1 not found"));
  }

  if (retrieveWireData(2, wireData)) {   // retrieve 2nd data set and print it
    DPRINT(F("Wire2 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire2 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire2 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire2 Diameter = "));
    DPRINTLN(wireData.Diameter);
    DPRINT(F("Wire2 Offset = "));
    DPRINTLN(wireData.Offset);
    DPRINTLN("");
  }
  else {
    DPRINTLN(F("ID#2 not found"));
  }

  if (deleteWireData(1)) {                   // delete 1st data set
    DPRINTLN(F("Delete wire Id#1 success"));
  }
  else {
    DPRINTLN(F("Delete wire Id#1 Fail"));
  }

  if (deleteWireData(2)) {                   // delete 2nd data set
    DPRINTLN(F("Delete wire Id#2 success"));
  }
  else {
    DPRINTLN(F("Delete wire Id#2 Fail"));
  }

  if (retrieveWireData(1, wireData)) {       // search for deleted data
    DPRINT(F("Wire1 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire1 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire1 Diameter = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Offset = "));
    DPRINTLN(wireData.Offset);
  }
  else {
    DPRINTLN(F("ID#1 not found"));
  }

  if (retrieveWireData(2, wireData)) {       // search for deleted data
    DPRINT(F("Wire1 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire1 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire1 Diameter = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Offset = "));
    DPRINTLN(wireData.Offset);
  }
  else {
    DPRINTLN(F("ID#2 not found"));
  }
}

// ============================================
//                 loop()
// ============================================
void loop() {
  // put your main code here, to run repeatedly:

}

// ============================================
//                 writeNewData()
// ============================================
void writeNewData(WireRollData &thisData) {
  uint16_t newDataSlot = findEmptyDataSlot(maxIdAddress());
  DPRINT(F("newDataSlot = "));
  DPRINTLN(newDataSlot);
  DPRINTLN("");  
  eeprom_update_word(newDataSlot, thisData.Id);
  eeprom_update_byte((newDataSlot += sizeof(thisData.Id)),       thisData.Gauge);
  eeprom_update_byte((newDataSlot += sizeof(thisData.Gauge)),    thisData.Color);
  eeprom_update_byte((newDataSlot += sizeof(thisData.Color)),    thisData.Diameter);
  eeprom_update_byte((newDataSlot += sizeof(thisData.Diameter)), thisData.Offset);
  eeprom_update_word((newDataSlot += sizeof(thisData.Offset)),   thisData.InchesUsed);
} // End writeNewData()

// ============================================
//                 retrieveWireData()
// ============================================
// get data from EEPROM and store it in wireData

bool retrieveWireData(uint16_t ID, WireRollData & thisData) {
  bool success = false;
  for (uint16_t i = FIRST_DATA_ADDRESS; i <= LAST_DATA_ADDRESS; i += DATA_BLOCK_SIZE) {
    if (eeprom_read_word(i) == ID) {
      thisData.Id         = eeprom_read_word(i);
      thisData.Gauge      = eeprom_read_byte((i += sizeof(thisData.Id)));
      thisData.Color      = eeprom_read_byte((i += sizeof(thisData.Gauge)));
      thisData.Diameter   = eeprom_read_byte((i += sizeof(thisData.Color)));
      thisData.Offset     = eeprom_read_byte((i += sizeof(thisData.Diameter)));
      thisData.InchesUsed = eeprom_read_word((i += sizeof(thisData.Offset)));
      success = true;
    } // End if
  } // End for
  return success;
} // End retrieveWireData()

// ============================================
//                 deleteWireData()
// ============================================

bool deleteWireData(uint16_t wireId) {
  bool success = false;
  for (uint16_t i = FIRST_DATA_ADDRESS; i <= LAST_DATA_ADDRESS; i += DATA_BLOCK_SIZE) {
    if (eeprom_read_word(i) == wireId) {
      eeprom_write_word(i, ERASE_DATA);
      success = true;
    } // End if
  } // End for
  return success;
} // End deleteWireData()

// ============================================
//                 findEmptyDataSlot()
// ============================================

uint16_t findEmptyDataSlot(uint16_t address) {
  while (eeprom_read_word (address)) {
    address += DATA_BLOCK_SIZE;
    if (address > LAST_DATA_ADDRESS) address = FIRST_DATA_ADDRESS;
  } // End while
  return address;
} // End findEmptyDataSlot()

// ============================================
//                 maxIdAddress()
// ============================================
// gets the address of the highest wire id# currently used

uint16_t maxIdAddress(void) {
  uint16_t address;
  for (uint16_t i = FIRST_DATA_ADDRESS; i <= LAST_DATA_ADDRESS; i += DATA_BLOCK_SIZE) {
    if (uint16_t newID = eeprom_read_word (i)) {
      delay(5); // removing or reducing this produces errors in the output
      static uint16_t maxID;
      if (newID > maxID) {
        maxID = newID;
        address = i;
      } // End if
    } // End if
  } // End for;
  return address;
} // End maxIdAddress()

I think the approach is fine.

You can probably make life a lot easier by using EEPROM.h instead of avr/eeprom.h. It supports the get and put methods that can retrieve or write a complete struct in one go without you having to write the individual elements of the struct; the put method also saves a little on eeprom wear as it does not update cells that did not change.

Thank you for confirming my idea is sound.

I tried several times yesterday to implement the equivalent methods (eeprom_update_block, eeprom_read_block) in avr/eeprom.h with little luck. Then after reading your reply this morning, I gave it another shot and now it’s working correctly. I am using avr/eeprom.h becauase it compiles about 25% smaller than EEPROM.h.

// **************************************************************************************************
//  Developing code to manage EEPROM while storing a struct for my Wirebot project
//
//  MUST initialize EEPROM to all zero's prior to first use
// **************************************************************************************************

// Debug code Thanks to LarryD post #8 https://forum.arduino.cc/index.php?topic=215334.0
#define DEBUG   //If you comment this line, the DPRINT & DPRINTLN lines are defined as blank.
#ifdef  DEBUG    //Macros are usually in all capital letters.
#define DSERIAL_BEGIN(...)   Serial.begin(__VA_ARGS__) // DSERIAL is a macro, debug Serial.begin
#define DPRINT(...)    Serial.print(__VA_ARGS__)       // DPRINT is a macro, debug print
#define DPRINTLN(...)  Serial.println(__VA_ARGS__)     // DPRINTLN is a macro, debug print with new line
#else
#define DSERIAL_BEGIN(...)     // now defines a blank line
#define DPRINT(...)
#define DPRINTLN(...)
#endif

#include <avr/eeprom.h>

// ================= Data Structs ================

typedef struct {
  uint16_t Id;
  uint8_t Gauge;
  uint8_t Color;
  uint8_t Diameter;
  uint8_t Offset;
  uint16_t InchesUsed = 0;
} WireRollData;

WireRollData wireData;

// ================= Constants ================

constexpr uint16_t DATA_BLOCK_SIZE       = sizeof(wireData);
constexpr uint16_t NUM_AVAILABLE_ADDRESS = 1024;
constexpr uint16_t FIRST_DATA_ADDRESS    = 0;
constexpr uint16_t LAST_DATA_ADDRESS     = NUM_AVAILABLE_ADDRESS - DATA_BLOCK_SIZE;
constexpr uint16_t NUM_DATA_BLOCKS       = LAST_DATA_ADDRESS / DATA_BLOCK_SIZE;
constexpr uint16_t ERASE_DATA            = 0;

// ================= Function Prototypes ================

void writeNewData(WireRollData &thisData);
bool retrieveWireData(uint16_t ID, WireRollData &thisData);
bool deleteWireData(uint16_t wireId);
uint16_t findEmptyDataSlot(uint16_t address);
uint16_t maxIdAddress(void);

// ============================================
//                 setup()
// ============================================

void setup() {
  DSERIAL_BEGIN(115200);
  DPRINTLN(DATA_BLOCK_SIZE);
  DPRINTLN(NUM_DATA_BLOCKS);
  DPRINTLN(LAST_DATA_ADDRESS);

  wireData.Id       = 1;
  wireData.Gauge    = 18;
  wireData.Color    = 1;
  wireData.Diameter = 115;
  wireData.Offset   = 0;

  writeNewData(wireData);               // Save 1st data set to EEPROM

  wireData.Id       = 2;
  wireData.Gauge    = 16;
  wireData.Color    = 2;
  wireData.Diameter = 117;
  wireData.Offset   = 1;

  writeNewData(wireData);               // Save 2nd data set to EEPROM

  if (retrieveWireData(1, wireData)) {   // retrieve 1st data set and print it
    DPRINT(F("Wire1 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire1 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire1 Diameter = "));
    DPRINTLN(wireData.Diameter);
    DPRINT(F("Wire1 Offset = "));
    DPRINTLN(wireData.Offset);
    DPRINTLN("");
  }
  else {
    DPRINTLN(F("ID#1 not found"));
  }

  if (retrieveWireData(2, wireData)) {   // retrieve 2nd data set and print it
    DPRINT(F("Wire2 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire2 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire2 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire2 Diameter = "));
    DPRINTLN(wireData.Diameter);
    DPRINT(F("Wire2 Offset = "));
    DPRINTLN(wireData.Offset);
    DPRINTLN("");
  }
  else {
    DPRINTLN(F("ID#2 not found"));
  }

  if (deleteWireData(1)) {                   // delete 1st data set
    DPRINTLN(F("Delete wire Id#1 success"));
  }
  else {
    DPRINTLN(F("Delete wire Id#1 Fail"));
  }

  if (deleteWireData(2)) {                   // delete 2nd data set
    DPRINTLN(F("Delete wire Id#2 success"));
  }
  else {
    DPRINTLN(F("Delete wire Id#2 Fail"));
  }

  if (retrieveWireData(1, wireData)) {       // search for deleted data
    DPRINT(F("Wire1 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire1 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire1 Diameter = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Offset = "));
    DPRINTLN(wireData.Offset);
  }
  else {
    DPRINTLN(F("ID#1 not found"));
  }

  if (retrieveWireData(2, wireData)) {       // search for deleted data
    DPRINT(F("Wire1 Id = "));
    DPRINTLN(wireData.Id);
    DPRINT(F("Wire1 Gauge = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Color = "));
    DPRINTLN(wireData.Color);
    DPRINT(F("Wire1 Diameter = "));
    DPRINTLN(wireData.Gauge);
    DPRINT(F("Wire1 Offset = "));
    DPRINTLN(wireData.Offset);
  }
  else {
    DPRINTLN(F("ID#2 not found"));
  }
}

// ============================================
//                 loop()
// ============================================

void loop() {
  // put your main code here, to run repeatedly:

}

// ============================================
//                 writeNewData()
// ============================================

void writeNewData(WireRollData &thisData) {
  uint16_t newDataSlot = findEmptyDataSlot(maxIdAddress());
  DPRINT(F("newDataSlot = "));
  DPRINTLN(newDataSlot);
  DPRINTLN("");
  eeprom_update_block(&thisData, (uint16_t*)newDataSlot, DATA_BLOCK_SIZE); 
} // End writeNewData()

// ============================================
//                 retrieveWireData()
// ============================================
// get data from EEPROM and store it in wireData

bool retrieveWireData(uint16_t ID, WireRollData & thisData) {
  bool success = false;
  for (uint16_t i = FIRST_DATA_ADDRESS; i <= LAST_DATA_ADDRESS; i += DATA_BLOCK_SIZE) {
    if (eeprom_read_word(i) == ID) {
      eeprom_read_block(&thisData, (uint16_t*)i, DATA_BLOCK_SIZE);
      success = true;
    } // End if
  } // End for
  return success;
} // End retrieveWireData()

// ============================================
//                 deleteWireData()
// ============================================

bool deleteWireData(uint16_t wireId) {
  bool success = false;
  for (uint16_t i = FIRST_DATA_ADDRESS; i <= LAST_DATA_ADDRESS; i += DATA_BLOCK_SIZE) {
    if (eeprom_read_word(i) == wireId) {
      eeprom_write_word(i, ERASE_DATA);
      success = true;
    } // End if
  } // End for
  return success;
} // End deleteWireData()

// ============================================
//                 findEmptyDataSlot()
// ============================================

uint16_t findEmptyDataSlot(uint16_t address) {
  while (eeprom_read_word (address)) {
    address += DATA_BLOCK_SIZE;
    if (address > LAST_DATA_ADDRESS) address = FIRST_DATA_ADDRESS;
  } // End while
  return address;
} // End findEmptyDataSlot()

// ============================================
//                 maxIdAddress()
// ============================================
// gets the address of the highest wire id# currently used

uint16_t maxIdAddress(void) {
  uint16_t address;
  for (uint16_t i = FIRST_DATA_ADDRESS; i <= LAST_DATA_ADDRESS; i += DATA_BLOCK_SIZE) {
    if (uint16_t newID = eeprom_read_word (i)) {
      delay(5); // removing or reducing this produces errors in the output
      static uint16_t maxID;
      if (newID > maxID) {
        maxID = newID;
        address = i;
      } // End if
    } // End if
  } // End for;
  return address;
} // End maxIdAddress()

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.