Merge branch 'feature/freertos_tickless_idle' into 'master'

freertos,esp32: automatic light sleep support

See merge request idf/esp-idf!2247
This commit is contained in:
Angus Gratton 2018-05-21 10:59:00 +08:00
commit 6363396eba
13 changed files with 426 additions and 14 deletions

View file

@ -59,6 +59,10 @@ void esp_vApplicationIdleHook()
#ifdef CONFIG_PM_ENABLE
esp_pm_impl_idle_hook();
#endif
}
extern void esp_vApplicationWaitiHook( void )
{
asm("waiti 0");
}

View file

@ -22,10 +22,12 @@
#include "esp_pm.h"
#include "esp_log.h"
#include "esp_crosscore_int.h"
#include "esp_clk.h"
#include "soc/rtc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/xtensa_timer.h"
#include "xtensa/core-macros.h"
@ -39,6 +41,16 @@
*/
#define CCOMPARE_UPDATE_TIMEOUT 1000000
/* When changing CCOMPARE, don't allow changes if the difference is less
* than this. This is to prevent setting CCOMPARE below CCOUNT.
*/
#define CCOMPARE_MIN_CYCLES_IN_FUTURE 1000
/* When light sleep is used, wake this number of microseconds earlier than
* the next tick.
*/
#define LIGHT_SLEEP_EARLY_WAKEUP_US 100
#ifdef CONFIG_PM_PROFILING
#define WITH_PROFILING
#endif
@ -107,7 +119,7 @@ static const char* s_freq_names[] __attribute__((unused)) = {
[RTC_CPU_FREQ_2M] = "2"
};
/* Whether automatic light sleep is enabled. Currently always false */
/* Whether automatic light sleep is enabled */
static bool s_light_sleep_en = false;
/* When configuration is changed, current frequency may not match the
@ -177,9 +189,11 @@ esp_err_t esp_pm_configure(const void* vconfig)
#endif
const esp_pm_config_esp32_t* config = (const esp_pm_config_esp32_t*) vconfig;
#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE
if (config->light_sleep_enable) {
return ESP_ERR_NOT_SUPPORTED;
}
#endif
if (config->min_cpu_freq == RTC_CPU_FREQ_2M) {
/* Minimal APB frequency to achieve 1MHz REF_TICK frequency is 5 MHz */
@ -401,10 +415,9 @@ static void IRAM_ATTR do_switch(pm_mode_t new_mode)
*/
static void IRAM_ATTR update_ccompare()
{
const uint32_t ccompare_min_cycles_in_future = 1000;
uint32_t ccount = XTHAL_GET_CCOUNT();
uint32_t ccompare = XTHAL_GET_CCOMPARE(XT_TIMER_INDEX);
if ((ccompare - ccompare_min_cycles_in_future) - ccount < UINT32_MAX / 2) {
if ((ccompare - CCOMPARE_MIN_CYCLES_IN_FUTURE) - ccount < UINT32_MAX / 2) {
uint32_t diff = ccompare - ccount;
uint32_t diff_scaled = (diff * s_ccount_mul + s_ccount_div - 1) / s_ccount_div;
if (diff_scaled < _xt_tick_divisor) {
@ -453,6 +466,56 @@ void IRAM_ATTR esp_pm_impl_isr_hook()
ESP_PM_TRACE_EXIT(ISR_HOOK, core_id);
}
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
bool IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime )
{
bool result = false;
portENTER_CRITICAL(&s_switch_lock);
if (s_mode == PM_MODE_LIGHT_SLEEP && !s_is_switching) {
/* Calculate how much we can sleep */
int64_t next_esp_timer_alarm = esp_timer_get_next_alarm();
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);
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);
#ifdef CONFIG_PM_TRACE
/* to force tracing GPIOs to keep state */
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
/* Enter sleep */
int core_id = xPortGetCoreID();
ESP_PM_TRACE_ENTER(SLEEP, core_id);
int64_t sleep_start = esp_timer_get_time();
esp_light_sleep_start();
int64_t slept_us = esp_timer_get_time() - sleep_start;
ESP_PM_TRACE_EXIT(SLEEP, core_id);
uint32_t slept_ticks = slept_us / (portTICK_PERIOD_MS * 1000LL);
if (slept_ticks > 0) {
/* Adjust RTOS tick count based on the amount of time spent in sleep */
vTaskStepTick(slept_ticks);
/* Trigger tick interrupt, since sleep time was longer
* than portTICK_PERIOD_MS. Note that setting INTSET does not
* work for timer interrupt, and changing CCOMPARE would clear
* the interrupt flag.
*/
XTHAL_SET_CCOUNT(XTHAL_GET_CCOMPARE(XT_TIMER_INDEX) - 16);
while (!(XTHAL_GET_INTERRUPT() & BIT(XT_TIMER_INTNUM))) {
;
}
}
result = true;
}
}
portEXIT_CRITICAL(&s_switch_lock);
return result;
}
#endif //CONFIG_FREERTOS_USE_TICKLESS_IDLE
#ifdef WITH_PROFILING
void esp_pm_impl_dump_stats(FILE* out)
{

View file

@ -27,6 +27,7 @@ static const int DRAM_ATTR s_trace_io[] = {
BIT(18), BIT(18), // ESP_PM_TRACE_FREQ_SWITCH
BIT(19), BIT(19), // ESP_PM_TRACE_CCOMPARE_UPDATE
BIT(25), BIT(26), // ESP_PM_TRACE_ISR_HOOK
BIT(27), BIT(27), // ESP_PM_TRACE_SLEEP
};
void esp_pm_trace_init()

View file

@ -22,6 +22,7 @@ typedef enum {
ESP_PM_TRACE_FREQ_SWITCH,
ESP_PM_TRACE_CCOMPARE_UPDATE,
ESP_PM_TRACE_ISR_HOOK,
ESP_PM_TRACE_SLEEP,
ESP_PM_TRACE_TYPE_MAX
} esp_pm_trace_event_t;

View file

@ -7,7 +7,14 @@
#include "esp_clk.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "driver/timer.h"
#include "driver/rtc_io.h"
#include "esp32/ulp.h"
#include "soc/rtc_io_reg.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_gpio_channel.h"
TEST_CASE("Can dump power management lock stats", "[pm]")
{
@ -48,4 +55,256 @@ TEST_CASE("Can switch frequency using esp_pm_configure", "[pm]")
switch_freq(orig_freq_mhz);
}
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
static void light_sleep_enable()
{
const esp_pm_config_esp32_t pm_config = {
.max_cpu_freq = rtc_clk_cpu_freq_get(),
.min_cpu_freq = RTC_CPU_FREQ_XTAL,
.light_sleep_enable = true
};
ESP_ERROR_CHECK( esp_pm_configure(&pm_config) );
}
static void light_sleep_disable()
{
const esp_pm_config_esp32_t pm_config = {
.max_cpu_freq = rtc_clk_cpu_freq_get(),
.min_cpu_freq = rtc_clk_cpu_freq_get(),
};
ESP_ERROR_CHECK( esp_pm_configure(&pm_config) );
}
TEST_CASE("Automatic light occurs when tasks are suspended", "[pm]")
{
/* To figure out if light sleep takes place, use Timer Group timer.
* It will stop working while in light sleep.
*/
timer_config_t config = {
.counter_dir = TIMER_COUNT_UP,
.divider = 80 /* 1 us per tick */
};
timer_init(TIMER_GROUP_0, TIMER_0, &config);
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
timer_start(TIMER_GROUP_0, TIMER_0);
light_sleep_enable();
for (int ticks_to_delay = CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP;
ticks_to_delay < CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP * 10;
++ticks_to_delay) {
/* Wait until next tick */
vTaskDelay(1);
/* The following delay should cause light sleep to start */
uint64_t count_start;
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &count_start);
vTaskDelay(ticks_to_delay);
uint64_t count_end;
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &count_end);
int timer_diff_us = (int) (count_end - count_start);
const int us_per_tick = 1 * portTICK_PERIOD_MS * 1000;
printf("%d %d\n", ticks_to_delay * us_per_tick, timer_diff_us);
TEST_ASSERT(timer_diff_us < ticks_to_delay * us_per_tick);
}
light_sleep_disable();
}
TEST_CASE("Can wake up from automatic light sleep by GPIO", "[pm]")
{
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 16 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
/* Set up GPIO used to wake up RTC */
const int ext1_wakeup_gpio = 25;
const int ext_rtc_io = RTCIO_GPIO25_CHANNEL;
TEST_ESP_OK(rtc_gpio_init(ext1_wakeup_gpio));
rtc_gpio_set_direction(ext1_wakeup_gpio, RTC_GPIO_MODE_INPUT_OUTPUT);
rtc_gpio_set_level(ext1_wakeup_gpio, 0);
/* Enable wakeup */
TEST_ESP_OK(esp_sleep_enable_ext1_wakeup(1ULL << ext1_wakeup_gpio, ESP_EXT1_WAKEUP_ANY_HIGH));
/* To simplify test environment, we'll use a ULP program to set GPIO high */
ulp_insn_t ulp_code[] = {
I_DELAY(65535), /* about 8ms, given 8MHz ULP clock */
I_WR_REG_BIT(RTC_CNTL_HOLD_FORCE_REG, RTC_CNTL_PDAC1_HOLD_FORCE_S, 0),
I_WR_REG_BIT(RTC_GPIO_OUT_REG, ext_rtc_io + RTC_GPIO_OUT_DATA_S, 1),
I_DELAY(1000),
I_WR_REG_BIT(RTC_GPIO_OUT_REG, ext_rtc_io + RTC_GPIO_OUT_DATA_S, 0),
I_WR_REG_BIT(RTC_CNTL_HOLD_FORCE_REG, RTC_CNTL_PDAC1_HOLD_FORCE_S, 1),
I_END(),
I_HALT()
};
TEST_ESP_OK(ulp_set_wakeup_period(0, 1000 /* us */));
size_t size = sizeof(ulp_code)/sizeof(ulp_insn_t);
TEST_ESP_OK(ulp_process_macros_and_load(0, ulp_code, &size));
light_sleep_enable();
for (int i = 0; i < 10; ++i) {
/* Set GPIO low */
REG_CLR_BIT(rtc_gpio_desc[ext1_wakeup_gpio].reg, rtc_gpio_desc[ext1_wakeup_gpio].hold_force);
rtc_gpio_set_level(ext1_wakeup_gpio, 0);
REG_SET_BIT(rtc_gpio_desc[ext1_wakeup_gpio].reg, rtc_gpio_desc[ext1_wakeup_gpio].hold_force);
/* Wait for the next tick */
vTaskDelay(1);
/* Start ULP program */
ulp_run(0);
const int delay_ms = 200;
const int delay_ticks = delay_ms / portTICK_PERIOD_MS;
int64_t start_rtc = esp_clk_rtc_time();
int64_t start_hs = esp_timer_get_time();
uint32_t start_tick = xTaskGetTickCount();
/* Will enter sleep here */
vTaskDelay(delay_ticks);
int64_t end_rtc = esp_clk_rtc_time();
int64_t end_hs = esp_timer_get_time();
uint32_t end_tick = xTaskGetTickCount();
printf("%lld %lld %u\n", end_rtc - start_rtc, end_hs - start_hs, end_tick - start_tick);
TEST_ASSERT_INT32_WITHIN(3, delay_ticks, end_tick - start_tick);
TEST_ASSERT_INT32_WITHIN(2 * portTICK_PERIOD_MS * 1000, delay_ms * 1000, end_hs - start_hs);
TEST_ASSERT_INT32_WITHIN(2 * portTICK_PERIOD_MS * 1000, delay_ms * 1000, end_rtc - start_rtc);
}
REG_CLR_BIT(rtc_gpio_desc[ext1_wakeup_gpio].reg, rtc_gpio_desc[ext1_wakeup_gpio].hold_force);
rtc_gpio_deinit(ext1_wakeup_gpio);
light_sleep_disable();
}
typedef struct {
int delay_us;
int result;
SemaphoreHandle_t done;
} delay_test_arg_t;
static void test_delay_task(void* p)
{
delay_test_arg_t* arg = (delay_test_arg_t*) p;
vTaskDelay(1);
uint64_t start = esp_clk_rtc_time();
vTaskDelay(arg->delay_us / portTICK_PERIOD_MS / 1000);
uint64_t stop = esp_clk_rtc_time();
arg->result = (int) (stop - start);
xSemaphoreGive(arg->done);
vTaskDelete(NULL);
}
TEST_CASE("vTaskDelay duration is correct with light sleep enabled", "[pm]")
{
light_sleep_enable();
delay_test_arg_t args = {
.done = xSemaphoreCreateBinary()
};
const int delays[] = { 10, 20, 50, 100, 150, 200, 250 };
const int delays_count = sizeof(delays) / sizeof(delays[0]);
for (int i = 0; i < delays_count; ++i) {
int delay_ms = delays[i];
args.delay_us = delay_ms * 1000;
xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 0);
TEST_ASSERT( xSemaphoreTake(args.done, delay_ms * 10 / portTICK_PERIOD_MS) );
printf("CPU0: %d %d\n", args.delay_us, args.result);
TEST_ASSERT_INT32_WITHIN(1000 * portTICK_PERIOD_MS * 2, args.delay_us, args.result);
#if portNUM_PROCESSORS == 2
xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 1);
TEST_ASSERT( xSemaphoreTake(args.done, delay_ms * 10 / portTICK_PERIOD_MS) );
printf("CPU1: %d %d\n", args.delay_us, args.result);
TEST_ASSERT_INT32_WITHIN(1000 * portTICK_PERIOD_MS * 2, args.delay_us, args.result);
#endif
}
vSemaphoreDelete(args.done);
light_sleep_disable();
}
/* This test is similar to the one in test_esp_timer.c, but since we can't use
* ref_clock, this test uses RTC clock for timing. Also enables automatic
* light sleep.
*/
TEST_CASE("esp_timer produces correct delays with light sleep", "[pm]")
{
// no, we can't make this a const size_t (§6.7.5.2)
#define NUM_INTERVALS 16
typedef struct {
esp_timer_handle_t timer;
size_t cur_interval;
int intervals[NUM_INTERVALS];
int64_t t_start;
SemaphoreHandle_t done;
} test_args_t;
void timer_func(void* arg)
{
test_args_t* p_args = (test_args_t*) arg;
int64_t t_end = esp_clk_rtc_time();
int32_t ms_diff = (t_end - p_args->t_start) / 1000;
printf("timer #%d %dms\n", p_args->cur_interval, ms_diff);
p_args->intervals[p_args->cur_interval++] = ms_diff;
// Deliberately make timer handler run longer.
// We check that this doesn't affect the result.
ets_delay_us(10*1000);
if (p_args->cur_interval == NUM_INTERVALS) {
printf("done\n");
TEST_ESP_OK(esp_timer_stop(p_args->timer));
xSemaphoreGive(p_args->done);
}
}
light_sleep_enable();
const int delay_ms = 100;
test_args_t args = {0};
esp_timer_handle_t timer1;
esp_timer_create_args_t create_args = {
.callback = &timer_func,
.arg = &args,
.name = "timer1",
};
TEST_ESP_OK(esp_timer_create(&create_args, &timer1));
args.timer = timer1;
args.t_start = esp_clk_rtc_time();
args.done = xSemaphoreCreateBinary();
TEST_ESP_OK(esp_timer_start_periodic(timer1, delay_ms * 1000));
TEST_ASSERT(xSemaphoreTake(args.done, delay_ms * NUM_INTERVALS * 2));
TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, args.cur_interval);
for (size_t i = 0; i < NUM_INTERVALS; ++i) {
TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * delay_ms, args.intervals[i]);
}
TEST_ESP_OK( esp_timer_dump(stdout) );
TEST_ESP_OK( esp_timer_delete(timer1) );
vSemaphoreDelete(args.done);
light_sleep_disable();
#undef NUM_INTERVALS
}
#endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE
#endif // CONFIG_PM_ENABLE

