feat(esp_hw_support): support configurable uarts behavior before sleep

This commit is contained in:
wuzhenghui
2025-04-15 11:58:11 +08:00
parent b13047f8ca
commit da0ae0e56d
5 changed files with 266 additions and 114 deletions
+1
View File
@@ -63,6 +63,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: 2026 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
+53 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -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
+6 -113
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -77,6 +77,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"
@@ -214,17 +215,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)
@@ -242,9 +232,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
@@ -626,99 +616,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);
#ifdef UART_LL_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.
*/
@@ -1037,11 +934,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.
@@ -1235,7 +1128,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: 2026 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);
#ifdef UART_LL_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;
}