Reset is a real PITA…

We all know how often an ESP8266 project can get a little messy if our beloved ESP does an unexpected reboot. Reasons for that can be power supply issues oder watchdog errors or even other “dark magic” going on. While it seems good to look for the real reason there is always the unexpected happening so why not be prepared?

The ESP8266 has a builtin RTC which has about 0.5k of battery powered RAM. Good to know: this RAM is not cleared during reboots. (Since it is battery powered, of course it is gone once your power will “really” fail.) So why not use this RAM for a backup of your sketches most valuable variables? These are the ones that hold the state of your project.

You can do that RTC mem management all by yourself or use the RTCVars library. (By the time writing it is still in the process of being integrated into the Arduino IDE builtin library repository.) It is hosted on GitHub: https://github.com/highno/rtcvars

Using this, converting your existing project to a reset resistant long runner is pretty easy. Let’s have a look at an example:

Basic concept

The library uses the idea of a state. Related to your project’s state are a set of variables. If you would have the values of these variables, your code can basically recover to the point where the state’s variables were saved the last time. The library requires you to register your states variables at the start (at least if you follow the basic example, see the advanced example at GitHub on more complex setups) typically within the setup() function. To make it easy to integrate, all you need to register is a pointer to that variable. You can keep using your variable as usual afterwards – as long as you don’t change its memory position. (If you don’t know what that means, you are most likely doing alright 😉 )

To save your set of registered variables to the RTC memory, you simply call the saveToRTC() function. It automatically gets the values of all registered vars and transfers these into RTC memory. Additionally it stores some header information to see if there is valid data in the RTC memory. Remember to call this function after all relevant changes to state variables (aka state changes or state transitions). While it may cost some CPU cycles to store the information, it is of no danger to call that function very often. RTC memory is real memory – there is no flash wearing off or other negative effects. If your code will change more than one registered variable in a row, you should call the saveToRTC() method only after changing the last value. Your program expects the changes to all of those variables “at once”, restoring a set where one had not yet been changed would most likely lead to an unexpected behavior.

So how do you get your state variables’ values back after a reset? Easy, too: right after registering all variables (yes, we wouldn’t know where to put the saved values!) just call the loadFromRTC()function. If it returns true, all values have been restores successfully. So check the return value and prepare other values needed for this state (e.g. set timers to zero, stuff like that). It it returns false, just execute your existing setup code.

So to sum it all up: mark your “critical” variables by registering them in the setup() function, try to load them at startup, do a normal setup if loading fails. Save the values each time their values change.

Example

This example will show the basic usage of RTCVars and is commented to be easy to understand:

#include <RTCVars.h>

RTCVars state; // create the state object
int reset_counter;                      // we want to keep these values after reset
int program_step;

void setup() {
  Serial.begin(115200);                 // allow debug output

  state.registerVar( &reset_counter );  // we send a pointer to each of our variables
  state.registerVar( &program_step );

  if (state.loadFromRTC()) {            // we load the values from rtc memory back into the registered variables
    reset_counter++;
    Serial.println("This is reset no. " + (String)reset_counter);
    state.saveToRTC();                  // since we changed a state relevant variable, we store the new values
  } else {
    reset_counter = 0;                  // cold boot part
    Serial.println("This seems to be a cold boot. We don't have a valid state on RTC memory");
    program_step = 0;
  }
}

void loop() {
  // do your work here - try to reset your chip externally or internally but don't power off...
  Serial.println("Current state is " + (String)program_step);
  program_step = (program_step == 7) ? 1 : program_step + 1;
  Serial.println("New state is " + (String)program_step);
  state.saveToRTC();                    // there are no parameters because it only needs the vars' definiton once 
  delay(1000);
}

First the library is included by #include <RTCVars.h>. Then an RTCVars object named state is created via RTCVars state;. This object now is the interface to all functionality. Be sure to register all variables before reading the old state from RTC memory. The state is invalid if the registered variables differ in total size to the saved state. It seems good practice to use globally defined vars only and register all of them in the setup() function. If you really need to keep track of different, state-specific variables, look at the advanced usage.

Registering does nothing but keeping track of where the variables are to find in memory. Of course reading the state from RTC memory would require to save the values in the corresponding variables. Therefore you need to register them even before you call state.loadFromRTC(). If everything works out, the call returns true. If not there are problems with the state or it has been a cold boot. See advanced usage for further information on error handling.

Later on, everytime a change is made to some of the vars registered and have a consistent state is esablished, state.saveToRTC() is called to push these values to RTC memory.

Supported types and size

Please note, that there are two limits in this lib: memory and the number of managed variables. Due to the fact how storage of vars is organized, there is a fixed upper boundry other than memory. By default, the limit is 32 variables, the types are byte, char, int, long, float. Since these types do not exceed 8 bytes each, the RTC memory is not the limit here (512 bytes – 28 starting offset – 7 header/checksum = 477 bytes). See advanced usage (on GitHub) for more functionality to control these settings.

A quick note at the end: the ESP32 handles RTC memory differently (clear on boot), so this method does not work there. The author likes to hear of ESP32 veterans on how to reproduce the functionality on an ESP32. Feel free to contact me at: friedrichs.lars@gmail.com

5 thoughts on “Make your state variables survive (unexpected) reboots”

  1. Hi,

    I have a char variable of size 32 bytes that I’d like to store in RTCmem and recover on a wemos d1 mini pro, but I’ve not been successful with this. It restores just the first byte, and I’m afraid I’m not advanced enough to figure out why.

    How would I register that char of length 32?

    Thanks,

    nycguy

    1. it would appear that the library is only able to register the address of the element you are saving, not the length. strings are arrays of chars and when you registerVar you are only setting the address of the first element.
      try:
      char mystring[32];
      int *str0;
      int *str1; //etc….

      str0 = &mystr[0];
      str1 = &mystr[4]; //etc…

      register the strXs and in setup if the restore from rtc passes, manually copy the strXs to the separate pieces of the mystr[] array.

      1. You are totally right on that. While designing the lib I believed that having Strings holding state information is unlikely. If you need that, you will need a fixed buffer and save it the way shown.

Leave a Reply

Your email address will not be published. Required fields are marked *

Captcha loading...