Skip to content

esp32 latest builds PWM bug #7887

Closed
Closed

Description

@Carglglz

In the latest builds (esp32-20210924-unstable-v1.17-68-g35fb90bd5.bin, esp32-20211009-unstable-v1.17-74-gd42cba0d2.bin) PWM object fails to set frequency and duty cycle.

Probably there is a mismatch between the build process and PR #7817

PWM methods:

['__class__', 'deinit', 'duty', 'freq', 'init']

So following latest docs duty_ns and duty_u16 are missing

And the errors are:

>>> pwm.duty(200)
E (227090) ledc: ledc_set_duty(485): speed_mode argument is invalid
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: (-258, 'ESP_ERR_INVALID_ARG')

>>> pwm.freq(200)
E (256550) ledc: ledc_bind_channel_timer(220): speed_mode argument is invalid
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: failed to bind timer to channel

Activity

  1. robert-hh commented on Oct 9, 2021

    @robert-hh
    Contributor

    "So following latest docs duty_ns and duty_u16 are missing"
    It's common in MicroPython that not every port offers every method, even if it would be easy to support them here as a scaling of the arguments.
    About the error message: I cannot replicate them, using e.g. Pin 14 for the PWM output. Which Pin did you try?

  2. Carglglz commented on Oct 9, 2021

    @Carglglz
    ContributorAuthor

    I use Pin 25, but I've just figured out what's going on, in the latest versions after PR #7817

    >>> from machine import Pin, PWM
    >>> pwm0 = PWM(Pin(25), freq=4000, duty=512)
    >>> pwm0.deinit()
    >>> pwm0.freq(2000)
    E (238560) ledc: ledc_bind_channel_timer(220): speed_mode argument is invalid
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: failed to bind timer to channel
    >>> pwm0.init()
    >>> pwm0.freq(2000)
    >>> pwm0.deinit()
    >>> pwm0.freq()
    E (288050) ledc: ledc_get_freq(537): speed_mode argument is invalid
    258
    >>> pwm0.duty()
    E (304560) ledc: ledc_get_duty(505): speed_mode argument is invalid
    264192
    >>> pwm0.init()
    >>> pwm0.duty()
    512
    >>> pwm0.freq()
    5000
    >>> pwm0.deinit()
    >>>
    

    The frequency cannot be set or get after pmw0.deinit() , however in previous versions it was possible to do so

    >>> from machine import Pin, PWM
    >>> pwm0 = PWM(Pin(25), freq=4000, duty=512)
    >>> pwm0.deinit()
    >>> pwm0.freq(2000)
    >>> pwm0.init()
    >>> pwm0.deinit()
    >>> pwm0.freq()
    2000
    >>> pwm0.duty()
    E (154719) ledc: ledc_get_duty(506): channel argument is invalid
    258
    >>> pwm0.init()
    >>> pwm0.freq()
    2000
    >>> pwm0.duty()
    512
    

    I don't know which behaviour is the correct one, but It's kind of a breaking change ? Perhaps worth mention it in changelog/release/docs ?

  3. robert-hh commented on Oct 9, 2021

    @robert-hh
    Contributor

    The only thing that the docs say about deinit() is, that it stops the PWM output. with v1.17 the PWM changed a lot, from a more static single frequency design to dynamic multiple frequencies. Still deinit() stops the output, but it seems to release resources bound to that python object. So what seems to be missing is a stop()/start() pair of methods, unless deinit()/init() can be considered as such.

  4. IhorNehrutsa commented on Oct 11, 2021

    @IhorNehrutsa
    Contributor

    I will add duty_ns() and duty_u16()

  5. robert-hh commented on Oct 11, 2021

    @robert-hh
    Contributor

    What seems undefined is the numeric range of duty(). For esp32, it is implemented as 0..1023. I implemented it for MIMXRT as percent value. ESP8266 has the 0..1023 range, WiPy 0..10000, pyb has a percent setting in the pyb module.
    duty_ns() and duty_u16() is well defined.

  6. IhorNehrutsa commented on Oct 12, 2021

    @IhorNehrutsa
    Contributor

    Correspond to
    ESP32 PWM #7806

  7. kdschlosser commented on Oct 14, 2021

    @kdschlosser

    I am going to chime in here. With the current code the duty resolution is capped for some reason.

    This is a version that i am running where the duty resolution can be changed by the user so it will support as large or as small a resolution as needed. I also added a function that will return the maximum duty that can be set.

    I know it will need to be refined and maybe have the resolution values set as class constants. It will give the general idea.

    ports\esp32\macine_pwm.c
    /*
    * This file is part of the Micro Python 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 Ihor Nehrutsa
    *
    * 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.
    */
    
    #include "py/runtime.h"
    #include "py/mphal.h"
    
    #include "driver/ledc.h"
    #include "esp_err.h"
    
    #include <math.h>
    
    #define PWM_DBG(...)
    // #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__)
    
    // Total number of channels
    #define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX)
    typedef struct _chan_t {
       // Which channel has which GPIO pin assigned?
       // (-1 if not assigned)
       gpio_num_t pin;
       // Which channel has which timer assigned?
       // (-1 if not assigned)
       int timer_idx;
    } chan_t;
    // List of PWM channels
    STATIC chan_t chans[PWM_CHANNEL_MAX];
    
    // channel_idx is an index (end-to-end sequential numbering) for all channels
    // available on the chip and described in chans[]
    #define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel)
    #define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX)
    #define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX)
    
    // Total number of timers
    #define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX)
    // List of timer configs
    STATIC ledc_timer_config_t timers[PWM_TIMER_MAX];
    
    // timer_idx is an index (end-to-end sequential numbering) for all timers
    // available on the chip and configured in timers[]
    #define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer)
    #define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX)
    #define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX)
    
    // Params for PW operation
    // 5khz is default frequency
    #define PWFREQ (5000)
    
    // 10-bit resolution (compatible with esp8266 PWM)
    #define PWRES (LEDC_TIMER_10_BIT)
    
    // 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;
       gpio_num_t pin;
       bool active;
       int mode;
       int channel;
       int timer;
       uint8_t duty_resolution;
    } machine_pwm_obj_t;
    
    STATIC void pwm_init(void) {
       // Initial condition: no channels assigned
       for (int i = 0; i < PWM_CHANNEL_MAX; ++i) {
           chans[i].pin = -1;
           chans[i].timer_idx = -1;
       }
    
       // Prepare all timers config
       // Initial condition: no timers assigned
       for (int i = 0; i < PWM_TIMER_MAX; ++i) {
           timers[i].duty_resolution = PWRES;
           // unset timer is -1
           timers[i].freq_hz = -1;
           timers[i].speed_mode = TIMER_IDX_TO_MODE(i);
           timers[i].timer_num = TIMER_IDX_TO_TIMER(i);
           timers[i].clk_cfg = LEDC_AUTO_CLK;
       }
    }
    
    STATIC void configure_channel(machine_pwm_obj_t *self) {
       ledc_channel_config_t cfg = {
           .channel = self->channel,
           .duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2,
           .gpio_num = self->pin,
           .intr_type = LEDC_INTR_DISABLE,
           .speed_mode = self->mode,
           .timer_sel = self->timer,
       };
       if (ledc_channel_config(&cfg) != ESP_OK) {
           mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin);
       }
    }
    
    STATIC uint8_t set_freq(int newval, ledc_timer_config_t *timer, uint8_t max_duty_resolution) {
       // If already set, do nothing
       if (newval == timer->freq_hz) {
           return max_duty_resolution;
       }
    
       // Find the highest bit resolution for the requested frequency
       if (newval <= 0) {
           newval = 1;
       }
    
       uint8_t res = (uint8_t) log2(LEDC_APB_CLK_HZ / newval);
    
       if (res > max_duty_resolution) {
           res = max_duty_resolution;
       }
    
       // Configure the new resolution and frequency
       timer->duty_resolution = res;
       timer->freq_hz = newval;
    
       // set freq
       esp_err_t err = ledc_timer_config(timer);
       if (err != ESP_OK) {
           if (err == ESP_FAIL) {
               PWM_DBG("timer timer->speed_mode %d, timer->timer_num %d, timer->clk_cfg %d, timer->freq_hz  %d, timer->duty_resolution %d)", timer->speed_mode, timer->timer_num, timer->clk_cfg, timer->freq_hz, timer->duty_resolution);
               mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("bad frequency %d"), newval);
           } else {
               check_esp_err(err);
           }
       }
       return res;
    }
    
    STATIC int get_duty(machine_pwm_obj_t *self) {
       uint32_t duty = ledc_get_duty(self->mode, self->channel);
       duty <<= self->duty_resolution - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
       return duty;
    }
    
    STATIC void set_duty(machine_pwm_obj_t *self, int duty) {
       if ((duty < 0) || (duty > (1 << self->duty_resolution) - 1)) {
           mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be between 0 and %u"), (1 << self->duty_resolution) - 1);
       }
       duty &= (1 << self->duty_resolution) - 1;
       duty >>= self->duty_resolution - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
       check_esp_err(ledc_set_duty(self->mode, self->channel, duty));
       check_esp_err(ledc_update_duty(self->mode, self->channel));
       // check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, duty, (1 << PWRES) - 1)); // thread safe function ???
    
       // Bug: Sometimes duty is not set right now.
       // See https://github.com/espressif/esp-idf/issues/7288
       /*
       if (duty != get_duty(self)) {
           PWM_DBG("\n duty_set %u %u %d %d \n", duty, get_duty(self), PWRES, timers[TIMER_IDX(self->mode, self->timer)].duty_resolution);
       }
       */
    }
    
    /******************************************************************************/
    #define SAME_FREQ_ONLY (true)
    #define SAME_FREQ_OR_FREE (false)
    #define ANY_MODE (-1)
    // Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer
    STATIC int find_timer(int freq, bool same_freq_only, int mode) {
       int free_timer_idx_found = -1;
       // Find a free PWM Timer using the same freq
       for (int timer_idx = 0; timer_idx < PWM_TIMER_MAX; ++timer_idx) {
           if ((mode == ANY_MODE) || (mode == TIMER_IDX_TO_MODE(timer_idx))) {
               if (timers[timer_idx].freq_hz == freq) {
                   // A timer already uses the same freq. Use it now.
                   return timer_idx;
               }
               if (!same_freq_only && (free_timer_idx_found == -1) && (timers[timer_idx].freq_hz == -1)) {
                   free_timer_idx_found = timer_idx;
                   // Continue to check if a channel with the same freq is in use.
               }
           }
       }
    
       return free_timer_idx_found;
    }
    
    // Return true if the timer is in use in addition to current channel
    STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx) {
       for (int i = 0; i < PWM_CHANNEL_MAX; ++i) {
           if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) {
               return true;
           }
       }
    
       return false;
    }
    
    // Find a free PWM channel, also spot if our pin is already mentioned.
    // Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel
    STATIC int find_channel(int pin, int mode) {
       int avail_idx = -1;
       int channel_idx;
       for (channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) {
           if ((mode == ANY_MODE) || (mode == CHANNEL_IDX_TO_MODE(channel_idx))) {
               if (chans[channel_idx].pin == pin) {
                   break;
               }
               if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) {
                   avail_idx = channel_idx;
               }
           }
       }
       if (channel_idx >= PWM_CHANNEL_MAX) {
           channel_idx = avail_idx;
       }
       return channel_idx;
    }
    
    /******************************************************************************/
    // 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->active) {
           int duty = get_duty(self);
           mp_printf(print, ", freq=%u, duty=%u", ledc_get_freq(self->mode, self->timer), duty);
           mp_printf(print, ", resolution=%u", timers[TIMER_IDX(self->mode, self->timer)].duty_resolution);
           mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer);
       }
       mp_printf(print, ")");
    }
    
    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_resolution };
       static const mp_arg_t allowed_args[] = {
           { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} },
           { MP_QSTR_duty, MP_ARG_INT, {.u_int = -1} },
           { MP_QSTR_duty_resolution, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = LEDC_TIMER_10_BIT} },
       };
       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);
    
       int channel_idx = find_channel(self->pin, ANY_MODE);
       if (channel_idx == -1) {
           mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes
       }
    
       int freq = args[ARG_freq].u_int;
       uint8_t duty_resolution = args[ARG_duty_resolution].u_int;
    
       if ((freq < -1) || (freq > 40000000)) {
           mp_raise_ValueError(MP_ERROR_TEXT("freqency must be between 1Hz and 40MHz"));
       }
       // Check if freq wasn't passed as an argument
       if (freq == -1) {
           // Check if already set, otherwise use the default freq.
           // Possible case:
           // pwm = PWM(pin, freq=1000, duty=256)
           // pwm = PWM(pin, duty=128)
           if (chans[channel_idx].timer_idx != -1) {
               freq = timers[chans[channel_idx].timer_idx].freq_hz;
           }
           if (freq < 0) {
               freq = PWFREQ;
           }
       }
    
       int timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx));
       if (timer_idx == -1) {
           timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, ANY_MODE);
       }
       if (timer_idx == -1) {
           mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in all modes
       }
    
       int mode = TIMER_IDX_TO_MODE(timer_idx);
       if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) {
           // unregister old channel
           chans[channel_idx].pin = -1;
           chans[channel_idx].timer_idx = -1;
           // find new channel
           channel_idx = find_channel(self->pin, mode);
           if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) {
               mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode
           }
       }
       self->mode = mode;
       self->timer = TIMER_IDX_TO_TIMER(timer_idx);
       self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx);
    
       // New PWM assignment
       if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) {
           configure_channel(self);
           chans[channel_idx].pin = self->pin;
       }
       chans[channel_idx].timer_idx = timer_idx;
       self->active = true;
    
       // Set timer frequency
       self->duty_resolution = set_freq(freq, &timers[timer_idx], duty_resolution);
    
       // Set duty cycle?
       int duty = args[ARG_duty].u_int;
       if (duty != -1) {
           set_duty(self, duty);
       }
    
       // Reset the timer if low speed
       if (self->mode == LEDC_LOW_SPEED_MODE) {
           check_esp_err(ledc_timer_rst(self->mode, self->timer));
       }
    }
    
    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, MP_OBJ_FUN_ARGS_MAX, true);
       gpio_num_t pin_id = machine_pin_get_id(args[0]);
    
       // create PWM object from the given pin
       machine_pwm_obj_t *self = m_new_obj(machine_pwm_obj_t);
       self->base.type = &machine_pwm_type;
       self->pin = pin_id;
       self->active = false;
       self->mode = -1;
       self->channel = -1;
       self->timer = -1;
       self->duty_resolution = LEDC_TIMER_10_BIT;
    
       // start the PWM subsystem if it's not already running
       if (!pwm_inited) {
           pwm_init();
           pwm_inited = true;
       }
    
       // start the PWM running for this channel
       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);
    }
    
    STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) {
       int chan = CHANNEL_IDX(self->mode, self->channel);
    
       // Valid channel?
       if ((chan >= 0) && (chan < PWM_CHANNEL_MAX)) {
           // Clean up timer if necessary
           if (!is_timer_in_use(chan, chans[chan].timer_idx)) {
               check_esp_err(ledc_timer_rst(self->mode, self->timer));
               // Flag it unused
               timers[chans[chan].timer_idx].freq_hz = -1;
           }
    
           // Mark it unused, and tell the hardware to stop routing
           check_esp_err(ledc_stop(self->mode, chan, 0));
           // Disable ledc signal for the pin
           // gpio_matrix_out(self->pin, SIG_GPIO_OUT_IDX, false, false);
           if (self->mode == LEDC_LOW_SPEED_MODE) {
               gpio_matrix_out(self->pin, LEDC_LS_SIG_OUT0_IDX + self->channel, false, true);
           } else {
               #if LEDC_SPEED_MODE_MAX > 1
               #if CONFIG_IDF_TARGET_ESP32
               gpio_matrix_out(self->pin, LEDC_HS_SIG_OUT0_IDX + self->channel, false, true);
               #else
               #error Add supported CONFIG_IDF_TARGET_ESP32_xxx
               #endif
               #endif
           }
           chans[chan].pin = -1;
           chans[chan].timer_idx = -1;
           self->active = false;
           self->mode = -1;
           self->channel = -1;
           self->timer = -1;
       }
    }
    
    STATIC mp_obj_t mp_machine_pwm_maximum_duty_get(machine_pwm_obj_t *self) {
       return MP_OBJ_NEW_SMALL_INT((1 << self->duty_resolution) - 1);
    }
    
    STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) {
       return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer));
    }
    
    STATIC void mp_machine_pwm_freq_set(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
       machine_pwm_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
    
       enum { ARG_freq, ARG_duty_resolution };
       static const mp_arg_t allowed_args[] = {
           { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} },
           { MP_QSTR_duty_resolution, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = LEDC_TIMER_10_BIT} },
       };
       mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
       mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
    
       int freq = args[ARG_freq].u_int;
    
       if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) {
           return;
       }
    
       int current_timer_idx = chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx;
       bool current_in_use = is_timer_in_use(CHANNEL_IDX(self->mode, self->channel), current_timer_idx);
    
       // Check if an already running timer with the same freq is running
       int new_timer_idx = find_timer(freq, SAME_FREQ_ONLY, self->mode);
    
       // If no existing timer was found, and the current one is in use, then find a new one
       if ((new_timer_idx == -1) && current_in_use) {
           // Have to find a new timer
           new_timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, self->mode);
    
           if (new_timer_idx == -1) {
               mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in current mode
           }
       }
    
       if ((new_timer_idx != -1) && (new_timer_idx != current_timer_idx)) {
           // Bind the channel to the new timer
           chans[self->channel].timer_idx = new_timer_idx;
    
           if (ledc_bind_channel_timer(self->mode, self->channel, TIMER_IDX_TO_TIMER(new_timer_idx)) != ESP_OK) {
               mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel"));
           }
    
           if (!current_in_use) {
               // Free the old timer
               check_esp_err(ledc_timer_rst(self->mode, self->timer));
               // Flag it unused
               timers[current_timer_idx].freq_hz = -1;
           }
    
           current_timer_idx = new_timer_idx;
       }
       self->mode = TIMER_IDX_TO_MODE(current_timer_idx);
       self->timer = TIMER_IDX_TO_TIMER(current_timer_idx);
    
       uint8_t duty_resolution = args[ARG_duty_resolution].u_int;
    
       // Set the freq
       self->duty_resolution = set_freq(freq, &timers[current_timer_idx], duty_resolution);
    
       // Reset the timer if low speed
       if (self->mode == LEDC_LOW_SPEED_MODE) {
           check_esp_err(ledc_timer_rst(self->mode, self->timer));
       }
    }
    
    STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) {
       return MP_OBJ_NEW_SMALL_INT(get_duty(self));
    }
    
    STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) {
       set_duty(self, duty);
    }
    extmod\macine_pwm.c

    '''c
    /*

    • This file is part of the MicroPython project, http://micropython.org/
    • The MIT License (MIT)
    • Copyright (c) 2020-2021 Damien P. George
    • 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.
      */

    #include "py/runtime.h"

    #if MICROPY_PY_MACHINE_PWM

    #include "extmod/machine_pwm.h"

    #ifdef MICROPY_PY_MACHINE_PWM_INCLUDEFILE
    #include MICROPY_PY_MACHINE_PWM_INCLUDEFILE
    #endif

    #if MICROPY_PY_MACHINE_PWM_INIT
    STATIC mp_obj_t machine_pwm_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
    mp_machine_pwm_init_helper(args[0], n_args - 1, args + 1, kw_args);
    return mp_const_none;
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pwm_init_obj, 1, machine_pwm_init);
    #endif

    // PWM.deinit()
    STATIC mp_obj_t machine_pwm_deinit(mp_obj_t self_in) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    mp_machine_pwm_deinit(self);
    return mp_const_none;
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pwm_deinit_obj, machine_pwm_deinit);

    STATIC mp_obj_t machine_pwm_maximum_duty(mp_obj_t self_in) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    return mp_machine_pwm_maximum_duty_get(self);
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pwm_maximum_duty_obj, machine_pwm_maximum_duty);

    // PWM.freq([value])
    STATIC mp_obj_t machine_pwm_freq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
    if (n_args == 1) {
    // Get frequency.
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
    return mp_machine_pwm_freq_get(self);
    } else {
    // Set the frequency.
    mp_machine_pwm_freq_set(n_args, pos_args, kw_args);
    return mp_const_none;
    }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pwm_freq_obj, 1, machine_pwm_freq);

    #if MICROPY_PY_MACHINE_PWM_DUTY
    // PWM.duty([duty])
    STATIC mp_obj_t machine_pwm_duty(size_t n_args, const mp_obj_t *args) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    if (n_args == 1) {
    // Get duty cycle.
    return mp_machine_pwm_duty_get(self);
    } else {
    // Set duty cycle.
    mp_int_t duty = mp_obj_get_int(args[1]);
    mp_machine_pwm_duty_set(self, duty);
    return mp_const_none;
    }
    }

    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_duty_obj, 1, 2, machine_pwm_duty);
    #endif

    #if MICROPY_PY_MACHINE_PWM_DUTY_U16_NS

    // PWM.duty_u16([value])
    STATIC mp_obj_t machine_pwm_duty_u16(size_t n_args, const mp_obj_t *args) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    if (n_args == 1) {
    // Get duty cycle.
    return mp_machine_pwm_duty_get_u16(self);
    } else {
    // Set duty cycle.
    mp_int_t duty_u16 = mp_obj_get_int(args[1]);
    mp_machine_pwm_duty_set_u16(self, duty_u16);
    return mp_const_none;
    }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_duty_u16_obj, 1, 2, machine_pwm_duty_u16);

    // PWM.duty_ns([value])
    STATIC mp_obj_t machine_pwm_duty_ns(size_t n_args, const mp_obj_t *args) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    if (n_args == 1) {
    // Get duty cycle.
    return mp_machine_pwm_duty_get_ns(self);
    } else {
    // Set duty cycle.
    mp_int_t duty_ns = mp_obj_get_int(args[1]);
    mp_machine_pwm_duty_set_ns(self, duty_ns);
    return mp_const_none;
    }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_duty_ns_obj, 1, 2, machine_pwm_duty_ns);

    #endif

    STATIC const mp_rom_map_elem_t machine_pwm_locals_dict_table[] = {
    #if MICROPY_PY_MACHINE_PWM_INIT
    { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pwm_init_obj) },
    #endif
    { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_pwm_deinit_obj) },
    { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_pwm_freq_obj) },
    #if MICROPY_PY_MACHINE_PWM_DUTY
    { MP_ROM_QSTR(MP_QSTR_duty), MP_ROM_PTR(&machine_pwm_duty_obj) },
    #endif
    #if MICROPY_PY_MACHINE_PWM_DUTY_U16_NS
    { MP_ROM_QSTR(MP_QSTR_duty_u16), MP_ROM_PTR(&machine_pwm_duty_u16_obj) },
    { MP_ROM_QSTR(MP_QSTR_duty_ns), MP_ROM_PTR(&machine_pwm_duty_ns_obj) },
    #endif
    { MP_ROM_QSTR(MP_QSTR_maximum_duty), MP_ROM_PTR(&machine_pwm_maximum_duty_obj) },
    };
    STATIC MP_DEFINE_CONST_DICT(machine_pwm_locals_dict, machine_pwm_locals_dict_table);

    const mp_obj_type_t machine_pwm_type = {
    { &mp_type_type },
    .name = MP_QSTR_PWM,
    .print = mp_machine_pwm_print,
    .make_new = mp_machine_pwm_make_new,
    .locals_dict = (mp_obj_dict_t *)&machine_pwm_locals_dict,
    };

    #endif // MICROPY_PY_MACHINE_PWM
    '''

    extmod\macine_pwm.h
    /*
     * This file is part of the MicroPython project, http://micropython.org/
     *
     * The MIT License (MIT)
     *
     * Copyright (c) 2021 Damien P. George
     *
     * 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.
     */
    
    #ifndef MICROPY_INCLUDED_EXTMOD_MACHINE_PWM_H
    #define MICROPY_INCLUDED_EXTMOD_MACHINE_PWM_H
    
    #include "py/obj.h"
    
    // A port must provide this type, but it's otherwise opaque.
    typedef struct _machine_pwm_obj_t machine_pwm_obj_t;
    
    // This PWM class is implemented by machine_pwm.c.
    extern const mp_obj_type_t machine_pwm_type;
    
    // A port must provide implementations of these low-level PWM functions, either as global
    // linker symbols, or included directly if MICROPY_PY_MACHINE_PWM_INCLUDEFILE is defined.
    #ifndef MICROPY_PY_MACHINE_PWM_INCLUDEFILE
    void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
    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);
    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);
    void mp_machine_pwm_deinit(machine_pwm_obj_t *self);
    mp_obj_t mp_machine_pwm_maximum_duty_get(machine_pwm_obj_t *self)
    mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self);
    void mp_machine_pwm_freq_set(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
    mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self);
    void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty);
    mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self);
    void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16);
    mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self);
    void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns);
    #endif
    
    #endif // MICROPY_INCLUDED_EXTMOD_MACHINE_PWM_H

    The maximum duty resolution that is supported by the ESP32 is 20 bit and right now it is capped at 10 bit.

  8. kdschlosser commented on Oct 14, 2021

    @kdschlosser

    IDK if other MCUs have an adjustable duty resolution or not, I haven't looked into it... I am sure that giving the user the flexibility to set it to whatever it is they want would be ideal. I believe it can be done without breaking any of the API as seen with the ESP32 example.

    With the ESP32 if a user sets it higher then what is allowed for the frequency supplied it will be lowered, hence the reason for adding the function to get the maximum possible duty that can be set. Right now a user is operating blindly with respect to that.

    If wanting to use a 0-100 value I would highly recommend being able to pass floats. if only integers are able to be passed you end up with a 7 bit duty resolution. floats or uint32_t take up the same amount of memory so it might be ideal to go with floats and have the reranging from a 0.0-100.0 scale to the scale needed based on the duty resolution used can be handled in C code.

    using a % based scale would keep everything uniform no matter what the duty resolution is that is set, the duty resolution would allow for a higher precision

  9. IhorNehrutsa commented on Oct 15, 2021

    @IhorNehrutsa
    Contributor

    Could anyone test PWM.duty_u16() and PWM.duty_ns() with an oscilloscope?

  10. IhorNehrutsa commented on Oct 19, 2021

    @IhorNehrutsa
    Contributor

    See the latest commits of #7907
    Please report bugs to #7907

  11. IhorNehrutsa commented on Dec 3, 2021

    @IhorNehrutsa
    Contributor

    May closed. Fixed in #7907

  12. ghost closed this as completedon Dec 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions