Skip to content

Commit 94250e4

Browse files
committed
sleep: optimize light sleep wakeup latency
1 parent ac623a9 commit 94250e4

File tree

4 files changed

+270
-88
lines changed

4 files changed

+270
-88
lines changed

components/esp32/include/esp_sleep.h

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ typedef enum {
3838
ESP_PD_DOMAIN_RTC_PERIPH, //!< RTC IO, sensors and ULP co-processor
3939
ESP_PD_DOMAIN_RTC_SLOW_MEM, //!< RTC slow memory
4040
ESP_PD_DOMAIN_RTC_FAST_MEM, //!< RTC fast memory
41+
ESP_PD_DOMAIN_XTAL, //!< XTAL oscillator
4142
ESP_PD_DOMAIN_MAX //!< Number of domains
4243
} esp_sleep_pd_domain_t;
4344

components/esp32/sleep_modes.c

+108-42
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <sys/param.h>
1818
#include "esp_attr.h"
1919
#include "esp_sleep.h"
20+
#include "esp_timer_impl.h"
2021
#include "esp_log.h"
2122
#include "esp_clk.h"
2223
#include "esp_newlib.h"
@@ -42,6 +43,19 @@
4243
// Time from VDD_SDIO power up to first flash read in ROM code
4344
#define VDD_SDIO_POWERUP_TO_FLASH_READ_US 700
4445

46+
// Extra time it takes to enter and exit light sleep and deep sleep
47+
// For deep sleep, this is until the wake stub runs (not the app).
48+
#ifdef CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL
49+
#define LIGHT_SLEEP_TIME_OVERHEAD_US (650 + 30 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
50+
#define DEEP_SLEEP_TIME_OVERHEAD_US (650 + 100 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
51+
#else
52+
#define LIGHT_SLEEP_TIME_OVERHEAD_US (250 + 30 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
53+
#define DEEP_SLEEP_TIME_OVERHEAD_US (250 + 100 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
54+
#endif // CONFIG_ESP32_RTC_CLOCK_SOURCE
55+
56+
// Minimal amount of time we can sleep for
57+
#define LIGHT_SLEEP_MIN_TIME_US 200
58+
4559
#define CHECK_SOURCE(source, value, mask) ((s_config.wakeup_triggers & mask) && \
4660
(source == value))
4761

@@ -56,9 +70,11 @@ typedef struct {
5670
uint32_t ext1_rtc_gpio_mask : 18;
5771
uint32_t ext0_trigger_level : 1;
5872
uint32_t ext0_rtc_gpio_num : 5;
59-
} deep_sleep_config_t;
73+
uint32_t sleep_time_adjustment;
74+
uint64_t rtc_ticks_at_sleep_start;
75+
} sleep_config_t;
6076

61-
static deep_sleep_config_t s_config = {
77+
static sleep_config_t s_config = {
6278
.pd_options = { ESP_PD_OPTION_AUTO, ESP_PD_OPTION_AUTO, ESP_PD_OPTION_AUTO },
6379
.wakeup_triggers = 0
6480
};
@@ -125,12 +141,31 @@ void esp_deep_sleep(uint64_t time_in_us)
125141
esp_deep_sleep_start();
126142
}
127143

144+
static void IRAM_ATTR suspend_uarts()
145+
{
146+
for (int i = 0; i < 3; ++i) {
147+
REG_SET_BIT(UART_FLOW_CONF_REG(i), UART_FORCE_XOFF);
148+
uart_tx_wait_idle(i);
149+
}
150+
}
151+
152+
static void IRAM_ATTR resume_uarts()
153+
{
154+
for (int i = 0; i < 3; ++i) {
155+
REG_CLR_BIT(UART_FLOW_CONF_REG(i), UART_FORCE_XOFF);
156+
REG_SET_BIT(UART_FLOW_CONF_REG(i), UART_FORCE_XON);
157+
REG_CLR_BIT(UART_FLOW_CONF_REG(i), UART_FORCE_XON);
158+
}
159+
}
160+
128161
static uint32_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags)
129162
{
130-
// Flush UARTs so that output is not lost due to APB frequency change
131-
uart_tx_wait_idle(0);
132-
uart_tx_wait_idle(1);
133-
uart_tx_wait_idle(2);
163+
// Stop UART output so that output is not lost due to APB frequency change
164+
suspend_uarts();
165+
166+
// Save current frequency and switch to XTAL
167+
rtc_cpu_freq_t cpu_freq = rtc_clk_cpu_freq_get();
168+
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_XTAL);
134169

