Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ESP32 ESP-IDF example illustrating how to go back to sleep from deep sleep wake stub
/*
* This sample illustrates how to go back to deep sleep from the
* deep sleep wake stub.
*
* Consider the use case of counting pulses from an external sensor,
* where the pulses arrive at a relatively slow rate.
*
* ESP32 is configured to go into deep sleep mode, and wake up from
* a GPIO pin connected to the external pulse source.
* Once the pulse arrives, ESP32 wakes up from deep sleep and runs
* deep sleep wake stub. This stub function is stored in RTC fast
* memory, so it can run without waiting for the whole firmware
* to be loaded from flash.
*
* This function (called wake_stub below) increments the pulse counter,
* stored in RTC_SLOW_MEM. This memory is also preserved when going
* into deep sleep. Then the wake stub decides whether to continue
* booting the firmware, or to go back to sleep. In this simple example,
* the stub starts firmware when the pulse counter reaches 100.
* Note: in real application, counting needs to be continued when the
* application has started, for example using the PCNT peripheral.
*
*/
#include <stdio.h>
#include <string.h>
#include "esp_sleep.h"
#include "esp_attr.h"
#include "rom/rtc.h"
#include "rom/ets_sys.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/uart_reg.h"
#include "soc/timer_group_reg.h"
// Pin used for pulse counting
// GPIO0 is RTC_GPIO11 (see esp32_chip_pin_list_en.pdf)
#define PULSE_CNT_GPIO_NUM 0
#define PULSE_CNT_RTC_GPIO_NUM 11
#define PULSE_CNT_IS_LOW() \
((REG_GET_FIELD(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT) \
& BIT(PULSE_CNT_RTC_GPIO_NUM)) == 0)
// Pulse counter value, stored in RTC_SLOW_MEM
static size_t RTC_DATA_ATTR s_pulse_count;
static size_t RTC_DATA_ATTR s_max_pulse_count;
// Function which runs after exit from deep sleep
static void RTC_IRAM_ATTR wake_stub();
void app_main(void)
{
if (rtc_get_reset_reason(0) == DEEPSLEEP_RESET) {
printf("Wake up from deep sleep\n");
printf("Pulse count=%d\n", s_pulse_count);
} else {
printf("Not a deep sleep wake up\n");
}
s_pulse_count = 0;
s_max_pulse_count = 20;
printf("Going to deep sleep in 1 second\n");
printf("Will wake up after %d pulses\n", s_max_pulse_count);
vTaskDelay(1000/portTICK_PERIOD_MS);
// Set the wake stub function
esp_set_deep_sleep_wake_stub(&wake_stub);
// Wake up on low logic level
ESP_ERROR_CHECK( esp_sleep_enable_ext1_wakeup(
1LL << PULSE_CNT_GPIO_NUM, ESP_EXT1_WAKEUP_ALL_LOW) );
// Enter deep sleep
esp_deep_sleep_start();
}
static const char RTC_RODATA_ATTR wake_fmt_str[] = "count=%d\n";
static const char RTC_RODATA_ATTR sleep_fmt_str[] = "sleeping\n";
static void RTC_IRAM_ATTR wake_stub()
{
// Increment the pulse counter
s_pulse_count++;
// and print the pulse counter value:
ets_printf(wake_fmt_str, s_pulse_count);
if (s_pulse_count >= s_max_pulse_count) {
// On revision 0 of ESP32, this function must be called:
esp_default_wake_deep_sleep();
// Return from the wake stub function to continue
// booting the firmware.
return;
}
// Pulse count is <s_max_pulse_count, go back to sleep
// and wait for more pulses.
// Wait for pin level to be high.
// If we go to sleep when the pin is still low, the chip
// will wake up again immediately. Hardware doesn't have
// edge trigger support for deep sleep wakeup.
do {
while (PULSE_CNT_IS_LOW()) {
// feed the watchdog
REG_WRITE(TIMG_WDTFEED_REG(0), 1);
}
// debounce, 10ms
ets_delay_us(10000);
} while (PULSE_CNT_IS_LOW());
// Print status
ets_printf(sleep_fmt_str);
// Wait for UART to end transmitting.
while (REG_GET_FIELD(UART_STATUS_REG(0), UART_ST_UTX_OUT)) {
;
}
// Set the pointer of the wake stub function.
REG_WRITE(RTC_ENTRY_ADDR_REG, (uint32_t)&wake_stub);
// Go to sleep.
CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN);
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN);
// A few CPU cycles may be necessary for the sleep to start...
while (true) {
;
}
// never reaches here.
}
@Khalid7777777

This comment has been minimized.

Copy link

commented Mar 30, 2019

