You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
"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?
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.
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.
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>#definePWM_DBG(...)
// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__)// Total number of channels#definePWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX)
typedefstruct_chan_t {
// Which channel has which GPIO pin assigned?// (-1 if not assigned)gpio_num_tpin;
// Which channel has which timer assigned?// (-1 if not assigned)inttimer_idx;
} chan_t;
// List of PWM channelsSTATICchan_tchans[PWM_CHANNEL_MAX];
// channel_idx is an index (end-to-end sequential numbering) for all channels// available on the chip and described in chans[]#defineCHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel)
#defineCHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX)
#defineCHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX)
// Total number of timers#definePWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX)
// List of timer configsSTATICledc_timer_config_ttimers[PWM_TIMER_MAX];
// timer_idx is an index (end-to-end sequential numbering) for all timers// available on the chip and configured in timers[]#defineTIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer)
#defineTIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX)
#defineTIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX)
// Params for PW operation// 5khz is default frequency#definePWFREQ (5000)
// 10-bit resolution (compatible with esp8266 PWM)#definePWRES (LEDC_TIMER_10_BIT)
// Config of timer upon which we run all PWM'ed GPIO pinsSTATICboolpwm_inited= false;
// MicroPython PWM object structtypedefstruct_machine_pwm_obj_t {
mp_obj_base_tbase;
gpio_num_tpin;
boolactive;
intmode;
intchannel;
inttimer;
uint8_tduty_resolution;
} machine_pwm_obj_t;
STATICvoidpwm_init(void) {
// Initial condition: no channels assignedfor (inti=0; i<PWM_CHANNEL_MAX; ++i) {
chans[i].pin=-1;
chans[i].timer_idx=-1;
}
// Prepare all timers config// Initial condition: no timers assignedfor (inti=0; i<PWM_TIMER_MAX; ++i) {
timers[i].duty_resolution=PWRES;
// unset timer is -1timers[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;
}
}
STATICvoidconfigure_channel(machine_pwm_obj_t*self) {
ledc_channel_config_tcfg= {
.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);
}
}
STATICuint8_tset_freq(intnewval, ledc_timer_config_t*timer, uint8_tmax_duty_resolution) {
// If already set, do nothingif (newval==timer->freq_hz) {
returnmax_duty_resolution;
}
// Find the highest bit resolution for the requested frequencyif (newval <= 0) {
newval=1;
}
uint8_tres= (uint8_t) log2(LEDC_APB_CLK_HZ / newval);
if (res>max_duty_resolution) {
res=max_duty_resolution;
}
// Configure the new resolution and frequencytimer->duty_resolution=res;
timer->freq_hz=newval;
// set freqesp_err_terr=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);
}
}
returnres;
}
STATICintget_duty(machine_pwm_obj_t*self) {
uint32_tduty=ledc_get_duty(self->mode, self->channel);
duty <<= self->duty_resolution-timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
returnduty;
}
STATICvoidset_duty(machine_pwm_obj_t*self, intduty) {
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); } */
}
/******************************************************************************/#defineSAME_FREQ_ONLY (true)
#defineSAME_FREQ_OR_FREE (false)
#defineANY_MODE (-1)
// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timerSTATICintfind_timer(intfreq, boolsame_freq_only, intmode) {
intfree_timer_idx_found=-1;
// Find a free PWM Timer using the same freqfor (inttimer_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.returntimer_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.
}
}
}
returnfree_timer_idx_found;
}
// Return true if the timer is in use in addition to current channelSTATICboolis_timer_in_use(intcurrent_channel_idx, inttimer_idx) {
for (inti=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 channelSTATICintfind_channel(intpin, intmode) {
intavail_idx=-1;
intchannel_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;
}
returnchannel_idx;
}
/******************************************************************************/// MicroPython bindings for PWMSTATICvoidmp_machine_pwm_print(constmp_print_t*print, mp_obj_tself_in, mp_print_kind_tkind) {
machine_pwm_obj_t*self=MP_OBJ_TO_PTR(self_in);
mp_printf(print, "PWM(pin=%u", self->pin);
if (self->active) {
intduty=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, ")");
}
STATICvoidmp_machine_pwm_init_helper(machine_pwm_obj_t*self,
size_tn_args, constmp_obj_t*pos_args, mp_map_t*kw_args) {
enum { ARG_freq, ARG_duty, ARG_duty_resolution };
staticconstmp_arg_tallowed_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_targs[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args,
MP_ARRAY_SIZE(allowed_args), allowed_args, args);
intchannel_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
}
intfreq=args[ARG_freq].u_int;
uint8_tduty_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 argumentif (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;
}
}
inttimer_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
}
intmode=TIMER_IDX_TO_MODE(timer_idx);
if (CHANNEL_IDX_TO_MODE(channel_idx) !=mode) {
// unregister old channelchans[channel_idx].pin=-1;
chans[channel_idx].timer_idx=-1;
// find new channelchannel_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 assignmentif ((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 frequencyself->duty_resolution=set_freq(freq, &timers[timer_idx], duty_resolution);
// Set duty cycle?intduty=args[ARG_duty].u_int;
if (duty!=-1) {
set_duty(self, duty);
}
// Reset the timer if low speedif (self->mode==LEDC_LOW_SPEED_MODE) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
}
}
STATICmp_obj_tmp_machine_pwm_make_new(constmp_obj_type_t*type,
size_tn_args, size_tn_kw, constmp_obj_t*args) {
mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);
gpio_num_tpin_id=machine_pin_get_id(args[0]);
// create PWM object from the given pinmachine_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 runningif (!pwm_inited) {
pwm_init();
pwm_inited= true;
}
// start the PWM running for this channelmp_map_tkw_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);
returnMP_OBJ_FROM_PTR(self);
}
STATICvoidmp_machine_pwm_deinit(machine_pwm_obj_t*self) {
intchan=CHANNEL_IDX(self->mode, self->channel);
// Valid channel?if ((chan >= 0) && (chan<PWM_CHANNEL_MAX)) {
// Clean up timer if necessaryif (!is_timer_in_use(chan, chans[chan].timer_idx)) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
// Flag it unusedtimers[chans[chan].timer_idx].freq_hz=-1;
}
// Mark it unused, and tell the hardware to stop routingcheck_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 {
#ifLEDC_SPEED_MODE_MAX>1#ifCONFIG_IDF_TARGET_ESP32gpio_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;
}
}
STATICmp_obj_tmp_machine_pwm_maximum_duty_get(machine_pwm_obj_t*self) {
returnMP_OBJ_NEW_SMALL_INT((1 << self->duty_resolution) -1);
}
STATICmp_obj_tmp_machine_pwm_freq_get(machine_pwm_obj_t*self) {
returnMP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer));
}
STATICvoidmp_machine_pwm_freq_set(size_tn_args, constmp_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 };
staticconstmp_arg_tallowed_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_targs[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);
intfreq=args[ARG_freq].u_int;
if (freq==timers[TIMER_IDX(self->mode, self->timer)].freq_hz) {
return;
}
intcurrent_timer_idx=chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx;
boolcurrent_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 runningintnew_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 oneif ((new_timer_idx==-1) &¤t_in_use) {
// Have to find a new timernew_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 timerchans[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 timercheck_esp_err(ledc_timer_rst(self->mode, self->timer));
// Flag it unusedtimers[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_tduty_resolution=args[ARG_duty_resolution].u_int;
// Set the freqself->duty_resolution=set_freq(freq, &timers[current_timer_idx], duty_resolution);
// Reset the timer if low speedif (self->mode==LEDC_LOW_SPEED_MODE) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
}
}
STATICmp_obj_tmp_machine_pwm_duty_get(machine_pwm_obj_t*self) {
returnMP_OBJ_NEW_SMALL_INT(get_duty(self));
}
STATICvoidmp_machine_pwm_duty_set(machine_pwm_obj_t*self, mp_int_tduty) {
set_duty(self, duty);
}
/* * 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. */#ifndefMICROPY_INCLUDED_EXTMOD_MACHINE_PWM_H#defineMICROPY_INCLUDED_EXTMOD_MACHINE_PWM_H#include"py/obj.h"// A port must provide this type, but it's otherwise opaque.typedefstruct_machine_pwm_obj_tmachine_pwm_obj_t;
// This PWM class is implemented by machine_pwm.c.externconstmp_obj_type_tmachine_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.#ifndefMICROPY_PY_MACHINE_PWM_INCLUDEFILEvoidmp_machine_pwm_print(constmp_print_t*print, mp_obj_tself_in, mp_print_kind_tkind);
mp_obj_tmp_machine_pwm_make_new(constmp_obj_type_t*type, size_tn_args, size_tn_kw, constmp_obj_t*args);
voidmp_machine_pwm_init_helper(machine_pwm_obj_t*self, size_tn_args, constmp_obj_t*pos_args, mp_map_t*kw_args);
voidmp_machine_pwm_deinit(machine_pwm_obj_t*self);
mp_obj_tmp_machine_pwm_maximum_duty_get(machine_pwm_obj_t*self)
mp_obj_tmp_machine_pwm_freq_get(machine_pwm_obj_t*self);
voidmp_machine_pwm_freq_set(size_tn_args, constmp_obj_t*pos_args, mp_map_t*kw_args);
mp_obj_tmp_machine_pwm_duty_get(machine_pwm_obj_t*self);
voidmp_machine_pwm_duty_set(machine_pwm_obj_t*self, mp_int_tduty);
mp_obj_tmp_machine_pwm_duty_get_u16(machine_pwm_obj_t*self);
voidmp_machine_pwm_duty_set_u16(machine_pwm_obj_t*self, mp_int_tduty_u16);
mp_obj_tmp_machine_pwm_duty_get_ns(machine_pwm_obj_t*self);
voidmp_machine_pwm_duty_set_ns(machine_pwm_obj_t*self, mp_int_tduty_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.
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
Activity
robert-hh commentedon Oct 9, 2021
"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?
Carglglz commentedon Oct 9, 2021
I use Pin 25, but I've just figured out what's going on, in the latest versions after PR #7817
The frequency cannot be set or get after
pmw0.deinit(), however in previous versions it was possible to do soI 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 ?
robert-hh commentedon Oct 9, 2021
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.
IhorNehrutsa commentedon Oct 11, 2021
I will add duty_ns() and duty_u16()
robert-hh commentedon Oct 11, 2021
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.
IhorNehrutsa commentedon Oct 12, 2021
Correspond to
ESP32 PWM #7806
kdschlosser commentedon Oct 14, 2021
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
extmod\macine_pwm.c
'''c
/*
*/
#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
The maximum duty resolution that is supported by the ESP32 is 20 bit and right now it is capped at 10 bit.
kdschlosser commentedon Oct 14, 2021
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
IhorNehrutsa commentedon Oct 15, 2021
Could anyone test PWM.duty_u16() and PWM.duty_ns() with an oscilloscope?
IhorNehrutsa commentedon Oct 19, 2021
See the latest commits of #7907
Please report bugs to #7907
IhorNehrutsa commentedon Dec 3, 2021
May closed. Fixed in #7907