Hello! Welcome to the final lesson in our module on Designing for Industrial Reliability.
In our previous lessons, we've made our Arduino system physically and logically robust. We've protected it from electrical noise and damaging overvoltage/overcurrent events, and we've implemented a watchdog timer to recover from software freezes. However, there's one more critical aspect of reliability: what happens when the power goes out? All the variables and states stored in the Arduino's SRAM are lost.
This lesson addresses the learning outcome: Use the onboard EEPROM to store critical configuration parameters and maintain state after power loss. We will learn how to save important data—like calibration values, operational settings, or counters—so that our device can restart exactly where it left off, a non-negotiable requirement for most industrial equipment.
1. Understanding Arduino's Memory Landscape
For industrial applications, it's crucial to understand where your data lives. An Arduino microcontroller, like the one on an industrial PLC from Industrial Shields, has three types of memory.
{
"type": "reading",
"title": "Storing the MAC of your PLC Arduino in the EEPROM",
"id": "[LINK](https://www.industrialshields.com/blog/arduino-industrial-1/storing-the-mac-of-your-plc-arduino-in-the-eeprom-non-volatile-memory-174)",
"url": "https://www.industrialshields.com/blog/arduino-industrial-1/storing-the-mac-of-your-plc-arduino-in-the-eeprom-non-volatile-memory-174",
"relevant_section_indices": [
0
],
"par_intro": "The article 'Storing the MAC of your PLC Arduino in the EEPROM' from Industrial Shields provides an excellent overview of the three memory types from an industrial perspective.",
"par_directions": "Please read the 'Introduction' section. Focus on the distinction between Flash, SRAM, and EEPROM, and why EEPROM is essential for retaining information between restarts.",
"estimated_time": "5 minutes"
}
As the article explains:
- Flash Memory: Non-volatile. Stores your program code (the sketch).
- SRAM (Static RAM): Volatile. Stores your variables while the program is running. This memory is fast but is completely erased when power is lost.
- EEPROM (Electrically Erasable Programmable Read-Only Memory): Non-volatile. This is a small storage space designed specifically to hold data that must survive a power cycle.
EEPROM is our tool for persistence. We can use it to store motor tuning parameters, sensor calibration offsets, network addresses, or even the last known state of a machine.
2. Key Characteristics and Limitations of EEPROM
Before we start writing code, it's vital to understand two key characteristics of EEPROM that influence how we use it.
- It's relatively slow, especially for writing.
- It has a limited write endurance. The memory cells can only be written to a finite number of times before they wear out.
{
"type": "video",
"title": "EP5 – How to Program Arduino – Save Settings in EEPROM",
"id": "[LINK](https://www.youtube.com/watch?v=qqkRVkGcfbk)",
"video_id": "qqkRVkGcfbk",
"relevant_section_indices": [
0
],
"par_intro": "Let's watch a segment from the video 'How to Program Arduino – Save Settings in EEPROM' by ForOurGood. It clearly explains the concept of write endurance and why we must be careful.",
"par_directions": "Watch from 02:32 to 04:23. The video explains what EEPROM is and discusses the write cycle limitation (typically 100,000 cycles for the ATmega328P used in the Arduino Uno). Pay close attention to the warning about not writing to the EEPROM inside your main `loop()` function."
}
The key takeaway is that EEPROM is for data that changes infrequently. You should only write to it when a setting actually changes, not continuously. Because of your experience with STM32 microcontrollers, you'll find the video's later comparison (04:23 - 05:59) interesting, as some STM32s emulate EEPROM using flash memory, which has even lower write endurance. This reinforces the need for careful management.
3. The EEPROM.h Library: A Professional Approach
Arduino provides a built-in library, EEPROM.h, to make working with this memory easy. While there are basic functions like EEPROM.read() and EEPROM.write() for single bytes, a much more robust and scalable method is to use EEPROM.get() and EEPROM.put(). These functions can read and write entire variables of any data type, including custom structures (struct).
The most powerful technique, especially for complex industrial applications, is to group all your persistent settings into a single C++ struct.
Advantages of using a struct:
- Organization: All settings are managed in one consolidated block of code.
- Efficiency: You can save or load all settings with a single command.
- Maintainability: It's easy to add, remove, or change settings without having to manage dozens of individual memory addresses.
Let's see how this is done in practice.
{
"type": "video",
"title": "EP5 – How to Program Arduino – Save Settings in EEPROM",
"id": "[LINK](https://www.youtube.com/watch?v=qqkRVkGcfbk)",
"video_id": "qqkRVkGcfbk",
"relevant_section_indices": [
2,
3
],
"par_intro": "The same video from ForOurGood provides a fantastic, step-by-step guide to creating a robust settings manager using a `struct`.",
"par_directions": "Please watch from 06:41 to 13:36. This is the core of our lesson. The video will show you:\n1. The basic EEPROM functions (`get`, `put`).\n2. How to define a `struct` to hold all your settings (booleans, integers, floats, and even arrays).\n3. How to save and load this entire structure with a single command."
}
4. Building a Resilient Settings Manager
Just saving and loading data isn't enough for a reliable system. We need to handle two potential problems:
- First Boot: What happens the very first time the device is powered on? The EEPROM will contain random garbage data.
- Firmware Updates: What if you update your code and change the
struct(e.g., add a new setting)? The old data in EEPROM will no longer match the new structure, leading to data corruption.
The solution is to add a "check value" (also called a "magic number" or version identifier) to our settings structure. The logic is simple:
- On startup, load the settings from EEPROM.
- Check if the check value in the loaded data matches the expected value in your code.
- If it matches: The data is valid. Proceed as normal.
- If it doesn't match: The data is invalid (first boot or corrupted). Load a set of safe, default values into your settings variable, and then immediately save these defaults to the EEPROM.
This ensures your device always starts in a known, safe state.
{
"type": "video",
"title": "EP5 – How to Program Arduino – Save Settings in EEPROM",
"id": "[LINK](https://www.youtube.com/watch?v=qqkRVkGcfbk)",
"video_id": "qqkRVkGcfbk",
"relevant_section_indices": [
4,
5
],
"par_intro": "Let's continue with the ForOurGood video, which explains and implements this check value concept perfectly.",
"par_directions": "Watch from 14:13 to 19:56. This segment demonstrates:\n1. Adding a check value to the `struct` to detect corruption or version changes.\n2. Creating functions to `setDefaults()`, `setLoad()`, and `setSave()`.\n3. The crucial logic inside `setLoad()` that checks the value and loads defaults if the check fails."
}
Test your understanding!
You have an application with settings stored in EEPROM using a struct with a check value. You decide to add a new parameter to your struct. You compile and upload the new firmware without clearing the EEPROM first. What will happen when the Arduino restarts, and why is this the desired behavior?
Show answer
When the Arduino restarts, it will load the old, smaller struct data from EEPROM into the new, larger struct variable in SRAM. Because the size and layout have changed, the check value will be read from the wrong memory location and will not match the expected value.
The program will detect this mismatch, conclude that the EEPROM data is invalid, and then execute the logic to load the default settings. It will then save these new, complete default settings (including the new parameter) to the EEPROM. This is the desired behavior because it prevents data corruption and ensures the device starts with a valid, known configuration, making the system robust against firmware updates.
5. A Complete Industrial Example
Let's put this all together into a practical sketch. Imagine we have a device that controls a pump. We need to store two critical parameters:
max_pressure_psi: A safety setpoint.total_run_cycles: An operational counter for maintenance scheduling.
We will create a settings manager that loads these values on startup, uses them in the main loop, allows updating them via the Serial Monitor, and increments the cycle counter, saving it periodically.
Here is the complete code implementing the robust struct-based approach.
#include <EEPROM.h>
// 1. Define a check value. Change this if you change the struct.
#define SETTINGS_VERSION 123
// 2. Define the structure for all settings.
struct MachineSettings {
unsigned int version_check; // Our check value to detect corruption
float max_pressure_psi;
unsigned long total_run_cycles;
};
// 3. Create a global variable to hold the current settings.
MachineSettings settings;
void setup() {
Serial.begin(9600);
while (!Serial); // Wait for Serial Monitor
Serial.println("Booting up...");
loadSettings(); // Load settings from EEPROM or defaults
Serial.println("Current Settings Loaded:");
printSettings();
// Increment the run cycle counter for this boot-up
settings.total_run_cycles++;
saveSettings(); // Save the new cycle count
}
void loop() {
// Main logic would use the settings, e.g.:
// if (readPressureSensor() > settings.max_pressure_psi) {
// shutdownPump();
// }
// Example: Allow updating the max pressure via Serial Monitor
// e.g., send "P=150.5"
if (Serial.available() > 0) {
String command = Serial.readStringUntil('\n');
if (command.startsWith("P=")) {
command.remove(0, 2); // Remove "P="
float newPressure = command.toFloat();
if (newPressure > 0) {
settings.max_pressure_psi = newPressure;
saveSettings(); // Save the new value immediately
Serial.println("New max pressure saved:");
printSettings();
}
}
}
// To demonstrate persistence, we can't block here.
// In a real app, you wouldn't just delay.
delay(1000);
}
// 4. Function to load default settings
void loadDefaults() {
Serial.println("Loading default settings...");
settings.version_check = SETTINGS_VERSION;
settings.max_pressure_psi = 120.0; // A safe default
settings.total_run_cycles = 0;
}
// 5. Function to print the current settings
void printSettings() {
Serial.print(" - Max Pressure: ");
Serial.print(settings.max_pressure_psi);
Serial.println(" PSI");
Serial.print(" - Total Run Cycles: ");
Serial.println(settings.total_run_cycles);
}
// 6. Function to load settings from EEPROM
void loadSettings() {
// Read the settings from EEPROM into our 'settings' variable
EEPROM.get(0, settings);
// Check if the version matches
if (settings.version_check != SETTINGS_VERSION) {
// Data is corrupt or from a different firmware version.
// Load defaults and save them.
loadDefaults();
saveSettings();
}
}
// 7. Function to save the current settings to EEPROM
void saveSettings() {
EEPROM.put(0, settings);
Serial.println("Settings saved to EEPROM.");
}
How to Test This Code:
- Upload the sketch to your Arduino and open the Serial Monitor. On the first run, you will see it load the default values. The run cycle counter will be 1.
- Reset your Arduino. You will see it boot up again. This time, it will load the values from EEPROM, and the run cycle counter will be 2.
- Send a command like
P=150.5through the Serial Monitor. The new pressure will be saved. - Reset the board again. The device will remember both the new pressure setting (150.5) and the incremented run cycle counter (3).
- Now, change the
SETTINGS_VERSIONin the code (e.g., to124) and re-upload. When it restarts, it will detect the version mismatch and revert to the hard-coded default values.
Conclusion
You have now mastered a fundamental technique for building reliable industrial devices. By using the onboard EEPROM to store critical parameters, you ensure that your system can withstand power interruptions and recover to a known, functional state.
Key Takeaways:
- EEPROM is the Arduino's non-volatile memory, essential for data that must persist through power cycles.
- It has a limited write lifespan, so you must design your code to write only when necessary, avoiding writes in a tight loop. Using
EEPROM.put()helps as it only writes if the data has changed. - The most robust method for managing settings is to group them in a C++
struct. - A versioning or "check value" system is crucial for detecting data corruption and ensuring a safe startup, especially after firmware updates.
- Encapsulating your logic into
loadSettings(),saveSettings(), andloadDefaults()functions creates clean, maintainable, and reliable code.
Preview of the Next Lesson:
This lesson concludes our module on Designing for Industrial Reliability. We've covered software recovery (watchdog), hardware protection (power filtering, circuit protection), and state persistence (EEPROM). The next lesson begins our final capstone project, where you will design and build a system that reads multiple industrial sensors on an Arduino. This project will require you to apply everything you've learned so far to create a complete, mini industrial control system.
Can't find a good explanation? Sign up and we'll make it for you