feature: skip console uart flush and sleep when estimated uart flush time exceeds the sleep duration to avoid rtos tick jump failed

This commit is contained in:
wuzhenghui
2023-06-02 18:44:25 +08:00
parent 0e1bcddd70
commit 94a10372cc
2 changed files with 76 additions and 33 deletions

View File

@ -127,6 +127,16 @@
// 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)
@ -225,7 +235,7 @@ static void ext0_wakeup_prepare(void);
#if SOC_PM_SUPPORT_EXT1_WAKEUP
static void ext1_wakeup_prepare(void);
#endif
static esp_err_t timer_wakeup_prepare(void);
static esp_err_t timer_wakeup_prepare(int64_t sleep_duration);
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
static void touch_wakeup_prepare(void);
#endif
@ -385,12 +395,14 @@ static void IRAM_ATTR flush_uarts(void)
}
}
static uint32_t s_suspended_uarts_bmap = 0;
/**
* Suspend enabled uarts and return suspended uarts bit map
*/
static uint32_t IRAM_ATTR suspend_uarts(void)
static IRAM_ATTR void suspend_uarts(void)
{
uint32_t suspended_uarts_bmap = 0;
s_suspended_uarts_bmap = 0;
for (int i = 0; i < SOC_UART_NUM; ++i) {
#ifndef CONFIG_IDF_TARGET_ESP32
if (!periph_ll_periph_enabled(PERIPH_UART0_MODULE + i)) {
@ -398,7 +410,7 @@ static uint32_t IRAM_ATTR suspend_uarts(void)
}
#endif
uart_ll_force_xoff(i);
suspended_uarts_bmap |= BIT(i);
s_suspended_uarts_bmap |= BIT(i);
#if SOC_UART_SUPPORT_FSM_TX_WAIT_SEND
uint32_t uart_fsm = 0;
do {
@ -408,19 +420,57 @@ static uint32_t IRAM_ATTR suspend_uarts(void)
while (uart_ll_get_fsm_status(i) != 0) {}
#endif
}
return suspended_uarts_bmap;
}
static void IRAM_ATTR resume_uarts(uint32_t uarts_resume_bmap)
static void IRAM_ATTR resume_uarts(void)
{
for (int i = 0; i < SOC_UART_NUM; ++i) {
if (uarts_resume_bmap & 0x1) {
if (s_suspended_uarts_bmap & 0x1) {
uart_ll_force_xon(i);
}
uarts_resume_bmap >>= 1;
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 bool light_sleep_uart_prepare(uint32_t pd_flags, int64_t sleep_duration)
{
bool should_skip_sleep = false;
#if !SOC_PM_SUPPORT_TOP_PD
suspend_uarts();
#else
if (pd_flags & PMU_SLEEP_PD_TOP) {
if ((s_config.wakeup_triggers & RTC_TIMER_TRIG_EN) &&
// +1 is for cover the last charactor 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 */
esp_rom_uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM);
}
} else {
suspend_uarts();
}
#endif
return should_skip_sleep;
}
/**
* These save-restore workaround should be moved to lower layer
*/
@ -482,21 +532,8 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m
// For deep sleep, wait for the contents of UART FIFO to be sent.
bool deep_sleep = (mode == ESP_SLEEP_MODE_DEEP_SLEEP);
bool should_skip_sleep = false;
uint32_t suspended_uarts_bmap = 0;
if (deep_sleep) {
flush_uarts();
} else {
#if SOC_PM_SUPPORT_TOP_PD
if (pd_flags & PMU_SLEEP_PD_TOP) {
flush_uarts();
} else
#endif
{
suspended_uarts_bmap = suspend_uarts();
}
}
int64_t sleep_duration = (int64_t) s_config.sleep_duration - (int64_t) s_config.sleep_time_adjustment;
#if SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256
//Keep the RTC8M_CLK on if RTC clock is rc_fast_d256.
@ -522,6 +559,13 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m
spi_flash_set_clock_src(MSPI_CLK_SRC_ROM_DEFAULT);
#endif
// Sleep UART prepare
if (deep_sleep) {
flush_uarts();
} else {
should_skip_sleep = light_sleep_uart_prepare(pd_flags, sleep_duration);
}
// Save current frequency and switch to XTAL
rtc_cpu_freq_config_t cpu_freq_config;
rtc_clk_cpu_freq_get_config(&cpu_freq_config);
@ -615,9 +659,8 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m
#endif
// Configure timer wakeup
if (s_config.wakeup_triggers & RTC_TIMER_TRIG_EN) {
if (timer_wakeup_prepare() != ESP_OK) {
result = ESP_ERR_SLEEP_REJECT;
if (!should_skip_sleep && (s_config.wakeup_triggers & RTC_TIMER_TRIG_EN)) {
if (timer_wakeup_prepare(sleep_duration) != ESP_OK) {
should_skip_sleep = true;
}
}
@ -628,7 +671,9 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m
}
#endif
if (!should_skip_sleep) {
if (should_skip_sleep) {
result = ESP_ERR_SLEEP_REJECT;
} else {
if (deep_sleep) {
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
esp_sleep_isolate_digital_gpio();
@ -717,7 +762,7 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m
}
// re-enable UART output
resume_uarts(suspended_uarts_bmap);
resume_uarts();
return result ? ESP_ERR_SLEEP_REJECT : ESP_OK;
}
@ -1138,9 +1183,8 @@ esp_err_t esp_sleep_enable_timer_wakeup(uint64_t time_in_us)
return ESP_OK;
}
static esp_err_t timer_wakeup_prepare(void)
static esp_err_t timer_wakeup_prepare(int64_t sleep_duration)
{
int64_t sleep_duration = (int64_t) s_config.sleep_duration - (int64_t) s_config.sleep_time_adjustment;
if (sleep_duration < 0) {
sleep_duration = 0;
}
@ -1150,8 +1194,7 @@ static esp_err_t timer_wakeup_prepare(void)
#if SOC_LP_TIMER_SUPPORTED
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
// When pd_top is supported, light_sleep will flush uart, and the sleep overhead time will become an unpredictable value,
// here is the last timer wake-up validity check
// Last timer wake-up validity check
if ((sleep_duration == 0) || \
(target_wakeup_tick < lp_timer_hal_get_cycle_count() + SLEEP_TIMER_ALARM_TO_SLEEP_TICKS)) {
// Treat too short sleep duration setting as timer reject
@ -1162,6 +1205,7 @@ static esp_err_t timer_wakeup_prepare(void)
#else
rtc_hal_set_wakeup_timer(target_wakeup_tick);
#endif
return ESP_OK;
}

View File

@ -135,8 +135,7 @@ menu "Power Management"
be powered down, the uart FIFO will be flushed before sleep to avoid data loss, however, this has the
potential to block the sleep process and cause the wakeup time to be skipped, which will cause the tick
of freertos to not be compensated correctly when returning from sleep and cause the system to crash.
To avoid this, you can use ESP_PM_NO_LIGHT_SLEEP to protect your UART transmission, fflush the stdout
after printing, or increase FREERTOS_IDLE_TIME_BEFORE_SLEEP threshold in menuconfig.
To avoid this, you can increase FREERTOS_IDLE_TIME_BEFORE_SLEEP threshold in menuconfig.
config PM_UPDATE_CCOMPARE_HLI_WORKAROUND
bool