mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(esp_pm): add tickless idle support in WAITI mode to reduce power consumption
This commit is contained in:
@@ -1170,9 +1170,12 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, esp_sleep_m
|
||||
#endif
|
||||
|
||||
// Configure timer wakeup
|
||||
bool timer_wakeup_armed = false;
|
||||
if (!should_skip_sleep && (s_config.wakeup_triggers & RTC_TIMER_TRIG_EN)) {
|
||||
if (timer_wakeup_prepare(sleep_duration) != ESP_OK) {
|
||||
should_skip_sleep = allow_sleep_rejection ? true : false;
|
||||
} else {
|
||||
timer_wakeup_armed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,6 +1188,10 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, esp_sleep_m
|
||||
} else {
|
||||
result = esp_sleep_start_safe(sleep_flags, reject_triggers, deep_sleep, &config);
|
||||
}
|
||||
if (timer_wakeup_armed) {
|
||||
/* Disarm leftover comparator after non-timer wakeups so it cannot fire into other LP/RTC timer users. */
|
||||
rtc_timer_hal_clear_wakeup_time(0);
|
||||
}
|
||||
#if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION
|
||||
if (sleep_flags & RTC_SLEEP_PD_VDDSDIO) {
|
||||
/* Cache Suspend 2: If previous sleep powerdowned the flash, suspend cache here so that the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
set(priv_requires esp_system esp_driver_gpio esp_timer esp_hal_uart)
|
||||
set(priv_requires esp_system esp_driver_gpio esp_timer esp_hal_uart esp_hal_rtc_timer esp_hal_systimer hal freertos)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
return() # This component is not supported by the POSIX/Linux simulator
|
||||
|
||||
@@ -78,6 +78,20 @@ menu "Power Management"
|
||||
This feature is intended to be used when lower power consumption is needed
|
||||
while there is enough place in IRAM to place source code.
|
||||
|
||||
config PM_TICKLESS_IDLE_WAITI
|
||||
bool "Enable tickless idle without entering light sleep"
|
||||
depends on PM_ENABLE
|
||||
depends on FREERTOS_USE_TICKLESS_IDLE
|
||||
depends on FREERTOS_SYSTICK_USES_SYSTIMER
|
||||
depends on SOC_RTC_TIMER_SUPPORTED
|
||||
default n
|
||||
help
|
||||
When enabled, tickless idle also applies when the system does not enter
|
||||
light sleep. This includes DFS-only configurations (light sleep disabled),
|
||||
and cases where light sleep is enabled but not taken (for example when
|
||||
blocked by ESP_PM_NO_LIGHT_SLEEP or other PM locks). This can reduce idle
|
||||
power consumption in those situations.
|
||||
|
||||
config PM_SLP_DISABLE_GPIO
|
||||
bool "Disable all GPIO when chip at sleep"
|
||||
depends on FREERTOS_USE_TICKLESS_IDLE
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -84,8 +84,10 @@ void esp_pm_impl_switch_mode(pm_mode_t mode, pm_mode_switch_t lock_or_unlock, pm
|
||||
void esp_pm_impl_init(void);
|
||||
|
||||
/**
|
||||
* @brief Hook function for the idle task
|
||||
* Must be called from the IDLE task on each CPU before entering waiti state.
|
||||
* @brief Release the per-core RTOS PM lock so DFS can drop frequency while idle.
|
||||
*
|
||||
* Safe to call more than once per idle entry: subsequent calls are no-ops until
|
||||
* an ISR / leave_idle() re-acquires the lock.
|
||||
*/
|
||||
void esp_pm_impl_idle_hook(void);
|
||||
|
||||
@@ -110,6 +112,17 @@ void esp_pm_impl_dump_stats(FILE* out);
|
||||
*/
|
||||
void esp_pm_impl_waiti(void);
|
||||
|
||||
#if CONFIG_PM_TICKLESS_IDLE_WAITI
|
||||
/**
|
||||
* @brief Execute a planned tickless WAITI from the idle task, if any.
|
||||
*
|
||||
* Handles RTOS PM lock release around WFI. Call once from esp_vApplicationIdleHook();
|
||||
*
|
||||
* @return true if tickless WAITI ran (idle hook should return early)
|
||||
*/
|
||||
bool esp_pm_impl_tickless_waiti(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Callback function type for peripherals to skip light sleep.
|
||||
*
|
||||
|
||||
@@ -4,6 +4,9 @@ entries:
|
||||
if PM_RTOS_IDLE_OPT = y:
|
||||
pm_impl:esp_pm_impl_idle_hook (noflash)
|
||||
pm_impl:esp_pm_impl_waiti (noflash)
|
||||
if PM_TICKLESS_IDLE_WAITI = y:
|
||||
pm_impl:esp_pm_impl_tickless_waiti (noflash)
|
||||
pm_impl:tickless_waiti_enter (noflash)
|
||||
|
||||
if PM_SLP_IRAM_OPT = y:
|
||||
pm_impl:esp_pm_impl_get_cpu_freq (noflash)
|
||||
|
||||
+315
-9
@@ -30,7 +30,6 @@
|
||||
#include "hal/uart_ll.h"
|
||||
#include "hal/uart_types.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -54,6 +53,36 @@
|
||||
#include "esp_memory_utils.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
#if CONFIG_PM_TICKLESS_IDLE_WAITI
|
||||
#include "hal/rtc_timer_hal.h"
|
||||
#include "hal/systimer_ll.h"
|
||||
#include "esp_private/systimer.h"
|
||||
#include "esp_private/freertos_idf_additions_priv.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#if CONFIG_ESP_TASK_WDT_EN
|
||||
#include "esp_private/esp_task_wdt.h"
|
||||
#endif
|
||||
#if CONFIG_ESP_INT_WDT
|
||||
#include "esp_private/esp_int_wdt.h"
|
||||
#endif
|
||||
#if SOC_LP_PERIPH_SHARE_INTERRUPT
|
||||
#include "esp_private/rtc_ctrl.h"
|
||||
#endif
|
||||
|
||||
#if SOC_LP_PERIPH_SHARE_INTERRUPT
|
||||
#define TICKLESS_WAITI_LP_TIMER_INTR_SOURCE ETS_LP_TIMER_INTR_SOURCE
|
||||
#else
|
||||
#if CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S31
|
||||
#define TICKLESS_WAITI_LP_TIMER_INTR_SOURCE ETS_LP_TIMER_REG_0_INTR_SOURCE
|
||||
#else
|
||||
#define TICKLESS_WAITI_LP_TIMER_INTR_SOURCE ETS_LP_RTC_TIMER_INTR_SOURCE
|
||||
/* LP_RTC_TIMER is shared by the LP timer alarm and brownout detector (see power_supply_periph.c). */
|
||||
#define TICKLESS_WAITI_LP_TIMER_INTR_STATUS ((uint32_t)&LP_TIMER.int_st)
|
||||
#define TICKLESS_WAITI_LP_TIMER_INTR_MASK BIT(31) /* LP_TIMER_SOC_WAKEUP_INT_ST */
|
||||
#endif
|
||||
#endif /* !SOC_LP_PERIPH_SHARE_INTERRUPT */
|
||||
#endif /* CONFIG_PM_TICKLESS_IDLE_WAITI */
|
||||
|
||||
#define MHZ (1000000)
|
||||
|
||||
#ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
|
||||
@@ -71,10 +100,10 @@
|
||||
#define CCOMPARE_PREPARE_CYCLES_IN_FREQ_UPDATE 60
|
||||
#endif // CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
|
||||
|
||||
/* When light sleep is used, wake this number of microseconds earlier than
|
||||
/* When tickless idle is used, wake this number of microseconds earlier than
|
||||
* the next tick.
|
||||
*/
|
||||
#define LIGHT_SLEEP_EARLY_WAKEUP_US 100
|
||||
#define TICKLESS_IDLE_EARLY_WAKEUP_US 100
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
/* Minimal divider at which REF_CLK_FREQ can be obtained */
|
||||
@@ -825,6 +854,254 @@ static inline void IRAM_ATTR other_core_should_skip_light_sleep(int core_id)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ---------------------------------------- Tickless WAITI Implementation ------------------------------------------
|
||||
* Use the LP/RTC timer to bound the maximum idle duration, then enter WAITI with interrupts
|
||||
* enabled. The OS tick systimer keeps running during WAITI; the LP timer alarm (or any other
|
||||
* interrupt) takes the CPU out of WFI.
|
||||
*
|
||||
* Multi-core: the LP/RTC timer has a single shared alarm comparator (timer_id=0), programmed
|
||||
* with the earliest deadline among the parked cores. The LP timer interrupt is only routed to
|
||||
* core 0, so its ISR calls portYIELD_CORE() on the other parked core. Whenever any core leaves
|
||||
* WAITI it yields any remaining parked sibling.
|
||||
*
|
||||
* Two-phase park: portSUPPRESS_TICKS_AND_SLEEP() (vApplicationSleep) runs under xKernelLock with
|
||||
* interrupts masked, so WFI cannot be issued there. Phase 1 arms the LP alarm, suppresses the OS
|
||||
* tick, and records the wake deadline in s_waiti_plan[]; phase 2 runs in esp_vApplicationIdleHook()
|
||||
* on the next idle-loop iteration to enter WFI, disarm, restore the tick, and advance xTickCount
|
||||
* via xTaskCatchUpTicks() on core 0.
|
||||
* esp_pm_impl_waiti() handles plain WFI for shorter idle windows.
|
||||
* --------------------------------------------------------------------------------------------------------------- */
|
||||
|
||||
#if CONFIG_PM_TICKLESS_IDLE_WAITI
|
||||
/* Protected by s_switch_lock */
|
||||
typedef struct {
|
||||
bool valid;
|
||||
int64_t deadline_us; // absolute esp_timer time at which to wake.
|
||||
} tickless_waiti_plan_t;
|
||||
static tickless_waiti_plan_t s_waiti_plan[CONFIG_FREERTOS_NUMBER_OF_CORES];
|
||||
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
static volatile uint32_t s_waiti_armed_mask; // cores that currently hold an armed LP/RTC deadline.
|
||||
static volatile uint32_t s_waiti_parked_mask; // cores currently blocked in the WAITI WFI.
|
||||
static uint64_t s_waiti_target_rtc[CONFIG_FREERTOS_NUMBER_OF_CORES]; // per-core wakeup deadline in RTC slow-clock ticks.
|
||||
static uint64_t s_waiti_programmed_target; // deadline currently programmed into the shared comparator.
|
||||
#endif
|
||||
|
||||
static void IRAM_ATTR tickless_waiti_isr(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
#if SOC_LP_PERIPH_SHARE_INTERRUPT
|
||||
SET_PERI_REG_MASK(RTC_CNTL_INT_CLR_REG, RTC_CNTL_MAIN_TIMER_INT_CLR_M);
|
||||
#else
|
||||
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 0);
|
||||
#endif
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
/* The shared LP/RTC timer interrupt is only routed to core 0; wake the core1 if parked. */
|
||||
int core_id = esp_cpu_get_core_id();
|
||||
if (s_waiti_parked_mask & BIT(1 - core_id)) {
|
||||
portYIELD_CORE(1 - core_id);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Suppress this core's periodic OS-tick interrupt. Only the interrupt is masked; the periodic
|
||||
* comparator and the systimer counter keep free-running, so the exact number of ticks elapsed while
|
||||
* masked is preserved in the counter. */
|
||||
FORCE_INLINE_ATTR void tickless_waiti_suppress_sys_tick_intr(int core_id)
|
||||
{
|
||||
systimer_ll_enable_alarm_int(&SYSTIMER, SYSTIMER_ALARM_OS_TICK_CORE0 + core_id, false);
|
||||
}
|
||||
|
||||
FORCE_INLINE_ATTR void tickless_waiti_restore_sys_tick_intr(int core_id)
|
||||
{
|
||||
systimer_ll_enable_alarm_int(&SYSTIMER, SYSTIMER_ALARM_OS_TICK_CORE0 + core_id, true);
|
||||
}
|
||||
|
||||
FORCE_INLINE_ATTR void tickless_waiti_arm_alarm(int core_id, int64_t sleep_time_us)
|
||||
{
|
||||
uint32_t cal_val = esp_clk_slowclk_cal_get();
|
||||
uint64_t rtc_ticks = rtc_time_us_to_slowclk((uint64_t)sleep_time_us, cal_val);
|
||||
uint64_t now_rtc = rtc_timer_hal_get_cycle_count(0);
|
||||
/* rtc_time_us_to_slowclk() truncates; a zero delta makes target == now_rtc and the
|
||||
* alarm will never fire. Clamp to one slow-clock tick. */
|
||||
uint64_t target = now_rtc + MAX(rtc_ticks, 1);
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
/* The comparator is shared, so program it with the earliest deadline among all armed cores.
|
||||
* Only reprogram when this core lowers the deadline or the alarm was idle, to avoid
|
||||
* clobbering an earlier deadline that another core still depends on. */
|
||||
s_waiti_target_rtc[core_id] = target;
|
||||
bool was_empty = (s_waiti_armed_mask == 0);
|
||||
s_waiti_armed_mask |= BIT(core_id);
|
||||
uint64_t min_target = target;
|
||||
if ((s_waiti_armed_mask & BIT(1 - core_id)) && s_waiti_target_rtc[1 - core_id] < target) {
|
||||
min_target = s_waiti_target_rtc[1 - core_id];
|
||||
}
|
||||
if (was_empty || (min_target < s_waiti_programmed_target)) {
|
||||
rtc_timer_hal_set_wakeup_time(0, min_target);
|
||||
s_waiti_programmed_target = min_target;
|
||||
}
|
||||
#else
|
||||
(void)core_id;
|
||||
rtc_timer_hal_set_wakeup_time(0, target);
|
||||
#endif
|
||||
}
|
||||
|
||||
FORCE_INLINE_ATTR void tickless_waiti_disarm_alarm(int core_id)
|
||||
{
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
s_waiti_armed_mask &= ~BIT(core_id);
|
||||
if (s_waiti_armed_mask != 0) {
|
||||
/* The leaving core may have owned the programmed min; reprogram to the earliest
|
||||
* deadline still armed so the sibling is not woken early or spuriously. */
|
||||
uint64_t min_target = s_waiti_target_rtc[1 - core_id];
|
||||
if (min_target != s_waiti_programmed_target) {
|
||||
rtc_timer_hal_set_wakeup_time(0, min_target);
|
||||
s_waiti_programmed_target = min_target;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#else
|
||||
(void)core_id;
|
||||
#endif
|
||||
|
||||
#if SOC_LP_PERIPH_SHARE_INTERRUPT
|
||||
CLEAR_PERI_REG_MASK(RTC_CNTL_SLP_TIMER1_REG, RTC_CNTL_MAIN_TIMER_ALARM_EN_M);
|
||||
SET_PERI_REG_MASK(RTC_CNTL_INT_CLR_REG, RTC_CNTL_MAIN_TIMER_INT_CLR_M);
|
||||
#else
|
||||
rtc_timer_ll_set_target_enable(&LP_TIMER, 0, false);
|
||||
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool tickless_waiti_enter(void)
|
||||
{
|
||||
int core_id = xPortGetCoreID();
|
||||
if (!s_skipped_light_sleep[core_id] || !s_waiti_plan[core_id].valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&s_switch_lock);
|
||||
s_waiti_plan[core_id].valid = false;
|
||||
|
||||
int64_t remaining_us = s_waiti_plan[core_id].deadline_us - esp_timer_get_time();
|
||||
if ((remaining_us < configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL)
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
/* core 0 owns the global tick; it must not suppress it while core 1 is still active. */
|
||||
|| (core_id == 0 && !(s_waiti_parked_mask & BIT(1)))
|
||||
#endif
|
||||
) {
|
||||
/* Abort without Claim/CatchUp: systimer counter kept running while the alarm int was
|
||||
* masked, so the next SysTickIsrHandler will recover missed ticks via its diff path. */
|
||||
tickless_waiti_disarm_alarm(core_id);
|
||||
tickless_waiti_restore_sys_tick_intr(core_id);
|
||||
portEXIT_CRITICAL(&s_switch_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool stop_global_tick = (core_id == 0);
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
s_waiti_parked_mask |= BIT(core_id);
|
||||
#if CONFIG_ESP_INT_WDT && CONFIG_ESP_INT_WDT_CHECK_CPU1
|
||||
if (core_id == 1) {
|
||||
/* The interrupt watchdog feed runs from core 0's tick hook and, with CPU1 liveness checking,
|
||||
* requires core 1 to keep ticking. Pause that check while core 1 is intentionally idle. */
|
||||
esp_int_wdt_pause_cpu1_checking(true);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
portEXIT_CRITICAL(&s_switch_lock);
|
||||
|
||||
if (stop_global_tick) {
|
||||
/* Tick hook no longer runs while the global tick is suppressed, so the watchdogs it feeds
|
||||
* would time out. Pause them across the WAITI window. */
|
||||
#if CONFIG_ESP_TASK_WDT_EN
|
||||
esp_task_wdt_stop();
|
||||
#endif
|
||||
#if CONFIG_ESP_INT_WDT
|
||||
esp_int_wdt_pause();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Lock-free context with interrupts enabled: the LP/RTC alarm (or any other interrupt) takes
|
||||
* the CPU out of WFI, and an ISR may portYIELD_CORE() this core. */
|
||||
esp_cpu_wait_for_intr();
|
||||
|
||||
if (stop_global_tick) {
|
||||
#if CONFIG_ESP_INT_WDT
|
||||
esp_int_wdt_resume();
|
||||
#endif
|
||||
#if CONFIG_ESP_TASK_WDT_EN
|
||||
esp_task_wdt_restart();
|
||||
#endif
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&s_switch_lock);
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
s_waiti_parked_mask &= ~BIT(core_id);
|
||||
#if CONFIG_ESP_INT_WDT && CONFIG_ESP_INT_WDT_CHECK_CPU1
|
||||
if (core_id == 1) {
|
||||
esp_int_wdt_pause_cpu1_checking(false);
|
||||
}
|
||||
#endif
|
||||
// We may have been woken by an unrelated interrupt rather than the LP timer; make sure
|
||||
// the sibling leaves WAITI and restores the tick.
|
||||
if (s_waiti_parked_mask & BIT(1 - core_id)) {
|
||||
portYIELD_CORE(1 - core_id);
|
||||
}
|
||||
#endif
|
||||
tickless_waiti_disarm_alarm(core_id);
|
||||
uint32_t elapsed_ticks = xPortSysTickClaimElapsedTicks(core_id);
|
||||
tickless_waiti_restore_sys_tick_intr(core_id);
|
||||
portEXIT_CRITICAL(&s_switch_lock);
|
||||
|
||||
if (core_id == 0 && elapsed_ticks > 0) {
|
||||
(void) xTaskCatchUpTicks((TickType_t) elapsed_ticks);
|
||||
}
|
||||
|
||||
/* Wake path re-acquires the RTOS lock via leave_idle(); drop it again so
|
||||
* vApplicationSleep() in this idle iteration can enter light sleep / prepare WAITI. */
|
||||
esp_pm_impl_idle_hook();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool esp_pm_impl_tickless_waiti(void)
|
||||
{
|
||||
/* Release RTOS PM lock before any WFI so DFS can drop to min_freq. */
|
||||
esp_pm_impl_idle_hook();
|
||||
return tickless_waiti_enter();
|
||||
}
|
||||
|
||||
static void tickless_waiti_init(void)
|
||||
{
|
||||
#if SOC_LP_PERIPH_SHARE_INTERRUPT
|
||||
rtc_isr_register(tickless_waiti_isr, NULL, RTC_CNTL_MAIN_TIMER_INT_ENA_M, 0);
|
||||
SET_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_MAIN_TIMER_INT_ENA_M);
|
||||
#else
|
||||
#if CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S31
|
||||
ESP_ERROR_CHECK(esp_intr_alloc(TICKLESS_WAITI_LP_TIMER_INTR_SOURCE, ESP_INTR_FLAG_IRAM, tickless_waiti_isr, NULL, NULL));
|
||||
#else
|
||||
ESP_ERROR_CHECK(esp_intr_alloc_intrstatus(TICKLESS_WAITI_LP_TIMER_INTR_SOURCE,
|
||||
ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED,
|
||||
TICKLESS_WAITI_LP_TIMER_INTR_STATUS,
|
||||
TICKLESS_WAITI_LP_TIMER_INTR_MASK,
|
||||
tickless_waiti_isr, NULL, NULL));
|
||||
#endif
|
||||
rtc_timer_ll_alarm_intr_enable(&LP_TIMER, 0, true);
|
||||
#endif
|
||||
}
|
||||
#endif /* CONFIG_PM_TICKLESS_IDLE_WAITI */
|
||||
|
||||
// Compute how long the CPU may stay idle: the smaller of the FreeRTOS expected idle window
|
||||
// and the time until the next esp_timer alarm.
|
||||
FORCE_INLINE_ATTR int64_t pm_get_min_idle_us(TickType_t xExpectedIdleTime)
|
||||
{
|
||||
int64_t now = esp_timer_get_time();
|
||||
int64_t next_esp_timer_alarm = esp_timer_get_next_alarm_for_wake_up();
|
||||
int64_t time_until_next_alarm = next_esp_timer_alarm - now;
|
||||
int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime;
|
||||
return MIN(wakeup_delay_us, time_until_next_alarm);
|
||||
}
|
||||
|
||||
// Adjust RTOS tick count based on the amount of time spent in sleep.
|
||||
FORCE_INLINE_ATTR void pm_step_tick(int64_t slept_us, TickType_t xExpectedIdleTime)
|
||||
{
|
||||
@@ -866,11 +1143,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
|
||||
int core_id = xPortGetCoreID();
|
||||
if (!should_skip_light_sleep(core_id)) {
|
||||
/* Calculate how much we can sleep */
|
||||
int64_t next_esp_timer_alarm = esp_timer_get_next_alarm_for_wake_up();
|
||||
int64_t now = esp_timer_get_time();
|
||||
int64_t time_until_next_alarm = next_esp_timer_alarm - now;
|
||||
int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime;
|
||||
int64_t sleep_time_us = MIN(wakeup_delay_us, time_until_next_alarm);
|
||||
int64_t sleep_time_us = pm_get_min_idle_us(xExpectedIdleTime);
|
||||
int64_t slept_us = 0;
|
||||
#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS
|
||||
uint32_t cycle = esp_cpu_get_cycle_count();
|
||||
@@ -878,7 +1151,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
|
||||
sleep_time_us -= (esp_cpu_get_cycle_count() - cycle) / (esp_clk_cpu_freq() / 1000000ULL);
|
||||
#endif
|
||||
if (sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL) {
|
||||
esp_sleep_enable_timer_wakeup(sleep_time_us - LIGHT_SLEEP_EARLY_WAKEUP_US);
|
||||
esp_sleep_enable_timer_wakeup(sleep_time_us - TICKLESS_IDLE_EARLY_WAKEUP_US);
|
||||
/* Enter sleep */
|
||||
ESP_PM_TRACE_ENTER(SLEEP, core_id);
|
||||
int64_t sleep_start = esp_timer_get_time();
|
||||
@@ -904,6 +1177,35 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
|
||||
esp_pm_execute_exit_sleep_callbacks(slept_us);
|
||||
#endif
|
||||
}
|
||||
#if CONFIG_PM_TICKLESS_IDLE_WAITI
|
||||
else {
|
||||
/* Light sleep was skipped. This covers both the case where light sleep is globally
|
||||
* disabled (DFS-only configuration, s_light_sleep_en == false) and the case where it is
|
||||
* enabled but currently forbidden (a power management lock keeps s_mode away from
|
||||
* PM_MODE_LIGHT_SLEEP, a mode switch is in progress, or a peripheral vetoed sleep). In all
|
||||
* of these we can still suppress the OS tick and enter WAITI to save power.
|
||||
*
|
||||
* WFI must run with interrupts enabled, which is not the case here (xKernelLock is held).
|
||||
* esp_pm_impl_tickless_waiti() enters WFI on the next idle-loop iteration.
|
||||
* On failure to tickless-park, esp_pm_impl_waiti() handles plain WFI. */
|
||||
int64_t sleep_time_us = pm_get_min_idle_us(xExpectedIdleTime);
|
||||
|
||||
if ((sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL)
|
||||
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
|
||||
/* core 0 owns the global tick; it must not suppress it while core 1 is still active. */
|
||||
&& (core_id != 0 || (s_waiti_parked_mask & BIT(1)))
|
||||
#endif
|
||||
) {
|
||||
s_waiti_plan[core_id].deadline_us = esp_timer_get_time() + sleep_time_us;
|
||||
s_waiti_plan[core_id].valid = true;
|
||||
/* Wake slightly early so that slow-clock inaccuracy biases towards an early
|
||||
* (rather than late) wakeup; xTaskCatchUpTicks() on core 0 recover the exact
|
||||
* tick count after WFI. */
|
||||
tickless_waiti_arm_alarm(core_id, sleep_time_us - TICKLESS_IDLE_EARLY_WAKEUP_US);
|
||||
tickless_waiti_suppress_sys_tick_intr(core_id);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PM_TICKLESS_IDLE_WAITI */
|
||||
portEXIT_CRITICAL(&s_switch_lock);
|
||||
}
|
||||
#endif //CONFIG_FREERTOS_USE_TICKLESS_IDLE
|
||||
@@ -1020,6 +1322,10 @@ void esp_pm_impl_init(void)
|
||||
s_cpu_freq_by_mode[i] = default_config;
|
||||
}
|
||||
|
||||
#if CONFIG_PM_TICKLESS_IDLE_WAITI
|
||||
tickless_waiti_init();
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_DFS_INIT_AUTO
|
||||
int xtal_freq_mhz = esp_clk_xtal_freq() / MHZ;
|
||||
esp_pm_config_t cfg = {
|
||||
|
||||
@@ -40,8 +40,16 @@ void esp_vApplicationTickHook(void)
|
||||
|
||||
void esp_vApplicationIdleHook(void)
|
||||
{
|
||||
bool can_go_idle = true;
|
||||
int core = xPortGetCoreID();
|
||||
|
||||
#if CONFIG_PM_TICKLESS_IDLE_WAITI
|
||||
/* Two-phase WAITI: plan is armed in vApplicationSleep(); execute it here (kernel lock free). */
|
||||
if (esp_pm_impl_tickless_waiti()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool can_go_idle = true;
|
||||
for (int n = 0; n < MAX_HOOKS; n++) {
|
||||
if (idle_cb[core][n] != NULL && !idle_cb[core][n]()) {
|
||||
can_go_idle = false;
|
||||
|
||||
@@ -331,6 +331,10 @@ menu "FreeRTOS"
|
||||
when no tasks need to run. To skip unnecessary wake-up initialize a timer with the
|
||||
"skip_unhandled_events" option as true.
|
||||
|
||||
While the OS tick interrupt is suppressed during tickless idle, tick hooks are not invoked
|
||||
for those suppressed ticks. This applies to esp_register_freertos_tick_hook() callbacks and
|
||||
to configUSE_TICK_HOOK.
|
||||
|
||||
If disabled, automatic light sleep support will be disabled.
|
||||
|
||||
config FREERTOS_IDLE_TIME_BEFORE_SLEEP
|
||||
|
||||
@@ -201,6 +201,24 @@
|
||||
|
||||
#endif /* ( !CONFIG_FREERTOS_SMP && ( configNUM_CORES > 1 ) ) */
|
||||
|
||||
#if CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER
|
||||
|
||||
/*
|
||||
* Reconcile the given core's OS-tick accounting against the free-running counter while its
|
||||
* OS-tick interrupt is still masked.
|
||||
*
|
||||
* Updates s_handled_systicks using the same accounting as SysTickIsrHandler(), so
|
||||
* re-enabling the alarm afterwards will not reprocess suppressed ticks. The caller should
|
||||
* restore the OS-tick interrupt and then advance the RTOS tick by the returned amount.
|
||||
*
|
||||
* @param cpu_id Core whose suppressed OS-tick accounting should be reconciled.
|
||||
*
|
||||
* @return Number of whole OS-tick periods that elapsed while masked.
|
||||
*/
|
||||
uint32_t xPortSysTickClaimElapsedTicks( int cpu_id );
|
||||
|
||||
#endif /* CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER */
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* TASK UTILITIES (PRIVATE)
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -50,6 +50,9 @@ void SysTickIsrHandler(void *arg);
|
||||
|
||||
static uint32_t s_handled_systicks[configNUM_CORES] = { 0 };
|
||||
|
||||
/* Systimer HAL layer object */
|
||||
static systimer_hal_context_t systimer_hal;
|
||||
|
||||
/**
|
||||
* @brief Set up the systimer peripheral to generate the tick interrupt
|
||||
*
|
||||
@@ -65,8 +68,6 @@ void vSystimerSetup(void)
|
||||
#else
|
||||
const unsigned level = ESP_INTR_FLAG_LEVEL1;
|
||||
#endif
|
||||
/* Systimer HAL layer object */
|
||||
static systimer_hal_context_t systimer_hal;
|
||||
/* set system timer interrupt vector */
|
||||
|
||||
ESP_ERROR_CHECK(esp_intr_alloc(ETS_SYSTIMER_TARGET0_INTR_SOURCE + cpuid, ESP_INTR_FLAG_IRAM | level, SysTickIsrHandler, &systimer_hal, NULL));
|
||||
@@ -155,6 +156,18 @@ void SysTickIsrHandler(void *arg)
|
||||
ESP_PM_TRACE_EXIT(TICK, cpuid);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t xPortSysTickClaimElapsedTicks(int cpu_id)
|
||||
{
|
||||
uint32_t alarm_id = SYSTIMER_ALARM_OS_TICK_CORE0 + (uint32_t)cpu_id;
|
||||
uint32_t total = systimer_hal_get_counter_value(&systimer_hal, SYSTIMER_COUNTER_OS_TICK) / systimer_ll_get_alarm_period(systimer_hal.dev, alarm_id);
|
||||
uint32_t elapsed = total - s_handled_systicks[cpu_id];
|
||||
if (elapsed == 0) {
|
||||
return 0;
|
||||
}
|
||||
s_handled_systicks[cpu_id] = total;
|
||||
return elapsed;
|
||||
}
|
||||
#endif /* CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER */
|
||||
|
||||
/* ------------------------------------------------ Common Port Tick ---------------------------------------------------
|
||||
|
||||
@@ -17,8 +17,8 @@ extern "C"
|
||||
typedef enum {
|
||||
ETS_LP_RTC_INTR_SOURCE = 0,
|
||||
ETS_LP_WDT_INTR_SOURCE,
|
||||
ETS_LP_TIMER_REG0_INTR_SOURCE,
|
||||
ETS_LP_TIMER_REG1_INTR_SOURCE,
|
||||
ETS_LP_TIMER_REG_0_INTR_SOURCE,
|
||||
ETS_LP_TIMER_REG_1_INTR_SOURCE,
|
||||
ETS_MB_HP_INTR_SOURCE,
|
||||
ETS_MB_LP_INTR_SOURCE,
|
||||
ETS_PMU_0_INTR_SOURCE,
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
const char *const esp_isr_names[] = {
|
||||
[ETS_LP_RTC_INTR_SOURCE] = "LP_RTC",
|
||||
[ETS_LP_WDT_INTR_SOURCE] = "LP_WDT",
|
||||
[ETS_LP_TIMER_REG0_INTR_SOURCE] = "LP_TIMER_REG0",
|
||||
[ETS_LP_TIMER_REG1_INTR_SOURCE] = "LP_TIMER_REG1",
|
||||
[ETS_LP_TIMER_REG_0_INTR_SOURCE] = "LP_TIMER_REG0",
|
||||
[ETS_LP_TIMER_REG_1_INTR_SOURCE] = "LP_TIMER_REG1",
|
||||
[ETS_MB_HP_INTR_SOURCE] = "MB_HP",
|
||||
[ETS_MB_LP_INTR_SOURCE] = "MB_LP",
|
||||
[ETS_PMU_0_INTR_SOURCE] = "PMU_0",
|
||||
|
||||
Reference in New Issue
Block a user