← ESP32 PLC Persistent Counters with NVS Preferences
Hydraulic moving floor (BLE app)ESP32 PLC 38RNVSDatalogging
ESP32 PLC Persistent Counters with NVS Preferences — full example
Store cycle counters, serial numbers and totals in ESP32 NVS flash so they survive reboots. Arduino Preferences example for an industrial PLC 38R.
Complete, runnable program for the ESP32 PLC 38R (nvs-persistent-counters-38r.ino): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
/*
* COMPLETE EXAMPLE — Persistent counters in non-volatile memory (Preferences)
*
* Hardware: ESP32 PLC 38R (Industrial Shields)
* Based on: hydraulic moving floor project, AppLink.cpp (NVS)
*
* Wiring:
* I0_0 "Load cycle" push button (simulates end of load)
* I0_1 "Unload cycle" push button (simulates end of unload)
*
* Logic:
* - Serial number, load / unload / lifetime counters, oil volume and
* activation flag are stored in NVS ("nvdata" partition).
* - They survive reboots, power outages and OTA reflashes.
* - Writes are deferred (dirty flag + minimum interval) to avoid
* wearing out the flash on every pulse.
*
* Integration with other catalog modules:
* - The counters are reported to the app over BLE as [C1]loads [C2]unloads
* (see ejemplos/piso-movil/ble-mobile-app-control-38r.ino).
* - The oil volume is computed with the pulse flow meter
* (see ejemplos/piso-movil/pulse-flow-meter-38r.ino).
*/
#include
#define BTN_LOAD I0_0
#define BTN_UNLOAD I0_1
// NVS keys (max. 15 characters)
#define KEY_SERIAL "serial_num"
#define KEY_LOADS "cnt_load"
#define KEY_UNLOADS "cnt_unload"
#define KEY_LIFE "cnt_life"
#define KEY_OIL "oil_volume"
#define KEY_ACTIVE "activated"
#define SAVE_INTERVAL_MS 5000 // minimum time between flash writes
Preferences prefs;
String serialNum;
int counterLoad = 0;
int counterUnload = 0;
int counterLife = 0; // total machine cycles
float oilVolume = 0.0; // liters of oil moved
bool activated = false;
bool dirty = false; // there is data pending to be saved
uint32_t lastSaveMs = 0;
void loadNVS() {
prefs.begin("nvdata", true); // read-only
serialNum = prefs.getString(KEY_SERIAL, "SN-0000");
counterLoad = prefs.getInt(KEY_LOADS, 0);
counterUnload = prefs.getInt(KEY_UNLOADS, 0);
counterLife = prefs.getInt(KEY_LIFE, 0);
oilVolume = prefs.getFloat(KEY_OIL, 0.0);
activated = prefs.getBool(KEY_ACTIVE, false);
prefs.end();
}
void saveNVS() {
prefs.begin("nvdata", false); // read/write
prefs.putInt(KEY_LOADS, counterLoad);
prefs.putInt(KEY_UNLOADS, counterUnload);
prefs.putInt(KEY_LIFE, counterLife);
prefs.putFloat(KEY_OIL, oilVolume);
prefs.putBool(KEY_ACTIVE, activated);
prefs.end();
dirty = false;
lastSaveMs = millis();
Serial.println("-> NVS saved");
}
void setup() {
Serial.begin(115200);
pinMode(BTN_LOAD, INPUT);
pinMode(BTN_UNLOAD, INPUT);
loadNVS(); // the counters come back exactly as they were after every reboot
Serial.println("=== Persistent counters (NVS) ===");
Serial.println("Serial: " + serialNum);
Serial.println("Loads: " + String(counterLoad));
Serial.println("Unloads: " + String(counterUnload));
Serial.println("Lifetime: " + String(counterLife));
Serial.println("Oil: " + String(oilVolume, 1) + " L");
Serial.println("Activated: " + String(activated ? "yes" : "no"));
}
void loop() {
// Rising edge with simple debounce
static bool prevLoad = false, prevUnload = false;
bool load = digitalRead(BTN_LOAD);
bool unload = digitalRead(BTN_UNLOAD);
if (load && !prevLoad) {
counterLoad++;
counterLife++;
oilVolume += 12.5; // estimated liters per load cycle
dirty = true;
Serial.println("Load #" + String(counterLoad));
}
if (unload && !prevUnload) {
counterUnload++;
counterLife++;
dirty = true;
Serial.println("Unload #" + String(counterUnload));
}
prevLoad = load;
prevUnload = unload;
// Deferred write: protects the flash from a write on every pulse
if (dirty && millis() - lastSaveMs > SAVE_INTERVAL_MS) {
saveNVS();
}
delay(50);
}
Download the full project pack — freeThis example + the related ones + bill of materials