feature: add example deep sleep wake stub

Closes https://github.com/espressif/esp-idf/issues/8208
This commit is contained in:
jiangguangming 2023-04-25 19:23:36 +08:00 committed by wuzhenghui
parent f6aafd3539
commit 24a38e3153
12 changed files with 388 additions and 0 deletions

View File

@ -45,6 +45,10 @@ if(NOT BOOTLOADER_BUILD)
list(APPEND srcs "port/${target}/systimer.c")
endif()
if(CONFIG_SOC_RTC_FAST_MEM_SUPPORTED)
list(APPEND srcs "sleep_wake_stub.c")
endif()
else()
# Requires "_esp_error_check_failed()" function
list(APPEND priv_requires "esp_system")

View File

@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_log.h"
#include "esp_sleep.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RTC_STR(str) (__extension__({static const RTC_RODATA_ATTR char _fmt[] = (str); (const char *)&_fmt;}))
#define RTC_LOG_FORMAT(letter, format) LOG_COLOR_ ## letter format LOG_RESET_COLOR "\n"
#define ESP_RTC_LOG( level, format, ... ) if (LOG_LOCAL_LEVEL >= level) { esp_rom_printf(RTC_STR(format), ##__VA_ARGS__); \
esp_wake_stub_uart_tx_wait_idle(0); }
#define ESP_RTC_LOGE( format, ... ) ESP_RTC_LOG(ESP_LOG_ERROR, RTC_LOG_FORMAT(E, format), ##__VA_ARGS__)
#define ESP_RTC_LOGW( format, ... ) ESP_RTC_LOG(ESP_LOG_WARN, RTC_LOG_FORMAT(W, format), ##__VA_ARGS__)
#define ESP_RTC_LOGI( format, ... ) ESP_RTC_LOG(ESP_LOG_INFO, RTC_LOG_FORMAT(I, format), ##__VA_ARGS__)
#define ESP_RTC_LOGD( format, ... ) ESP_RTC_LOG(ESP_LOG_DEBUG, RTC_LOG_FORMAT(D, format), ##__VA_ARGS__)
#define ESP_RTC_LOGV( format, ... ) ESP_RTC_LOG(ESP_LOG_VERBOSE, RTC_LOG_FORMAT(V, format), ##__VA_ARGS__)
/**
* @brief Enter deep-sleep mode from deep sleep wake stub code
*
* This should be called from the wake stub code.
*
* @param new_stub new wake stub function will be set
*/
void esp_wake_stub_sleep(esp_deep_sleep_wake_stub_fn_t new_stub);
/**
* @brief Wait while uart transmission is in progress
*
* This function is waiting while uart transmission is not completed,
* and this function should be called from the wake stub code.
*
* @param uart_no UART port to wait idle
*/
void esp_wake_stub_uart_tx_wait_idle(uint8_t uart_no);
/**
* @brief Set wakeup time from deep sleep stub.
*
* This should be called from the wake stub code.
*
* @param time_in_us wakeup time in us
*/
void esp_wake_stub_set_wakeup_time(uint64_t time_in_us);
/**
* @brief Get wakeup cause from deep sleep stub.
*
* This should be called from the wake stub code.
*
* @return wakeup casue value
*/
uint32_t esp_wake_stub_get_wakeup_cause(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <string.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_sleep.h"
#include "soc/soc.h"
#include "soc/rtc.h"
#include "soc/soc_caps.h"
#include "hal/rtc_cntl_ll.h"
#include "hal/uart_ll.h"
#include "sdkconfig.h"
#include "esp_rom_uart.h"
#include "esp_rom_sys.h"
#ifdef CONFIG_IDF_TARGET_ESP32
#include "esp32/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32C3
#include "esp32c3/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32H2
#include "esp32h2/rom/rtc.h"
#endif
void RTC_IRAM_ATTR esp_wake_stub_sleep(esp_deep_sleep_wake_stub_fn_t new_stub)
{
#if SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY
extern char _rtc_text_start[];
#if CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM
extern char _rtc_noinit_end[];
size_t rtc_fast_length = (size_t)_rtc_noinit_end - (size_t)_rtc_text_start;
#else
extern char _rtc_force_fast_end[];
size_t rtc_fast_length = (size_t)_rtc_force_fast_end - (size_t)_rtc_text_start;
#endif // CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM
esp_rom_set_rtc_wake_addr((esp_rom_wake_func_t)new_stub, rtc_fast_length);
#else
// Set the pointer of the wake stub function.
REG_WRITE(RTC_ENTRY_ADDR_REG, (uint32_t)new_stub);
set_rtc_memory_crc();
#endif // SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_MEM
// Go to sleep.
rtc_cntl_ll_sleep_enable();
// A few CPU cycles may be necessary for the sleep to start...
while (true) {};
// never reaches here.
}
void RTC_IRAM_ATTR esp_wake_stub_uart_tx_wait_idle(uint8_t uart_no)
{
while (!uart_ll_is_tx_idle(UART_LL_GET_HW(uart_no))) {};
}
void RTC_IRAM_ATTR esp_wake_stub_set_wakeup_time(uint64_t time_in_us)
{
uint64_t rtc_count_delta = rtc_cntl_ll_time_to_count(time_in_us);
uint64_t rtc_curr_count = rtc_cntl_ll_get_rtc_time();
rtc_cntl_ll_set_wakeup_timer(rtc_curr_count + rtc_count_delta);
}
uint32_t RTC_IRAM_ATTR esp_wake_stub_get_wakeup_cause(void)
{
return rtc_cntl_ll_get_wakeup_cause();
}

View File

@ -36,6 +36,12 @@ examples/system/console/basic:
temporary: true
reason: lack of runners
examples/system/deep_sleep_wake_stub:
disable:
- if: IDF_TARGET == "esp32c2"
temporary: true
reason: target(s) is not supported yet
examples/system/efuse:
disable_test:
- if: IDF_TARGET == "esp32s3"

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(deep_sleep_wake_stub)

View File

@ -0,0 +1,75 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# Deep Sleep Wake Stub Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
The [Deep-sleep wake stub](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/deep-sleep-stub.html) is used to RTC fast boot mode that avoid the SPI flash booting, thus speeding up the wakeup process. This example demonstrates how to implement the wake stub.
In this example, the `CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP` Kconfig option is used, which allows you to reduce the boot time of the bootloader during waking up from deep sleep. The bootloader stores in rtc memory the address of a running partition and uses it when it wakes up. This example allows you to skip all image checks and speed up the boot.
## How to use example
### Hardware Required
This example should be able to run on any commonly available ESP32/ESP32-S2/ESP32-S3/ESP32-C3 development board without any extra hardware if only **Timer** wake up sources are used.
### Configure the project
```
idf.py menuconfig
```
* **Wake up time** can be configured via `Example configuration > Wake up interval in seconds`
Wake up sources that are unused or unconnected should be disabled in configuration to prevent inadvertent triggering of wake up as a result of floating pins.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
On initial startup, this example will detect that this is the first boot and output the following log:
```
...
I (309) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
Not a deep sleep reset
Enabling timer wakeup, 10s
Entering deep sleep
```
The ESP chips will then enter deep sleep. When a timer wake up occurs, if deep sleep wake stub enabled, the ESP chips will boot from RTC memory and execute stub code. The output log such as the following:
```
...
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x5 (DSLEEP),boot:0x8 (SPI_FAST_FLASH_BOOT)
wake stub: wakeup count is 1, wakeup cause is 8
wake stub: going to deep sleep
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x5 (DSLEEP),boot:0x8 (SPI_FAST_FLASH_BOOT)
wake stub: wakeup count is 2, wakeup cause is 8
wake stub: going to deep sleep
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x5 (DSLEEP),boot:0x8 (SPI_FAST_FLASH_BOOT)
wake stub: wakeup count is 3, wakeup cause is 8
wake stub: going to deep sleep
```

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "wake_stub_example_main.c"
"rtc_wake_stub_example.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,10 @@
menu "Example Configuration"
config WAKE_UP_TIME
int "Wake up interval in seconds"
default 10
range 1 60
help
Configurable wake up interval in seconds.
endmenu

View File

@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <inttypes.h>
#include "esp_sleep.h"
#include "esp_wake_stub.h"
#include "sdkconfig.h"
/*
* Deep sleep wake stub function is a piece of code that will be loaded into 'RTC Fast Memory'.
* The first way is to use the RTC_IRAM_ATTR attribute to place a function into RTC memory,
* The second way is to place the function into any source file whose name starts with rtc_wake_stub.
* Files names rtc_wake_stub* have their contents automatically put into RTC memory by the linker.
*
* First, call esp_set_deep_sleep_wake_stub to set the wake stub function as the RTC stub entry,
* The wake stub function runs immediately as soon as the chip wakes up - before any normal
* initialisation, bootloader, or ESP-IDF code has run. After the wake stub runs, the SoC
* can go back to sleep or continue to start ESP-IDF normally.
*
* Wake stub code must be carefully written, there are some rules for wake stub:
* 1) The wake stub code can only access data loaded in RTC memory.
* 2) The wake stub code can only call functions implemented in ROM or loaded into RTC Fast Memory.
* 3) RTC memory must include any read-only data (.rodata) used by the wake stub.
*/
// counter value, stored in RTC memory
static uint32_t s_count = 0;
static const uint32_t s_max_count = 20;
// wakeup_cause stored in RTC memory
static uint32_t wakeup_cause;
// wake up stub function stored in RTC memory
void wake_stub_example(void)
{
// Get wakeup cause.
wakeup_cause = esp_wake_stub_get_wakeup_cause();
// Increment the counter.
s_count++;
// Print the counter value and wakeup cause.
ESP_RTC_LOGI("wake stub: wakeup count is %d, wakeup cause is %d", s_count, wakeup_cause);
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;
}
// s_count is < s_max_count, go back to deep sleep.
// Set wakeup time in stub, if need to check GPIOs or read some sensor periodically in the stub.
esp_wake_stub_set_wakeup_time(CONFIG_WAKE_UP_TIME*1000000);
// Print status.
ESP_RTC_LOGI("wake stub: going to deep sleep");
// Set stub entry, then going to deep sleep again.
esp_wake_stub_sleep(&wake_stub_example);
}

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void wake_stub_example(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_sleep.h"
#include "esp_wake_stub.h"
#include "driver/rtc_io.h"
#include "rtc_wake_stub_example.h"
// sleep_enter_time stored in RTC memory
static RTC_DATA_ATTR struct timeval sleep_enter_time;
void app_main(void)
{
struct timeval now;
gettimeofday(&now, NULL);
int sleep_time_ms = (now.tv_sec - sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - sleep_enter_time.tv_usec) / 1000;
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) {
printf("Wake up from timer. Time spent in deep sleep: %dms\n", sleep_time_ms);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
const int wakeup_time_sec = CONFIG_WAKE_UP_TIME;
printf("Enabling timer wakeup, %ds\n", wakeup_time_sec);
esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000);
#if CONFIG_IDF_TARGET_ESP32
// Isolate GPIO12 pin from external circuits. This is needed for modules
// which have an external pull-up resistor on GPIO12 (such as ESP32-WROVER)
// to minimize current consumption.
rtc_gpio_isolate(GPIO_NUM_12);
#endif
// Set the wake stub function
esp_set_deep_sleep_wake_stub(&wake_stub_example);
printf("Entering deep sleep\n");
gettimeofday(&sleep_enter_time, NULL);
esp_deep_sleep_start();
}

View File

@ -0,0 +1 @@
CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y