Merge branch 'feat/support_configurable_behavior_for_sleep_console' into 'master'

feat(esp_hw_support): Support configurable console uart behavior before sleep

Closes PM-396

See merge request espressif/esp-idf!38409
This commit is contained in:
Wu Zheng Hui
2025-12-25 12:31:35 +08:00
9 changed files with 467 additions and 139 deletions
+1
View File
@@ -59,6 +59,7 @@ if(NOT non_os_build)
if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED)
list(APPEND srcs "sleep_modem.c"
"sleep_modes.c"
"sleep_uart.c"
"sleep_console.c"
"sleep_mspi.c"
"sleep_usb.c"
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_sleep.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Prepare all enabled UARTs for sleep based on their configured handling modes
*
* This function iterates through all UART ports and applies the appropriate handling
* strategy (flush, suspend, or discard) based on each UART's configured mode and
* the sleep parameters. Only enabled UARTs are processed.
*
* @param sleep_flags Sleep configuration flags (e.g., PMU_SLEEP_PD_TOP to indicate TOP domain power down)
* @param deep_sleep true if entering deep sleep, false for light sleep
*/
void sleep_uart_prepare(uint32_t sleep_flags, bool deep_sleep);
/**
* @brief Resume UARTs that were suspended during sleep preparation
*
* This function restores transmission for UARTs that were suspended (via XOFF)
* during sleep preparation. Only UARTs that were actually suspended are resumed
* (tracked via internal bitmap). UARTs that were flushed or discarded are not affected.
*/
void sleep_uart_resume(void);
/**
* @brief Set the UART handling mode for a specific UART port
*
* This function configures how a specific UART port should be handled during
* sleep entry. The configured mode will be used (and possibly resolved if set
* to ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART) when sleep_uart_prepare() is called.
*
* @param uart_num UART port number (0 to SOC_UART_HP_NUM-1)
* @param handling_mode Handling mode to configure (see esp_sleep_uart_handling_mode_t)
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if uart_num is out of range or handling_mode is invalid
*/
esp_err_t sleep_uart_set_handling_mode(int uart_num, esp_sleep_uart_handling_mode_t handling_mode);
#ifdef __cplusplus
}
#endif
@@ -139,6 +139,21 @@ enum {
ESP_ERR_SLEEP_TOO_SHORT_SLEEP_DURATION = ESP_ERR_INVALID_ARG,
};
/**
* @brief UART handling mode before entering sleep
*
* These modes define how UARTs are handled when the chip enters sleep mode.
* The behavior affects data integrity, power consumption, and sleep entry time.
*/
typedef enum {
ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART, //!< Automatically select flush or suspend based on sleep type and power domain configuration. For deep sleep, always flush. For light sleep, suspend if UART remains powered, flush/discard if UART power domain is powered down.
ESP_SLEEP_ALWAYS_FLUSH_UART, //!< Always wait for all data in TX FIFO to be transmitted before sleep. Ensures data integrity but increases power consumption and sleep entry time.
ESP_SLEEP_ALWAYS_SUSPEND_UART, //!< Suspend UART transmission after current frame completes. If UART remains powered during sleep, transmission resumes after wake. If UART power domain is powered down, unsent data will be lost.
ESP_SLEEP_ALWAYS_DISCARD_UART, //!< Discard all data in TX/RX FIFOs and enter sleep immediately. Fastest sleep entry and lowest power, but all unsent data is lost.
ESP_SLEEP_NO_HANDLING, //!< Do not perform any UART handling before sleep. UART state is not modified.
} esp_sleep_uart_handling_mode_t;
/**
* @brief Disable wakeup source
*
@@ -779,6 +794,43 @@ void esp_default_wake_deep_sleep(void);
*/
void esp_deep_sleep_disable_rom_logging(void);
/**
* @brief Configure how the console UART is handled when entering sleep
*
* This function configures the handling behavior for the console UART (CONFIG_ESP_CONSOLE_UART_NUM)
* during sleep modes. The console UART is typically used for debug output, so its handling mode
* affects whether debug messages are preserved or discarded before sleep.
*
* @param handling_mode Handling method, one of the following strategies:
* - ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART (default):
* Automatically selects the appropriate strategy based on sleep type and power domain:
* - Deep sleep: Always flush to avoid data loss
* - Light sleep: Suspend if UART remains powered, flush if UART power domain is powered down
* - ESP_SLEEP_ALWAYS_FLUSH_UART:
* Wait for all data in TX FIFO to be fully transmitted before entering sleep.
* Ensures all debug output is visible but increases sleep entry time and power consumption.
* - ESP_SLEEP_ALWAYS_SUSPEND_UART:
* Wait for current UART frame to complete, then suspend the UART state machine.
* If UART remains powered during light sleep, transmission resumes after wake.
* If UART power domain is powered down, unsent data will be lost.
* - ESP_SLEEP_ALWAYS_DISCARD_UART:
* Discard all unsent data in UART FIFO and enter sleep immediately.
* Fastest sleep entry and lowest power, but all unsent debug output is lost.
* - ESP_SLEEP_NO_HANDLING:
* Do not perform any handling on the console UART before sleep.
* Can be used to disable the default UART handling behavior.
*
* @note When CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is enabled, the console UART
* will always be flushed regardless of the configured mode to ensure debug
* output is visible even when cache is disabled.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if handling_mode is not valid
* - ESP_ERR_NOT_SUPPORTED if no console UART is configured (CONFIG_ESP_CONSOLE_UART_NUM == -1)
*/
esp_err_t esp_sleep_set_console_uart_handling_mode(esp_sleep_uart_handling_mode_t handling_mode);
#if CONFIG_IDF_TARGET_ESP32
/**
* @brief If analog-related peripherals(ADC, TOUCH) is not used in monitor mode, analog low power mode
+5 -112
View File
@@ -78,6 +78,7 @@
#include "esp_private/cache_utils.h"
#include "esp_private/brownout.h"
#include "esp_private/sleep_console.h"
#include "esp_private/sleep_uart.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/sleep_modem.h"
#include "esp_private/sleep_flash.h"
@@ -211,17 +212,6 @@
// Actually costs 80us, using the fastest slow clock 150K calculation takes about 16 ticks
#define SLEEP_TIMER_ALARM_TO_SLEEP_TICKS (16)
#define SLEEP_UART_FLUSH_DONE_TO_SLEEP_US (450)
#if SOC_PM_SUPPORT_TOP_PD
// IDF console uses 8 bits data mode without parity, so each char occupy 8(data)+1(start)+1(stop)=10bits
#define UART_FLUSH_US_PER_CHAR (10*1000*1000 / CONFIG_ESP_CONSOLE_UART_BAUDRATE)
#define CONCATENATE_HELPER(x, y) (x##y)
#define CONCATENATE(x, y) CONCATENATE_HELPER(x, y)
#define CONSOLE_UART_DEV (&CONCATENATE(UART, CONFIG_ESP_CONSOLE_UART_NUM))
#endif
#define LIGHT_SLEEP_TIME_OVERHEAD_US DEFAULT_HARDWARE_OUT_OVERHEAD_US
#ifdef CONFIG_ESP_SYSTEM_RTC_EXT_XTAL
#define DEEP_SLEEP_TIME_OVERHEAD_US (650 + 100 * 240 / CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ)
@@ -239,9 +229,9 @@
(source == value))
#if CONFIG_PM_SLP_IRAM_OPT
# define SLEEP_FN_ATTR FORCE_IRAM_ATTR
#define SLEEP_FN_ATTR FORCE_IRAM_ATTR
#else
# define SLEEP_FN_ATTR
#define SLEEP_FN_ATTR
#endif
#define MAX_DSLP_HOOKS 3
@@ -623,99 +613,6 @@ static SLEEP_FN_ATTR void resume_timers(uint32_t sleep_flags) {
}
}
// [refactor-todo] provide target logic for body of uart functions below
static SLEEP_FN_ATTR void flush_uarts(void)
{
for (int i = 0; i < SOC_UART_HP_NUM; ++i) {
if (uart_ll_is_enabled(i)) {
esp_rom_output_tx_wait_idle(i);
}
}
}
static uint32_t s_suspended_uarts_bmap = 0;
/**
* Suspend enabled uarts and return suspended uarts bit map.
* Must be called from critical sections.
*/
static SLEEP_FN_ATTR void suspend_uarts(void)
{
s_suspended_uarts_bmap = 0;
for (int i = 0; i < SOC_UART_HP_NUM; ++i) {
if (!uart_ll_is_enabled(i)) {
continue;
}
uart_ll_force_xoff(i);
s_suspended_uarts_bmap |= BIT(i);
#if SOC_UART_SUPPORT_FSM_TX_WAIT_SEND
uint32_t uart_fsm = 0;
do {
uart_fsm = uart_ll_get_tx_fsm_status(i);
} while (!(uart_fsm == UART_LL_FSM_IDLE || uart_fsm == UART_LL_FSM_TX_WAIT_SEND));
#else
while (uart_ll_get_tx_fsm_status(i) != 0) {}
#endif
}
}
// Must be called from critical sections
static SLEEP_FN_ATTR void resume_uarts(void)
{
for (int i = 0; i < SOC_UART_HP_NUM; ++i) {
if (s_suspended_uarts_bmap & 0x1) {
uart_ll_force_xon(i);
}
s_suspended_uarts_bmap >>= 1;
}
}
/*
UART prepare strategy in sleep:
Deepsleep : flush the fifo before enter sleep to avoid data loss
Lightsleep:
Chips not support PD_TOP: Suspend uart before cpu freq switch
Chips support PD_TOP:
For sleep which will not power down the TOP domain (uart belongs it), we can just suspend the UART.
For sleep which will power down the TOP domain, we need to consider whether the uart flushing will
block the sleep process and cause the rtos target tick to be missed upon waking up. It's need to
estimate the flush time based on the number of bytes in the uart FIFO, if the predicted flush
completion time has exceeded the wakeup time, we should abandon the flush, skip the sleep and
return ESP_ERR_SLEEP_REJECT.
*/
static SLEEP_FN_ATTR bool light_sleep_uart_prepare(uint32_t sleep_flags, int64_t sleep_duration)
{
bool should_skip_sleep = false;
#if !SOC_PM_SUPPORT_TOP_PD || !CONFIG_ESP_CONSOLE_UART
suspend_uarts();
#else
#ifdef CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION
#define FORCE_FLUSH_CONSOLE_UART 1
#else
#define FORCE_FLUSH_CONSOLE_UART 0
#endif
if (FORCE_FLUSH_CONSOLE_UART || (sleep_flags & PMU_SLEEP_PD_TOP)) {
if ((s_config.wakeup_triggers & RTC_TIMER_TRIG_EN) &&
// +1 is for cover the last character flush time
(sleep_duration < (int64_t)((UART_LL_FIFO_DEF_LEN - uart_ll_get_txfifo_len(CONSOLE_UART_DEV) + 1) * UART_FLUSH_US_PER_CHAR) + SLEEP_UART_FLUSH_DONE_TO_SLEEP_US)) {
should_skip_sleep = true;
} else {
/* Only flush the uart_num configured to console, the transmission integrity of
other uarts is guaranteed by the UART driver */
if (CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM != -1) {
esp_rom_output_tx_wait_idle(CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM);
}
}
} else {
suspend_uarts();
}
#endif
return should_skip_sleep;
}
/**
* LP peripherals prepare XTAL, FOSC or other clocks as the clock source for sleep.
*/
@@ -1046,11 +943,7 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, uint32_t cl
int64_t sleep_duration = (int64_t) s_config.sleep_duration - (int64_t) s_config.sleep_time_adjustment;
// Sleep UART prepare
if (deep_sleep) {
flush_uarts();
} else {
should_skip_sleep = light_sleep_uart_prepare(sleep_flags, sleep_duration);
}
sleep_uart_prepare(sleep_flags, deep_sleep);
#if CONFIG_ESP_PHY_ENABLED && SOC_DEEP_SLEEP_SUPPORTED
// Do deep-sleep PHY related callback, which need to be executed when the PLL clock is exists.
@@ -1252,7 +1145,7 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, uint32_t cl
esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(cpu_freq_config.source_freq_mhz, cpu_freq_config.freq_mhz);
#endif
// re-enable UART output
resume_uarts();
sleep_uart_resume();
return result ? ESP_ERR_SLEEP_REJECT : ESP_OK;
}
+152
View File
@@ -0,0 +1,152 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_sleep.h"
#include "esp_private/sleep_uart.h"
#include "esp_rom_serial_output.h"
#include "hal/uart_ll.h"
#if SOC_PM_SUPPORT_TOP_PD
#include "esp_private/esp_pmu.h"
#include "hal/pmu_ll.h"
#endif
#if CONFIG_PM_SLP_IRAM_OPT
#define SLEEP_UART_FN_ATTR FORCE_IRAM_ATTR
#else
#define SLEEP_UART_FN_ATTR
#endif
// UART handling mode configuration for each UART port
static esp_sleep_uart_handling_mode_t s_uart_handling[SOC_UART_HP_NUM] = {
[0 ... SOC_UART_HP_NUM - 1] = ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART
};
// Bitmap of suspended UARTs for resume
static uint32_t s_suspended_uarts_bmap = 0;
// Suspend a single UART and record it in the bitmap
static SLEEP_UART_FN_ATTR void suspend_uart(int uart_num)
{
uart_ll_force_xoff(uart_num);
s_suspended_uarts_bmap |= BIT(uart_num);
#if SOC_UART_SUPPORT_FSM_TX_WAIT_SEND
uint32_t uart_fsm = 0;
do {
uart_fsm = uart_ll_get_tx_fsm_status(uart_num);
} while (!(uart_fsm == UART_LL_FSM_IDLE || uart_fsm == UART_LL_FSM_TX_WAIT_SEND));
#else
while (uart_ll_get_tx_fsm_status(uart_num) != 0) {}
#endif
}
/**
* @brief Determine the actual UART handling mode based on configuration and sleep parameters
*
* Default strategy selection:
* - Deep sleep: Always flush FIFO before entering sleep to avoid data loss
* - Light sleep (chips without PD_TOP support): Suspend UART before CPU frequency switch
* - Light sleep (chips with PD_TOP support):
* - If TOP domain is NOT powered down: Suspend UART for faster sleep entry
* - If TOP domain IS powered down: Flush console UART for data integrity,
* discard non-console UARTs to save power
*
* Special handling:
* - Console UART with cache-safe assertion enabled: Always flush to ensure
* debug output is visible even if cache is disabled
*/
static SLEEP_UART_FN_ATTR esp_sleep_uart_handling_mode_t get_uart_handling_mode(int uart_num, esp_sleep_uart_handling_mode_t configured_handling, uint32_t sleep_flags, bool deep_sleep)
{
esp_sleep_uart_handling_mode_t handling = configured_handling;
__attribute__((unused)) bool is_console_uart = false;
#if (CONFIG_ESP_CONSOLE_UART_NUM != -1)
is_console_uart = (uart_num == CONFIG_ESP_CONSOLE_UART_NUM);
#if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION
if (is_console_uart) {
handling = ESP_SLEEP_ALWAYS_FLUSH_UART;
}
#endif /* CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION */
#endif /* CONFIG_ESP_CONSOLE_UART_NUM != -1 */
// Resolve AUTO mode into specific strategy
if (handling == ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART) {
// Default: flush for deep sleep, suspend for light sleep
handling = deep_sleep ? ESP_SLEEP_ALWAYS_FLUSH_UART : ESP_SLEEP_ALWAYS_SUSPEND_UART;
#if SOC_PM_SUPPORT_TOP_PD
// If TOP domain (where UART belongs) is powered down during sleep:
// - Console UART: flush to preserve debug output
// - Non-console UART: discard to save power and enter sleep faster
if (sleep_flags & PMU_SLEEP_PD_TOP) {
handling = is_console_uart ? ESP_SLEEP_ALWAYS_FLUSH_UART : ESP_SLEEP_ALWAYS_DISCARD_UART;
}
#endif
}
return handling;
}
void SLEEP_UART_FN_ATTR sleep_uart_prepare(uint32_t sleep_flags, bool deep_sleep)
{
s_suspended_uarts_bmap = 0;
for (int i = 0; i < SOC_UART_HP_NUM; ++i) {
if (!uart_ll_is_enabled(i)) {
continue;
}
esp_sleep_uart_handling_mode_t handling = get_uart_handling_mode(i, s_uart_handling[i], sleep_flags, deep_sleep);
switch (handling) {
case ESP_SLEEP_ALWAYS_FLUSH_UART:
esp_rom_output_tx_wait_idle(i);
break;
case ESP_SLEEP_ALWAYS_SUSPEND_UART:
suspend_uart(i);
break;
case ESP_SLEEP_ALWAYS_DISCARD_UART:
// Suspend uart first before reset uart to avoid garbled code.
suspend_uart(i);
uart_ll_txfifo_rst(UART_LL_GET_HW(i));
uart_ll_rxfifo_rst(UART_LL_GET_HW(i));
break;
default:
// ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART should have been resolved in get_uart_handling_mode
break;
}
}
}
void SLEEP_UART_FN_ATTR sleep_uart_resume(void)
{
for (int i = 0; i < SOC_UART_HP_NUM; ++i) {
if (s_suspended_uarts_bmap & 0x1) {
uart_ll_force_xon(i);
}
s_suspended_uarts_bmap >>= 1;
}
}
esp_err_t sleep_uart_set_handling_mode(int uart_num, esp_sleep_uart_handling_mode_t handling_mode)
{
if (handling_mode > ESP_SLEEP_NO_HANDLING) {
return ESP_ERR_INVALID_ARG;
}
if (uart_num < 0 || uart_num >= SOC_UART_HP_NUM) {
return ESP_ERR_INVALID_ARG;
}
s_uart_handling[uart_num] = handling_mode;
return ESP_OK;
}
esp_err_t esp_sleep_set_console_uart_handling_mode(esp_sleep_uart_handling_mode_t handling_mode)
{
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
#if (CONFIG_ESP_CONSOLE_UART_NUM != -1)
ret = sleep_uart_set_handling_mode(CONFIG_ESP_CONSOLE_UART_NUM, handling_mode);
#endif
return ret;
}
+37 -26
View File
@@ -840,6 +840,30 @@ static inline void IRAM_ATTR other_core_should_skip_light_sleep(int core_id)
#endif
}
// Adjust RTOS tick count based on the amount of time spent in sleep.
FORCE_INLINE_ATTR void pm_step_tick(int64_t slept_us)
{
uint32_t slept_ticks = slept_us / (portTICK_PERIOD_MS * 1000LL);
if (slept_ticks) {
/* Adjust RTOS tick count based on the amount of time spent in sleep */
vTaskStepTick(slept_ticks);
#ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
/* 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.
*/
esp_cpu_set_cycle_count(XTHAL_GET_CCOMPARE(XT_TIMER_INDEX) - 16);
while (!(XTHAL_GET_INTERRUPT() & BIT(XT_TIMER_INTNUM))) {
;
}
#else
portYIELD_WITHIN_API();
#endif
}
}
void vApplicationSleep( TickType_t xExpectedIdleTime )
{
portENTER_CRITICAL(&s_switch_lock);
@@ -862,36 +886,23 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
/* Enter sleep */
ESP_PM_TRACE_ENTER(SLEEP, core_id);
int64_t sleep_start = esp_timer_get_time();
if (esp_light_sleep_start() != ESP_OK){
#ifdef WITH_PROFILING
s_light_sleep_reject_counts++;
} else {
s_light_sleep_counts++;
#endif
}
esp_err_t err = esp_light_sleep_start();
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);
#ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
/* 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.
*/
esp_cpu_set_cycle_count(XTHAL_GET_CCOMPARE(XT_TIMER_INDEX) - 16);
while (!(XTHAL_GET_INTERRUPT() & BIT(XT_TIMER_INTNUM))) {
;
}
#else
portYIELD_WITHIN_API();
#endif
// If the sleep request was rejected, the SYSTIMER_COUNTER_OS_TICK remains accurate.
// In this case, there is no need to call vTaskStepTick, because the OS tick count will
// automatically catch up in the next systick interrupt handler.
if (err == ESP_OK) {
pm_step_tick(slept_us);
}
other_core_should_skip_light_sleep(core_id);
#ifdef WITH_PROFILING
if (err == ESP_OK) {
s_light_sleep_counts++;
} else {
s_light_sleep_reject_counts++;
}
#endif
}
#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS
esp_pm_execute_exit_sleep_callbacks(slept_us);
@@ -17,7 +17,8 @@ set(SRC "test_app_main.c"
"test_task_wdt.c")
if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED OR CONFIG_SOC_DEEP_SLEEP_SUPPORTED)
list(APPEND SRC "test_sleep.c")
list(APPEND SRC "test_sleep.c"
"test_sleep_uart.c")
endif()
if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM)
@@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "unity.h"
#include "esp_sleep.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
/////////////////////////// UART Handling API Test Cases ////////////////////////////////////
TEST_CASE("esp_sleep_set_console_uart_handling_mode parameter validation", "[sleep_uart]")
{
// Test invalid handling mode
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_NO_HANDLING + 1));
// Test valid parameters
TEST_ASSERT_EQUAL(ESP_OK, esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART));
TEST_ASSERT_EQUAL(ESP_OK, esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_FLUSH_UART));
TEST_ASSERT_EQUAL(ESP_OK, esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_SUSPEND_UART));
TEST_ASSERT_EQUAL(ESP_OK, esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_DISCARD_UART));
TEST_ASSERT_EQUAL(ESP_OK, esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_NO_HANDLING));
// Restore default
TEST_ASSERT_EQUAL(ESP_OK, esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART));
}
#if SOC_LIGHT_SLEEP_SUPPORTED
/////////////////////////// pytest-based UART Output Verification Test Cases ////////////////////////////////////
/*
* Test FLUSH mode: All data should be completely output before sleep
* pytest verifies: FLUSH_START -> all FLUSH_DATA_X -> FLUSH_SLEEP -> FLUSH_END
*/
TEST_CASE("UART flush mode output verification", "[sleep_uart_output]")
{
TEST_ESP_OK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_FLUSH_UART));
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1000000)); // 1s
printf("<<<FLUSH_START>>>\n");
for (int i = 0; i < 10; i++) {
printf("FLUSH_DATA_%d\n", i);
}
printf("<<<FLUSH_SLEEP>>>\n");
fflush(stdout);
esp_light_sleep_start();
printf("<<<FLUSH_END>>>\n");
TEST_ESP_OK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART));
}
/*
* Test SUSPEND mode: Data in TX FIFO should continue after wakeup
* pytest verifies: SUSPEND_START -> all SUSPEND_DATA_X -> SUSPEND_SLEEP -> SUSPEND_END
*/
TEST_CASE("UART suspend mode output verification", "[sleep_uart_output]")
{
TEST_ESP_OK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_SUSPEND_UART));
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1000000)); // 1s
printf("<<<SUSPEND_START>>>\n");
for (int i = 0; i < 10; i++) {
printf("SUSPEND_DATA_%d\n", i);
}
printf("<<<SUSPEND_SLEEP>>>\n");
fflush(stdout);
esp_light_sleep_start();
printf("<<<SUSPEND_END>>>\n");
TEST_ESP_OK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART));
}
/*
* Test DISCARD mode: Data in TX FIFO should be discarded before sleep
* pytest verifies: DISCARD_START appears, DISCARD_END appears,
* but DISCARD_DATA_9_SHOULD_BE_LOST marker should NOT appear (data discarded)
*/
TEST_CASE("UART discard mode output verification", "[sleep_uart_output]")
{
TEST_ESP_OK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_DISCARD_UART));
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1000000)); // 1s
// This marker must be flushed to ensure it appears
printf("<<<DISCARD_START>>>\n");
fflush(stdout);
// Print data without flush - this will stay in FIFO and be discarded
for (int i = 0; i < 10; i++) {
printf("DISCARD_DATA_%02d_SHOULD_BE_LOST\n", i);
}
// This marker should NOT appear because FIFO will be reset
printf("<<<DISCARD_BEFORE_SLEEP>>>\n");
fflush(stdout);
esp_light_sleep_start();
// Small delay to ensure UART is stable after wakeup
vTaskDelay(pdMS_TO_TICKS(10));
printf("<<<DISCARD_END>>>\n");
TEST_ESP_OK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART));
}
#endif // SOC_LIGHT_SLEEP_SUPPORTED
@@ -1,5 +1,7 @@
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import time
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@@ -26,6 +28,55 @@ def test_esp_system(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=60)
def esp_reset_and_wait_ready(dut: Dut) -> None:
dut.serial.hard_reset()
time.sleep(0.5)
dut.expect_exact('Press ENTER to see the list of tests')
@pytest.mark.generic
@idf_parametrize('config', ['default'], indirect=['config'])
@idf_parametrize(
'target',
[target for target in soc_filtered_targets('SOC_LIGHT_SLEEP_SUPPORTED == 1')],
indirect=['target'],
)
def test_sleep_uart_handling(dut: Dut) -> None:
"""Test UART handling modes during light sleep."""
# Test FLUSH mode output
esp_reset_and_wait_ready(dut)
dut.write('"UART flush mode output verification"')
dut.expect_exact('<<<FLUSH_START>>>')
for i in range(10):
dut.expect_exact(f'FLUSH_DATA_{i}')
dut.expect_exact('<<<FLUSH_SLEEP>>>')
dut.expect_exact('<<<FLUSH_END>>>')
# Test SUSPEND mode output - verify data continues after wakeup with sleep delay
esp_reset_and_wait_ready(dut)
dut.write('"UART suspend mode output verification"')
dut.expect_exact('<<<SUSPEND_START>>>')
start_time = time.time()
for i in range(10):
dut.expect_exact(f'SUSPEND_DATA_{i}')
dut.expect_exact('<<<SUSPEND_SLEEP>>>')
dut.expect_exact('<<<SUSPEND_END>>>')
end_time = time.time()
elapsed = end_time - start_time
# Sleep duration is 1 second, so total time should be >= 1s
assert elapsed >= 1.0, f'SUSPEND mode: expected >= 1s delay due to sleep, but got {elapsed:.2f}s'
# Test DISCARD mode output - verify data is discarded
esp_reset_and_wait_ready(dut)
dut.write('"UART discard mode output verification"')
dut.expect_exact('<<<DISCARD_START>>>')
# Capture output between START and END, verify DISCARD_DATA_9_SHOULD_BE_LOST is NOT present
output = dut.expect(r'<<<DISCARD_END>>>', timeout=10)
assert '<<<DISCARD_DATA_9_SHOULD_BE_LOST>>>' not in output.group().decode(), (
'DISCARD mode failed: data should have been discarded but DISCARD_DATA_9_SHOULD_BE_LOST marker appeared'
)
@pytest.mark.generic
@idf_parametrize('config', ['default'], indirect=['config'])
@idf_parametrize('target', ['supported_targets'], indirect=['target'])