Change EEPROM Addresses Automatically

I would like to record the total time that my lawn mower has been running. For now, I am using the setButton == HIGH as the trigger to start the timer. The problem: I realized that if I count and save time into an EEPROM byte once every second I will reach the 100,000 cycle read/write limit in only 27.77 hours.

To help this, I decided to only save time every 5 seconds but that still only lasts 138.88 hours before I need to write to another address. My idea was to create a way to automatically change eeprom addresses when I reached a certain time. The idea worked (as shown in my code), however if I turn off the unit and turn it back on, the minutes data is frozen and the unit acts weird (counts time wrong).

In order to actually see and confirm the eeprom address changes, I print the data stored into the bytes on my lcd. Does anyone know of a way to automatically change eeprom addresses that will also work when its turned off and back on? Am I making a mistake in my code somewhere?

Extra info:

  • Using Arduino Uno
  • 16x2 lcd (no connection problems)
  • No compiler complaints
  • everything works as expected until I reset it (i.e. EEPROM addresses change just fine until then)
#include <EEPROM.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd (5,6,9,10,11,12);

	//TIMERS//		
int secondsAddress;
const int secondsAddressReference = 0;

int minutesAddress = 9;
const int minutesAddressReference = 250;

int hours1Address = 350;
int hours2Address = 355;

//unsigned long totalHours = (hours1Val + (hours2Val * 250));

unsigned long timer = 0;
unsigned long start = millis();

	//HARDWARE//	
int upButton = 8;
int downButton = 0;
int setButton = 1;
int Backlight = 4;
int led = 2;

int y;

void setup()
{
	lcd.begin(16,2);
	pinMode(upButton,INPUT_PULLUP);	
	pinMode(downButton,INPUT_PULLUP);
	pinMode(setButton,INPUT_PULLUP);
	pinMode(led,OUTPUT);
	pinMode(Backlight,OUTPUT);
	digitalWrite(led,LOW);
	digitalWrite(Backlight,HIGH);		
	
	if (EEPROM.read(23)==0xff)
	{
		for (int i=0; i<400; i++)
		{
			EEPROM.write(i,0);
		}
	}
	delay(250);

}

void loop()
{			
	int secondsVal = EEPROM.read(secondsAddress);
	int minutesVal = EEPROM.read(minutesAddress);
	int hours1Val = EEPROM.read(hours1Address);
	int hours2Val = EEPROM.read(hours2Address);	
	
	//Sets the initial secondsAddress to 1 then changes to whatever 
	//is written in the secondsAddressReference slot
	/*if (EEPROM.read(1)==0)
	{ 
		int secondsAddress = 1;		
	}
	
	else
	{
		secondsAddress = EEPROM.read(secondsAddressReference);
	}*/
	
	//Starts the timer when spark signal detected	
	byte button = digitalRead(upButton);
	
	if ((millis() - start >=1000) && (button == HIGH))
	{
		start = millis();
		timer = timer+1;
		lcd.setCursor(0,0);
		lcd.print (timer); 
	}
	
	if (timer>=3)		//Should be 5
	{
		timer = 0;
		secondsVal++;
		EEPROM.write(secondsAddress, secondsVal);
		lcd.setCursor(0,1);
		lcd.print(EEPROM.read(secondsAddress));
	}
	
	if (secondsVal>=3)		//Should be 72
	{
		EEPROM.write(secondsAddress,0);
		minutesVal++;
		EEPROM.write(minutesAddress,minutesVal);
		lcd.setCursor(4,1);
		lcd.print(EEPROM.read(minutesAddress));
	}
	
	if (minutesVal>=3)		//Should be >=9, reset to 0 then add +1 to hours1Val
	{
		EEPROM.write(minutesAddress,0);
		hours1Val++;
		EEPROM.write(hours1Address,hours1Val);
		lcd.setCursor(8,1);
		lcd.print(EEPROM.read(hours1Address));
	}
	
	if (hours1Val>=3)		//Should be when hours1val==200, reset to 0 and add +1 to hours2Val **Worked when == and not >=**
	{
		EEPROM.write(hours1Address,0);
		hours2Val++;
		EEPROM.write(hours2Address,hours2Val);
		lcd.setCursor(12,1);
		lcd.print(EEPROM.read(hours2Address));
	}
		hours2Val = EEPROM.read(hours2Address);
		
	switch (hours2Val)
	{
			case 0:
			secAd1();
			break;	
			case 2:
			secAd2();
			break;
			case 4:
			secAd3();
			break;			
	}
	
	int val1 = EEPROM.read(1);
	lcd.setCursor(4,0);
	lcd.print(val1);
	
	int val2 = EEPROM.read(2);
	lcd.setCursor(8,0);
	lcd.print(val2);
	
	int val3 = EEPROM.read(3);
	lcd.setCursor(12,0);
	lcd.print(val3);	
	
}

	void secAd1()
	{
		secondsAddress = 1;
	}
	
	void secAd2()
	{
		secondsAddress = 2;
	}
	
	void secAd3()
	{
		secondsAddress = 3;
	}

