Skip to content

Commit

Permalink
esp32/machine_pwm.c: Fix lightsleep.
Browse files Browse the repository at this point in the history
Rename light_sleep_enable to lightsleep.

Signed-off-by: Ihor Nehrutsa <Ihor.Nehrutsa@gmail.com>
  • Loading branch information
IhorNehrutsa committed Feb 20, 2025
1 parent 59338e1 commit 117fedc
Showing 1 changed file with 94 additions and 47 deletions.
141 changes: 94 additions & 47 deletions ports/esp32/machine_pwm.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
#include "esp_clk_tree.h"
#include "py/mpprint.h"

#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, " | %d at %s\n", __LINE__, __FILE__);
#define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, " | %d at %s\n", __LINE__, __FILE__);

#define FADE 0

Expand Down Expand Up @@ -80,7 +80,7 @@ typedef struct _machine_pwm_obj_t {
int8_t mode;
int8_t channel;
int8_t timer;
bool light_sleep_enable;
bool lightsleep;
int32_t freq;
int8_t duty_scale; // DUTY_10 if duty(), DUTY_16 if duty_u16(), DUTY_NS if duty_ns()
int duty_ui; // saved values of UI duty
Expand All @@ -90,9 +90,9 @@ typedef struct _machine_pwm_obj_t {
} machine_pwm_obj_t;

typedef struct _chans_t {
int8_t pin; // Which channel has which GPIO pin assigned? (-1 if not assigned)
int8_t timer; // Which channel has which timer assigned? (-1 if not assigned)
bool light_sleep_enable; // Is light sleep enable has been set for this pin
int8_t pin; // Which channel has which GPIO pin assigned? (-1 if not assigned)
int8_t timer; // Which channel has which timer assigned? (-1 if not assigned)
bool lightsleep; // Is light sleep enable has been set for this pin
} chans_t;

// List of PWM channels
Expand All @@ -109,33 +109,38 @@ static timers_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX];

// register-unregister channel
static void register_channel(int mode, int channel, int pin, int timer) {
if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_CHANNEL_MAX)) {
chans[mode][channel].pin = pin;
chans[mode][channel].timer = timer;
}
chans[mode][channel].pin = pin;
chans[mode][channel].timer = timer;
}

static void unregister_channel(int mode, int channel) {
register_channel(mode, channel, -1, -1);
chans[mode][channel].lightsleep = false;
}

static void unregister_timer(int mode, int timer) {
timers[mode][timer].freq = -1; // unused timer freq is -1
timers[mode][timer].duty_resolution = 0;
timers[mode][timer].clk_cfg = LEDC_AUTO_CLK;
}
#define unregister_channel(mode, channel) register_channel(mode, channel, -1, -1)

static void pwm_init(void) {
for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) {
// Initial condition: no channels assigned
for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) {
unregister_channel(mode, channel);
chans[mode][channel].light_sleep_enable = false;
}
// Initial condition: no timers assigned
for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) {
timers[mode][timer].freq = -1; // unset timer is -1
timers[mode][timer].duty_resolution = 0;
timers[mode][timer].clk_cfg = LEDC_AUTO_CLK;
unregister_timer(mode, timer);
}
}
}

// Returns true if the timer is in use in addition to current channel
static bool is_timer_in_use(int mode, int current_channel, int timer) {
for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) {
if ((channel != current_channel) && (chans[mode][channel].timer == timer)) {
if ((channel != current_channel) && (chans[mode][channel].timer == timer) && (chans[mode][channel].pin >= 0)) {
return true;
}
}
Expand All @@ -156,22 +161,19 @@ static void pwm_deinit(int mode, int channel, int level) {
.speed_mode = mode,
.timer_num = timer,
};
check_esp_err(ledc_timer_config(&ledc_timer));
// Flag it unused
timers[mode][timer].freq = -1;
timers[mode][timer].duty_resolution = 0;
timers[mode][timer].clk_cfg = LEDC_AUTO_CLK;
ledc_timer_config(&ledc_timer);
unregister_timer(mode, timer);
}
}

