# Deep-sleep 唤醒存根

[English]

## 简介

与 Light-sleep 和 Modem-sleep 模式相比，Deep-sleep 模式的唤醒时间要长得多，因为在这种情况下 ROM 和 RAM 都被关闭，而 CPU 需要更多时间进行 SPI 引导。不过 ESP32-S2 支持在退出 Deep-sleep 模式时运行“Deep-sleep 唤醒存根”，此功能在芯片被唤醒后，在任何正常初始化、引导加载程序或 ESP-IDF 代码运行之前会立即运行。

具体来说，从 Deep-sleep 模式中唤醒后，ESP32-S2 开始部分初始化，然后使用 CRC 对 RTC 快速内存进行验证，如果验证通过，则执行唤醒存根代码。

由于 ESP32-S2 刚刚从 Deep-sleep 模式唤醒，大多数外设处于复位状态，SPI flash 也未被映射，因此，唤醒存根代码只能调用在 ROM 中实现或加载到 RTC 快速内存中的函数，后者在 Deep-sleep 期间保留内容。

综上所述，通过在应用程序中调用唤醒存根功能，可以在从 Deep-sleep 模式中唤醒时快速运行一些代码，而无需等待整个启动过程。但是，存根大小受 RTC 快速内存大小的限制。

ESP32-S2 支持 RTC 快速内存和 RTC 慢速内存。唤醒存根代码应加载到 RTC 快速内存中，代码使用的数据应存储到 RTC 快速内存或 RTC 慢速内存中。

接下来介绍如何在应用程序中实现唤醒存根代码。

## 唤醒存根的实现

调用函数 `esp_wake_deep_sleep()` 可在 esp-idf 中实现唤醒存根。每当 SoC 从 Deep-sleep 中唤醒时，都会执行此函数。此函数与默认函数 `esp_default_wake_deep_sleep()` 弱链接，因此如果应用程序包含名为 `esp_wake_deep_sleep()` 的函数，则会覆盖 esp-idf 中的默认函数。

请注意，如果只想调用 Deep-sleep 功能，则不必在应用程序中实现 `esp_wake_deep_sleep()` 函数，只有当希望在 SoC 唤醒后立即做一些特殊行为时才需要用到该函数。

在开发自定义的唤醒存根时，首先应调用默认函数 `esp_default_wake_deep_sleep()`。

此外，如果想在运行时切换不同的唤醒存根，可以调用函数 `esp_set_deep_sleep_wake_stub()`。

参照以下步骤，可以在应用程序中实现唤醒存根函数：

- 将唤醒存根代码加载到 RTC 快速内存中

- 将数据加载到 RTC 内存中

### 将唤醒存根代码加载到 RTC 快速内存中

唤醒存根代码只能调用存放在 ROM 中或加载到 RTC 快速内存中的函数，其他所有 RAM 位置都未初始化，且包含随机数据。虽然唤醒存根代码可以使用其他 RAM 区域进行临时存储，但这些区域的内容在回到 Deep-sleep 模式或启动 esp-idf 时将被覆盖。

唤醒存根代码是主 esp-idf 应用程序的一部分。在 esp-idf 正常运行期间，函数可以像常规程序一样调用唤醒存根代码或访问 RTC 内存。

唤醒存根代码必须驻留在 RTC 快速内存中，这可以通过两种方式实现。

- 使用 `RTC_IRAM_ATTR` 属性将 `esp_wake_deep_sleep()` 函数放到 RTC 快速内存中：

    void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
        esp_default_wake_deep_sleep();
        // Add additional functionality here
    }

第一种方法适用于简短的代码段或包含“常规”代码和 "RTC" 代码的源文件。

- 将函数 `esp_wake_deep_sleep()` 放到任何名字以 `rtc_wake_stub` 开头的源文件中。以 `rtc_wake_stub*` 为名的文件中的内容会由链接器自动放入 RTC 快速内存中。

在 RTC 快速内存中编写较长的代码段时，建议使用第二种方法。

然而，以这种方式使用的任何字符串常量都必须被声明为数组，且使用 `RTC_RODATA_ATTR` 进行标记，如上文例子所示。

- 将数据放到任何名字以 `rtc_wake_stub` 开头的源文件中，如示例源文件 system/deep_sleep_wake_stub/main/rtc_wake_stub_example.c。

    if (s_count >= s_max_count) {
        // Reset s_count
        s_count = 0;

        // Set the default wake stub.
        // There is a default version of this function provided in esp-idf.
        esp_default_wake_deep_sleep();

        // Return from the wake stub function to continue
        // booting the firmware.
        return;
    }

在包含字符串或更复杂的代码段时，建议使用第二种方法。

