Skip to content

Commit

Permalink
Merge branch 'feature/ledc_sleep_retention_support' into 'master'
Browse files Browse the repository at this point in the history
feat(ledc): support ledc sleep mode selection

Closes IDFGH-12713, IDF-9740, IDF-9769, IDF-9909, IDF-10372, IDF-10394, IDF-8472, and IDFCI-2450

See merge request espressif/esp-idf!34097
  • Loading branch information
songruo committed Oct 24, 2024
2 parents 5dda8a3 + 7a51655 commit 92d3355
Show file tree
Hide file tree
Showing 64 changed files with 1,463 additions and 177 deletions.
15 changes: 14 additions & 1 deletion components/esp_driver_ledc/include/driver/ledc.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -33,6 +33,18 @@ extern "C" {
#define LEDC_ERR_DUTY (0xFFFFFFFF)
#define LEDC_ERR_VAL (-1)

/**
* @brief Strategies to be applied to the LEDC channel during system Light-sleep period
*/
typedef enum {
LEDC_SLEEP_MODE_NO_ALIVE_NO_PD = 0, /*!< The default mode: no LEDC output, and no power off the LEDC power domain. */
LEDC_SLEEP_MODE_NO_ALIVE_ALLOW_PD, /*!< The low-power-consumption mode: no LEDC output, and allow to power off the LEDC power domain.
This can save power, but at the expense of more RAM being consumed to save register context.
This option is only available on targets that support TOP domain to be powered down. */
LEDC_SLEEP_MODE_KEEP_ALIVE, /*!< The high-power-consumption mode: keep LEDC output when the system enters Light-sleep. */
LEDC_SLEEP_MODE_INVALID, /*!< Invalid LEDC sleep mode strategy */
} ledc_sleep_mode_t;

/**
* @brief Configuration parameters of LEDC channel for ledc_channel_config function
*/
Expand All @@ -44,6 +56,7 @@ typedef struct {
ledc_timer_t timer_sel; /*!< Select the timer source of channel (0 - LEDC_TIMER_MAX-1) */
uint32_t duty; /*!< LEDC channel duty, the range of duty setting is [0, (2**duty_resolution)] */
int hpoint; /*!< LEDC channel hpoint value, the range is [0, (2**duty_resolution)-1] */
ledc_sleep_mode_t sleep_mode; /*!< choose the desired behavior for the LEDC channel in Light-sleep */
struct {
unsigned int output_invert: 1;/*!< Enable (1) or disable (0) gpio output invert */
} flags; /*!< LEDC flags */
Expand Down
254 changes: 244 additions & 10 deletions components/esp_driver_ledc/src/ledc.c

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
set(srcs "test_app_main.c"
"test_ledc.c")
"test_ledc.c"
"test_ledc_sleep.c"
"test_ledc_utils.c")

# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
#include "esp_heap_caps.h"

// Some resources are lazy allocated in LEDC driver, the threshold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (400)
// This leak is large since LEDC driver does not provide channel delete mechanism
#define TEST_MEMORY_LEAK_THRESHOLD (500)

void setUp(void)
{
Expand Down
97 changes: 1 addition & 96 deletions components/esp_driver_ledc/test_apps/ledc/main/test_ledc.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,54 +22,7 @@
#include "driver/ledc.h"
#include "soc/ledc_struct.h"
#include "esp_clk_tree.h"

#define PULSE_IO 5

#define TEST_PWM_FREQ 2000

#if SOC_LEDC_SUPPORT_HS_MODE
#define TEST_SPEED_MODE LEDC_HIGH_SPEED_MODE
#define SPEED_MODE_LIST {LEDC_HIGH_SPEED_MODE, LEDC_LOW_SPEED_MODE}
#else
#define TEST_SPEED_MODE LEDC_LOW_SPEED_MODE
#define SPEED_MODE_LIST {LEDC_LOW_SPEED_MODE}
#endif

#if SOC_LEDC_SUPPORT_APB_CLOCK
#define TEST_DEFAULT_CLK_CFG LEDC_USE_APB_CLK
#elif SOC_LEDC_SUPPORT_PLL_DIV_CLOCK
#if SOC_CLK_TREE_SUPPORTED
#define TEST_DEFAULT_CLK_CFG LEDC_USE_PLL_DIV_CLK
#else
#define TEST_DEFAULT_CLK_CFG LEDC_USE_XTAL_CLK
#endif
#endif

static ledc_channel_config_t initialize_channel_config(void)
{
ledc_channel_config_t config;
memset(&config, 0, sizeof(ledc_channel_config_t));
config.gpio_num = PULSE_IO;
config.speed_mode = TEST_SPEED_MODE;
config.channel = LEDC_CHANNEL_0;
config.intr_type = LEDC_INTR_DISABLE;
config.timer_sel = LEDC_TIMER_0;
config.duty = 4000;
config.hpoint = 0;
return config;
}

static ledc_timer_config_t create_default_timer_config(void)
{
ledc_timer_config_t ledc_time_config;
memset(&ledc_time_config, 0, sizeof(ledc_timer_config_t));
ledc_time_config.speed_mode = TEST_SPEED_MODE;
ledc_time_config.duty_resolution = LEDC_TIMER_13_BIT;
ledc_time_config.timer_num = LEDC_TIMER_0;
ledc_time_config.freq_hz = TEST_PWM_FREQ;
ledc_time_config.clk_cfg = TEST_DEFAULT_CLK_CFG;
return ledc_time_config;
}
#include "test_ledc_utils.h"

static void fade_setup(void)
{
Expand Down Expand Up @@ -474,52 +427,6 @@ TEST_CASE("LEDC multi fade test", "[ledc]")

#if SOC_PCNT_SUPPORTED // Note. C61, C3, C2 do not have PCNT peripheral, the following test cases cannot be tested

#include "driver/pulse_cnt.h"

#define HIGHEST_LIMIT 10000
#define LOWEST_LIMIT -10000

static pcnt_unit_handle_t pcnt_unit;
static pcnt_channel_handle_t pcnt_chan;

static void setup_testbench(void)
{
pcnt_unit_config_t unit_config = {
.high_limit = HIGHEST_LIMIT,
.low_limit = LOWEST_LIMIT,
};
TEST_ESP_OK(pcnt_new_unit(&unit_config, &pcnt_unit));
pcnt_chan_config_t chan_config = {
.edge_gpio_num = PULSE_IO,
.level_gpio_num = -1,
};
TEST_ESP_OK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
TEST_ESP_OK(pcnt_unit_enable(pcnt_unit));
}

static void tear_testbench(void)
{
TEST_ESP_OK(pcnt_unit_disable(pcnt_unit));
TEST_ESP_OK(pcnt_del_channel(pcnt_chan));
TEST_ESP_OK(pcnt_del_unit(pcnt_unit));
}

// use PCNT to test the waveform of LEDC
static int wave_count(int last_time)
{
// The input ability of PULSE_IO is disabled after ledc driver install, so we need to re-enable it again
gpio_ll_input_enable(&GPIO, PULSE_IO);
int test_counter = 0;
TEST_ESP_OK(pcnt_unit_clear_count(pcnt_unit));
TEST_ESP_OK(pcnt_unit_start(pcnt_unit));
vTaskDelay(pdMS_TO_TICKS(last_time));
TEST_ESP_OK(pcnt_unit_stop(pcnt_unit));
TEST_ESP_OK(pcnt_unit_get_count(pcnt_unit, &test_counter));
return test_counter;
}

// the PCNT will count the frequency of it
static void frequency_set_get(ledc_mode_t speed_mode, ledc_timer_t timer, uint32_t desired_freq, int16_t theoretical_freq, int16_t error)
{
Expand Down Expand Up @@ -731,8 +638,6 @@ static void ledc_cpu_reset_test_second_stage(void)
int count;
TEST_ASSERT_EQUAL(ESP_RST_SW, esp_reset_reason());
setup_testbench();
// reconfigure the GPIO again, as the GPIO output ability has been disabled during initialize pcnt peripheral
ledc_set_pin(PULSE_IO, TEST_SPEED_MODE, LEDC_CHANNEL_0);
count = wave_count(1000);
TEST_ASSERT_UINT32_WITHIN(5, TEST_PWM_FREQ, count);
tear_testbench();
Expand Down
99 changes: 99 additions & 0 deletions components/esp_driver_ledc/test_apps/ledc/main/test_ledc_sleep.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "test_ledc_utils.h"
#include "esp_sleep.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/esp_pmu.h"
#include "soc/ledc_periph.h"
#include "esp_private/sleep_retention.h"

// Note. Test cases in this file cannot run one after another without reset

/**
* @brief Test LEDC can still output PWM signal after light sleep
*
* @param allow_pd Whether to allow powering down the peripheral in light sleep
*/
static void test_ledc_sleep_retention(bool allow_pd)
{
int pulse_count __attribute__((unused)) = 0;

ledc_timer_config_t ledc_time_config = create_default_timer_config();
TEST_ESP_OK(ledc_timer_config(&ledc_time_config));

ledc_channel_config_t ledc_ch_config = initialize_channel_config();
ledc_ch_config.sleep_mode = (allow_pd ? LEDC_SLEEP_MODE_NO_ALIVE_ALLOW_PD : LEDC_SLEEP_MODE_NO_ALIVE_NO_PD);
TEST_ESP_OK(ledc_channel_config(&ledc_ch_config));

vTaskDelay(50 / portTICK_PERIOD_MS);

#if SOC_PCNT_SUPPORTED
setup_testbench();
pulse_count = wave_count(1000);
TEST_ASSERT_UINT32_WITHIN(5, TEST_PWM_FREQ, pulse_count);
tear_testbench(); // tear down so that PCNT won't affect TOP PD
#endif

esp_sleep_context_t sleep_ctx;
esp_sleep_set_sleep_context(&sleep_ctx);

#if ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(true));
#endif
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(2 * 1000 * 1000));

printf("go to light sleep for 2 seconds\n");
TEST_ESP_OK(esp_light_sleep_start());
printf("Waked up! Let's see if LEDC peripheral can still work...\n");

#if ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(false));
#endif

printf("check if the sleep happened as expected\r\n");
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);
#if SOC_PMU_SUPPORTED
// check if the TOP power domain on/off as desired
TEST_ASSERT_EQUAL(allow_pd ? PMU_SLEEP_PD_TOP : 0, (sleep_ctx.sleep_flags) & PMU_SLEEP_PD_TOP);
#endif
esp_sleep_set_sleep_context(NULL);

if (allow_pd) {
// check if the RO duty_r register field get synced back
TEST_ASSERT_EQUAL(4000, ledc_get_duty(TEST_SPEED_MODE, LEDC_CHANNEL_0));
}

#if SOC_PCNT_SUPPORTED
setup_testbench();
pulse_count = wave_count(1000);
TEST_ASSERT_UINT32_WITHIN(5, TEST_PWM_FREQ, pulse_count);
tear_testbench();
#endif
}

TEST_CASE("ledc can output after light sleep (LEDC power domain xpd)", "[ledc]")
{
test_ledc_sleep_retention(false);
}

#if SOC_LEDC_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
TEST_CASE("ledc can output after light sleep (LEDC power domain pd)", "[ledc]")
{
// test retention feature
test_ledc_sleep_retention(true);

// ledc driver does not have channel release, we will do retention release here to avoid memory leak
sleep_retention_module_t module = ledc_reg_retention_info.module_id;
sleep_retention_module_free(module);
sleep_retention_module_deinit(module);
}
#endif
84 changes: 84 additions & 0 deletions components/esp_driver_ledc/test_apps/ledc/main/test_ledc_utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <string.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "test_ledc_utils.h"
#include "soc/soc_caps.h"

ledc_channel_config_t initialize_channel_config(void)
{
ledc_channel_config_t config;
memset(&config, 0, sizeof(ledc_channel_config_t));
config.gpio_num = PULSE_IO;
config.speed_mode = TEST_SPEED_MODE;
config.channel = LEDC_CHANNEL_0;
config.intr_type = LEDC_INTR_DISABLE;
config.timer_sel = LEDC_TIMER_0;
config.duty = 4000;
config.hpoint = 0;
return config;
}

ledc_timer_config_t create_default_timer_config(void)
{
ledc_timer_config_t ledc_time_config;
memset(&ledc_time_config, 0, sizeof(ledc_timer_config_t));
ledc_time_config.speed_mode = TEST_SPEED_MODE;
ledc_time_config.duty_resolution = LEDC_TIMER_13_BIT;
ledc_time_config.timer_num = LEDC_TIMER_0;
ledc_time_config.freq_hz = TEST_PWM_FREQ;
ledc_time_config.clk_cfg = TEST_DEFAULT_CLK_CFG;
return ledc_time_config;
}

// use PCNT to test the waveform of LEDC
#if SOC_PCNT_SUPPORTED
#include "driver/pulse_cnt.h"

#define HIGHEST_LIMIT 10000
#define LOWEST_LIMIT -10000

static pcnt_unit_handle_t pcnt_unit;
static pcnt_channel_handle_t pcnt_chan;

void setup_testbench(void)
{
pcnt_unit_config_t unit_config = {
.high_limit = HIGHEST_LIMIT,
.low_limit = LOWEST_LIMIT,
};
TEST_ESP_OK(pcnt_new_unit(&unit_config, &pcnt_unit));
pcnt_chan_config_t chan_config = {
.edge_gpio_num = PULSE_IO,
.level_gpio_num = -1,
};
TEST_ESP_OK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
TEST_ESP_OK(pcnt_unit_enable(pcnt_unit));
}

void tear_testbench(void)
{
TEST_ESP_OK(pcnt_unit_disable(pcnt_unit));
TEST_ESP_OK(pcnt_del_channel(pcnt_chan));
TEST_ESP_OK(pcnt_del_unit(pcnt_unit));
}

int wave_count(int last_time)
{
int test_counter = 0;
TEST_ESP_OK(pcnt_unit_clear_count(pcnt_unit));
TEST_ESP_OK(pcnt_unit_start(pcnt_unit));
vTaskDelay(pdMS_TO_TICKS(last_time));
TEST_ESP_OK(pcnt_unit_stop(pcnt_unit));
TEST_ESP_OK(pcnt_unit_get_count(pcnt_unit, &test_counter));
return test_counter;
}
#endif
Loading

0 comments on commit 92d3355

Please sign in to comment.