EEPROMex - Maximum number of writes

hi,

using the eepromex.h library, as i want an easy way to write larger numbers into a power off / failure state.

using a flow meter to read millilitres, the numbers can get reasonably high, but every single unit that i measure (pint) is 568ml, a keg holds 22 gallons, or 176 pints, or 99968ml, and the keg gets changed 8 times a week, 799744, and im allowing a few weeks worth of data collection, (8 weeks) 6397952, or 26 weeks ( 20,793,344ml )

i opted for unsigned long as the integer for the failsafes, and found that the standard eeprom needs a lot of clever additions to record this.

using the eepromex library makes the large numbers much easier to use.

i intend to use the eeprom update function once every 15 mins, as for 12 hours a day, the system will be recording the same number and will cause needless writes (being aware of the 100k cycles that are available to the chip)... this code for testing purposes does an update every 2 seconds, but will be changed.

i have the system operating nicely, im playing with the flow, resetting, seeing the saved numbers on the serial monitor and on the display and feeling quite chuffed with myself...

...and after a while i get a "maximum number of writes"

ive stuck a counter into the code, and this magic number happens after 100 writes / updates, and the system stops recording number changes.

how do i get around this?

oh, if i restart the process, then everything starts perfectly fine.

http://playground.arduino.cc/Code/EEPROMex

// Liquid flow rate sensor

// Measure the liquid/water flow rate using this code.
// Connect Vcc and Gnd of sensor to arduino, and the signal line to arduino digital pin 2.

#include "U8glib.h"
//#include "EEPROM.h"
#include "EEPROMex.h"

// adding the oled screen
U8GLIB_SSD1306_128X64 u8g(10, 9, 12);

byte statusLed    = 13;

byte sensorInterrupt = 0;  // 0 = digital pin 2
byte sensorPin       = 2;
unsigned int eepromAddress = 0;
unsigned int eepromSaveTime = 5000;
unsigned long currentTime;
int writeNumber = 0;


// The hall-effect flow sensor outputs approximately 4.5 pulses per second per
// litre/minute of flow.
float calibrationFactor = 4.5;

volatile byte pulseCount;

float flowRate;
unsigned int flowMillilitres;
unsigned long totalMillilitres;
unsigned int pints;

unsigned long oldTime;
unsigned long oldSaveTime;



void setup()
{
  // flip screen, if required
  // u8g.setRot180();
  // set font for oled
  u8g.setFont(u8g_font_unifont);

  // Initialize a serial connection for reporting values to the host
  Serial.begin(9600);

  // Set up the status LED line as an output
  pinMode(statusLed, OUTPUT);
  digitalWrite(statusLed, HIGH);  // We have an active-low LED attached

  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);

  pulseCount        = 0;
  flowRate          = 0.0;
  flowMillilitres   = 0;
  oldTime           = 0;
  oldSaveTime       = 0;
  totalMillilitres  = EEPROM.readLong(eepromAddress);
  pints             = totalMillilitres / 568;

  // The Hall-effect sensor is connected to pin 2 which uses interrupt 0.
  // Configured to trigger on a FALLING state change (transition from HIGH
  // state to LOW state)
  attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
  pinMode(13, OUTPUT);
}

/**
   Main program loop
*/
void loop() {
  currentTime = millis();

  if ( (currentTime - oldTime) > 1000) {   // Only process counters once per second

    // Disable the interrupt while calculating flow rate and sending the value to the host
    detachInterrupt(sensorInterrupt);

    // take a reading every second
    flowRate = ((1000.0 / (currentTime - oldTime)) * pulseCount) / calibrationFactor;

    // reset the timer
    oldTime = currentTime;

    // convert to litres
    flowMillilitres = (flowRate / 60) * 1000;

    // Add the millilitres passed in this second to the cumulative total
    totalMillilitres += flowMillilitres;

    // convert to pints
    pints = totalMillilitres / 568;

    unsigned int frac;

    // Print the flow rate for this second in litres / minute
    Serial.print("Flow rate: "); Serial.print(int(flowRate)); Serial.print("."); frac = (flowRate - int(flowRate)) * 10; Serial.print(frac, DEC); Serial.print("L/min");
    Serial.print("  Current Flow: "); Serial.print(flowMillilitres); Serial.print("mL/Sec");
    Serial.print("  Total: "); Serial.print(totalMillilitres); Serial.print("mL");
    Serial.print("  Pints: "); Serial.println(pints);

    // picture loop
    u8g.firstPage();
    do {
      draw();
    } while (u8g.nextPage() );

    // Reset the pulse counter so we can start incrementing again
    pulseCount = 0;

    // Enable the interrupt again now that we've finished sending output
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
  } // end of timed loop (1000)


  // manual reset
  if (Serial.available()) {
    char ch = Serial.read();
    if (ch == 'r') {
      totalMillilitres = 0;
      EEPROM.writeLong(eepromAddress, totalMillilitres);
      Serial.println("*** Manual Reset ***");
    }
  }

  // if enough time has passed, save the current reading
  if ( (currentTime - oldSaveTime) > eepromSaveTime) {

    // write the value to the eeprom
    //EEPROM.write(eepromAddress, totalMillilitres);
    // update the address in the eeprom if different
    EEPROM.updateLong(eepromAddress, totalMillilitres);
    Serial.print("data saved: "); Serial.print(EEPROM.readLong(eepromAddress)); Serial.print(" :: "); Serial.print(writeNumber);
    Serial.println();
    writeNumber++;
    oldSaveTime = currentTime;
  } // end of timed loop (eepromSaveTime)

}// end of void loop