The default value of any EEPROM address that has not be explicitly written to is 0xFF. So, you could read all the addresses until you find a -1. The one before that is the one that was last written to.

Or, you could write the address where data is to be stored in EEPROM, and read where to write data.

Do you REALLY need to track lawn-mower usage to the 5 second level of granularity?

	if (EEPROM.read(23)==0xff)
	{
		for (int i=0; i<400; i++)
		{
			EEPROM.write(i,0);
		}
	}

If you read the address at 23, and it is unused, why are you then trashing all of the first 400 bytes of data?

	int secondsVal = EEPROM.read(secondsAddress);
	int minutesVal = EEPROM.read(minutesAddress);
	int hours1Val = EEPROM.read(hours1Address);
	int hours2Val = EEPROM.read(hours2Address);

EEPROM stores data in byte sized increments. Why are you storing the bytes in ints?

So, you could read all the addresses until you find a -1. The one before that is the one that was last written to

This works if you never write a -1 in eeprom.

This works if you never write a -1 in eeprom.

Not a problem in this case

The default value of any EEPROM address that has not be explicitly written to is 0xFF. So, you could read all the addresses until you find a -1. The one before that is the one that was last written to.

  • Not sure if I understand that idea. Could you provide a little more detail on how that would be done? Thanks

Or, you could write the address where data is to be stored in EEPROM, and read where to write data.

-That's exactly what I did (or tried to do) using "secondsAddressReference". It works until I reset the arduino.

Do you REALLY need to track lawn-mower usage to the 5 second level of granularity?

-No, I could increase that, but eventually I would still have the same problem and now I've taken an interest into the idea of changing addresses automatically.

If you read the address at 23, and it is unused, why are you then trashing all of the first 400 bytes of data?

  • Because Atmel Studio likes to default and write 0xff to all slots before programming. This writes all the slots (and more) to zero before I begin. Address 23 was an arbitrary choice.

EEPROM stores data in byte sized increments. Why are you storing the bytes in ints?

-Ahh ok. Should this be changed to "byte secondsVal = EEPROM.read(secondsAddress);" instead?

Thanks for the response!

That's exactly what I did (or tried to do) using "secondsAddressReference". It works until I reset the arduino.

But, you don't store data at that address, or read data from that address.

Suppose that you are storing data in 2, 3, 4, and 5. You wear out those locations, so you want to move to 6, 7, 8, and 9. How is the Arduino going to know that, after reset, unless you store a value (2, then 6, then 10, etc.) in 0 and 1?

So, store the address of the first address in 0 and 1. Read that address first.

When you wear out the locations 2, 3, 4, and 5, change the value on 0 and 1.

  • Because Atmel Studio likes to default and write 0xff to all slots before programming.

So, using Atmel Studio, you are going to destroy all the data in EEPROM any time you need to change the sketch, making storing data in EEPROM useless. Time to ditch that stupid software.

Should this be changed to "byte secondsVal = EEPROM.read(secondsAddress);" instead?

Assuming that you are writing just one byte for secondsVal, yes.

But, you don't store data at that address, or read data from that address.

Suppose that you are storing data in 2, 3, 4, and 5. You wear out those locations, so you want to move to 6, 7, 8, and 9. How is the Arduino going to know that, after reset, unless you store a value (2, then 6, then 10, etc.) in 0 and 1?

So, store the address of the first address in 0 and 1. Read that address first.

When you wear out the locations 2, 3, 4, and 5, change the value on 0 and 1.

  • That was the first thing I tried, as that was more intuitive than the switch case, however that idea only works up until the unit is restarted...for some reason.

So, using Atmel Studio, you are going to destroy all the data in EEPROM any time you need to change the sketch, making storing data in EEPROM useless. Time to ditch that stupid software.

-Found the default and turned that off. Do you use the Arduino IDE or something else? Atmel Studio has cut my programming time in more than half with things like autofill and rename preferences...although I do agree the default eeprom thing is weird.

Still looking for a solution that works when the unit is restarted. ugh

That was the first thing I tried, as that was more intuitive than the switch case, however that idea only works up until the unit is restarted...for some reason.

Without seeing the code, I can not explain why it didn't work for you. It worked fine for me, when I used that approach.

Here’s the code I was using, except now something is wrong and the program hangs when hours2Val==2…

#include <EEPROM.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd (5,6,9,10,11,12);

  //TIMERS//    
int secondsAddress;
const int secondsAddressReference = 0;

int minutesAddress = 9;
const int minutesAddressReference = 250;

int hours1Address = 350;
int hours2Address = 355;

//unsigned long totalHours = (hours1Val + (hours2Val * 250));

unsigned long timer = 0;
unsigned long start = millis();

  //HARDWARE//  
int upButton = 8;
int downButton = 0;
int setButton = 1;
int Backlight = 4;
int led = 2;

int y;