135170
// Configure pins for external wakeup
136171
if (s_config.wakeup_triggers & RTC_EXT0_TRIG_EN) {
@@ -143,20 +178,32 @@ static uint32_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags)
143178
if (s_config.wakeup_triggers & RTC_ULP_TRIG_EN) {
144179
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_WAKEUP_FORCE_EN);
145180
}
181+
182+
// Enter sleep
183+
rtc_sleep_config_t config = RTC_SLEEP_CONFIG_DEFAULT(pd_flags);
184+
rtc_sleep_init(config);
185+
146186
// Configure timer wakeup
147187
if ((s_config.wakeup_triggers & RTC_TIMER_TRIG_EN) &&
148188
s_config.sleep_duration > 0) {
149189
timer_wakeup_prepare();
150190
}
191+
uint32_t result = rtc_sleep_start(s_config.wakeup_triggers, 0);
151192

152-
// Enter sleep
153-
rtc_sleep_config_t config = RTC_SLEEP_CONFIG_DEFAULT(pd_flags);
154-
rtc_sleep_init(config);
155-
return rtc_sleep_start(s_config.wakeup_triggers, 0);
193+
// Restore CPU frequency
194+
rtc_clk_cpu_freq_set(cpu_freq);
195+
196+
// re-enable UART output
197+
resume_uarts();
198+
199+
return result;
156200
}
157201