void pulseCounter() {
  // Increment the pulse counter
  pulseCount++;
}


void draw() {
  u8g.setPrintPos(0, 20);
  u8g.print("Pints: "); u8g.print(pints);
  u8g.setPrintPos(0, 40);
  u8g.print("Flow: "); u8g.print(flowMillilitres); u8g.print(" ml/s ");
  u8g.setPrintPos(60, 60);
  u8g.print("FlowZero");
}

Look at the library - it seems they limit the number of writes to 100:

EEPROMClassEx::EEPROMClassEx()
  :  _allowedWrites(100)
{
}

a way of changing that:

/**
 * Set global maximum of allowed writes
 */
void EEPROMClassEx::setMaxAllowedWrites(int allowedWrites) {
#ifdef _EEPROMEX_DEBUG
    _allowedWrites = allowedWrites;
#endif          
}

Don't kill your eeprom though, for example if the value is the same as last time, don't write it again.

J-M-L:
Don't kill your eeprom though, for example if the value is the same as last time, don't write it again.

yeah, hopefully! if i take the reading every 15 mins, on 35000 thats a years worth of updates, and as im using update, and only half the day the value is changing, then i would expect to see in the region of 2 years worth of data.

just about the length of time for a potential warranty... :slight_smile:

thanks!

edit, or are you recommending an if (last count == this count) {carry on } else { (eeprom.update) }?

so i keep the checking in the code, instead of letting the eeprom do the checking?

Unless programmed obsolescence is part of the design goals :slight_smile: you might want to use a rolling buffer scheme in EEPROM to save the most recent value (if I understood well your number will keep increasing) and just overwrite the smallest value in the rolling buffer. If you do this on 10 cells, then you have x10 the lifetime :slight_smile:

you might want to use a rolling buffer scheme in EEPROM to save the most recent value (if I understood well your number will keep increasing) and just overwrite the smallest value in the rolling buffer. If you do this on 10 cells, then you have x10 the lifetime :slight_smile:

Brilliant approach, @J-M-L !

and after 10x2 years = 20 years: by then the new EEPROM types can stand 1 million re-writes :wink: and @kelvinmead or his descendants can then go with the same app another 200 years+ :slight_smile:

so the writes to an eeprom, are to the single address's only? not total number of times the eeprom was written?

is there a way to check to see if an eeprom write has failed?

or would it be more like, value A waiting to write, write value A to address A, check address A, if address A != value A, write value A to address B, check address B... continue.

or are we more likely to see system failure when an eeprom dies?

so the writes to an eeprom, are to the single address's only?

YES

is there a way to check to see if an eeprom write has failed?

YES - just read what you have written and compare

or would it be more like, value A waiting to write, write value A to address A, check address A, if address A != value A, write value A to address B, check address B... continue.

I don't know exactly how the EEPROM routine works internally, but I suspect it would just write with a given internal timing and most likely it doesn't verify - so Arduino won't check that something is going wrong, but your application - but that is my personal speculation

No need to speculate, the datasheet explains it in full detail.

@MarkT:

what datasheet?

What I found in the Arduino reference:

Note

An EEPROM write takes 3.3 ms to complete. The EEPROM memory has a specified life of 100,000 write/erase cycles, so you may need to be careful about how often you write to it.

Is it this, what you are referring to?

EEPROMex has some nice features, as you've seen.
It handle 'deferred' writes internally, and manages block writes quite well.
I've been using it for a couple of years with no problem, but still keep track of your writes as a good habit.

There is a function in the EEPROMEX library - EEPROM.setMaxAllowedWrites(n);
Which allows you to override the default safety barrier.

Also, there is usually no need in most cases to write to EEPROM so often...
A small electrolytic in front of Vcc will keep the chip running easily long enough to save during a power failure.

Use a resistor divider between the unregulated DC supply, and ground (before the regulator and cap!), Then, when the ADC value goes below your nominated threshold - say 10v for a 12V supply - you initiate a crash stop scenario, and save EEPROM among other things.

If crash-stop() is a function. that calls save-eeprom(), you have a lot of flexibility how they are used in various failure scenarios - not just power failure.
e.g. I use it for a forced software reset, that allows the WDT to reset the chip, but ensure ant critical counters etc are saved before restarting.

I definitely second the crash save approach!
Makes life simple