mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
Merge branch 'feat/support_get_pm_lock_aquired_counts_v6.0' into 'release/v6.0'
feat(esp_pm): add APIs to get PM lock statistics / add light sleep tick overflow protection configuration (v6.0) See merge request espressif/esp-idf!45885
This commit is contained in:
@@ -241,6 +241,44 @@ menu "Power Management"
|
||||
NOTE: Enabling these callbacks may change sleep duration calculations based on time spent in callback and
|
||||
hence it is highly recommended to keep them as short as possible
|
||||
|
||||
config PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
|
||||
bool "Enable light sleep tick overflow protection"
|
||||
depends on FREERTOS_USE_TICKLESS_IDLE
|
||||
default n
|
||||
help
|
||||
Limits tick compensation after light sleep wakeup to prevent vTaskStepTick() assertion
|
||||
failure when actual wakeup overhead exceeds estimation (due to cache misses, CPU
|
||||
frequency changes, or flash latency variations).
|
||||
|
||||
When enabled:
|
||||
- Silently limits slept_ticks to xExpectedIdleTime when oversleep is within tolerance
|
||||
(configured by PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE), preventing assertion failure
|
||||
- Does not limit when oversleep exceeds tolerance (may indicate a bug), assertion failure may occur
|
||||
- May lose ticks in rare cases, causing xTickCount to lag behind esp_timer
|
||||
|
||||
When disabled (default):
|
||||
- Accurate tick compensation, better precision
|
||||
- May trigger assertion failure and system crash if overslept
|
||||
|
||||
Keep disabled by default to maintain RTOS tick accuracy. Enable only when you experience
|
||||
assertion failures related to vTaskStepTick() and can accept slight inaccuracy of RTOS
|
||||
tick time compared to real time.
|
||||
|
||||
config PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE
|
||||
int "Light sleep tick overflow tolerance"
|
||||
depends on PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
|
||||
default 2
|
||||
range 1 10
|
||||
help
|
||||
Maximum number of ticks that can be overslept before triggering.
|
||||
When oversleep is within this tolerance, the system silently limits slept_ticks to
|
||||
prevent assertion failure. When oversleep exceeds this tolerance, assertion failure
|
||||
may occur and system may crash.
|
||||
|
||||
Higher values provide more tolerance for estimation variance but may hide real issues
|
||||
and cause more tick loss, leading to greater RTOS tick time inaccuracy compared to real time.
|
||||
Lower values provide better RTOS tick accuracy but may cause false positives.
|
||||
|
||||
config PM_WORKAROUND_FREQ_LIMIT_ENABLED
|
||||
bool
|
||||
default y if SPI_FLASH_FREQ_LIMIT_C5_240MHZ
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -196,6 +196,54 @@ esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle);
|
||||
*/
|
||||
esp_err_t esp_pm_dump_locks(FILE* stream);
|
||||
|
||||
/**
|
||||
* @brief Structure to store PM lock statistics for each lock type
|
||||
*/
|
||||
typedef struct {
|
||||
size_t created; /*!< Number of locks of this type that have been created */
|
||||
size_t acquired; /*!< Total number of times locks of this type have been acquired */
|
||||
} esp_pm_lock_stats_t;
|
||||
|
||||
/**
|
||||
* @brief Structure to store statistics for a single PM lock instance
|
||||
*/
|
||||
typedef struct {
|
||||
size_t acquired; /*!< Current reference count of the lock (number of times it has been acquired without corresponding release) */
|
||||
#ifdef CONFIG_PM_PROFILING
|
||||
size_t times_taken; /*!< Number of times the lock has been taken (from not held to held state) */
|
||||
int64_t time_held; /*!< Total time the lock has been held (in microseconds) */
|
||||
#endif
|
||||
} esp_pm_lock_instance_stats_t;
|
||||
|
||||
/**
|
||||
* @brief Get statistics for all PM lock types
|
||||
*
|
||||
* This function returns the number of locks created for each lock type
|
||||
* and the total number of times locks of each type have been acquired.
|
||||
*
|
||||
* @param stats pointer to array of esp_pm_lock_stats_t with ESP_PM_LOCK_MAX elements
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if stats pointer is invalid
|
||||
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_get_lock_stats_all(esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX]);
|
||||
|
||||
/**
|
||||
* @brief Get statistics for a single PM lock instance
|
||||
*
|
||||
* This function returns statistics for a specific lock instance,
|
||||
* including the number of times it has been acquired and released.
|
||||
*
|
||||
* @param handle handle of the lock to get statistics for
|
||||
* @param stats pointer to esp_pm_lock_instance_stats_t structure to fill
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if handle or stats pointer is invalid
|
||||
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_lock_get_stats(esp_pm_lock_handle_t handle, esp_pm_lock_instance_stats_t *stats);
|
||||
|
||||
#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS
|
||||
/**
|
||||
* @brief Function prototype for light sleep callback functions (if CONFIG_FREERTOS_USE_TICKLESS_IDLE)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/param.h>
|
||||
#include <assert.h>
|
||||
@@ -834,10 +835,21 @@ static inline void IRAM_ATTR other_core_should_skip_light_sleep(int core_id)
|
||||
}
|
||||
|
||||
// Adjust RTOS tick count based on the amount of time spent in sleep.
|
||||
FORCE_INLINE_ATTR void pm_step_tick(int64_t slept_us)
|
||||
FORCE_INLINE_ATTR void pm_step_tick(int64_t slept_us, TickType_t xExpectedIdleTime)
|
||||
{
|
||||
uint32_t slept_ticks = slept_us / (portTICK_PERIOD_MS * 1000LL);
|
||||
if (slept_ticks) {
|
||||
#if CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
|
||||
/* Limit slept_ticks when oversleep is within tolerance to prevent assertion failure */
|
||||
if ((slept_ticks > xExpectedIdleTime) &&
|
||||
(slept_ticks <= (xExpectedIdleTime + CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE))) {
|
||||
slept_ticks = xExpectedIdleTime;
|
||||
}
|
||||
#endif // CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
|
||||
if (slept_ticks > xExpectedIdleTime) {
|
||||
ESP_EARLY_LOGE(TAG, "Light sleep overslept: expect %"PRIu32" idle ticks but slept %"PRIu32" ticks.",
|
||||
(uint32_t)xExpectedIdleTime, slept_ticks);
|
||||
}
|
||||
/* Adjust RTOS tick count based on the amount of time spent in sleep */
|
||||
vTaskStepTick(slept_ticks);
|
||||
|
||||
@@ -886,7 +898,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
|
||||
// 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);
|
||||
pm_step_tick(slept_us, xExpectedIdleTime);
|
||||
}
|
||||
other_core_should_skip_light_sleep(core_id);
|
||||
#ifdef WITH_PROFILING
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -147,6 +147,72 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_pm_get_lock_stats_all(esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX])
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
if (stats == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Initialize stats array
|
||||
for (int i = 0; i < ESP_PM_LOCK_MAX; i++) {
|
||||
stats[i].created = 0;
|
||||
stats[i].acquired = 0;
|
||||
}
|
||||
|
||||
_lock_acquire(&s_list_lock);
|
||||
|
||||
// Iterate through all locks and accumulate stats
|
||||
esp_pm_lock_t* it;
|
||||
SLIST_FOREACH(it, &s_list, next) {
|
||||
if (it->type < ESP_PM_LOCK_MAX) {
|
||||
stats[it->type].created++;
|
||||
// Sum the count of currently held locks
|
||||
stats[it->type].acquired += it->count;
|
||||
}
|
||||
}
|
||||
|
||||
_lock_release(&s_list_lock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_pm_lock_get_stats(esp_pm_lock_handle_t handle, esp_pm_lock_instance_stats_t *stats)
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
if (handle == NULL || stats == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Initialize stats structure
|
||||
stats->acquired = 0;
|
||||
#ifdef WITH_PROFILING
|
||||
stats->times_taken = 0;
|
||||
stats->time_held = 0;
|
||||
#endif
|
||||
|
||||
portENTER_CRITICAL(&handle->spinlock);
|
||||
stats->acquired = handle->count;
|
||||
#ifdef WITH_PROFILING
|
||||
stats->times_taken = handle->times_taken;
|
||||
stats->time_held = handle->time_held;
|
||||
// If the lock is currently held, add the time since it was last taken
|
||||
if (handle->count > 0) {
|
||||
pm_time_t now = pm_get_time();
|
||||
stats->time_held += now - handle->last_taken;
|
||||
}
|
||||
#endif
|
||||
portEXIT_CRITICAL(&handle->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_pm_dump_locks(FILE* stream)
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
@@ -160,7 +226,7 @@ esp_err_t esp_pm_dump_locks(FILE* stream)
|
||||
|
||||
_lock_acquire(&s_list_lock);
|
||||
#ifdef WITH_PROFILING
|
||||
fprintf(stream, "Time since bootup: %lld us\n", cur_time);
|
||||
fprintf(stream, "Time since boot up: %lld us\n", cur_time);
|
||||
#endif
|
||||
|
||||
fprintf(stream, "Lock stats:\n");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -47,6 +47,154 @@ TEST_CASE("Can dump power management lock stats", "[pm]")
|
||||
esp_pm_dump_locks(stdout);
|
||||
}
|
||||
|
||||
TEST_CASE("Test get PM lock statistics API", "[pm]")
|
||||
{
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_lock_stats_t init_stats[ESP_PM_LOCK_MAX], stats[ESP_PM_LOCK_MAX];
|
||||
|
||||
// Get initial stats
|
||||
TEST_ESP_OK(esp_pm_get_lock_stats_all(init_stats));
|
||||
|
||||
// Create a few locks of different types
|
||||
esp_pm_lock_handle_t lock1, lock2, lock3;
|
||||
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "cpu_lock", &lock1));
|
||||
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "apb_lock", &lock2));
|
||||
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "sleep_lock", &lock3));
|
||||
|
||||
// Check stats after creating locks
|
||||
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
|
||||
TEST_ASSERT_EQUAL(1, stats[ESP_PM_CPU_FREQ_MAX].created - init_stats[ESP_PM_CPU_FREQ_MAX].created);
|
||||
TEST_ASSERT_EQUAL(1, stats[ESP_PM_APB_FREQ_MAX].created - init_stats[ESP_PM_APB_FREQ_MAX].created);
|
||||
TEST_ASSERT_EQUAL(1, stats[ESP_PM_NO_LIGHT_SLEEP].created - init_stats[ESP_PM_NO_LIGHT_SLEEP].created);
|
||||
|
||||
// Acquire locks multiple times
|
||||
TEST_ESP_OK(esp_pm_lock_acquire(lock1));
|
||||
TEST_ESP_OK(esp_pm_lock_acquire(lock1)); // Acquire again (recursive)
|
||||
TEST_ESP_OK(esp_pm_lock_acquire(lock2));
|
||||
|
||||
// Check stats after acquiring locks
|
||||
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
|
||||
// Count total held locks (sum of all lock counts)
|
||||
TEST_ASSERT_EQUAL(2, stats[ESP_PM_CPU_FREQ_MAX].acquired - init_stats[ESP_PM_CPU_FREQ_MAX].acquired); // lock1 acquired twice
|
||||
TEST_ASSERT_EQUAL(1, stats[ESP_PM_APB_FREQ_MAX].acquired - init_stats[ESP_PM_APB_FREQ_MAX].acquired); // lock2 acquired once
|
||||
|
||||
// Release locks
|
||||
TEST_ESP_OK(esp_pm_lock_release(lock1));
|
||||
TEST_ESP_OK(esp_pm_lock_release(lock1)); // Release second acquisition
|
||||
TEST_ESP_OK(esp_pm_lock_release(lock2));
|
||||
|
||||
// Delete locks
|
||||
TEST_ESP_OK(esp_pm_lock_delete(lock1));
|
||||
TEST_ESP_OK(esp_pm_lock_delete(lock2));
|
||||
TEST_ESP_OK(esp_pm_lock_delete(lock3));
|
||||
|
||||
// Check stats after deleting locks
|
||||
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
|
||||
TEST_ASSERT_EQUAL(0, stats[ESP_PM_CPU_FREQ_MAX].created - init_stats[ESP_PM_CPU_FREQ_MAX].created);
|
||||
TEST_ASSERT_EQUAL(0, stats[ESP_PM_APB_FREQ_MAX].created - init_stats[ESP_PM_APB_FREQ_MAX].created);
|
||||
TEST_ASSERT_EQUAL(0, stats[ESP_PM_NO_LIGHT_SLEEP].created - init_stats[ESP_PM_NO_LIGHT_SLEEP].created);
|
||||
|
||||
// Test error cases
|
||||
// NULL stats pointer
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_get_lock_stats_all(NULL));
|
||||
#else
|
||||
// When PM is not enabled, function should return ESP_ERR_NOT_SUPPORTED
|
||||
esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX];
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_pm_get_lock_stats_all(stats));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Test get PM lock instance statistics API", "[pm]")
|
||||
{
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
// Create a lock
|
||||
esp_pm_lock_handle_t lock;
|
||||
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "test_lock", &lock));
|
||||
|
||||
// Get initial stats for the lock
|
||||
esp_pm_lock_instance_stats_t lock_stats;
|
||||
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
|
||||
TEST_ASSERT_EQUAL(0, lock_stats.acquired);
|
||||
|
||||
#if CONFIG_PM_PROFILING
|
||||
TEST_ASSERT_EQUAL(0, lock_stats.times_taken);
|
||||
TEST_ASSERT_EQUAL(0, lock_stats.time_held);
|
||||
#endif
|
||||
|
||||
// Acquire the lock multiple times
|
||||
TEST_ESP_OK(esp_pm_lock_acquire(lock));
|
||||
TEST_ESP_OK(esp_pm_lock_acquire(lock));
|
||||
TEST_ESP_OK(esp_pm_lock_acquire(lock));
|
||||
|
||||
// Get stats again
|
||||
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
|
||||
TEST_ASSERT_EQUAL(3, lock_stats.acquired);
|
||||
|
||||
#if CONFIG_PM_PROFILING
|
||||
TEST_ASSERT_EQUAL(1, lock_stats.times_taken);
|
||||
// The time_held should be greater than 0 if the lock is currently held
|
||||
// We can't predict the exact value, so we just check that it's non-negative
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, lock_stats.time_held);
|
||||
|
||||
// Store the time_held value for later comparison
|
||||
int32_t first_time_held = (int32_t)lock_stats.time_held;
|
||||
int64_t start_time = esp_timer_get_time();
|
||||
// Delay for a short period to increase the held time
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
int64_t end_time = esp_timer_get_time();
|
||||
uint32_t expected_time_held = first_time_held + (uint32_t)(end_time - start_time);
|
||||
|
||||
// Get stats again to check that time_held has increased
|
||||
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
|
||||
TEST_ASSERT_GREATER_THAN(first_time_held, lock_stats.time_held);
|
||||
|
||||
// Check that time_held is within a reasonable range using TEST_ASSERT_UINT64_WITHIN
|
||||
TEST_ASSERT_UINT32_WITHIN(10000, (uint32_t)expected_time_held, (uint32_t)lock_stats.time_held); // Allow 10ms tolerance
|
||||
#endif
|
||||
|
||||
// Release the lock once
|
||||
TEST_ESP_OK(esp_pm_lock_release(lock));
|
||||
|
||||
// Get stats again
|
||||
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
|
||||
TEST_ASSERT_EQUAL(2, lock_stats.acquired);
|
||||
|
||||
// Release remaining locks
|
||||
TEST_ESP_OK(esp_pm_lock_release(lock));
|
||||
TEST_ESP_OK(esp_pm_lock_release(lock));
|
||||
|
||||
#if CONFIG_PM_PROFILING
|
||||
// After releasing all locks, get stats to check time_held is updated correctly
|
||||
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
|
||||
TEST_ASSERT_EQUAL(0, lock_stats.acquired);
|
||||
|
||||
// Store the time_held value after releasing all locks
|
||||
int64_t time_after_release = lock_stats.time_held;
|
||||
|
||||
// Delay for a short period
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
|
||||
// Get stats again - time_held should not change since lock is not held
|
||||
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
|
||||
TEST_ASSERT_EQUAL(time_after_release, lock_stats.time_held);
|
||||
#endif
|
||||
|
||||
// Test error cases
|
||||
// NULL handle
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_lock_get_stats(NULL, &lock_stats));
|
||||
// NULL stats pointer
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_lock_get_stats(lock, NULL));
|
||||
|
||||
// Clean up
|
||||
TEST_ESP_OK(esp_pm_lock_delete(lock));
|
||||
#else
|
||||
// When PM is not enabled, function should return ESP_ERR_NOT_SUPPORTED
|
||||
esp_pm_lock_handle_t lock;
|
||||
esp_pm_lock_instance_stats_t lock_stats;
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_pm_lock_get_stats(lock, &lock_stats));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
|
||||
static void switch_freq(int mhz)
|
||||
|
||||
@@ -6,3 +6,4 @@ CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
# SMP FreeRTOS currently does not support power management IDF-4997
|
||||
CONFIG_FREERTOS_SMP=n
|
||||
CONFIG_PM_PROFILING=y
|
||||
|
||||
@@ -92,6 +92,43 @@ Light-sleep duration is chosen to wake up the chip before the nearest event (tas
|
||||
|
||||
To skip unnecessary wake-up, you can consider initializing an ``esp_timer`` with the ``skip_unhandled_events`` option as ``true``. Timers with this flag will not wake up the system and it helps to reduce consumption.
|
||||
|
||||
Automatic Light-sleep Time Compensation Mechanism
|
||||
-------------------------------------------------
|
||||
|
||||
ESP-IDF uses a predictive time compensation mechanism for automatic Light-sleep. The system measures the actual wakeup overhead after each Light-sleep cycle and uses this measurement to predict the wakeup overhead for the next sleep cycle.
|
||||
|
||||
The system calculates sleep duration based on the next scheduled event and subtracts the predicted wakeup overhead (from the previous cycle) to set the wakeup timer. After wakeup, since FreeRTOS systick interrupts are suspended during sleep, the system needs to call :cpp:func:`vTaskStepTick()` to compensate for the ticks elapsed during sleep, maintaining the accuracy of FreeRTOS tick count. Meanwhile, it measures the actual overhead and stores it for the next prediction, creating a feedback loop that adapts to system behavior.
|
||||
|
||||
However, actual overhead can vary due to cache misses, CPU frequency changes, flash latency variations, or hardware state restoration time. When actual overhead exceeds prediction, the actual sleep time may exceed the expected value, causing :cpp:func:`vTaskStepTick()` to receive an excessive tick compensation value, triggering assertion failure.
|
||||
|
||||
The :ref:`CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION` option provides a safety mechanism to prevent assertion failures when wakeup overhead exceeds prediction. When enabled, the system limits the tick compensation value to prevent overflow. This option can be enabled in menuconfig via ``Component config`` > ``Power Management`` > ``Enable light sleep tick overflow protection``.
|
||||
|
||||
When enabled, this option handles oversleep as follows:
|
||||
- If oversleep is within tolerance (configurable via :ref:`CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE`, default: 2 ticks), the system silently limits ``slept_ticks`` to ``xExpectedIdleTime``, preventing assertion failure
|
||||
- If oversleep exceeds tolerance (may indicate a bug), the system does not limit ticks, logs an error message, and assertion failure will occur
|
||||
- In rare edge cases, it may lose ticks, causing FreeRTOS tick count (``xTickCount``) to lag behind real time (``esp_timer``). Tasks using :cpp:func:`vTaskDelay()` may experience slightly longer delays than expected, and FreeRTOS software timers may have reduced accuracy.
|
||||
|
||||
When disabled (default), the system provides accurate tick compensation with better precision for time-critical applications. In edge cases, if wakeup overhead estimation is insufficient causing Light-sleep oversleep, assertion failure and system crash may occur.
|
||||
|
||||
It is recommended to keep this option disabled by default to maintain tick accuracy. Enable it only when you experience assertion failures related to :cpp:func:`vTaskStepTick()` and can accept slight inaccuracy of RTOS tick time compared to real time.
|
||||
|
||||
Debugging and Profiling
|
||||
-----------------------
|
||||
|
||||
The power management subsystem provides several functions to help debug and profile power management lock usage in applications:
|
||||
|
||||
- :cpp:func:`esp_pm_dump_locks` - Dumps a list of all currently created locks to a specified stream, showing their types, names, and current acquisition status.
|
||||
- :cpp:func:`esp_pm_get_lock_stats_all` - Retrieves statistics for all PM lock types, including the number of locks created and the number currently acquired.
|
||||
- :cpp:func:`esp_pm_lock_get_stats` - Gets detailed statistics for a specific lock instance, including acquisition count and (if profiling is enabled) the number of times taken and total time held.
|
||||
|
||||
These functions are particularly useful for:
|
||||
|
||||
1. Identifying leaks where locks are acquired but never released
|
||||
2. Understanding which components are preventing power savings
|
||||
3. Optimizing power consumption by analyzing lock usage patterns
|
||||
4. Debugging issues related to lock management in applications
|
||||
|
||||
To enable profiling features (timing information for individual locks), enable the :ref:`CONFIG_PM_PROFILING` option in menuconfig.
|
||||
|
||||
Dynamic Frequency Scaling and Peripheral Drivers
|
||||
------------------------------------------------
|
||||
|
||||
@@ -92,6 +92,43 @@ ESP-IDF 中集成的电源管理算法可以根据应用程序组件的需求,
|
||||
|
||||
为了跳过不必要的唤醒,可以将 ``skip_unhandled_events`` 选项设置为 ``true`` 来初始化 ``esp_timer``。带有此标志的定时器不会唤醒系统,有助于减少功耗。
|
||||
|
||||
自动 Light-sleep 时间补偿机制
|
||||
---------------------------------------
|
||||
|
||||
ESP-IDF 使用预测性时间补偿机制来实现自动 Light-sleep。系统会在每次 Light-sleep 周期后测量实际的唤醒开销,并使用该测量值来预测下一次睡眠周期的唤醒开销。
|
||||
|
||||
系统根据下一个计划事件计算睡眠持续时间,并减去预测的唤醒开销(来自上一周期)来设置唤醒定时器。唤醒后,由于睡眠期间 FreeRTOS systick 中断被暂停,系统需要调用 :cpp:func:`vTaskStepTick()` 来补偿睡眠期间经过的 tick 数,以保持 FreeRTOS tick 计数的准确性。同时,系统测量实际开销并记录,用于下次预测,形成自适应系统行为的反馈循环。
|
||||
|
||||
但实际开销可能因缓存未命中、CPU 频率变化、Flash 延迟变化或硬件状态恢复时间而有所不同。当实际开销超过预测值时,实际睡眠时间可能超过预期,导致 :cpp:func:`vTaskStepTick()` 接收到的 tick 补偿值过大,触发断言失败。
|
||||
|
||||
:ref:`CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION` 选项提供了一个安全机制,用于在唤醒开销超过预测时防止断言失败。启用后,系统会限制 tick 补偿值以防止溢出。在 menuconfig 中可通过 ``Component config`` > ``Power Management`` > ``Enable light sleep tick overflow protection`` 启用此选项。
|
||||
|
||||
启用该选项时,系统对睡过超时情况的处理如下:
|
||||
- 如果睡过超时在容忍范围内(可通过 :ref:`CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE` 配置,默认:2 个 tick),系统会静默地将 ``slept_ticks`` 限制为 ``xExpectedIdleTime``,防止断言失败
|
||||
- 如果睡过超时超过容忍范围(可能存在 bug),系统不会限制 tick,会抛出错误日志,并触发断言失败
|
||||
- 在极少数边缘场景下可能会丢失 tick,导致 FreeRTOS tick 计数(``xTickCount``)落后于真实时间(``esp_timer``),使用 :cpp:func:`vTaskDelay()` 的任务可能比预期延迟稍长,FreeRTOS 软件定时器精度可能降低。
|
||||
|
||||
禁用该选项时(默认),可以获得准确的 tick 补偿,对时间关键应用具有更好的精度。在边缘情况下, 如果唤醒开销估算不足导致 Light-sleep 睡过超时时,可能会触发断言失败导致系统崩溃。
|
||||
|
||||
建议默认保持禁用状态以维持 tick 精度。仅在遇到与 :cpp:func:`vTaskStepTick()` 相关的断言失败,且可以接受 RTOS tick 时间相较于真实时间轻微不准时启用。
|
||||
|
||||
调试和性能分析
|
||||
-----------------------
|
||||
|
||||
电源管理子系统提供了几个函数来帮助调试和分析应用程序中的电源管理锁使用情况:
|
||||
|
||||
- :cpp:func:`esp_pm_dump_locks` - 将所有当前创建的锁列表转储到指定流,显示其类型、名称和当前获取状态。
|
||||
- :cpp:func:`esp_pm_get_lock_stats_all` - 获取所有 PM 锁类型的统计信息,包括创建的锁数量和当前持有数。
|
||||
- :cpp:func:`esp_pm_lock_get_stats` - 获取特定锁实例的详细统计信息,包括获取计数(如果启用性能分析)和总占用时间。
|
||||
|
||||
这些函数特别适用于:
|
||||
|
||||
1. 识别获取但从未释放的锁导致的泄漏
|
||||
2. 了解哪些组件阻止了节能
|
||||
3. 通过分析锁使用模式来优化功耗
|
||||
4. 调试与应用程序中锁管理相关的问题
|
||||
|
||||
要启用性能分析功能(单个锁的计时信息),请在 menuconfig 中启用 :ref:`CONFIG_PM_PROFILING` 选项。
|
||||
|
||||
动态调频和外设驱动
|
||||
------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user