View file

@ -357,6 +357,31 @@ config FREERTOS_RUN_TIME_STATS_USING_CPU_CLK
endchoice
config FREERTOS_USE_TICKLESS_IDLE
bool "Tickless idle support"
depends on PM_ENABLE
default n
help
If power management support is enabled, FreeRTOS will be able to put
the system into light sleep mode when no tasks need to run for a number
of ticks. This number can be set using FREERTOS_IDLE_TIME_BEFORE_SLEEP option.
This feature is also known as "automatic light sleep".
Note that timers created using esp_timer APIs may prevent the system from
entering sleep mode, even when no tasks need to run.
If disabled, automatic light sleep support will be disabled.
config FREERTOS_IDLE_TIME_BEFORE_SLEEP
int "Minimum number of ticks to enter sleep mode for"
depends on FREERTOS_USE_TICKLESS_IDLE
default 3
range 2 4294967295
# Minimal value is 2 because of a check in FreeRTOS.h (search configEXPECTED_IDLE_TIME_BEFORE_SLEEP)
help
FreeRTOS will enter light sleep mode if no tasks need to run for this number
of ticks.
menuconfig FREERTOS_DEBUG_INTERNALS
bool "Debug FreeRTOS internals"
default n

View file

@ -289,6 +289,10 @@ extern void vPortCleanUpTCB ( void *pxTCB );
#define INCLUDE_eTaskGetState 1
#define configUSE_QUEUE_SETS 1
#define configUSE_TICKLESS_IDLE CONFIG_FREERTOS_USE_TICKLESS_IDLE
#if configUSE_TICKLESS_IDLE
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP
#endif //configUSE_TICKLESS_IDLE
#define configXT_BOARD 1 /* Board mode */
#define configXT_SIMULATOR 0

