Preferences in ESP32



Non−volatile storage is an important requirement for embedded systems. Often, we want the chip to remember a couple of things, like setup variables, WiFi credentials, etc. even between power cycles. It would be so inconvenient if we had to perform setup or config every time the device undergoes a power reset. ESP32 has two popular non-volatile storage methods: preferences and SPIFFS. While preferences are generally used for storing key-value pairs, SPIFFS (SPI Flash File System), as the name suggests, is used for storing files and documents. In this chapter, let's focus on preferences.

Preferences are stored in a section of the main flash memory with type as data and subtype as nvs. nvs stands for non−volatile storage. By default, 20 KB of space is reserved for preferences, so don't try to store a lot of bulky data in preferences. Use SPIFFS for bulky data (SPIFFS has 1.5 MB of reserved space by default). What kind of key−value pairs can be stored within preferences? Let's understand through the example code.

Code Walkthrough

We will use the example code provided. Go to File −> Examples −> Preferences −> StartCounter. It can also be found on GitHub.

This code keeps a count of how many times the ESP32 was reset. Therefore, every time it wakes up, it fetches the existing count from preferences, increments it by one, and saves the updated count back to preferences. It then resets the ESP32. You can see using the printed statements on the ESP32 that the value of the count is not lost between resets, that it is indeed non−volatile.

This code is very heavily commented, and therefore, largely, self-explanatory. Nevertheless, let's walk through the code.

We begin by including the Preferences library.

#include <Preferences.h>

Next, we create an object of Class Preferences.

Preferences preferences;

Now let's look at the setup line by line. We begin by initializing Serial.

void setup() {
   Serial.begin(115200);
   Serial.println();

Next, we open preferences with a namespace. Now, think of the preference storage like a bank locker−room. There are several lockers, and you open one at a time. The namespace is like the name of the locker. Within each locker, there are key−value pairs that you can access. If the locker whose name you mentioned does not exist, then it will be created, and then you can add key−value pairs to that locker. Why are there different lockers? To avoid clashes in the name. Say you have a WiFi library that uses preferences to store credentials and a BlueTooth library that also uses preferences to store credentials. Say both of these are being developed by different developers. What if both use the same key name credentials? This will obviously create a lot of confusion. However, if both of them have their keys in different lockers, there will be no confusion at all.

// Open Preferences with my-app namespace. Each application module, library, etc
// has to use a namespace name to prevent key name collisions. We will open storage in
// RW-mode (second parameter has to be false).
// Note: Namespace name is limited to 15 chars.
preferences.begin("my−app", false);

The second argument false of preferences.begin() indicates that we want to both read from and write to this locker. If it was true, we could only read from the locker, not write to it. Also, the namespace, as mentioned in the comments, shouldn't be more than 15 characters in length.

Next, the code has a couple of commented statements, which you can make use of depending on the requirement. One enables you to clear the locker, and the other helps you delete a particular key−value pair from the locker (having the key as "counter")

// Remove all preferences under the opened namespace
//preferences.clear();

// Or remove the counter key only
//preferences.remove("counter");

As a next step, we get the value associated with the key "counter". Now, for the first time when you run this program, there may be no such key existing. Therefore, we also provide a default value of 0 as an argument to the preferences.getUInt() function. What this tells ESP32 is that if the key "counter" doesn't exist, create a new key-value pair, with key as "counter" and value as 0. Also, note that we are using getUInt because the value is of type unsigned int. Other functions like getFloat, getString, etc. need to be called depending on the type of the value. The full list of options can be found here.

unsigned int counter = preferences.getUInt("counter", 0);

Next, we increment this count by one and print it on the Serial Monitor.

// Increase counter by 1
counter++;

// Print the counter to Serial Monitor
Serial.printf("Current counter value: %u\n", counter);

We then store this updated value back to non-volatile storage. We are basically updating the value for the key "counter". Next time the ESP32 reads the value of the key "counter", it will get the incremented value.

// Store the counter to the Preferences
preferences.putUInt("counter", counter);

Finally, we close the preferences locker and restart the ESP32 in 10 seconds.

// Close the Preferences
preferences.end();

// Wait 10 seconds
Serial.println("Restarting in 10 seconds...");
delay(10000);
   
// Restart ESP
ESP.restart();
}

Because we restart ESP32 before diving into the loop, the loop is never executed. Therefore, it is kept blank

void loop() {}
Serial Monitor Output

This example demonstrates quite well how ESP32 preferences storage is indeed non−volatile. When you check the printed statements on the Serial Monitor, you can see the count getting incremented between successive resets. This would not have happened with a local variable. It was only possible by using non−volatile storage through preferences.

References

Advertisements