Hi igrr
I am having problem with the stub continuosly rebooting when I try to use a log function. when I remove the log function it works as expected.
here the code and any help will be very much appreciated.
RTC_DATA_ATTR float result_a;
RTC_DATA_ATTR float ulp_temp;
void RTC_IRAM_ATTR esp_wake_deep_sleep(void)
{
esp_default_wake_deep_sleep();
if (PULSE_CNT_IS_LOW()) {
wakeup_tester = (ulp_lb_hundred_sec_counter++) % FAST_BUFFER_SIZE;
//esp_default_wake_deep_sleep();
result_a = logf(ulp_temp & UINT16_MAX);
if (wakeup_tester == 7) {
esp_default_wake_deep_sleep();
return;
}
}
else {
// On revision 0 of ESP32, this function must be called:
wakeup_tester = 0;
esp_default_wake_deep_sleep();
return;
}

// Print status
//ets_printf(sleep_fmt_str);
// Wait for UART to end transmitting.
while (REG_GET_FIELD(UART_STATUS_REG(0), UART_ST_UTX_OUT)) {
	;
}

// Set the pointer of the wake stub function.
REG_WRITE(RTC_ENTRY_ADDR_REG, (uint32_t)&esp_wake_deep_sleep);
// Go to sleep.
CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN);
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN);
// A few CPU cycles may be necessary for the sleep to start...
while (true) {
	;
}
// never reaches here.

}

@Khalid7777777

This comment has been minimized.

Copy link

commented Apr 2, 2019

please can I have the answer of the above. Is it possible to calculate the logarithm base 10 of an RTC fast or slow memory based variable and store the result in an other RTC fast or slow memory location inside the above wake up stub ? your answer will be much appreciated :)

@igrr

This comment has been minimized.

Copy link
Owner Author

commented Jun 27, 2019

@Khalid7777777 no, logf will likely not work in the wake stub. Since your variable is an integer, you may be able to achieve the behavior you need by finding the most significant bit set in the integer.

@baamiis

This comment has been minimized.

Copy link

commented Jun 27, 2019

Thanks for your answer.
I ended up defining logf function and placing it in the FAST_RTC memory. I ripped it out of Math.h. It works really well now.
Now when the system wakes up from the deep sleep, the stub gets executed and the logf function gets used to detect if we need to wake up fully or not. we ended up saving lots of power and we managed to satisfy the power consumption requirements.

thanks again

@nevercast

This comment has been minimized.

Copy link

commented Jul 5, 2019

The sleep code required to wake up via timer is quite involved, can you provide any help @igrr regarding setting the appropriate registers from wake stub to sleep for some time? I'm using external RTC XTAL (32khz), I believe I need to store the amount of clock cycles to sleep for ?

@nevercast

This comment has been minimized.

Copy link

commented Jul 17, 2019

I came up with this in my deepsleep stub to go to sleep for some time.

// Comment out this line if you're using the internal RTC RC (150KHz) oscillator.
#define USE_EXTERNAL_RTC_CRYSTAL
#ifdef USE_EXTERNAL_RTC_CRYSTAL
#define DEEP_SLEEP_TIME_OVERHEAD_US (650 + 100 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
#else
#define DEEP_SLEEP_TIME_OVERHEAD_US (250 + 100 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
#endif // USE_EXTERNAL_RTC_CRYSTAL

RTC_IRAM_ATTR void deepsleep_for_us(uint64_t duration_us) {
    // Feed watchdog
    REG_WRITE(TIMG_WDTFEED_REG(0), 1);
    // Get RTC calibration
    uint32_t period = REG_READ(RTC_SLOW_CLK_CAL_REG);
    // Calculate sleep duration in microseconds
    int64_t sleep_duration = (int64_t)duration_us - (int64_t)DEEP_SLEEP_TIME_OVERHEAD_US;
    if (sleep_duration < 0) {
        sleep_duration = 0;
    }
    // Convert microseconds to RTC clock cycles
    int64_t rtc_count_delta = (sleep_duration << RTC_CLK_CAL_FRACT) / period;
    // Feed watchdog
    REG_WRITE(TIMG_WDTFEED_REG(0), 1);
    // Get current RTC time
    SET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE);
    while (GET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID) == 0) {
        ets_delay_us(1);
    }
    SET_PERI_REG_MASK(RTC_CNTL_INT_CLR_REG, RTC_CNTL_TIME_VALID_INT_CLR);
    uint64_t now = READ_PERI_REG(RTC_CNTL_TIME0_REG);
    now |= ((uint64_t) READ_PERI_REG(RTC_CNTL_TIME1_REG)) << 32;
    // Set wakeup time
    uint64_t future = now + rtc_count_delta;
    WRITE_PERI_REG(RTC_CNTL_SLP_TIMER0_REG, future & UINT32_MAX);
    WRITE_PERI_REG(RTC_CNTL_SLP_TIMER1_REG, future >> 32);
    // Start RTC deepsleep timer
    REG_SET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA, RTC_TIMER_TRIG_EN); // Wake up on timer
    WRITE_PERI_REG(RTC_CNTL_SLP_REJECT_CONF_REG, 0); // Clear sleep rejection cause
    // Go to sleep
    CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN);
    SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN);
}

This is a simplification of the process that a normal deepsleep process goes through when given some microseconds. Since most of the functions required are not in IRAM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.