| /* |
| * This file is part of the MicroPython project, http://micropython.org/ |
| * |
| * The MIT License (MIT) |
| * |
| * Copyright (c) 2016-2021 Damien P. George |
| * Copyright (c) 2018 Alan Dragomirecky |
| * Copyright (c) 2020 Antoine Aubert |
| * Copyright (c) 2021, 2023-2025 Ihor Nehrutsa |
| * Copyright (c) 2024 Yoann Darche |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| |
| // This file is never compiled standalone, it's included directly from |
| // extmod/machine_pwm.c via MICROPY_PY_MACHINE_PWM_INCLUDEFILE. |
| |
| #include <math.h> |
| #include "py/mphal.h" |
| #include "esp_err.h" |
| #include "driver/ledc.h" |
| #include "soc/ledc_periph.h" |
| #include "soc/gpio_sig_map.h" |
| #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 FADE 0 |
| |
| // 10-bit user interface resolution compatible with esp8266 PWM.duty() |
| #define UI_RES_10_BIT (10) |
| #define DUTY_10 UI_RES_10_BIT |
| // Maximum duty value on 10-bit resolution is 1024 but reduced to 1023 in UI |
| #define MAX_10_DUTY (1U << UI_RES_10_BIT) |
| |
| // 16-bit user interface resolution used in PWM.duty_u16() |
| #define UI_RES_16_BIT (16) |
| #define DUTY_16 UI_RES_16_BIT |
| // Maximum duty value on 16-bit resolution is 65536 but reduced to 65535 in UI |
| #define MAX_16_DUTY (1U << UI_RES_16_BIT) |
| |
| // ns user interface used in PWM.duty_ns() |
| #define DUTY_NS (1) |
| |
| // 5khz is default frequency |
| #define PWM_FREQ (5000) |
| // default duty 50% |
| #define PWM_DUTY ((1U << UI_RES_16_BIT) / 2) |
| |
| // All chips except esp32 and esp32s2 do not have timer-specific clock sources, which means clock source for all timers must be the same one. |
| #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 |
| // If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used |
| #define EMPIRIC_FREQ (10) // Hz |
| #endif |
| |
| // Config of timer upon which we run all PWM'ed GPIO pins |
| static bool pwm_inited = false; |
| |
| // MicroPython PWM object struct |
| typedef struct _machine_pwm_obj_t { |
| mp_obj_base_t base; |
| int8_t pin; |
| int8_t mode; |
| int8_t channel; |
| int8_t timer; |
| 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 |
| int channel_duty; // saved values of UI duty, calculated to raw channel->duty |
| bool output_invert; |
| bool output_invert_prev; |
| } 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 lightsleep; // Is light sleep enable has been set for this pin |
| } chans_t; |
| |
| // List of PWM channels |
| static chans_t chans[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_MAX]; |
| |
| typedef struct _timers_t { |
| int32_t freq; |
| int8_t duty_resolution; |
| ledc_clk_cfg_t clk_cfg; |
| } timers_t; |
| |
| // List of PWM timers |
| 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) { |
| 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; |
| } |
| |
| 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); |
| } |
| // Initial condition: no timers assigned |
| for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { |
| 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) && (chans[mode][channel].pin >= 0)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Deinit channel and timer if the timer is unused |
| static void pwm_deinit(int mode, int channel, int level) { |
| // Is valid channel? |
| if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_CHANNEL_MAX)) { |
| // Clean up timer if necessary |
| int timer = chans[mode][channel].timer; |
| if (timer >= 0) { |
| if (!is_timer_in_use(mode, channel, timer)) { |
| check_esp_err(ledc_timer_pause(mode, timer)); |
| ledc_timer_config_t ledc_timer = { |
| .deconfigure = true, |
| .speed_mode = mode, |
| .timer_num = timer, |
| }; |
| 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].lightsleep) { |
| // Enable SLP_SEL to change GPIO status automantically in lightsleep. |
| check_esp_err(gpio_sleep_sel_en(pin)); |
| chans[mode][channel].lightsleep = false; |
| } |
| } |
| unregister_channel(mode, channel); |
| } |
| } |
| |
| // This called from Ctrl-D soft reboot |
| void machine_pwm_deinit_all(void) { |
| if (pwm_inited) { |
| for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { |
| for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { |
| pwm_deinit(mode, channel, 0); |
| } |
| } |
| #if FADE |
| ledc_fade_func_uninstall(); |
| #endif |
| pwm_inited = false; |
| } |
| } |
| |
| static void pwm_is_active(machine_pwm_obj_t *self) { |
| if (self->timer < 0) { |
| mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM is inactive")); |
| } |
| } |
| |
| // Calculate the duty parameters based on an ns value |
| static int ns_to_duty(machine_pwm_obj_t *self, int ns) { |
| pwm_is_active(self); |
| int64_t duty = ((int64_t)ns * (int64_t)MAX_16_DUTY * self->freq + 500000000LL) / 1000000000LL; |
| if ((ns > 0) && (duty == 0)) { |
| duty = 1; |
| } else if (duty > MAX_16_DUTY) { |
| duty = MAX_16_DUTY; |
| } |
| return duty; |
| } |
| |
| static int duty_to_ns(machine_pwm_obj_t *self, int duty) { |
| pwm_is_active(self); |
| 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. |
| 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); |
| } |
| |
| static void apply_duty(machine_pwm_obj_t *self) { |
| pwm_is_active(self); |
| |
| int duty = 0; |
| if (self->duty_scale == DUTY_16) { |
| duty = self->duty_ui; |
| } else if (self->duty_scale == DUTY_10) { |
| duty = self->duty_ui << (UI_RES_16_BIT - UI_RES_10_BIT); |
| } else if (self->duty_scale == DUTY_NS) { |
| duty = ns_to_duty(self, self->duty_ui); |
| } |
| self->channel_duty = duty >> (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); |
| if ((chans[self->mode][self->channel].pin == -1) || (self->output_invert_prev != self->output_invert)) { |
| self->output_invert_prev = self->output_invert; |
| // New PWM assignment |
| ledc_channel_config_t cfg = { |
| .channel = self->channel, |
| .duty = self->channel_duty, |
| .gpio_num = self->pin, |
| .intr_type = LEDC_INTR_DISABLE, |
| .speed_mode = self->mode, |
| .timer_sel = self->timer, |
| .hpoint = 0, |
| .flags.output_invert = self->output_invert, |
| }; |
| check_esp_err(ledc_channel_config(&cfg)); |
| reconfigure_pin(self); |
| } else { |
| #if FADE |
| check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, self->channel_duty, 0)); |
| #else |
| check_esp_err(ledc_set_duty(self->mode, self->channel, self->channel_duty)); |
| check_esp_err(ledc_update_duty(self->mode, self->channel)); |
| #endif |
| } |
| 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].lightsleep = true; |
| } |
| register_channel(self->mode, self->channel, self->pin, self->timer); |
| } |
| |
| static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { |
| unsigned int resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); |
| if (resolution > UI_RES_16_BIT) { |
| // limit resolution to user interface |
| resolution = UI_RES_16_BIT; |
| } |
| // Note: On ESP32, ESP32S2, ESP32S3, ESP32C3, ESP32C2, ESP32C6, ESP32H2, ESP32P4, due to a hardware bug, |
| // 100% duty cycle (i.e. 2**duty_res) is not reachable when the binded timer selects the maximum duty |
| // resolution. For example, the max duty resolution on ESP32C3 is 14-bit width, then set duty to (2**14) |
| // will mess up the duty calculation in hardware. |
| // Reduce the resolution from 14 to 13 bits to resolve the hardware bug. |
| if (resolution >= SOC_LEDC_TIMER_BIT_WIDTH) { |
| resolution -= 1; |
| } |
| return resolution; |
| } |
| |
| static uint32_t get_duty_u16(machine_pwm_obj_t *self) { |
| pwm_is_active(self); |
| int duty = ledc_get_duty(self->mode, self->channel) << (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); |
| if (duty != MAX_16_DUTY) { |
| return duty; |
| } else { |
| return MAX_16_DUTY - 1; |
| } |
| } |
| |
| static uint32_t get_duty_u10(machine_pwm_obj_t *self) { |
| // Scale down from 16 bit to 10 bit resolution |
| return get_duty_u16(self) >> (UI_RES_16_BIT - UI_RES_10_BIT); |
| } |
| |
| static uint32_t get_duty_ns(machine_pwm_obj_t *self) { |
| return duty_to_ns(self, get_duty_u16(self)); |
| } |
| |
| static void check_duty_u16(machine_pwm_obj_t *self, int duty) { |
| if ((duty < 0) || (duty > MAX_16_DUTY - 1)) { |
| mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), MAX_16_DUTY); |
| } |
| if (duty == MAX_16_DUTY - 1) { |
| duty = MAX_16_DUTY; |
| } |
| self->duty_scale = DUTY_16; |
| self->duty_ui = duty; |
| } |
| |
| static void set_duty_u16(machine_pwm_obj_t *self, int duty) { |
| check_duty_u16(self, duty); |
| apply_duty(self); |
| } |
| |
| static void check_duty_u10(machine_pwm_obj_t *self, int duty) { |
| if ((duty < 0) || (duty > MAX_10_DUTY - 1)) { |
| mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_10_DUTY - 1); |
| } |
| if (duty == MAX_10_DUTY - 1) { |
| duty = MAX_10_DUTY; |
| } |
| self->duty_scale = DUTY_10; |
| self->duty_ui = duty; |
| } |
| |
| static void set_duty_u10(machine_pwm_obj_t *self, int duty) { |
| check_duty_u10(self, duty); |
| apply_duty(self); |
| } |
| |
| static void check_duty_ns(machine_pwm_obj_t *self, int ns) { |
| if ((ns < 0) || (ns > duty_to_ns(self, MAX_16_DUTY))) { |
| mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, MAX_16_DUTY)); |
| } |
| self->duty_scale = DUTY_NS; |
| self->duty_ui = ns; |
| } |
| |
| static void set_duty_ns(machine_pwm_obj_t *self, int ns) { |
| check_duty_ns(self, ns); |
| apply_duty(self); |
| } |
| |
| #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) |
| // This check if a clock is already set in the timer list, if yes, return the LEDC_XXX value |
| static ledc_clk_cfg_t find_clock_in_use() { |
| for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { |
| for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { |
| if (timers[mode][timer].clk_cfg != LEDC_AUTO_CLK) { |
| return timers[mode][timer].clk_cfg; |
| } |
| } |
| } |
| 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) { |
| if ((freq <= 0) || (freq > upper_freq)) { |
| mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("frequency must be from 1Hz to %dMHz"), upper_freq / 1000000); |
| } |
| } |
| |
| // 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) || (self->lightsleep)) { |
| ledc_timer_config_t timer = {}; |
| timer.speed_mode = self->mode; |
| timer.timer_num = self->timer; |
| timer.freq_hz = freq; |
| timer.deconfigure = false; |
| |
| timer.clk_cfg = LEDC_AUTO_CLK; |
| #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) |
| ledc_clk_cfg_t clk_cfg = find_clock_in_use(); |
| if (clk_cfg != LEDC_AUTO_CLK) { |
| timer.clk_cfg = clk_cfg; |
| } else |
| #endif |
| if (self->lightsleep) { |
| timer.clk_cfg = LEDC_USE_RC_FAST_CLK; // 8 or 20 MHz |
| } else { |
| #if SOC_LEDC_SUPPORT_APB_CLOCK |
| timer.clk_cfg = LEDC_USE_APB_CLK; // 80 MHz |
| #elif SOC_LEDC_SUPPORT_PLL_DIV_CLOCK |
| timer.clk_cfg = LEDC_USE_PLL_DIV_CLK; // 60 or 80 or 96 MHz |
| #elif SOC_LEDC_SUPPORT_XTAL_CLOCK |
| timer.clk_cfg = LEDC_USE_XTAL_CLK; // 40 MHz |
| #else |
| #error No supported PWM / LEDC clocks. |
| #endif |
| |
| #ifdef EMPIRIC_FREQ // ESP32 and ESP32S2 only |
| if (freq < EMPIRIC_FREQ) { |
| timer.clk_cfg = LEDC_USE_REF_TICK; // 1 MHz |
| } |
| #endif |
| } |
| #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) |
| // Check for clock source conflict |
| 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 |
| |
| uint32_t src_clk_freq = 0; |
| check_esp_err(esp_clk_tree_src_get_freq_hz(timer.clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &src_clk_freq)); |
| // machine.freq(20_000_000) reduces APB_CLK_FREQ to 20MHz and the highest PWM frequency to 10MHz |
| check_freq_ranges(self, freq, src_clk_freq / 2); |
| |
| // Configure the new resolution |
| timer.duty_resolution = find_suitable_duty_resolution(src_clk_freq, self->freq); |
| // Configure timer - Set frequency |
| if ((timers[self->mode][self->timer].duty_resolution == timer.duty_resolution) && (timers[self->mode][self->timer].clk_cfg == timer.clk_cfg)) { |
| check_esp_err(ledc_set_freq(self->mode, self->timer, freq)); |
| } else { |
| check_esp_err(ledc_timer_config(&timer)); |
| } |
| // Reset the timer if low speed |
| 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; |
| } |
| } |
| |
| static bool is_free_channels(int mode, int pin) { |
| for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { |
| if ((chans[mode][channel].pin < 0) || (chans[mode][channel].pin == pin)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool is_free_timers(int mode, int32_t freq) { |
| for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { |
| if ((timers[mode][timer].freq < 0) || (timers[mode][timer].freq == freq)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Find self channel or free channel |
| static void find_channel(machine_pwm_obj_t *self, int *ret_mode, int *ret_channel, int32_t freq) { |
| // Try to find self channel first |
| for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { |
| #if SOC_LEDC_SUPPORT_HS_MODE |
| if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { |
| continue; |
| } |
| #endif |
| for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { |
| if (chans[mode][channel].pin == self->pin) { |
| *ret_mode = mode; |
| *ret_channel = channel; |
| return; |
| } |
| } |
| } |
| // Find free channel |
| for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { |
| #if SOC_LEDC_SUPPORT_HS_MODE |
| if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { |
| continue; |
| } |
| #endif |
| for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { |
| if ((chans[mode][channel].pin < 0) && is_free_timers(mode, freq)) { |
| *ret_mode = mode; |
| *ret_channel = channel; |
| return; |
| } |
| } |
| } |
| } |
| |
| // Returns timer with the same frequency, freq == -1 means free timer |
| 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->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { |
| continue; |
| } |
| #endif |
| if (is_free_channels(mode, self->pin)) { |
| for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { |
| if (timers[mode][timer].freq == freq) { |
| *ret_mode = mode; |
| *ret_timer = timer; |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| // 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 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; |
| int timer = -1; |
| // Check if an already running timer with the required frequency is running |
| find_timer(self, freq, &mode, &timer); |
| if (timer < 0) { |
| // Try to reuse self timer |
| if ((self->mode >= 0) && (self->channel >= 0)) { |
| if (!is_timer_in_use(self->mode, self->channel, self->timer)) { |
| mode = self->mode; |
| timer = self->timer; |
| } |
| } |
| // If no existing timer and channel was found, then try to find free timer in any mode |
| if (timer < 0) { |
| find_timer(self, -1, &mode, &timer); |
| } |
| } |
| if (timer < 0) { |
| 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 register |
| if (self->timer != timer) { |
| unregister_channel(self->mode, self->channel); |
| // 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) { |
| select_timer(self, freq); |
| set_freq(self, freq); |
| apply_duty(self); |
| } |
| |
| // ****************************************************************************** |
| // MicroPython bindings for PWM |
| |
| static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { |
| machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); |
| mp_printf(print, "PWM(Pin(%u)", self->pin); |
| if (self->timer >= 0) { |
| mp_printf(print, ", freq=%u", ledc_get_freq(self->mode, self->timer)); |
| if (self->duty_scale == DUTY_10) { |
| mp_printf(print, ", duty=%d", get_duty_u10(self)); |
| } else if (self->duty_scale == DUTY_NS) { |
| mp_printf(print, ", duty_ns=%d", get_duty_ns(self)); |
| } else { |
| mp_printf(print, ", duty_u16=%d", get_duty_u16(self)); |
| } |
| if (self->output_invert) { |
| mp_printf(print, ", invert=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, ")"); |
| } |
| } |
| |
| // This called from pwm.init() method |
| // |
| // Check the current mode. |
| // If the frequency is changed, try to find a timer with the same frequency |
| // in the current mode, otherwise in the new mode. |
| // If the mode is changed, release the channel and select a new channel in the new mode. |
| // Then set the frequency with the same duty. |
| 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_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_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->lightsleep = args[ARG_lightsleep].u_bool; |
| |
| int freq = args[ARG_freq].u_int; |
| if (freq != -1) { |
| check_freq_ranges(self, freq, 40000000); |
| } |
| |
| int duty = args[ARG_duty].u_int; |
| int duty_u16 = args[ARG_duty_u16].u_int; |
| int duty_ns = args[ARG_duty_ns].u_int; |
| if (duty_u16 >= 0) { |
| check_duty_u16(self, duty_u16); |
| } else if (duty_ns >= 0) { |
| check_duty_ns(self, duty_ns); |
| } else if (duty >= 0) { |
| check_duty_u10(self, duty); |
| } else if (self->duty_scale == 0) { |
| self->duty_scale = DUTY_16; |
| self->duty_ui = PWM_DUTY; |
| } |
| |
| self->output_invert = args[ARG_invert].u_bool; |
| |
| // Check the current mode and channel |
| int mode = -1; |
| 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->lightsleep ? "light sleep capable " : "", self->lightsleep ? LEDC_CHANNEL_MAX : LEDC_SPEED_MODE_MAX *LEDC_CHANNEL_MAX); |
| } |
| self->mode = mode; |
| self->channel = channel; |
| |
| // Check if freq wasn't passed as an argument |
| if ((freq == -1) && (mode >= 0) && (channel >= 0)) { |
| // Check if already set, otherwise use the default freq. |
| // It is possible in case: |
| // pwm = PWM(pin, freq=1000, duty=256) |
| // pwm = PWM(pin, duty=128) |
| if (chans[mode][channel].timer >= 0) { |
| freq = timers[mode][chans[mode][channel].timer].freq; |
| } |
| if (freq <= 0) { |
| freq = PWM_FREQ; |
| } |
| } |
| set_freq_duty(self, freq); |
| } |
| |
| static void self_reset(machine_pwm_obj_t *self) { |
| self->mode = -1; |
| self->channel = -1; |
| self->timer = -1; |
| self->freq = -1; |
| self->duty_scale = 0; |
| self->duty_ui = 0; |
| self->channel_duty = -1; |
| self->output_invert = false; |
| self->output_invert_prev = false; |
| self->lightsleep = false; |
| } |
| |
| // This called from PWM() constructor |
| static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, |
| size_t n_args, size_t n_kw, const mp_obj_t *args) { |
| mp_arg_check_num(n_args, n_kw, 1, 2, true); |
| |
| // start the PWM subsystem if it's not already running |
| if (!pwm_inited) { |
| pwm_init(); |
| #if FADE |
| ledc_fade_func_install(0); |
| #endif |
| pwm_inited = true; |
| } |
| |
| // create PWM object from the given pin |
| machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); |
| self_reset(self); |
| |
| self->pin = machine_pin_get_id(args[0]); |
| |
| // Process the remaining parameters. |
| mp_map_t kw_args; |
| mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); |
| mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); |
| return MP_OBJ_FROM_PTR(self); |
| } |
| |
| // This called from pwm.deinit() method |
| static void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { |
| pwm_deinit(self->mode, self->channel, self->output_invert); |
| self_reset(self); |
| } |
| |
| // Set and get methods of PWM class |
| |
| static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { |
| pwm_is_active(self); |
| return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); |
| } |
| |
| static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { |
| pwm_is_active(self); |
| check_freq_ranges(self, freq, 40000000); |
| if (freq == timers[self->mode][self->timer].freq) { |
| return; |
| } |
| set_freq_duty(self, freq); |
| } |
| |
| static mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { |
| return MP_OBJ_NEW_SMALL_INT(get_duty_u10(self)); |
| } |
| |
| static void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) { |
| set_duty_u10(self, duty); |
| } |
| |
| static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { |
| return MP_OBJ_NEW_SMALL_INT(get_duty_u16(self)); |
| } |
| |
| static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) { |
| set_duty_u16(self, duty_u16); |
| } |
| |
| static mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { |
| return MP_OBJ_NEW_SMALL_INT(get_duty_ns(self)); |
| } |
| |
| static void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) { |
| set_duty_ns(self, duty_ns); |
| } |