View file

@ -365,9 +365,13 @@ typedef struct {
#define PRIVILEGED_DATA
#endif
extern void esp_vApplicationIdleHook( void );
extern void esp_vApplicationWaitiHook( void );
void _xt_coproc_release(volatile void * coproc_sa_base);
bool vApplicationSleep( TickType_t xExpectedIdleTime );
#define portSUPPRESS_TICKS_AND_SLEEP( idleTime ) vApplicationSleep( idleTime )
// porttrace
#if configUSE_TRACE_FACILITY_2

View file

@ -2151,6 +2151,23 @@ void vTaskSuspendAll( void )
#if ( configUSE_TICKLESS_IDLE != 0 )
static BaseType_t xHaveReadyTasks()
{
for (int i = tskIDLE_PRIORITY + 1; i < configMAX_PRIORITIES; ++i)
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ i ] ) ) > 0 )
{
return pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
return pdFALSE;
}
static TickType_t prvGetExpectedIdleTime( void )
{
TickType_t xReturn;
@ -2161,7 +2178,18 @@ void vTaskSuspendAll( void )
{
xReturn = 0;
}
else if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > 1 )
#if portNUM_PROCESSORS > 1
/* This function is called from Idle task; in single core case this
* means that no higher priority tasks are ready to run, and we can
* enter sleep. In SMP case, there might be ready tasks waiting for
* the other CPU, so need to check all ready lists.
*/
else if( xHaveReadyTasks() )
{
xReturn = 0;
}
#endif // portNUM_PROCESSORS > 1
else if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > portNUM_PROCESSORS )
{
/* There are other idle priority tasks in the ready state. If
time slicing is used then the very next tick interrupt must be
@ -3405,7 +3433,6 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters )
#endif /* configUSE_IDLE_HOOK */
{
/* Call the esp-idf hook system */
extern void esp_vApplicationIdleHook( void );
esp_vApplicationIdleHook();
}
@ -3417,6 +3444,7 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters )
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime;
BaseType_t xEnteredSleep = pdFALSE;
/* It is not desirable to suspend then resume the scheduler on
each iteration of the idle task. Therefore, a preliminary
@ -3427,7 +3455,6 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters )
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
// vTaskSuspendAll();
taskENTER_CRITICAL(&xTaskQueueMutex);
{
/* Now the scheduler is suspended, the expected idle
@ -3439,7 +3466,7 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters )
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
xEnteredSleep = portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();
}
else
@ -3448,13 +3475,21 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters )
}
}
taskEXIT_CRITICAL(&xTaskQueueMutex);
// ( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* It might be possible to enter tickless idle again, so skip
* the fallback sleep hook if tickless idle was successful
*/
if ( !xEnteredSleep )
{
esp_vApplicationWaitiHook();
}
}
#else
esp_vApplicationWaitiHook();
#endif /* configUSE_TICKLESS_IDLE */
}
}

