Files
micropython/ports/esp32/machine_pwm.c
IhorNehrutsa 150a5aa3a1 esp32/machine_pwm: Improve PWM and make its API match other ports.
This reduce inconsistencies between esp32 PWM and other ports:
1. duty_u16() high value is 2**16-1 == 65535
2. Invert PWM wave with invert=1 parameter
3. Enable PWM in light sleep mode
4. Allow PWM output and read pulse input simultaneously on the same Pin()
5. Code refactoring

Co-Authored-By: Angus Gratton <angus@redyak.com.au>
Co-Authored-By: robert-hh <robert@hammelrath.com>
Co-Authored-By: Andrew Leech <andrew.leech@planetinnovation.com.au>
Co-Authored-By: Yoann Darche <yoannd@hotmail.com>

Signed-off-by: Ihor Nehrutsa <Ihor.Nehrutsa@gmail.com>
2025-05-16 12:35:58 +10:00

789 lines
29 KiB
C

/*
* 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);
}