int pin = chans[mode][channel].pin;
if (pin >= 0) {
// Disable LEDC output, and set idle level
check_esp_err(ledc_stop(mode, channel, level));
if (chans[mode][channel].light_sleep_enable) {
if (chans[mode][channel].lightsleep) {
// Enable SLP_SEL to change GPIO status automantically in lightsleep.
check_esp_err(gpio_sleep_sel_en(pin));
chans[mode][channel].light_sleep_enable = false;
chans[mode][channel].lightsleep = false;
}
}
unregister_channel(mode, channel);
Expand Down Expand Up @@ -216,9 +218,10 @@ static int duty_to_ns(machine_pwm_obj_t *self, int duty) {
return ((int64_t)duty * 1000000000LL + (int64_t)self->freq * (int64_t)(MAX_16_DUTY / 2)) / ((int64_t)self->freq * (int64_t)MAX_16_DUTY);
}

// Reconfigure PWM pin output as input/output. This allows to read the pin level.
// Reconfigure PWM pin output as input/output.
static void reconfigure_pin(machine_pwm_obj_t *self) {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 0)
// This allows to read the pin level.
gpio_set_direction(self->pin, GPIO_MODE_INPUT_OUTPUT);
#endif
esp_rom_gpio_connect_out_signal(self->pin, ledc_periph_signal[self->mode].sig_out0_idx + self->channel, self->output_invert, 0);
Expand Down Expand Up @@ -259,10 +262,10 @@ static void apply_duty(machine_pwm_obj_t *self) {
check_esp_err(ledc_update_duty(self->mode, self->channel));
#endif
}
if (self->light_sleep_enable) {
if (self->lightsleep) {
// Disable SLP_SEL to change GPIO status automantically in lightsleep.
check_esp_err(gpio_sleep_sel_dis(self->pin));
chans[self->mode][self->channel].light_sleep_enable = true;
chans[self->mode][self->channel].lightsleep = true;
}
register_channel(self->mode, self->channel, self->pin, self->timer);
}
Expand Down Expand Up @@ -360,6 +363,16 @@ static ledc_clk_cfg_t find_clock_in_use() {
}
return LEDC_AUTO_CLK;
}

// Check if a timer is already set with a different clock source
static bool is_timer_with_different_clock(int mode, int current_timer, ledc_clk_cfg_t clk_cfg) {
for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) {
if ((timer != current_timer) && (clk_cfg != LEDC_AUTO_CLK) && (timers[mode][timer].clk_cfg != LEDC_AUTO_CLK) && (timers[mode][timer].clk_cfg != clk_cfg)) {
return true;
}
}
return false;
}
#endif

