ESP32/Sleep and wake-up/72. What survives deep sleep
Your chip
Your language
Sleep and wake-up · 72 of 81

What survives deep sleep

RAM is gone, the sketch restarts from the top, and the boot counter reads zero. Three places to leave yourself a note, with three different lifetimes and three different costs.

/esp32/what-survives-deep-sleep · arduino · S3

Three stores, three lifetimes

What is still there when it wakes
Deep sleep
What happens between boots
RAM
RTC memory
NVS
Three ways to keep a boot counter. They are indistinguishable while the board is powered, which is why this bug survives every test done on a bench with a USB cable plugged in. Run it and watch which numbers come back.
Survives deep sleepSurvives hibernationSurvives a power cutCost to write
Ordinary variablenononofree
RTC_DATA_ATTRyesnonofree
NVS / a fileyesyesyesa flash write

They look identical on a bench with a USB cable in, which is why this bug is usually found in the field.

RTC memory is the default answer

8 kB, no wear, free to write, and it holds exactly the things a duty-cycled sensor needs to carry across: a boot count, the last reading, the Wi-Fi channel and BSSID that worked last time, a flag saying the sensor is already calibrated.

Losing it costs you one slow cycle, not correctness. That is the test for whether something belongs here: if the answer to "what if this is missing" is "do the slow thing", RTC memory is right.

NVS for the things you cannot re-derive

Wi-Fi credentials, a device id, a calibration constant somebody measured with a reference instrument. Those must outlive a flat battery, and flash is the only place that does.

Flash also wears out. A write on every wake, at one wake a minute, is half a million cycles a year — well past what the part is rated for. The pattern that works is a running value in RTC memory, flushed to NVS every so often, and flushed once more before a deliberate shutdown.

Store values, not addresses

Anything in RTC memory is bytes that happen to be still there. A pointer from the previous boot points into a heap that no longer exists, and a String or a std::vector is a pointer wearing a nicer name. Plain integers, floats, fixed arrays and structs of those survive; anything that allocates does not.

On your S3
ChipXtensa LX7 · 2 × 240 MHz
Board settingESP32S3 Dev Module
Default I2CSDA 8 · SCL 9
Watch out forThe port vanishes after upload

The code

Three counters, one sleep cycle. Only two of them mean anything after the wake, and only one of them survives the battery being changed.

survive.ino
#include <esp_sleep.h>
#include <Preferences.h>

int ramCount = 0;                          // gone every wake
RTC_DATA_ATTR int rtcCount = 0;            // survives deep sleep
RTC_DATA_ATTR float lastReading = 0;
Preferences prefs;

void setup() {
  Serial.begin(115200);
  delay(100);

  prefs.begin("boot", false);
  int nvsCount = prefs.getInt("n", 0) + 1;

  ramCount++;
  rtcCount++;

  Serial.printf("ram %d   rtc %d   nvs %d   last %.1f\n",
                ramCount, rtcCount, nvsCount, lastReading);

  lastReading = 21.4;
  // Write to flash only when it changed, and not on every wake.
  if (rtcCount % 20 == 0) prefs.putInt("n", nvsCount);
  prefs.end();

  esp_sleep_enable_timer_wakeup(10ULL * 1000000);
  esp_deep_sleep_start();
}

void loop() {}

RTC_DATA_ATTR variables live in 8 kB of RTC slow memory. They survive deep sleep, are lost in hibernation, and are lost on any power cut or EN reset.

When it does not work

My RTC_DATA_ATTR counter resets every time

Something is doing a full reset rather than a wake. Pressing EN, a brown-out, a crash and re-uploading all clear RTC memory. esp_reset_reason() will say which — a real wake reports ESP_RST_DEEPSLEEP.

It survives sleep and not a battery change

RTC memory is silicon, kept alive by the RTC power domain, and that domain has no power when the battery does not. Anything that must outlive a power cut belongs in NVS or a file.

The board slowed down and then stopped storing anything

Flash wear. NVS wear-levels, but a write on every wake is still tens of thousands of erase cycles a year. Keep the running value in RTC memory and flush it to NVS occasionally, which is what the modulo above does.

A pointer stored in RTC memory crashes on wake

The heap does not survive, so an address from the last boot points at nothing. Store values, not references — and structs only if they contain no pointers.

Where this goes next

The last option: a tiny second processor that stays awake for microamps, watches a pin or an ADC, and only wakes the real CPU when something is worth waking it for.

The ULP low-power core

Edit this page — content/esp32/what-survives-deep-sleep.mdx

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse ESP32 on the forum