While working on the next release of PixelMaestro, I came across a problem of storing long-term data on an Arduino. I have a system in place where configuration changes are sent to an Arduino over USB as byte arrays, and as the Arduino reads the array it applies the changes. The problem is those changes disappear as soon as you restart the device. How, then, does one store persistent data without having to add an SD card or extra memory? Enter EEPROM.
What is EEPROM?
EEPROM (short for electrically erasable programmable read-only memory) is a special type of memory that stores data without having to be powered. Any data written to EEPROM persists until you overwrite it, making it ideal for storing long-term data that changes infrequently. Imagine a flash drive smaller than your fingernail.
Arduinos have a built-in EEPROM that can store anywhere from 512 to 4096 bytes (0.5 – 4KB) depending on the type of Arduino. Using the Arduino library, you can manipulate EEPROM as if it were a regular array. The only limitation is the lifespan: EEPROM has a maximum lifespan of 100,000 writes, which isn’t much if you plan on writing to it frequently. However, the real-world life expectancy can end up being much longer than this depending on how you use it.
EEPROM in Action
The way I use EEPROM is fairly straightforward: read in a series of bytes sent over a serial connection and store them. When starting the Arduino, check the EEPROM to see if we have any data stored in it, and if we do, execute it. You can see the full Arduino sketch by clicking here.
There’s a lot going on in that sketch that’s specific to PixelMaestro, so I’ll provide some context.
PixelMaestro uses a method of sending data over a serial connection that consists of arrays of bytes (called Cues). Each Cue starts with the bytes “PMC” (for PixelMaestro Cue). Each Cue also contains its own size, so the receiver knows exactly how many bytes the Cue contains. When an Arduino receives a Cue over a serial connection, an object called a CueController reads in each byte one-by-one. When the serial connection is done reading, the CueController reconstructs the Cue, then runs it as a command.
In this sketch, multiple Cues are being sent to the Arduino at once. This collection of Cues is known as a Cuefile. The Cuefile begins with the bytes “ROMBEG” and ends with the bytes “ROMEND”, indicating the start and end of the Cuefile (this is important once we start writing the Cuefile to EEPROM). Once the Cuefile is completely written to EEPROM, we can then run the EEPROM’s contents through the CueController as if we were passing in a normal Cue.
If this sounds confusing, just remember two things: a Cuefile is a group of commands that we want to run, and our goal is to store a Cuefile in EEPROM.
Stepping Through the Sketch
The most important part of the sketch is the run_eeprom_cue() function. This function steps through each byte of EEPROM and passes it to the CueController. The CueController uses these bytes to reconstruct and run the Cuefile.
void run_eeprom_cue() { for (int index = 0; index < EEPROM.length(); index++) { maestro.get_cue_controller()->read(EEPROM[index]); } }
In setup(), we run this method if we detect a Cuefile inside of EEPROM. To do this, we check the first three bytes of EEPROM for “PMC”, which can be found at the start of every Cue:
void setup() { if (EEPROM[0] == 'P' && EEPROM[1] == 'M' && EEPROM[2] == 'C') { run_eeprom_cue(); } }
loop() is where things get a bit trickier. Remember how Cuefiles start with “ROMBEG” and “ROMEND”? These actually act as triggers that tell the Arduino when to start writing serial data to EEPROM and when to stop. Without these flags, we would just keep writing serial input to EEPROM indefinitely. Not only would this needlessly shorten the life of EEPROM, but it would screw up our Cuefile by writing it in the wrong location in EEPROM.
We set the write flag using the (ironically named) boolean variable “eeprom_read”. If eeprom_read is true, any serial input we detect is written to EEPROM. The “ROMBEG” header sets eeprom_read to true, and the “ROMEND” header sets it to false. The sketch includes some additional variables for detecting these flags and tracking the current EEPROM write index.
// In loop(): // If we read in the EEPROM start header, write the following serial data to EEPROM. if (header[0] == 'R' && header[1] == 'O' && header[2] == 'M' && header[3] == 'B' && header[4] == 'E' && header[5] == 'G') { eeprom_read = true; } // If we read in the EEPROM end header, stop writing to EEPROM and reset the eeprom read index. else if (header[0] == 'R' && header[1] == 'O' && header[2] == 'M' && header[3] == 'E' && header[4] == 'N' && header[5] == 'D') { eeprom_read = false; eeprom_index = 0; run_eeprom_cue(); }
Now, whenever we restart the Arduino, we immediately scan EEPROM for the presence of Cues and run them through the CueController.
What’s the Use?
My goal was to create a code-free way to reconfigure an Arduino over USB, and EEPROM was the best way to do it. In the case of PixelMaestro, this means creating and uploading custom LED patterns. Previously, you had to write your own Arduino sketches and upload them through the IDE. Now you can send designs directly from PixelMaestro Studio to your device, where it will persist across reboots. My goal now is to refine the process, add error checking, and reduce the memory footprint on the Arduino side.
As always, you’re welcome to clone or fork the repository and play with the code yourself. The core library is available here, and PixelMaestro Studio (the accompanying desktop app) is available here. The Arduino sketch is available here, and the GUI class that interacts with it is available here. You can learn more about using EEPROM on Arduino here.
Happy hacking!
If you want people to use this you might want to figure out a set of instructions on installing your libraries. No matter what is done your ino is missing dependencies whether it be platform.io or arduino ide. Imported into both, simply won’t compile because of:
WS2812.h: No such file or directory OR
cRGB.h: No such file or directory
And there’s no apparent solution. I was excited to find your project but now I’m just irritated.
Hey Chris, sorry you’re having trouble. Those files belong to the LightWS2812 library, so you would need to install that before you can use any of the ino files in the WS2812 folder. PlatformIO should have installed it automatically when you tried compiling. Same for the NeoPixel examples. I’m honestly not sure why that didn’t happen.
If you do already have LightWS2812 installed, let me know and I’ll try to figure out what’s going on.