启用 Kconfig 选项 CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP 可以减少唤醒时间。更多信息请参阅 从 Deep-sleep 模式快速启动。

上述所有函数在 esp_hw_support/include/esp_sleep.h 中声明。

### 将唤醒存根数据加载到 RTC 内存中

RTC 内存必须包含唤醒存根代码使用的只读数据。除非是从 Deep-sleep 中唤醒，其他所有 SoC 重新启动时，RTC 内存中的数据会被初始化。从 Deep-sleep 中唤醒时，将保留进入睡眠前存在的数据。唤醒存根代码使用的数据必须驻留在 RTC 内存（RTC 快速内存或 RTC 慢速内存）中。

有两种方法可以指定此数据：

- 使用 `RTC_DATA_ATTR` 和 `RTC_RODATA_ATTR` 属性分别指定可写和只读数据。

    RTC_DATA_ATTR int wake_count;

    void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
        esp_default_wake_deep_sleep();
        static RTC_RODATA_ATTR const char fmt_str[] = "Wake count %d\n";
        esp_rom_printf(fmt_str, wake_count++);
    }

这些数据被存放在 RTC 内存区域中，可以通过名为 CONFIG_ESP32S2_RTCDATA_IN_FAST_MEM 的 menuconfig 选项进行配置，且此选项允许为 ULP 程序保留慢速内存区域。在默认选项中，这些数据被放入 RTC 慢速内存中，一旦启用上述选项，标记有 `RTC_DATA_ATTR` 和 `RTC_RODATA_ATTR` 的数据将被放入 RTC 快速内存中。此选项依赖于 CONFIG_FREERTOS_UNICORE 选项，因为 RTC 快速内存只能由 PRO_CPU 访问。

`RTC_FAST_ATTR` 和 `RTC_SLOW_ATTR` 属性分别可用于指定被强制放入 RTC 快速内存和 RTC 慢速内存中的数据。

## 应用示例

- system/deep_sleep_wake_stub 演示如何使用 ESP32-S2 上的深度睡眠唤醒存根，以便在唤醒后立即执行一些任务（唤醒存根代码），然后再返回睡眠状态。

## 测量从 Deep-sleep 唤醒到唤醒存根执行的时间

在某些低功耗场景下，开发者可能希望测量 ESP32-S2 芯片从 Deep-sleep 唤醒到执行唤醒存根所需的时间。

本节介绍了两种测量该唤醒时长的方法。

### 方法一：使用 CPU 周期计数器估算

该方法利用 CPU 的内部周期计数器来估算唤醒时间。在存根函数（类型为 esp_deep_sleep_wake_stub_fn_t）的开头，读取当前的 CPU 周期计数，并根据运行的 CPU 频率将其转换为时间。

参考示例：system/deep_sleep_wake_stub。

运行示例后，你将看到类似如下的日志：

    Enabling timer wakeup, 10s
    Entering deep sleep
    ESP-ROM:esp32c3-api1-20210207
    Build:Feb  7 2021
    rst:0x5 (DSLEEP),boot:0xc (SPI_FAST_FLASH_BOOT)
    wake stub: wakeup count is 1, wakeup cause is 8, wakeup cost 12734 us
    wake stub: going to deep sleep
    ESP-ROM:esp32c3-api1-20210207
    Build:Feb  7 2021
    rst:0x5 (DSLEEP),boot:0xc (SPI_FAST_FLASH_BOOT)

其中 `wakeup cost 12734 us` 表示从 Deep-sleep 唤醒到唤醒存根执行之间的时间。

方法一的优点：

- 不需要外部硬件。

- 实现简单。

方法一的局限性：

- 测量的时长可能包含部分初始化流程。

- 不适用于超高精度的时序分析。

### 方法二：使用 GPIO 管脚和逻辑分析仪

你可以使用一个 GPIO 管脚作为唤醒源，另一个 GPIO 管脚用于指示唤醒存根开始执行。通过在逻辑分析仪上观察这些 GPIO 的电平变化，可以准确测量从唤醒到存根执行的时间。

例如，在下图中，GPIO4 作为唤醒源，GPIO5 用于指示唤醒存根开始执行。GPIO4 和 GPIO5 的高电平之间的时长即为从唤醒到存根执行的时间。

从唤醒到存根执行的时间

其中 `2.657ms` 表示从 Deep-sleep 唤醒到唤醒存根执行之间的时间。

方法二的优点：

- 精度高。

- 适用于验证硬件时序行为。

方法二的局限性：

- 需要外部设备（逻辑分析仪或示波器）。

- 在定制板上可能需要测试引脚布线。

### 建议

- 对于快速估算或纯软件测试，方法一已足够。

- 对于精确验证和硬件级时序分析，推荐使用方法二。

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。