View file

@ -20,11 +20,15 @@ Power management can be enabled at compile time, using :ref:`CONFIG_PM_ENABLE` o
Enabling power management features comes at the cost of increased interrupt latency. Extra latency depends on a number of factors, among which are CPU frequency, single/dual core mode, whether frequency switch needs to be performed or not. Minimal extra latency is 0.2us (when CPU frequency is 240MHz, and frequency scaling is not enabled), maximum extra latency is 40us (when frequency scaling is enabled, and a switch from 40MHz to 80MHz is performed on interrupt entry).
Dynamic frequency scaling (DFS) can be enabled in the application by calling :cpp:func:`esp_pm_configure` function. Its argument is a structure defining frequency scaling settings (for ESP32, minimum and maximum CPU frequencies). Alternatively, :ref:`CONFIG_PM_DFS_INIT_AUTO` option can be enabled in menuconfig. If enabled, maximal CPU frequency is determined by :ref:`CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ` setting, and minimal CPU frequency is set to the XTAL frequency.
Dynamic frequency scaling (DFS) and automatic light sleep can be enabled in the application by calling :cpp:func:`esp_pm_configure` function. Its argument is a structure defining frequency scaling settings (for ESP32, minimum and maximum CPU frequencies). Alternatively, :ref:`CONFIG_PM_DFS_INIT_AUTO` option can be enabled in menuconfig. If enabled, maximal CPU frequency is determined by :ref:`CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ` setting, and minimal CPU frequency is set to the XTAL frequency.
.. note::
:cpp:func:`esp_pm_configure` function also has provisions for enabling automatic light sleep mode. However this feature is not fully supported yet, so `esp_pm_configure` will return an `ESP_ERR_NOT_SUPPORTED` if automatic light sleep is requested.
Automatic light sleep is based on FreeRTOS Tickless Idle functionality. :cpp:func:`esp_pm_configure` will return an `ESP_ERR_NOT_SUPPORTED` error if :ref:`CONFIG_FREERTOS_USE_TICKLESS_IDLE` option is not enabled in menuconfig, but automatic light sleep is requested.
.. note::
In light sleep, peripherals are clock gated, and interrupts (from GPIOs and internal peripherals) will not be generated. Wakeup source described in :doc:`Sleep Modes <sleep_modes>` documentation can be used to wake from light sleep state. For example, EXT0 and EXT1 wakeup source can be used to wake up from a GPIO.
Power Management Locks
----------------------
@ -42,7 +46,7 @@ In ESP32, three types of locks are supported:
Requests APB frequency to be at the maximal supported value. For ESP32, this is 80 MHz.
``ESP_PM_NO_LIGHT_SLEEP``
Prevents automatic light sleep from being used. Note: currently taking this lock has no effect, as automatic light sleep is never used.
Prevents automatic light sleep from being used.
Power Management Algorithm for the ESP32
@ -70,6 +74,13 @@ When dynamic frequency scaling is enabled, CPU frequency will be switched as fol
2. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure` (usually, XTAL).
- When none of the locks are aquired, and light sleep is enabled in a call to :cpp:func:`esp_pm_configure`, the system will go into light sleep mode. The duration of light sleep will be determined by:
- FreeRTOS tasks blocked with finite timeouts
- Timers registered with :doc:`High resolution timer <esp_timer>` APIs
Light sleep will duration will be chosen to wake up before the nearest event (task being unblocked, or timer elapses).
Dynamic Frequency Scaling and Peripheral Drivers
------------------------------------------------

View file

@ -19,9 +19,9 @@ Once wakeup sources are configured, application can enter sleep mode using :cpp:
WiFi/BT and sleep modes
-----------------------
In deep sleep mode, wireless peripherals are powered down. Before entering sleep mode, applications must disable WiFi and BT using appropriate calls ( :cpp:func:`esp_bluedroid_disable`, :cpp:func:`esp_bt_controller_disable`, :cpp:func:`esp_wifi_stop`).
In deep sleep and light sleep modes, wireless peripherals are powered down. Before entering deep sleep or light sleep modes, applications must disable WiFi and BT using appropriate calls (:cpp:func:`esp_bluedroid_disable`, :cpp:func:`esp_bt_controller_disable`, :cpp:func:`esp_wifi_stop`). WiFi and BT connections will not be maintained in deep sleep or light sleep, even if these functions are not called.
WiFi can coexist with light sleep mode, allowing the chip to go into light sleep mode when there is no network activity, and waking up the chip from light sleep mode when required. However **APIs described in this section can not be used for that purpose**. :cpp:func:`esp_light_sleep_start` forces the chip to enter light sleep mode, regardless of whether WiFi is active or not. Automatic entry into light sleep mode, coordinated with WiFi driver, will be supported using a separate set of APIs.
If WiFi connection needs to be maintained, enable WiFi modem sleep, and enable automatic light sleep feature (see :doc:`Power Management APIs <power_management>`). This will allow the system to wake up from sleep automatically when required by WiFi driver, thereby maintaining connection to the AP.
Wakeup sources
--------------

View file

@ -102,7 +102,10 @@ void app_main()
rtc_clk_cpu_freq_from_mhz(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &max_freq);
esp_pm_config_esp32_t pm_config = {
.max_cpu_freq = max_freq,
.min_cpu_freq = RTC_CPU_FREQ_XTAL
.min_cpu_freq = RTC_CPU_FREQ_XTAL,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
.light_sleep_enable = true
#endif
};
ESP_ERROR_CHECK( esp_pm_configure(&pm_config) );
#endif // CONFIG_PM_ENABLE

View file

@ -2,5 +2,7 @@
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=y
# Enable support for power management
CONFIG_PM_ENABLE=y
# Enable tickless idle mode
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
# Use RTC timer as reference
CONFIG_PM_USE_RTC_TIMER_REF=y