void setup()
{
  lcd.begin(16,2);
  pinMode(upButton,INPUT_PULLUP); 
  pinMode(downButton,INPUT_PULLUP);
  pinMode(setButton,INPUT_PULLUP);
  pinMode(led,OUTPUT);
  pinMode(Backlight,OUTPUT);
  digitalWrite(led,LOW);
  digitalWrite(Backlight,HIGH);   
  
  if (EEPROM.read(23)==0xff)
  {
    for (int i=0; i<400; i++)
    {
      EEPROM.write(i,0);
    }
  }
  delay(250);

}

void loop()
{     
  byte secondsVal = EEPROM.read(secondsAddress);
  byte minutesVal = EEPROM.read(minutesAddress);
  byte hours1Val = EEPROM.read(hours1Address);
  byte hours2Val = EEPROM.read(hours2Address);  
  
  //Sets the initial secondsAddress to 1 then changes to whatever 
  //is written in the secondsAddressReference slot
  if (EEPROM.read(1)==0)
  { 
    int secondsAddress = 1;   
  }
  
  else
  {
    secondsAddress = EEPROM.read(secondsAddressReference);
  }
  
  //Starts the timer when spark signal detected 
  byte button = digitalRead(upButton);
  
  if ((millis() - start >=1000) && (button == HIGH))
  {
    start = millis();
    timer = timer+1;
    lcd.setCursor(0,0);
    lcd.print (timer); 
  }
  
  if (timer==2)   //Should be 5
  {
    timer = 0;
    secondsVal++;
    EEPROM.write(secondsAddress, secondsVal);
    lcd.setCursor(0,1);
    lcd.print(EEPROM.read(secondsAddress));
  }
  
  if (secondsVal==2)    //Should be 72
  {
    EEPROM.write(secondsAddress,0);
    minutesVal++;
    EEPROM.write(minutesAddress,minutesVal);
    lcd.setCursor(4,1);
    lcd.print(EEPROM.read(minutesAddress));
  }
  
  if (minutesVal==2)    //Should be >=9, reset to 0 then add +1 to hours1Val
  {
    EEPROM.write(minutesAddress,0);
    hours1Val++;
    EEPROM.write(hours1Address,hours1Val);
    lcd.setCursor(8,1);
    lcd.print(EEPROM.read(hours1Address));
  }
  
  if (hours1Val==2)   //Should be when hours1val==200, reset to 0 and add +1 to hours2Val **Worked when == and not >=**
  {
    EEPROM.write(hours1Address,0);
    hours2Val++;
    EEPROM.write(hours2Address,hours2Val);
    lcd.setCursor(12,1);
    lcd.print(EEPROM.read(hours2Address));
  }
    hours2Val = EEPROM.read(hours2Address);
    
  if (hours2Val==2)
  {
    secondsAddress++;
    EEPROM.write(secondsAddressReference,secondsAddress);
    //secondsAddress = EEPROM.read(secondsAddressReference);
  }
    
    lcd.setCursor(4,0);
    lcd.print(EEPROM.read(1));
    
    lcd.setCursor(8,0);
    lcd.print(EEPROM.read(2));
    
    lcd.setCursor(12,0);
    lcd.print(EEPROM.read(3));
  }

Mr_Laggy: -Found the default and turned that off. Do you use the Arduino IDE or something else? Atmel Studio has cut my programming time in more than half with things like autofill and rename preferences...although I do agree the default eeprom thing is weird.

Hopefully this clears it up:

EEPROM can be initialized like PROGMEM. The Arduino IDE has disabled the EEPROM overwrite, however Atmel studio does it by default.

If you've seen a progmem declaration: const char pgmarr[] PROGMEM = "A string in flash";

You can do the same with the EEPROM using the EEMEM attribute. char eearr[] EEMEM = "A string in EEPROM";

Here’s the code I was using, except now something is wrong and the program hangs when hours2Val==2…

  if (EEPROM.read(23)==0xff)
  {
    for (int i=0; i<400; i++)
    {
      EEPROM.write(i,0);
    }
  }

You are still wiping out all the data in EEPROM, and then expecting what you store to still be there. I give up.

You are still wiping out all the data in EEPROM, and then expecting what you store to still be there. I give up.

  • huh? No its not...The if statement only works if address 23==0xff. If the unit is turned off and back on it doesn't do anything because address 23 has already been written as 0.

I got the addresses to start changing automatically but had to use switchcase instead of secondsAddressReference. Can you post the code you got to work so I can see where I went wrong?

  if (EEPROM.read(1)==0)
  { 
    int secondsAddress = 1;   
  }

Create a new, local, variable that immediately goes out of scope. Interesting...

The real problem is that you are hardcoding values for the addresses to read the initial data from, and then, after reading the initial data, you discover that the real data is somewhere else, so you write the modified initial data there.

You need to, in setup(), read the location where the data is. Then, in loop(), you need to read the data, modify it, and write it back, possibly to a new location that you also save.

Create a function called getLastUsedLocations(). Create a function called getLastSavedValues(); Create a function called saveCurrentData();

Put the appropriate code in those functions. Call those functions from setup() and loop().

This makes it far easier to see that you are performing the right steps, in the right order, in the right places, and doing the correct thing in each step. Or, that you are not doing that.