How to Retain Application Data Through Resets and Power Loss on the ESP32-C6

Every time the ESP32-C6 restarts or loses power, the onboard RAM resets, discarding important data like a boot counter, a user setting, or a tally of logged events. To preserve data through board resets, use non-volatile storage (NVS), which saves data to flash memory and reads it back on the next boot. In this guide, you’ll learn how to use the NVS on the ESP32-C6-DevKitC-1 by storing a boot counter in flash that survives a reset, a power cut, and a reflash.

Data Persistence with NVS

On the ESP32-C6, all the data you want to persist between boots lives on an 8 MB Serial Peripheral Interface (SPI) flash chip inside the ESP32-C6-WROOM-1 module. This includes the bootloader, the small program that starts your application, along with the application itself and any data you save.

A partition table divides that flash into regions. It is a small map stored at offset 0x8000, and the bootloader reads it on every boot to find your application. Within ESP-IDF, you can print the default table:

*******************************************************************************
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,24K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,1M,
*******************************************************************************

The nvs row is the 24 KB region where your data lives. When you reflash the board, ESP-IDF rewrites the bootloader, partition table, and application, leaving nvs untouched. Saved data also survives firmware updates, which is important for production devices running in the field.

How NVS Writes to Flash

Flash memory follows two rules. A write can only change bits from 1 to 0, so changing a stored value requires an erase first. An erase resets a whole 4 KB sector to all 1s, and each sector survives a limited number of erases. Espressif rates the WROOM module’s flash for 100,000 program/erase cycles per sector.

NVS differs by avoiding an erase on every write. It treats each sector as a page of 126 data entries, 32 bytes each. A new value goes into the next empty entry, and NVS marks the key’s previous entry as erased by clearing bits in the page header. NVS erases a sector only after its page fills, and it first copies any still-current values to a spare page. This spreads wear across the whole partition, which Espressif’s NVS documentation covers in full.

NVS also skips the flash write when you store the value a key already holds. Thus, a power loss during a write loses only that one new value, and everything saved earlier stays readable.

Write Data to NVS

Before you begin, make sure you have ESP-IDF v5.4 or later installed on your PC.

Create a new project within ESP-IDF and set the target chip to esp32c6. Then, add the nvs_flash component to main/CMakeLists.txt to allow your firmware application to use your board’s NVS.

idf_component_register(SRCS "persist.c"
                       INCLUDE_DIRS "."
                       REQUIRES nvs_flash)

Add the following code in your project’s main.c to initialize NVS and write a boot counter to both NVS and RAM. The boot counter tracks the number of power cycles on your ESP32-C6 board by incrementing with every power-up. With this application, you can see how data persists through reboots only in NVS.

#include <inttypes.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"

static const char *TAG = "persist";

// Lives in RAM. The startup code zeroes it on every boot.
static uint32_t ram_count;

// Mounts the default "nvs" partition. These two errors mean the partition
// holds a layout this code cannot use, because the partition shrank or a
// newer ESP-IDF wrote it. Erasing deletes every saved value.
static void storage_init(void)
{
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);
}

void app_main(void)
{
    storage_init();

    nvs_handle_t handle;
    ESP_ERROR_CHECK(nvs_open("app", NVS_READWRITE, &handle));

    // On any error, nvs_get_u32() leaves the variable untouched, so the
    // initializer doubles as the default for a key that was never written.
    uint32_t nvs_count = 0;
    esp_err_t err = nvs_get_u32(handle, "boot_count", &nvs_count);
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
        ESP_ERROR_CHECK(err);   // a real failure, so stop and report it
    }

    ram_count++;
    nvs_count++;

    ESP_ERROR_CHECK(nvs_set_u32(handle, "boot_count", nvs_count));

    // NVS currently writes during nvs_set_u32(), and nvs_commit() does nothing.
    // The API still requires the call, which keeps this code correct if a
    // future ESP-IDF release buffers writes in RAM.
    ESP_ERROR_CHECK(nvs_commit(handle));
    nvs_close(handle);

    ESP_LOGI(TAG, "ram_count=%" PRIu32 " nvs_count=%" PRIu32,
             ram_count, nvs_count);
}

Build the program, flash it, and open the serial terminal. You should see ram_count=1 and nvs_count=2 because the chip boots once when flashing finishes and again when it connects with the terminal.

Serial terminal showing ram_count=1 and nvs_count=2 on the ESP32-C6 after flashing

Verify Data Persistence

To verify your boot counter stays on your board through reboots, do the following three tests and read the log line after each.

  1. Press the RESET button. nvs_count should increase by one.
  2. Unplug the cable for a few seconds, plug it back in, and open the serial terminal. nvs_count should increase by two, once for the power-on boot and once for the monitor’s reset.
  3. Reflash your firmware and open the serial terminal. nvs_count should increase by two for the same reason as test 2.

Notice that ram_count stays at 1 after every test, while nvs_count keeps increasing. This verifies that only NVS data survives multiple power cycles.

Serial terminal showing ram_count fixed at 1 while nvs_count keeps increasing across resets

Note: To reset NVS, you must run an erase flash. Ordinary flashing will not reset the NVS data on your board.

Next Steps

Now you know how to use NVS to implement a true boot counter that can survive a reset, a power cut, and a firmware update.

Next, use NVS to store other types of data, such as a C struct, strings, or floating-point calibration values. If you just want to retain data through deep sleep, mark the variable RTC_DATA_ATTR and the chip keeps it in low-power memory with zero flash writes. The ESP32 sleep modes guide explains when that memory stays powered.


Ready to connect your devices anywhere on Earth? Get started with Hubble Network for free →