Extending The AVR EEPROM Write Limits Almost to SRAM Like Levels

I have come across an application problem to which I believe I've found a good solution. Before sharing, I will warn, I'm not a struct, template, union kind-of-guy. I try to keep thing simple and stick to Arduino programming language (for the most part). I welcome feed back on how I could have written this differently, to make it more memory or more cpu cycle friendly. Your feedback might also push me toward being more confident with object oriented cpp techniques.

My application problem is, I need to store non-volatile data once per minute over a greater than 5 year period, on a standard Arduino Uno/Nano/Mega, without adding SRAM. How can I use EEPROM memory to store a two separate counters; one holding the total uC run time (in minutes), the other keeping track of total fan run-time (also in minutes). The application use case will have 10's of thousands of microcontrollers deployed globally within a widget which will be queried to report these two time elements. Data will be used to trigger maintenance of said widgets.

Now I considered, without adding a RTC, accuracy might be an issue; millis() does not make a very good stopwatch! Also, every time the uC resets, part of the minute under count would be lost. However for my application, the units deployed are expected to run over a 5 to 7 year period. Accuracy of +/- 30 days is more than acceptable. To add, reading and writing to EEPROM is quite slow but again, more than acceptable for my application use case.

The typical EEPROM write limits of the Arduino Uno, Nano, Mega is ~100K writes. Keeping track of total number of minutes, over 5 years, would require more than 2.6M writes to memory. So I thought, what if i spread those writes across a larger block by utilizing the 1K of on chip EEPROM memory. This would enable many multiples of write cycles across the entire block.

So I created a 256 byte circle buffer which stores 64 x 4 byte (type long) minute values. Even if I choose to limit writes a more conservative 80K write cycles per EEPROM address (to be on the safe side) a 256 byte block allows me to write over 64 x 256 times or 5,120,000 different minute values across the entire buffer.

My circle buffer code consists of defining a base address for run minutes and a base address for fan run minutes. In my example I used a base address of 100 and 500 respectively. I also created a function (called only once upon Arduino initialization) which scans each block of EEPROM in search of the highest long value written within the block. Once the entire block is scanned, it returns a pointer address which allows us to recover the last recorded number of minutes. From there a counter located in the loop triggers every millis(60000), stores a new minute value to the circle buffer address and advances the pointer by 4 bytes. Once the circle buffer writes a value to the 260th memory location, it resets the pointed to the base address of the circle buffer.

To make the counter more accurate, I might later add a RTC which allows me to replace millis() with a new RTCmillis() function (more accurate count of each minute). The RTC has 56 bytes of battery backed-up SDRAM which I will likely use to store the total seconds counted in between minutes..

Again, I'm confident you more experienced programmer would likely laugh at my first year programming techniques. Your feedback, good and bad, is sincerely most welcomed.

Code looks something like this (covers only the total run time minute counter):

// offers very helpful features against writing different types to EEPROM
#include <EEPROMVar.h> 
#include <EEPROMex.h>

float seconds;
long minutes; //variable to hold minute count
const int buffer_size = 255; //EEPROM 256 buffer size - could be larger if needed
const int pwrCircBuff = 100; //circle buffer base address
int Addr_pnt_PoM; // pointer for next write EEPROM write

//returns the next write pointer value in long circular EEPROM buffer
int circFindPnt(int start) {
	long p = start; //zero the starting buffer pointer
	long pVal = 0;

	//scans the buffer for the largest long vlue then stores its address in p. 
	for (size_t i = start; i < start + buffer_size; i += 4) {
		long v = EEPROM.readLong(i);
		if (v > pVal) { 
			pVal = v;
			p = i;
		}
	}
	return p;
}

void setup(){
/*
.
. other code here.... 
.
*/
	Addr_pnt_PoM = circFindPnt(pwrCircBuff);
	minutes = EEPROM.readLong(Addr_pnt_PoM);
	if (Addr_pnt_PoM >= buffer_size + pwrCircBuff) Addr_pnt_PoM = pwrCircBuff;
	else Addr_pnt_PoM += 4; //otherwise set the pointer to the next pointer address

}

void loop()
{
/*.
. other code here... 
.
*/
	seconds = (float)(millis() - StartupTimer) / 1000;
	if (seconds > 58.46557215) //offers better accuracy which compensates for the latency in other code in my firmware (30,622 bytes used out of 30,720 available on the Nano flash)
	{
		StartupTimer = millis();
		minutes++;
		seconds = 0;
		Addr_pnt_PoM = circWrite(Addr_pnt_PoM, pwrCircBuff, minutes);
		Addr_pnt_PoM += 4;
	}
	days = minutes / 24.0 / 60.0;
}

type or paste code here

So have at it... bust my chops on what I did wrong. It would be nice to turn this into a library but I haven't the slightest clue or time to figure out how to make that happen.

Cheers!

Use a FRAM, up to 100,000,000,000,000 writes and forget about it.

Or since your already thinking of adding an RTC, use an RTC with RAM.

1 Like

Yes this is a well known technique, it is called "wear levelling". It is even automatically implemented in SD cards and USB memory sticks.
See
https://en.wikipedia.org/wiki/Wear_leveling

1 Like

Maybe this will help.

2 Likes

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