static void check_freq_ranges(machine_pwm_obj_t *self, int freq, int upper_freq) {
Expand All @@ -371,9 +384,7 @@ static void check_freq_ranges(machine_pwm_obj_t *self, int freq, int upper_freq)
// Set timer frequency
static void set_freq(machine_pwm_obj_t *self, unsigned int freq) {
self->freq = freq;
if (timers[self->mode][self->timer].freq != freq) {
timers[self->mode][self->timer].freq = freq;

if ((timers[self->mode][self->timer].freq != freq) || (self->lightsleep)) {
ledc_timer_config_t timer = {};
timer.speed_mode = self->mode;
timer.timer_num = self->timer;
Expand All @@ -382,7 +393,7 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) {

timer.clk_cfg = LEDC_AUTO_CLK;

if (self->light_sleep_enable) {
if (self->lightsleep) {
timer.clk_cfg = LEDC_USE_RC_FAST_CLK; // 8 or 20 MHz
} else {
#if SOC_LEDC_SUPPORT_APB_CLOCK
Expand All @@ -402,9 +413,9 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) {
#endif
}
#if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2)
// Check for clock source conflic
ledc_clk_cfg_t pwm_clk = find_clock_in_use();
if ((pwm_clk != LEDC_AUTO_CLK) && (pwm_clk != timer.clk_cfg)) {
// Check for clock source conflict
ledc_clk_cfg_t clk_cfg = find_clock_in_use();
if ((clk_cfg != LEDC_AUTO_CLK) && (clk_cfg != timer.clk_cfg)) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("one or more active timers use a different clock source, not supported by the current SoC."));
}
#endif
Expand All @@ -426,6 +437,7 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) {
if (self->mode == LEDC_LOW_SPEED_MODE) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
}
timers[self->mode][self->timer].freq = freq;
timers[self->mode][self->timer].duty_resolution = timer.duty_resolution;
timers[self->mode][self->timer].clk_cfg = timer.clk_cfg;
}
Expand Down Expand Up @@ -454,7 +466,7 @@ static void find_channel(machine_pwm_obj_t *self, int *ret_mode, int *ret_channe
// Try to find self channel first
for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) {
#if SOC_LEDC_SUPPORT_HS_MODE
if (self->light_sleep_enable && (mode == LEDC_HIGH_SPEED_MODE)) {
if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) {
continue;
}
#endif
Expand All @@ -469,7 +481,7 @@ static void find_channel(machine_pwm_obj_t *self, int *ret_mode, int *ret_channe
// Find free channel
for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) {
#if SOC_LEDC_SUPPORT_HS_MODE
if (self->light_sleep_enable && (mode == LEDC_HIGH_SPEED_MODE)) {
if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) {
continue;
}
#endif
Expand All @@ -487,7 +499,7 @@ static void find_channel(machine_pwm_obj_t *self, int *ret_mode, int *ret_channe
static void find_timer(machine_pwm_obj_t *self, int freq, int *ret_mode, int *ret_timer) {
for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) {
#if SOC_LEDC_SUPPORT_HS_MODE
if (self->light_sleep_enable && (mode == LEDC_HIGH_SPEED_MODE)) {
if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) {
continue;
}
#endif
Expand All @@ -505,7 +517,7 @@ static void find_timer(machine_pwm_obj_t *self, int freq, int *ret_mode, int *re

// Try to find a timer with the same frequency in the current mode, otherwise in the next mode.
// If no existing timer and channel was found, then try to find free timer in any mode.
// If the mode or channel is changed, release the channel and select(bind) a new channel in the next mode.
// If the mode or channel is changed, release the channel and register a new channel in the next mode.
static void select_timer(machine_pwm_obj_t *self, int freq) {
// mode, channel, timer may be -1(not defined) or actual values
int mode = -1;
Expand All @@ -526,16 +538,21 @@ static void select_timer(machine_pwm_obj_t *self, int freq) {
}
}
if (timer < 0) {
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM timers:%d"), self->light_sleep_enable ? "light sleep capable " : "", self->light_sleep_enable ? LEDC_TIMER_MAX : LEDC_SPEED_MODE_MAX *LEDC_TIMER_MAX);
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM timers:%d"), self->lightsleep ? "light sleep capable " : "", self->lightsleep ? LEDC_TIMER_MAX : LEDC_SPEED_MODE_MAX *LEDC_TIMER_MAX);
}
// If the timer is found, then bind
// If the timer is found, then register
if (self->timer != timer) {
unregister_channel(self->mode, self->channel);
// Bind the channel to the timer
// Rregister the channel to the timer
self->mode = mode;
self->timer = timer;
register_channel(self->mode, self->channel, -1, self->timer);
}
#if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2)
if (is_timer_with_different_clock(self->mode, self->timer, timers[self->mode][self->timer].clk_cfg)) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("one or more active timers use a different clock source, not supported by the current SoC."));
}
#endif
}

static void set_freq_duty(machine_pwm_obj_t *self, unsigned int freq) {
Expand All @@ -560,17 +577,47 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p
mp_printf(print, ", duty_u16=%d", get_duty_u16(self));
}
if (self->output_invert) {
mp_printf(print, ", invert=%s", self->output_invert ? "True" : "False");
mp_printf(print, ", invert=True");
}
if (self->light_sleep_enable) {
mp_printf(print, ", light_sleep_enable=True");
if (self->lightsleep) {
mp_printf(print, ", lightsleep=True");
}
mp_printf(print, ")");

#if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL
mp_printf(print, " # duty=%.2f%%", 100.0 * get_duty_u16(self) / MAX_16_DUTY);
mp_printf(print, ", raw_duty=%d, resolution=%d", ledc_get_duty(self->mode, self->channel), timers[self->mode][self->timer].duty_resolution);
mp_printf(print, ", mode=%d, timer=%d, channel=%d", self->mode, self->timer, self->channel);
int clk_cfg = timers[self->mode][self->timer].clk_cfg;
mp_printf(print, ", clk_cfg=%d=", clk_cfg);
if (clk_cfg == LEDC_USE_RC_FAST_CLK) {
mp_printf(print, "RC_FAST_CLK");
}
#if SOC_LEDC_SUPPORT_APB_CLOCK
else if (clk_cfg == LEDC_USE_APB_CLK) {
mp_printf(print, "APB_CLK");
}
#endif
#if SOC_LEDC_SUPPORT_XTAL_CLOCK
else if (clk_cfg == LEDC_USE_XTAL_CLK) {
mp_printf(print, "XTAL_CLK");
}
#endif
#if SOC_LEDC_SUPPORT_REF_TICK
else if (clk_cfg == LEDC_USE_REF_TICK) {
mp_printf(print, "REF_TICK");
}
#endif
#if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK
else if (clk_cfg == LEDC_USE_PLL_DIV_CLK) {
mp_printf(print, "PLL_CLK");
}
#endif
else if (clk_cfg == LEDC_AUTO_CLK) {
mp_printf(print, "AUTO_CLK");
} else {
mp_printf(print, "UNKNOWN");
}
#endif
} else {
mp_printf(print, ")");
Expand All @@ -587,20 +634,20 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p
static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self,
size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {

enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_light_sleep_enable };
enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_lightsleep };
mp_arg_t allowed_args[] = {
{ MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = self->output_invert} },
{ MP_QSTR_light_sleep_enable, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = self->light_sleep_enable} },
{ MP_QSTR_lightsleep, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = self->lightsleep} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args,
MP_ARRAY_SIZE(allowed_args), allowed_args, args);

self->light_sleep_enable = args[ARG_light_sleep_enable].u_bool;
self->lightsleep = args[ARG_lightsleep].u_bool;

int freq = args[ARG_freq].u_int;
if (freq != -1) {
Expand Down Expand Up @@ -628,7 +675,7 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self,
int channel = -1;
find_channel(self, &mode, &channel, freq);
if (channel < 0) {
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM channels:%d"), self->light_sleep_enable ? "light sleep capable " : "", self->light_sleep_enable ? LEDC_CHANNEL_MAX : LEDC_SPEED_MODE_MAX *LEDC_CHANNEL_MAX);
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM channels:%d"), self->lightsleep ? "light sleep capable " : "", self->lightsleep ? LEDC_CHANNEL_MAX : LEDC_SPEED_MODE_MAX *LEDC_CHANNEL_MAX);
}
self->mode = mode;
self->channel = channel;
Expand Down Expand Up @@ -659,7 +706,7 @@ static void self_reset(machine_pwm_obj_t *self) {
self->channel_duty = -1;
self->output_invert = false;
self->output_invert_prev = false;
self->light_sleep_enable = false;
self->lightsleep = false;
}

// This called from PWM() constructor
Expand Down

0 comments on commit 117fedc

Please sign in to comment.