ESP32 (38) – Factory reset

In the previous two posts of this tutorial, I explained how to perform an over-the-air update of the firmware running on the esp32 chip.

Sometimes you may need to revert to the factory firmware, that is the firmware stored in the flash memory when the chip was programmed. Many consumer devices have a button or a pin that, if you press it for some seconds, triggers a reset function:

A10317_image1

In this post I’ll show you how to add this functionality to your project.

Partitions

As explained in a previous post, the flash memory connected to the esp32 chip is divided into some partitions, based on a layout configured when you program the chip.

Partitions that can store firmwares are of the app type. The partition that contains the firmware programmed via USB, has the factory subtype.

The esp-idf framework includes a method to search partitions in the flash memory:

#include "esp_partition.h"
[...]
esp_partition_iterator_t esp_partition_find(
  esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label);

You can specify some filters to narrow down the results (they are not mandatory, use NULL if a filter is not needed): the type of the partition, the subtype and also a specific label.

If you want to look for the partition that contains the factory firmware, you can therefore write:

esp_partition_iterator_t pi = esp_partition_find(
  ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);

The method returns a partition iterator, that is an object that allows to scroll through the partitions found.

If the search was successful, this object is not NULL and you can get a pointer to the partition with the method:

const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator);

After the use, it’s important to release the iterator object with:

void esp_partition_iterator_release(esp_partition_iterator_t iterator);

After having obtained the correct partition, that contains the factory firmware, you only have to flag it as the boot partition and restart the chip:

if(pi != NULL) {
  const esp_partition_t* factory = esp_partition_get(pi);
  esp_partition_iterator_release(pi);
  if(esp_ota_set_boot_partition(factory) == ESP_OK) esp_restart();	
}

Demo

In the following video you can see how to perform a factory reset. In the video you can also learn how to “count” the number of seconds a button is pressed to trigger the reset function only after a fixed threshold (3 seconds in my example). Enjoy!

ESP32 (19) – NVS

In today’s tutorial you’ll learn how to permanently store information, so that they are preserved even if the esp32 chip is reset or power is removed.

NVS

NVS (Non Volatile Storage) is a library included in the esp-idf framework that allows to store information (in the form of key/value) in the flash memory, the content of which is not erased when you reset the chip or remove the power.

If you remember, in a previous post I explained how the external flash memory is organized. The main purpose of that memory is for sure to store the program that will be executed by the esp32 chip. However, it’s possible to divide the flash memory in partitions: the framework offers some ready-to-use partition tables but you’re free to define custom ones.

If you use the default partition table (“Single factory app, no OTA”), you may notice that it contains a partition of type data and subtype nvs:

nvs-01

The default size for that partition is 24Kbyte.

Thanks to the NVS library, you can store custom data into that partition. Information is organized in key/value pairs; a label (= key) with a maximum length of 15 characters is assigned to each value:

nvs-02

You can store different types of data: from numeric values to text strings and byte sequences (blobbinary large object). You’ll learn that the library provides specific methods based on the type of data you want to store or retrieve.

To be able to use the library in your code, first include the following headers:

#include "esp_partition.h"
#include "esp_err.h"
#include "nvs_flash.h"
#include "nvs.h"

Initialization

The first step to use the nvs partition is to initialize the library, with the command:

esp_err_t err = nvs_flash_init();

The command returns ESP_OK if successful; on the contrary it returns one of the error codes defined in the nvs.h file (see below). In particular, if the partition was resized or changed, you may get the ESP_ERR_NVS_NO_FREE_PAGES error code. This error can be resolved “formatting” the partition.

First you have to identify the nvs partition in the flash memory:

const esp_partition_t* nvs_partition = 
 esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL);      
if(!nvs_partition) printf("FATAL ERROR: No NVS partition found\n");

then you can format it with the esp_partition_erase_range() command:

err = (esp_partition_erase_range(nvs_partition, 0, nvs_partition->size));
if(err != ESP_OK) printf("FATAL ERROR: Unable to erase the partition\n");

The last step is to open the partition, you can do it in READONLY o READWRITE mode:

nvs_handle my_handle;
err = nvs_open("storage", NVS_READWRITE, &my_handle);
if (err != ESP_OK) printf("FATAL ERROR: Unable to open NVS\n");

Set – Get

Once the partition is open, you can store (set) new values or retrieve (get) existing ones:

There are different methods, based on the data type (i8 stands for integer with 8bits, u8 per unsigned integer 8bit…):

  • nvs_set_i8(), nvs_set_u8(), nvs_set_i16(), nvs_set_u16()…
  • nvs_set_str()
  • nvs_set_blob()

All the set methods require as parameters the partition handler, the key name and the value to be stored:

esp_err_t nvs_set_i8(nvs_handle handle, const char* key, int8_t value);

The only exception is the nvs_set_blob() method which requires as additional parameter the length of the byte array to be stored:

esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length);

After having called a set method, you have to commit the change with the method:

esp_err_t nvs_commit(nvs_handle handle);

To get data from the flash the library offers similar methods:

  • nvs_get_i8(), nvs_get_u8(), nvs_get_i16(), nvs_get_u16()…
  • nvs_get_str()
  • nvs_get_blob()

The parameters for those methods are the partition handler, the key name and a pointer to the variable that has to be updated with the value retrieved:

esp_err_t nvs_get_i8(nvs_handle handle, const char* key, int8_t* out_value);

Because of you cannot know a priori the size for string or blob values, you can do a “trick”: first call the nvs_get_string() method passing NULL as pointer to get the length of the value, then allocate a variable with the correct size and finally call again the method passing that variable to get the value:

size_t string_size;
esp_err_t err = nvs_get_str(my_handle, parameter, NULL, &string_size);
char* value = malloc(string_size);
err = nvs_get_str(my_handle, parameter, value, &string_size);

Erase

The library also include a couple of methods to erase the partition content:

esp_err_t nvs_erase_key(nvs_handle handle, const char* key);
esp_err_t nvs_erase_all(nvs_handle handle);

The first method is more selective and allows you to delete a single key while the second deletes the entire memory content.

Both the methods must be followed by the nvs_commit() command as explained before.

Error handling

All the methods in the nvs library return an error code (esp_err_t variable).

The possible errors are listed in the nvs.h file:

nvs-03

For example, if you’re calling a get method with a key that is not present in the flash, you’ll get the ESP_ERR_NOT_FOUND error. You should handle the different errors in your program:

esp_err_t err = nvs_get_i32(my_handle, parameter, &value);
if(err == ESP_ERR_NVS_NOT_FOUND) printf("\nKey %s not found\n", parameter);

Demo

For this tutorial I wrote a program that allows, using a simple command line, to store and retrieve information from the nvs partition. The source code is available on Github, here’s a video that shows how it works: