From 3c90d22010436ddc2348a7584f7a6a1d1a974d70 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Tue, 15 Apr 2025 11:58:11 +0800 Subject: [PATCH 1/3] feat(esp_hw_support): support configurable uarts behavior before sleep --- components/esp_hw_support/CMakeLists.txt | 1 + .../include/esp_private/sleep_uart.h | 54 +++++++ components/esp_hw_support/include/esp_sleep.h | 52 ++++++ components/esp_hw_support/sleep_modes.c | 117 +------------- components/esp_hw_support/sleep_uart.c | 152 ++++++++++++++++++ 5 files changed, 264 insertions(+), 112 deletions(-) create mode 100644 components/esp_hw_support/include/esp_private/sleep_uart.h create mode 100644 components/esp_hw_support/sleep_uart.c diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index 99d765aa35..fa9fb97515 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -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" diff --git a/components/esp_hw_support/include/esp_private/sleep_uart.h b/components/esp_hw_support/include/esp_private/sleep_uart.h new file mode 100644 index 0000000000..4d1e27f3b4 --- /dev/null +++ b/components/esp_hw_support/include/esp_private/sleep_uart.h @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include +#include +#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 diff --git a/components/esp_hw_support/include/esp_sleep.h b/components/esp_hw_support/include/esp_sleep.h index 19e9462cf4..53aacd53be 100644 --- a/components/esp_hw_support/include/esp_sleep.h +++ b/components/esp_hw_support/include/esp_sleep.h @@ -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 * @@ -775,6 +790,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 diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index d1a9146de4..e2769435f4 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -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; } diff --git a/components/esp_hw_support/sleep_uart.c b/components/esp_hw_support/sleep_uart.c new file mode 100644 index 0000000000..4a26a81477 --- /dev/null +++ b/components/esp_hw_support/sleep_uart.c @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#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; +} From f9a0d6ee8af96dfd9a677d5e365f117b11ef490b Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Wed, 10 Dec 2025 16:18:08 +0800 Subject: [PATCH 2/3] feat(esp_hw_support): add test case for esp_sleep_set_uart_handling --- .../main/CMakeLists.txt | 3 +- .../main/test_sleep_uart.c | 113 ++++++++++++++++++ .../pytest_esp_system_unity_tests.py | 51 ++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep_uart.c diff --git a/components/esp_system/test_apps/esp_system_unity_tests/main/CMakeLists.txt b/components/esp_system/test_apps/esp_system_unity_tests/main/CMakeLists.txt index 5fad87dc66..4542a3dad0 100644 --- a/components/esp_system/test_apps/esp_system_unity_tests/main/CMakeLists.txt +++ b/components/esp_system/test_apps/esp_system_unity_tests/main/CMakeLists.txt @@ -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) diff --git a/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep_uart.c b/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep_uart.c new file mode 100644 index 0000000000..d6379d5265 --- /dev/null +++ b/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep_uart.c @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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("<<>>\n"); + for (int i = 0; i < 10; i++) { + printf("FLUSH_DATA_%d\n", i); + } + printf("<<>>\n"); + fflush(stdout); + + esp_light_sleep_start(); + + printf("<<>>\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("<<>>\n"); + for (int i = 0; i < 10; i++) { + printf("SUSPEND_DATA_%d\n", i); + } + printf("<<>>\n"); + fflush(stdout); + + esp_light_sleep_start(); + + printf("<<>>\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("<<>>\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("<<>>\n"); + fflush(stdout); + + esp_light_sleep_start(); + + // Small delay to ensure UART is stable after wakeup + vTaskDelay(pdMS_TO_TICKS(10)); + printf("<<>>\n"); + + TEST_ESP_OK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART)); +} + +#endif // SOC_LIGHT_SLEEP_SUPPORTED diff --git a/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py b/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py index 9882eb7ef0..23357e1873 100644 --- a/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py +++ b/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py @@ -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('<<>>') + for i in range(10): + dut.expect_exact(f'FLUSH_DATA_{i}') + dut.expect_exact('<<>>') + dut.expect_exact('<<>>') + + # 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('<<>>') + start_time = time.time() + for i in range(10): + dut.expect_exact(f'SUSPEND_DATA_{i}') + dut.expect_exact('<<>>') + dut.expect_exact('<<>>') + 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('<<>>') + # Capture output between START and END, verify DISCARD_DATA_9_SHOULD_BE_LOST is NOT present + output = dut.expect(r'<<>>', timeout=10) + assert '<<>>' 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']) From 47651df5677f4397a90e95db43cf36dfff2f2fbe Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Tue, 9 Dec 2025 20:51:18 +0800 Subject: [PATCH 3/3] change(esp_pm): skip vTaskStepTick if lightsleep request is rejected --- components/esp_pm/pm_impl.c | 63 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/components/esp_pm/pm_impl.c b/components/esp_pm/pm_impl.c index 492cfacf94..bb3fd75e0e 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -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);