mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
+51
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user