-
-
Save igrr/54f7fbe0513ac14e1aea3fd7fbecfeab to your computer and use it in GitHub Desktop.
/* | |
* 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. | |
} |
@andrew-elder I use on the regular ESP32: GPIO_OUTPUT_SET(_LED, 1);
This works on the ESP32, C3, and I believe S2, I have not tested the S3 with this.
I was not setting the RTC MUX before writing to the output. I added
// Set RTC mux
REG_WRITE(RTC_IO_TOUCH_PAD9_REG, RTC_IO_TOUCH_PAD9_MUX_SEL_M);
and that fixed the issue.
@igrr - I created an issue https://esp32.com/viewtopic.php?f=13&t=27067 here. Is that the correct place?
@andrew-elder I would recommend https://github.com/espressif/esp-idf/issues — issues there are tracked (unlike the posts on the forum)
@igrr - is it ok to file my issue as a "bug report" - even though it isn't really a bug (or at least I wouldn't think it is)?
GPIO_OUTPUT_SET
@andrew-elder I use on the regular ESP32: GPIO_OUTPUT_SET(_LED, 1);
This works on the ESP32, C3, and I believe S2, I have not tested the S3 with this.
@helmut64 I'm trying to do the same, but I can't get GPIO_OUTPUT_SET to do anything when loading in the wake stub. I've tried both the GPIO and RTC_GPIO pin references, and the pin never goes high. Is there anything else you set/enabled to make the GPIO/RTC pin go high during the wake stub?
Sorry to barge in on this - just for the rare case someone looking for answers will get here and I'd like to share my findings about WAKE UP STUB and controlling RTC GPIOs. For wakeup - I managed to wake on both EXT1 and TIMER by setting up wakeup mask like this (before the SLEEP command:
#define WAKEUP_MASK 10 //both TIMER and EXT1 (2+8) REG_SET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA, WAKEUP_MASK); // Wake up on timer and EXT1
In this way, if one correctly sets EXT1 triggers (wakeup on '1') and the timer, either will wake up and execute the stub.As for controlling RTC_GPIOs - upon entering SLEEP, most RTC_GPIOs are set to HOLD enabled. To disable HOLD within the RAM STUB, the standard ESP-IDF functions cannot be used since they are inaccessible when RAM STUB is executed. The way around is to directly set/clear the relevant bits in the control registers. The HOLD function is controlled for RTC GPIOs by individual registers like the RTC_IO_TOUCH_PAD0_REG (for TOUCH0/GPIO4). To unhold - clear bit 31. To hold back - set bit 31:
REG_WRITE(RTC_IO_TOUCH_PAD0_REG,REG_READ(RTC_IO_TOUCH_PAD0_REG)|0x80000000); //hold TOUCH0 - GPIO4
REG_WRITE(RTC_IO_TOUCH_PAD0_REG,REG_READ(RTC_IO_TOUCH_PAD0_REG)&0x7fffffff); //Unhold TOUCH0 - GPIO4
Once unheld, the pins can be controlled by writing the relevant bits of the IO set and clear registers: RTC_GPIO_OUT_W1TS_REG - to set a GPIO to 1 RTC_GPIO_OUT_W1TC_REG - to set a GPIO to 0
#define SET_GPIO4 REG_WRITE(RTC_GPIO_OUT_W1TS_REG,BIT(RTC_GPIO_OUT_DATA_W1TS_S + 10)) //GPIO4 is RTC_GPIO_10 so it's BIT(10)
#define CLR_GPIO4 REG_WRITE(RTC_GPIO_OUT_W1TC_REG,BIT(RTC_GPIO_OUT_DATA_W1TC_S + 10)) //GPIO4 is RTC_GPIO_10 so it's BIT(10)
Hope this provides some well needed (and searched) information for struggling developers like myself. N.
Many thanks @ntasher, your guide on waking from both timer and EXT1/0 really helped me. However I’m still really struggling to set a pin (gpio_27) high in the wake stub. Do you have a code example you could share? I’ve tried the extracts you provided here but nothing happens! Many thanks
I use only GPIO_OUTPUT_SET(_LED, 1). This works on the C3 and regular ESP32. I tested this with lower GPIO pins only.
PS: I am using the wakeup stub only for showing some board activity by blinking ever 10 secs an LED, that user understand that the device is still working. The wakeup stub helps a lot to save energy for this status updates.
I use only GPIO_OUTPUT_SET(_LED, 1). This works on the C3 and regular ESP32. I tested this with lower GPIO pins only.
PS: I am using the wakeup stub only for showing some board activity by blinking ever 10 secs an LED, that user understand that the device is still working. The wakeup stub helps a lot to save energy for this status updates.
Many thanks - you pointed me in the right direction. Turns out there was nothing wrong with my idea - I'd just forgotten that I was holding the GPIO in my main code. Doing the following in the wake stub now works absolutely fine:
gpio_pad_unhold(GPIO_NUM_27); GPIO_OUTPUT_SET(GPIO_NUM_27,1); gpio_pad_hold(GPIO_NUM_27);
@helmut64 could you please share your example of how to blink an LED with the wake_stub
every few seconds? I have tried all your code snippets, but my C3 wakes up imideatly after reentering deep sleep if I have RTC_TIMER_TRIG_EN
enabled. Nevertheless, waking up with RTC_GPIO_TRIG_EN
alone works. Thank you in advance!
Please help:
i need read after wake up stub: digitalRead(dataPin)
and then return to stub slep
but system reset by watchdog: rst:0x7 (TG0WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
How to read GPIO input from a wake stub?
please help - i need working example
does anyone know if there is a way to calibrate the ADC in the wake stub? the reason i need this : i use the wake stud to check the value of GPIO36 which is the line of ADKEY, this line has 6 tact switches with a 1% resistor ladder, and i need the system to wake up only if one specific key is pressed. So far nothing special, it works, however the internal vref variance between chips is quite high, and in my resistor ladder i have to use relatively close values to ensure any key can trigger a falling edge, becauee my adkey driver relies on this (interrupt on falling edgee, followed by calibrated ADC readings, filtering and discremination).
So, for now i can read the raw value of the ADC in stub, and set a relatively wide range, but i have no guarantee that one chip in the batch will not be outside this range, for now the range is set to 900-1100 and i already saw raw values pretty close to both ends while tesing on few chips.
For reference, here is my resistor ladder
//bt 5.6K 175mV
//left 6.8K 210mV
//top 8.2K 250mV
//side top 10K 300mV
//side bt 12K 354mV
//enc 15K 430mV
and what i do at the end of the stub
adcValue = SENS.sar_meas_start1.meas1_data_sar;
SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PD; // adc power off
if(adcValue<900 || adcValue>1100){
REG_WRITE(RTC_ENTRY_ADDR_REG, (uint32_t)&esp_wake_deep_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);
while(true){;}
}
I also wonder if there is a way to set attenuation of the ADC in the stub.
@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.
@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.
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.
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.
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.
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.
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
@andrew-elder I'm sorry, I am not well familiar with gpio registers of the esp32-s3. Please open an issue at https://github.com/espressif/esp-idf/issues, IDF project team will try to help you.