Chapter 242: RTC Memory Usage in ESP-IDF
Chapter Objectives
By the end of this chapter, you will be able to:
- Explain what RTC memory is and how it differs from main RAM and Non-Volatile Storage (NVS).
- Understand the role of RTC memory in low-power applications.
- Use the
RTC_DATA_ATTR
attribute to store data that persists through deep sleep. - Place code into RTC memory for execution immediately after waking from deep sleep.
- Identify the amount of RTC memory available on different ESP32 variants.
- Implement a practical application that maintains state across deep sleep cycles.
- Troubleshoot common issues related to RTC memory usage.
Introduction
In the world of battery-powered IoT devices, power consumption is a paramount concern. The ESP32’s deep sleep mode is a powerful tool for building ultra-low-power applications, allowing the chip to consume only a few microamps. However, when the main CPUs are powered down in deep sleep, all data stored in the main system RAM (SRAM) is lost.
This presents a challenge: how can a device remember what it was doing or retain important data after waking up? While Non-Volatile Storage (NVS) can store data permanently, frequent writes can wear out the flash memory and are relatively slow and power-intensive. The solution lies in a special, low-power memory region that remains powered on even during deep sleep: RTC memory.
This chapter delves into the architecture and practical use of RTC memory. Mastering this feature is essential for creating efficient, stateful, and power-aware applications that can run for months or even years on a single battery.
Theory
The ESP32 is not just a single processor; it’s a System on a Chip (SoC) with multiple power domains. This means different parts of the chip can be independently powered on or off. The main CPUs, most peripherals, and the main SRAM reside in the digital core power domain, which is turned off during deep sleep.
However, a small, independent part of the SoC, the Real-Time Clock (RTC) subsystem, remains powered on. This subsystem includes the Ultra Low Power (ULP) co-processor, some essential peripherals (like RTC GPIOs and timers), and, most importantly for this chapter, a small amount of dedicated SRAM known as RTC memory.
Types of RTC Memory
RTC memory is divided into two main types:
- RTC Slow Memory: This is the primary memory region for storing data that needs to be preserved during deep sleep. It has a very low power consumption profile. Access to this memory from the main CPUs is slightly slower than a regular SRAM access, hence the name.
- RTC Fast Memory: This region is designed to store code that needs to execute immediately upon waking from deep sleep, even before the main system and SRAM are fully re-initialized. This is useful for time-critical boot-up sequences. The main CPUs can access this memory faster than RTC Slow memory.
Storing Data in RTC Memory
To instruct the compiler and linker to place a variable in RTC memory, ESP-IDF provides a special attribute: RTC_DATA_ATTR
. When you declare a global or static variable with this attribute, it will be placed in the RTC Slow memory region.
RTC_DATA_ATTR int boot_count = 0;
How does it work?
The RTC_DATA_ATTR macro is essentially __attribute__((section(“.rtc.data”))). This tells the linker to place the boot_count variable into a memory section named .rtc.data. The ESP-IDF linker script is pre-configured to map this section to the physical address of the RTC Slow memory.
Crucially, the bootloader is aware of this memory region. When the chip undergoes a normal power-on reset, the bootloader initializes the .rtc.data
section, so boot_count
will be set to 0
. However, when waking from deep sleep, the bootloader recognizes the wake-up cause and deliberately skips the initialization of this memory section, preserving its contents.
Storing Code in RTC Memory
Similarly, code can be placed in RTC Fast memory using RTC_FAST_ATTR
. This is an advanced feature typically used for executing code before the main application startup logic in app_main
is called.
RTC_FAST_ATTR void custom_wake_up_routine(void) {
// This code runs from RTC Fast Memory
// very early in the boot process.
}
This is useful for applications that need to interact with a sensor or perform a calculation immediately upon waking, without waiting for the full FreeRTOS scheduler and other services to start.
RTC Memory vs. NVS
It’s important to choose the right tool for the job. While both RTC memory and NVS can store data, they serve different purposes.
Feature | RTC Memory | Non-Volatile Storage (NVS) |
---|---|---|
Persistence | Survives deep sleep and RTC resets. Lost on power-off. | Survives full power-off cycles. |
Lifetime | Unlimited read/write cycles (SRAM). | Limited by flash wear (typically ~100,000 write cycles). |
Speed | Very fast access (direct memory access). | Slower access (involves flash read/write operations). |
Size | Very small (typically 4 KB – 16 KB). | Much larger (configurable, typically several MBs). |
Power Usage | Extremely low power to maintain during deep sleep. | Higher power consumption for write operations. |
Analogy | A temporary sticky note on your monitor for tomorrow. | A permanent entry in a hardcover lab notebook. |
Primary Use Case | Storing temporary state between deep sleep cycles (e.g., boot counters, sensor states). | Storing permanent configuration data (e.g., Wi-Fi credentials, device calibration). |
Analogy: Think of RTC memory as a small sticky note you leave on your desk overnight to remember what to do first thing in the morning. NVS is like a hardcover notebook where you store important information you need to keep for years.
Practical Examples
Let’s build a simple project to demonstrate the use of RTC memory. We will create a counter that tracks how many times the device has woken up from deep sleep.
Example: Deep Sleep Boot Counter
1. Create a New Project
In VS Code, create a new project using the “ESP-IDF: New Project” command from the command palette. Choose a template like sample_project
.
2. Write the Application Code
Open the main/main.c
file and replace its content with the following code.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_sleep.h"
#include "esp_log.h"
#include "driver/rtc_io.h"
// Define a tag for logging
static const char *TAG = "RTC_MEM_EXAMPLE";
// Use RTC_DATA_ATTR to place the variable in RTC memory
// This variable will retain its value across deep sleep cycles.
RTC_DATA_ATTR int boot_count = 0;
void app_main(void)
{
// Increment the boot count
boot_count++;
// Print the current boot count
ESP_LOGI(TAG, "Woke up! Boot count: %d", boot_count);
// Check the reason for waking up
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
// The first boot will be due to power-on reset.
// On the first boot, RTC memory is initialized, so boot_count will be 1.
// Subsequent boots will be due to the timer, and boot_count will be preserved.
if (cause != ESP_SLEEP_WAKEUP_TIMER) {
ESP_LOGI(TAG, "Initial boot-up. Configuring timer for deep sleep.");
} else {
ESP_LOGI(TAG, "Woke up from deep sleep via timer.");
}
// Define the sleep duration
const int deep_sleep_sec = 5;
ESP_LOGI(TAG, "Entering deep sleep for %d seconds...", deep_sleep_sec);
// Configure the timer as the wakeup source
esp_sleep_enable_timer_wakeup(deep_sleep_sec * 1000000);
// Enter deep sleep mode. The program will halt here and restart upon waking.
esp_deep_sleep_start();
}
Code Explanation:
flowchart TD subgraph ESP32 Wakes Up A(Start: app_main) style A fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 B["<b>boot_count++</b><br>Increment counter in RTC Memory"] style B fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF C{What was the<br>wake-up cause?} style C fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E end A --> B --> C C -- "Power-On-Reset" --> D["Log: <b>'Initial boot-up'</b><br>RTC_DATA_ATTR variables are<br>initialized to zero by bootloader"] style D fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF C -- "Timer" --> E["Log: <b>'Woke up from deep sleep'</b><br>RTC Memory content was preserved"] style E fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF subgraph Prepare for Sleep F["<b>esp_sleep_enable_timer_wakeup()</b><br>Set wake-up timer for 5 seconds"] style F fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF G["<b>esp_deep_sleep_start()</b><br>System enters deep sleep.<br>CPU powers down, RTC domain stays on."] style G fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B end D --> F E --> F F --> G subgraph Deep Sleep H["(Device is in low-power state...)<br><b>boot_count</b> is safe in RTC Memory"] style H fill:#E0E7FF,stroke:#4338CA,stroke-width:2px,color:#3730A3,stroke-dasharray: 5 5 end G -.-> H subgraph Wake Up Again I(Timer Fires After 5s) style I fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 end H -.-> I --> A
RTC_DATA_ATTR int boot_count = 0;
: We declare ourboot_count
variable with theRTC_DATA_ATTR
attribute. We initialize it to0
. This initialization only happens on a full power-on reset, not on a wake-up from deep sleep.boot_count++;
: This is the first thing we do inapp_main
. Each time the device boots (or wakes up), the counter is incremented.esp_sleep_get_wakeup_cause()
: This function is used to determine why the ESP32 woke up. This helps us confirm that the wake-ups after the first one are indeed from our timer.esp_sleep_enable_timer_wakeup()
: We configure the device to wake up after a specific duration, in this case, 5 seconds (5,000,000 microseconds).esp_deep_sleep_start()
: This function initiates the deep sleep cycle. The code execution stops here. After the timer expires, the chip will wake up and restart theapp_main
function from the beginning, but the value ofboot_count
in RTC memory will be preserved.
3. Build, Flash, and Monitor
- Connect your ESP32 board to your computer.
- In VS Code, select the correct COM port for your device.
- Click the “Build, Flash, and Monitor” button (the flame icon with a plug) at the bottom blue status bar.
- The project will build, flash to the device, and the serial monitor will open.
Observe the Output:
You should see the following sequence in your terminal, repeating every 5 seconds:
...
I (278) RTC_MEM_EXAMPLE: Woke up! Boot count: 1
I (278) RTC_MEM_EXAMPLE: Initial boot-up. Configuring timer for deep sleep.
I (288) RTC_MEM_EXAMPLE: Entering deep sleep for 5 seconds...
ets Jun 8 2016 00:22:57
rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
...
I (277) RTC_MEM_EXAMPLE: Woke up! Boot count: 2
I (277) RTC_MEM_EXAMPLE: Woke up from deep sleep via timer.
I (287) RTC_MEM_EXAMPLE: Entering deep sleep for 5 seconds...
ets Jun 8 2016 00:22:57
rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
...
I (277) RTC_MEM_EXAMPLE: Woke up! Boot count: 3
I (277) RTC_MEM_EXAMPLE: Woke up from deep sleep via timer.
I (287) RTC_MEM_EXAMPLE: Entering deep sleep for 5 seconds...
Notice how the Boot count
value correctly increments with each wake-up. This is definitive proof that the boot_count
variable is persisting in RTC memory during the deep sleep period.
Variant Notes
The amount of available RTC memory is an important hardware constraint and varies between ESP32 families. Always consult the datasheet for your specific chip.
ESP32 Variant | RTC Slow Memory (for RTC_DATA_ATTR) |
RTC Fast Memory (for RTC_FAST_ATTR) |
Key Applicability Notes |
---|---|---|---|
ESP32 (Original) | 8 KB | 8 KB | The original workhorse. Both memory types are fully available. |
ESP32-S2 | 8 KB | 8 KB | Similar to original ESP32; both memory types are supported. |
ESP32-S3 | 8 KB | 8 KB | Powerful variant with AI features. Supports both memory types. |
ESP32-C3 | 8 KB | N/A | RISC-V core. Has RTC Slow memory, but no RTC Fast memory. Using RTC_FAST_ATTR will cause a linker error. |
ESP32-C6 | 16 KB | N/A | Wi-Fi 6 + Zigbee/Thread. Larger 16KB RTC Slow memory, but no RTC Fast memory. |
ESP32-H2 | 4 KB | N/A | Bluetooth 5/Thread/Zigbee focus. Smallest RTC memory, and no RTC Fast memory. |
Warning: Attempting to use
RTC_FAST_ATTR
on a variant without RTC Fast Memory (like the ESP32-C3, C6, or H2) will result in a linker error during compilation. The hardware simply does not support it.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Forgetting RTC_DATA_ATTR | Variable resets to its initial value on every wake-up instead of incrementing. | Ensure the variable declaration includes the attribute. int boot_count; RTC_DATA_ATTR int boot_count; |
Exceeding RTC Memory Capacity | Linker error during compilation, e.g., region ‘rtc_data’ overflowed by X bytes. | Reduce the size of data stored in RTC memory. Only store essential state variables. For larger data, use NVS before sleeping. |
Confusing RTC Memory with NVS | Data is lost when the device’s power is completely removed (e.g., battery disconnected). | Use RTC memory only for state that needs to persist through deep sleep. For data that must survive a power cycle, use the Non-Volatile Storage (NVS) library. |
Using RTC_FAST_ATTR on Unsupported Chip | Linker error, often mentioning an undefined reference or an invalid memory region for a function. | Check the “Variant Notes” table. Only use RTC_FAST_ATTR on ESP32, ESP32-S2, and ESP32-S3. Remove it for C-series and H-series chips. |
Uninitialized Data on First Boot | On the very first power-up, a variable read from RTC memory contains a random “garbage” value. | Although the bootloader zero-initializes .rtc.data, robust code should handle the first-boot case explicitly. Check the wake-up reason or use a “magic number” in another RTC variable to validate data integrity before using it. |
Exercises
- Last Wake-up Timestamp: Modify the example project to store the time of the last wake-up. After waking, calculate and print the actual time elapsed since the last wake-up. (Hint: Use the
gettimeofday()
function to get a timestamp and store it in anRTC_DATA_ATTR
struct.) - Stateful Error Counter: Create an application that tries to connect to a (non-existent) Wi-Fi network. If it fails, increment an
RTC_DATA_ATTR
error counter, log the failure, and go to deep sleep for 10 seconds. If it somehow succeeds, reset the counter to zero. This simulates a device that retries a task but keeps track of consecutive failures across sleep cycles. - Simple State Machine: Implement a simple state machine for a weather sensor. Define three states:
READ_TEMPERATURE
,READ_HUMIDITY
, andGO_TO_SLEEP
. Store the current state in anRTC_DATA_ATTR
variable. Each time the device wakes up, it should perform the action for the current state and then transition to the next state before sleeping again.
Summary
- RTC Memory is Power-Efficient: It’s a small block of SRAM in the RTC power domain that remains powered on during deep sleep, preserving its contents.
RTC_DATA_ATTR
is Key: This attribute tells the ESP-IDF toolchain to place a variable in RTC Slow memory so it will persist across deep sleep cycles.- Persistence is Limited: RTC memory survives deep sleep and software resets, but not a full power-off cycle. For permanent storage, use NVS.
- Size is a Constraint: RTC memory is very small (4-16 KB depending on the variant). Use it judiciously for essential state information.
- RTC Fast Memory for Code: Some variants have RTC Fast Memory, allowing code placed with
RTC_FAST_ATTR
to run immediately upon wake-up, before the main application starts. - Variant Differences Matter: Not all ESP32 variants have the same amount of RTC memory, and some lack RTC Fast memory entirely. Always check the datasheet.
Further Reading
- ESP-IDF Programming Guide: Deep Sleep
- ESP-IDF Programming Guide: Linker Script Generation
- Your Specific ESP32 Variant’s Datasheet: Always the ultimate source of truth for hardware specifications like memory sizes.