An enhanced version of the original cmaglie/FlashStorage library, this is a safe version of the library for storing data structures in flash memory on SAMD21 and SAMD51 microcontrollers. Data persists across power cycles and resets, making it ideal for configuration storage, calibration values, and application state.
- Type-safe storage - Store any C/C++ struct or built-in types (int, float, bool, char, etc)
- Automatic data validation - Built-in checksums and data validity checking
- Write optimization - Skips no-change writes to preserve flash endurance
- Error handling - Comprehensive transaction validation with return values
- Corruption detection - Detects uninitialized, corrupted, or incompatible data
- Easy to use - Simple read/write API with one-line declarations
- Flash-friendly - Respects hardware alignment and erase boundaries
This is an enhanced version of the original FlashStorage library with safety improvements:
- Comprehensive bounds checking to prevent memory corruption
- Prevent integer overflow in address calculations
- Fixed SAMD51 cache handling per device errata
- Enhanced error handling to ensure safe use
- SAMD21 and SAMD51-based boards
- Download this repository as a ZIP file
- In Arduino IDE: Sketch → Include Library → Add .ZIP Library...
- Select the downloaded ZIP file
- Restart Arduino IDE
- Clone or download this repository
- Copy the
flashstoragefolder to your Arduino libraries directory:- macOS:
~/Documents/Arduino/libraries/ - Windows:
C:\Users\[username]\Documents\Arduino\libraries\ - Linux:
~/Arduino/libraries/
- macOS:
- Restart Arduino IDE
#include "SAMD_SafeFlashStorage.h"
// Create a flash storage instance for an int
FlashStorage(bootCounter, int);
void setup() {
Serial.begin(9600);
int count;
// Read from flash (returns false if no valid data)
if (bootCounter.read(&count)) {
// Valid data found - increment it
count++;
Serial.print("Boot count: ");
Serial.println(count);
} else {
// No valid data - this is the first run
count = 1;
Serial.println("First boot!");
}
// Write to flash
bootCounter.write(count);
}See examples/BasicUsage/BasicUsage.ino for a complete working example that demonstrates:
- Reading existing configuration or initializing defaults
- Writing configuration to flash
- Verifying data integrity
- No-change write optimization
- Persistence via a "Boot" counter
FlashStorage(name, DataType);- name: Unique identifier for this storage (must be unique across your sketch)
- DataType: Your struct or POD type (plain old data - no pointers, virtual functions, or dynamic types)
bool write(DataType data);Writes data to flash memory. Returns true on success, false on error.
Note: Automatically skips write if data hasn't changed to preserve flash.
Example:
int myValue = 42;
if (myStorage.write(myValue)) {
Serial.println("Saved!");
} else {
Serial.println("Write error.");
}bool read(DataType *data);Reads data from flash memory. Returns true if valid data found, false if uninitialized/corrupted.
Example:
int myValue;
if (myStorage.read(&myValue)) {
// Valid data found - use myValue
Serial.println(myValue);
} else {
// No valid data - we must manually set a default
// (the pointer version doesn't do this automatically)
myValue = 0;
}DataType read();Returns the stored value, or a default value if read fails.
What happens on failure:
intreturns0floatreturns0.0boolreturnsfalse- Structs return with all fields set to zero
Important: This version doesn't tell you if the read failed. Use the pointer version read(&variable) if you need to know whether valid data was found.
Example:
int myValue = myStorage.read();
// If read failed, myValue will be 0
// If read succeeded, myValue will contain the stored value
// You can't tell which happened!// Good
if (configStore.write(config)) {
Serial.println("Saved successfully");
} else {
Serial.println("Save failed!");
}Configuration config;
if (!configStore.read(&config)) {
// First run or corrupted data - set defaults
config.bootCount = 1;
config.sensorInterval = 60;
config.enableLogging = true;
config.calibrationValue = 1.0;
}Flash memory has limited write cycles (~10,000-100,000 erase cycles per block)
// ***** BAD ***** - Writes constantly:
void loop() {
config.counter++;
configStore.write(config); // This will eventually wear out device flash
delay(100);
}Note: The library automatically skips writes when data hasn't changed. Regardless, avoid calling write() unnecessarily.
// ***** GOOD ***** - Plain data types only:
typedef struct {
uint32_t id;
float values[10]; // Fixed-size arrays OK
char name[32]; // Fixed-size strings OK
} ValidConfig;
// ***** BAD ***** - Dynamic/complex types:
typedef struct {
String name; // Dynamic allocation - DON'T USE
float *values; // Pointer - DON'T USE
std::vector<int> v; // STL containers - DON'T USE
} InvalidConfig;Flash operations are NOT safe to call from interrupt service routines (ISRs):
// Never call from ISR:
void ISR_handler() {
configStore.write(config); // DON'T DO THIS!
}Note: The library automatically disables interrupts during flash operations to prevent conflicts. You do not need to manually wrap calls with noInterrupts()/interrupts(). Do not call flash operations from within an ISR, as this would attempt to disable interrupts while already in an interrupt context.
## Memory Considerations
### Flash Allocation
The library allocates flash memory aligned to page boundaries. Page size varies based on total device NVS. The following are typical values for most development boards:
- **Typical SAMD21**: 256-byte pages
- **Typical SAMD51**: 8192-byte pages
A small struct like:
```cpp
struct Config {
uint32_t value1; // 4 bytes
uint16_t value2; // 2 bytes
}; // 6 bytes + 4 bytes overhead = 10 bytes actual
Will allocate at least one full page. This is a hardware limitation, as flash can only be erased in full pages.
- Maximum practical size: ~8KB per structure
- Multiple small structures are more efficient than one large structure
- Padding/alignment may increase actual size
Check your structure size:
Serial.print("Config size: ");
Serial.println(sizeof(Configuration));Cause: Trying to compile for an unsupported platform (AVR, ESP32, etc.)
Solution: This library only works on SAMD21 and SAMD51 boards.
Possible causes:
- First time running - No data written yet (normal)
- Bounds check failure - Very large structures
- Flash corruption - Rare hardware issue
Solutions:
- Always initialize defaults when
read()returns false - After changing struct, expect old data to be invalid
- Check structure size with
sizeof()
Possible causes:
- Write not called - Check if
write()is actually executing - Upload erases flash - Some bootloaders may erase all user NVS pages
- Power loss during write - Flash write interrupted
Solutions:
- Verify
write()returnstrue - Check serial output for write confirmation
- Ensure stable power during writes
You can store multiple independent structures:
typedef struct {
uint8_t brightness;
uint8_t volume;
} UserSettings;
typedef struct {
float offset;
float scale;
} SensorCalibration;
FlashStorage(settings, UserSettings);
FlashStorage(calibration, SensorCalibration);
void setup() {
UserSettings s;
SensorCalibration c;
// Always check if reads succeeded
if (!settings.read(&s)) {
// No valid data - initialize defaults
s.brightness = 128;
s.volume = 50;
}
if (!calibration.read(&c)) {
// No valid data - initialize defaults
c.offset = 0.0;
c.scale = 1.0;
}
// Each has independent storage and validation
}The library automatically validates:
- Structure identity - Hash of name + size
- Data integrity - Checksum of data bytes
This protects against:
- Reading wrong variable
- Structure size changes
- Bit corruption
- Uninitialized flash
Hash Collisions: The library uses a 16-bit hash to identify FlashStorage instances. With numerous instances in a project (unlikely), hash collisions could occur. To minimize this risk:
- Use unique, descriptive variable names
- Limit the total number of FlashStorage instances per project (<10 is best)
Typical write times (erasing + writing ~256 bytes on SAMD21):
- First write (erase+write): 500-2000 µs
- Optimized write (unchanged data): 20-100 µs
- This library is based on the original FlashStorage library: Arduino LLC / Cristian Maglie