158202
void IRAM_ATTR esp_deep_sleep_start()
159203
{
204+
// record current RTC time
205+
s_config.rtc_ticks_at_sleep_start = rtc_time_get();
206+
160207
// Configure wake stub
161208
if (esp_get_deep_sleep_wake_stub() == NULL) {
162209
esp_set_deep_sleep_wake_stub(esp_wake_deep_sleep);
@@ -165,8 +212,11 @@ void IRAM_ATTR esp_deep_sleep_start()
165212
// Decide which power domains can be powered down
166213
uint32_t pd_flags = get_power_down_flags();
167214

215+
// Correct the sleep time
216+
s_config.sleep_time_adjustment = DEEP_SLEEP_TIME_OVERHEAD_US;
217+
168218
// Enter sleep
169-
esp_sleep_start(RTC_SLEEP_PD_DIG | RTC_SLEEP_PD_VDDSDIO | pd_flags);
219+
esp_sleep_start(RTC_SLEEP_PD_DIG | RTC_SLEEP_PD_VDDSDIO | RTC_SLEEP_PD_XTAL | pd_flags);
170220

171221
// Because RTC is in a slower clock domain than the CPU, it
172222
// can take several CPU cycles for the sleep mode to start.
@@ -201,11 +251,11 @@ static void rtc_wdt_disable()
201251
* Placed into IRAM as flash may need some time to be powered on.
202252
*/
203253
static esp_err_t esp_light_sleep_inner(uint32_t pd_flags,
204-
rtc_cpu_freq_t cpu_freq, uint32_t flash_enable_time_us,
254+
uint32_t flash_enable_time_us,
205255
rtc_vddsdio_config_t vddsdio_config) IRAM_ATTR __attribute__((noinline));
206256

207257
static esp_err_t esp_light_sleep_inner(uint32_t pd_flags,
208-
rtc_cpu_freq_t cpu_freq, uint32_t flash_enable_time_us,
258+
uint32_t flash_enable_time_us,
209259
rtc_vddsdio_config_t vddsdio_config)
210260
{
211261
// Enter sleep
@@ -217,9 +267,6 @@ static esp_err_t esp_light_sleep_inner(uint32_t pd_flags,
217267
rtc_vddsdio_set_config(vddsdio_config);
218268
}
219269

220-
// Restore CPU frequency
221-
rtc_clk_cpu_freq_set(cpu_freq);
222-
223270
// If SPI flash was powered down, wait for it to become ready
224271
if (pd_flags & RTC_SLEEP_PD_VDDSDIO) {
225272
// Wait for the flash chip to start up
@@ -231,53 +278,61 @@ static esp_err_t esp_light_sleep_inner(uint32_t pd_flags,
231278
esp_err_t esp_light_sleep_start()
232279
{
233280
static portMUX_TYPE light_sleep_lock = portMUX_INITIALIZER_UNLOCKED;
234-
235281
portENTER_CRITICAL(&light_sleep_lock);
236-
int other_cpu = xPortGetCoreID() ? 0 : 1;
237-
esp_cpu_stall(other_cpu);
238-
239-
// Other CPU is stalled, need to disable DPORT protection
240-
esp_dport_access_int_pause();
282+
s_config.rtc_ticks_at_sleep_start = rtc_time_get();
283+
uint64_t frc_time_at_start = esp_timer_get_time();
284+
DPORT_STALL_OTHER_CPU_START();
241285

242286
// Decide which power domains can be powered down
243287
uint32_t pd_flags = get_power_down_flags();
244288

289+
// Amount of time to subtract from actual sleep time.
290+
// This is spent on entering and leaving light sleep.
291+
s_config.sleep_time_adjustment = LIGHT_SLEEP_TIME_OVERHEAD_US;
292+
245293
// Decide if VDD_SDIO needs to be powered down;
246294
// If it needs to be powered down, adjust sleep time.
247295
const uint32_t flash_enable_time_us = VDD_SDIO_POWERUP_TO_FLASH_READ_US
248296
+ CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY;
249297

250-
// Don't power down VDD_SDIO if pSRAM is used.
251298
#ifndef CONFIG_SPIRAM_SUPPORT
252-
if (s_config.sleep_duration > FLASH_PD_MIN_SLEEP_TIME_US &&
253-
s_config.sleep_duration > flash_enable_time_us) {
299+
const uint32_t vddsdio_pd_sleep_duration = MAX(FLASH_PD_MIN_SLEEP_TIME_US,
300+
flash_enable_time_us + LIGHT_SLEEP_TIME_OVERHEAD_US + LIGHT_SLEEP_MIN_TIME_US);
301+
302+
if (s_config.sleep_duration > vddsdio_pd_sleep_duration) {
254303
pd_flags |= RTC_SLEEP_PD_VDDSDIO;
255-
s_config.sleep_duration -= flash_enable_time_us;
304+
s_config.sleep_time_adjustment += flash_enable_time_us;
256305
}
257306
#endif //CONFIG_SPIRAM_SUPPORT
307+
258308
rtc_vddsdio_config_t vddsdio_config = rtc_vddsdio_get_config();
259309

260310
// Safety net: enable WDT in case exit from light sleep fails
261311
rtc_wdt_enable(1000);
262312

263-
// Save current CPU frequency, light sleep will switch to XTAL
264-
rtc_cpu_freq_t cpu_freq = rtc_clk_cpu_freq_get();
265-
266313
// Enter sleep, then wait for flash to be ready on wakeup
267-
esp_err_t err = esp_light_sleep_inner(pd_flags, cpu_freq,
314+
esp_err_t err = esp_light_sleep_inner(pd_flags,
268315
flash_enable_time_us, vddsdio_config);
269316

270-
// At this point, if FRC1 is used for timekeeping, time will be lagging behind.
271-
// This will update the microsecond count based on RTC timer.
272-
esp_set_time_from_rtc();
317+
// FRC1 has been clock gated for the duration of the sleep, correct for that.
318+
uint64_t rtc_ticks_at_end = rtc_time_get();
319+
uint64_t frc_time_at_end = esp_timer_get_time();
273320

274-
// However, we do not advance RTOS ticks here; doing so would be rather messy,
275-
// as ticks can only be advanced on CPU0.
276-
// If this is needed by the application, automatic light sleep (tickless idle)
277-
// will handle that better.
321+
uint64_t rtc_time_diff = rtc_time_slowclk_to_us(rtc_ticks_at_end - s_config.rtc_ticks_at_sleep_start,
322+
esp_clk_slowclk_cal_get());
323+
uint64_t frc_time_diff = frc_time_at_end - frc_time_at_start;
324+
325+
int64_t time_diff = rtc_time_diff - frc_time_diff;
326+
/* Small negative values (up to 1 RTC_SLOW clock period) are possible,
327+
* for very small values of sleep_duration. Ignore those to keep esp_timer
328+
* monotonic.
329+
*/
330+
if (time_diff > 0) {
331+
esp_timer_impl_advance(time_diff);
332+
}
333+
esp_set_time_from_rtc();
278334

279-
esp_cpu_unstall(other_cpu);
280-
esp_dport_access_int_resume();
335+
DPORT_STALL_OTHER_CPU_END();
281336
rtc_wdt_disable();
282337
portEXIT_CRITICAL(&light_sleep_lock);
283338
return err;
@@ -343,9 +398,13 @@ esp_err_t esp_sleep_enable_timer_wakeup(uint64_t time_in_us)
343398
static void timer_wakeup_prepare()
344399
{
345400
uint32_t period = esp_clk_slowclk_cal_get();
346-
uint64_t rtc_count_delta = rtc_time_us_to_slowclk(s_config.sleep_duration, period);
347-
uint64_t cur_rtc_count = rtc_time_get();
348-
rtc_sleep_set_wakeup_time(cur_rtc_count + rtc_count_delta);
401+
int64_t sleep_duration = (int64_t) s_config.sleep_duration - (int64_t) s_config.sleep_time_adjustment;
402+
if (sleep_duration < 0) {
403+
sleep_duration = 0;
404+
}
405+
int64_t rtc_count_delta = rtc_time_us_to_slowclk(sleep_duration, period);
406+
407+
rtc_sleep_set_wakeup_time(s_config.rtc_ticks_at_sleep_start + rtc_count_delta);
349408
}
350409

351410
esp_err_t esp_sleep_enable_touchpad_wakeup()
@@ -561,6 +620,10 @@ static uint32_t get_power_down_flags()
561620
}
562621
}
563622

623+
if (s_config.pd_options[ESP_PD_DOMAIN_XTAL] == ESP_PD_OPTION_AUTO) {
624+
s_config.pd_options[ESP_PD_DOMAIN_XTAL] = ESP_PD_OPTION_OFF;
625+
}
626+
564627
const char* option_str[] = {"OFF", "ON", "AUTO(OFF)" /* Auto works as OFF */};
565628
ESP_LOGD(TAG, "RTC_PERIPH: %s, RTC_SLOW_MEM: %s, RTC_FAST_MEM: %s",
566629
option_str[s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH]],
@@ -578,5 +641,8 @@ static uint32_t get_power_down_flags()
578641
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] != ESP_PD_OPTION_ON) {
579642
pd_flags |= RTC_SLEEP_PD_RTC_PERIPH;
580643
}
644+
if (s_config.pd_options[ESP_PD_DOMAIN_XTAL] != ESP_PD_OPTION_ON) {
645+
pd_flags |= RTC_SLEEP_PD_XTAL;
646+
}
581647
return pd_flags;
582648
}

0 commit comments

Comments
 (0)