Skip to content

Instantly share code, notes, and snippets.

@igrr
Last active November 25, 2023 21:27
Show Gist options
  • Save igrr/54f7fbe0513ac14e1aea3fd7fbecfeab to your computer and use it in GitHub Desktop.
Save igrr/54f7fbe0513ac14e1aea3fd7fbecfeab to your computer and use it in GitHub Desktop.
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.
}
@igrr
Copy link
Author

igrr commented Jul 7, 2023

@0x0fe I would probably try to use the ADC calibration API to convert a few raw values into calibrated voltage values, then use linear interpolation to calculate the raw values which correspond to the threshold voltages you need. (Essentially, implement a makeshift "real voltage to ADC count" conversion which IDF doesn't provide.)
Then, store these ADC counts in variables and use these variables in the ULP program.

@0x0fe
Copy link

0x0fe commented Jul 7, 2023

@igrr interresting idea, but here i dont specially need to use the calibrated RAW adc values, i can use the millivolt values as in the rest of the firmware, my problem is more to read the efuse vref from the stub, so that i can calculate the calibrated millivolt value.

@0x0fe
Copy link

0x0fe commented Jul 7, 2023

oh, maybe i can store the characteristics in some RTC variables after initial boot and then use these directly to make the conversion in the stub? my concern is how much i can do in the stub, if i recall well i cannot even multiply.

@helmut64
Copy link

helmut64 commented Jul 7, 2023

The problem with the stub-code is that you cannot call anything which is located in the flash memory (e.g. C-Library helper for the 64-bit multiply). The easiest way is to disassemble your stub code and verify for any calls outside.

@0x0fe
Copy link

0x0fe commented Jul 7, 2023

yes, i checked the calibrations function, and it seems very unlikely i can perform any of that in the stub. So i guess @igrr idea is still the best way, when the system goes to sleep, i know that this specifc key is pressed, i can store the raw ADC value corresponding to the key press in an RTC variable and based my stub range check on this value. Still it would be nice to set the attenuation correctly in the stub, i dont know what it defaults to.

@0x0fe
Copy link

0x0fe commented Jul 7, 2023

so, i did that:
at runtime, when the key is pressed, store its raw value in an RTC_DATA_ATTR.
In the stub, check the adc value against this RTC variable, adding some range below and over it.
It works fine.

@peteDDD
Copy link

peteDDD commented Nov 25, 2023

This is great code, and I have used it successfully on ESP32 but it does not work on ESP32-S3. Specifically, as far as I can tell, I have it down to one instruction not working on the ESP32-S3: the setting of the wake stub from within the wake stub.

// Set the pointer of the wake stub function.
REG_WRITE(RTC_ENTRY_ADDR_REG, (uint32_t)&wake_stub);

I have written about this here and hope we can find some code that does work on the ESP32-S3 https://esp32.com/viewtopic.php?f=2&t=37002&p=123908#p123908

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment