summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/esp32/quickref.rst22
-rw-r--r--docs/esp32/tutorial/pwm.rst106
-rw-r--r--docs/library/machine.PWM.rst31
-rw-r--r--ports/esp32/machine_pwm.c422
-rw-r--r--ports/esp32/main.c2
-rw-r--r--ports/esp32/modmachine.h2
-rw-r--r--ports/esp32/mpconfigport.h1
7 files changed, 448 insertions, 138 deletions
diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst
index 7391a4aa4..97b6fba38 100644
--- a/docs/esp32/quickref.rst
+++ b/docs/esp32/quickref.rst
@@ -218,20 +218,24 @@ range from 1Hz to 40MHz but there is a tradeoff; as the base frequency
*increases* the duty resolution *decreases*. See
`LED Control <https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/ledc.html>`_
for more details.
-Currently the duty cycle has to be in the range of 0-1023.
-Use the ``machine.PWM`` class::
+Use the :ref:`machine.PWM <machine.PWM>` class::
from machine import Pin, PWM
- pwm0 = PWM(Pin(0)) # create PWM object from a pin
- pwm0.freq() # get current frequency (default 5kHz)
- pwm0.freq(1000) # set frequency
- pwm0.duty() # get current duty cycle (default 512, 50%)
- pwm0.duty(200) # set duty cycle
- pwm0.deinit() # turn off PWM on the pin
+ pwm0 = PWM(Pin(0)) # create PWM object from a pin
+ pwm0.freq() # get current frequency (default 5kHz)
+ pwm0.freq(1000) # set PWM frequency from 1Hz to 40MHz
+ pwm0.duty() # get current duty cycle, range 0-1023 (default 512, 50%)
+ pwm0.duty(256) # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%)
+ pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%)
+ pwm0.duty_u16() # get current duty cycle, range 0-65535
+ pwm0.duty_ns(250_000) # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%)
+ pwm0.duty_ns() # get current pulse width in ns
+ pwm0.deinit() # turn off PWM on the pin
pwm2 = PWM(Pin(2), freq=20000, duty=512) # create and configure in one go
+ print(pwm2) # view PWM settings
ESP chips have different hardware peripherals:
@@ -251,6 +255,8 @@ but only 8 different PWM frequencies are available, the remaining 8 channels mus
have the same frequency. On the other hand, 16 independent PWM duty cycles are
possible at the same frequency.
+See more examples in the :ref:`esp32_pwm` tutorial.
+
ADC (analog to digital conversion)
----------------------------------
diff --git a/docs/esp32/tutorial/pwm.rst b/docs/esp32/tutorial/pwm.rst
index 0c1afb213..12d10a86b 100644
--- a/docs/esp32/tutorial/pwm.rst
+++ b/docs/esp32/tutorial/pwm.rst
@@ -1,4 +1,4 @@
-.. _esp32_pwm:
+.. _esp32_pwm:
Pulse Width Modulation
======================
@@ -11,7 +11,7 @@ compared with the length of a single period (low plus high time). Maximum
duty cycle is when the pin is high all of the time, and minimum is when it is
low all of the time.
-More comprehensive example with all 16 PWM channels and 8 timers::
+* More comprehensive example with all 16 PWM channels and 8 timers::
from machine import Pin, PWM
try:
@@ -29,21 +29,87 @@ More comprehensive example with all 16 PWM channels and 8 timers::
except:
pass
-Output is::
-
- PWM(pin=15, freq=100, duty=64, resolution=10, mode=0, channel=0, timer=0)
- PWM(pin=2, freq=100, duty=128, resolution=10, mode=0, channel=1, timer=0)
- PWM(pin=4, freq=200, duty=192, resolution=10, mode=0, channel=2, timer=1)
- PWM(pin=16, freq=200, duty=256, resolution=10, mode=0, channel=3, timer=1)
- PWM(pin=18, freq=300, duty=320, resolution=10, mode=0, channel=4, timer=2)
- PWM(pin=19, freq=300, duty=384, resolution=10, mode=0, channel=5, timer=2)
- PWM(pin=22, freq=400, duty=448, resolution=10, mode=0, channel=6, timer=3)
- PWM(pin=23, freq=400, duty=512, resolution=10, mode=0, channel=7, timer=3)
- PWM(pin=25, freq=500, duty=576, resolution=10, mode=1, channel=0, timer=0)
- PWM(pin=26, freq=500, duty=640, resolution=10, mode=1, channel=1, timer=0)
- PWM(pin=27, freq=600, duty=704, resolution=10, mode=1, channel=2, timer=1)
- PWM(pin=14, freq=600, duty=768, resolution=10, mode=1, channel=3, timer=1)
- PWM(pin=12, freq=700, duty=832, resolution=10, mode=1, channel=4, timer=2)
- PWM(pin=13, freq=700, duty=896, resolution=10, mode=1, channel=5, timer=2)
- PWM(pin=32, freq=800, duty=960, resolution=10, mode=1, channel=6, timer=3)
- PWM(pin=33, freq=800, duty=1023, resolution=10, mode=1, channel=7, timer=3)
+ Output is::
+
+ PWM(Pin(15), freq=100, duty=64, resolution=10, mode=0, channel=0, timer=0)
+ PWM(Pin(2), freq=100, duty=128, resolution=10, mode=0, channel=1, timer=0)
+ PWM(Pin(4), freq=200, duty=192, resolution=10, mode=0, channel=2, timer=1)
+ PWM(Pin(16), freq=200, duty=256, resolution=10, mode=0, channel=3, timer=1)
+ PWM(Pin(18), freq=300, duty=320, resolution=10, mode=0, channel=4, timer=2)
+ PWM(Pin(19), freq=300, duty=384, resolution=10, mode=0, channel=5, timer=2)
+ PWM(Pin(22), freq=400, duty=448, resolution=10, mode=0, channel=6, timer=3)
+ PWM(Pin(23), freq=400, duty=512, resolution=10, mode=0, channel=7, timer=3)
+ PWM(Pin(25), freq=500, duty=576, resolution=10, mode=1, channel=0, timer=0)
+ PWM(Pin(26), freq=500, duty=640, resolution=10, mode=1, channel=1, timer=0)
+ PWM(Pin(27), freq=600, duty=704, resolution=10, mode=1, channel=2, timer=1)
+ PWM(Pin(14), freq=600, duty=768, resolution=10, mode=1, channel=3, timer=1)
+ PWM(Pin(12), freq=700, duty=832, resolution=10, mode=1, channel=4, timer=2)
+ PWM(Pin(13), freq=700, duty=896, resolution=10, mode=1, channel=5, timer=2)
+ PWM(Pin(32), freq=800, duty=960, resolution=10, mode=1, channel=6, timer=3)
+ PWM(Pin(33), freq=800, duty=1023, resolution=10, mode=1, channel=7, timer=3)
+
+* Example of a smooth frequency change::
+
+ from utime import sleep
+ from machine import Pin, PWM
+
+ F_MIN = 500
+ F_MAX = 1000
+
+ f = F_MIN
+ delta_f = 1
+
+ p = PWM(Pin(5), f)
+ print(p)
+
+ while True:
+ p.freq(f)
+
+ sleep(10 / F_MIN)
+
+ f += delta_f
+ if f >= F_MAX or f <= F_MIN:
+ delta_f = -delta_f
+
+ See PWM wave at Pin(5) with an oscilloscope.
+
+* Example of a smooth duty change::
+
+ from utime import sleep
+ from machine import Pin, PWM
+
+ DUTY_MAX = 2**16 - 1
+
+ duty_u16 = 0
+ delta_d = 16
+
+ p = PWM(Pin(5), 1000, duty_u16=duty_u16)
+ print(p)
+
+ while True:
+ p.duty_u16(duty_u16)
+
+ sleep(1 / 1000)
+
+ duty_u16 += delta_d
+ if duty_u16 >= DUTY_MAX:
+ duty_u16 = DUTY_MAX
+ delta_d = -delta_d
+ elif duty_u16 <= 0:
+ duty_u16 = 0
+ delta_d = -delta_d
+
+ See PWM wave at Pin(5) with an oscilloscope.
+
+Note: the Pin.OUT mode does not need to be specified. The channel is initialized
+to PWM mode internally once for each Pin that is passed to the PWM constructor.
+
+The following code is wrong::
+
+ pwm = PWM(Pin(5, Pin.OUT), freq=1000, duty=512) # Pin(5) in PWM mode here
+ pwm = PWM(Pin(5, Pin.OUT), freq=500, duty=256) # Pin(5) in OUT mode here, PWM is off
+
+Use this code instead::
+
+ pwm = PWM(Pin(5), freq=1000, duty=512)
+ pwm.init(freq=500, duty=256)
diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst
index f2273d8b4..4c72255d8 100644
--- a/docs/library/machine.PWM.rst
+++ b/docs/library/machine.PWM.rst
@@ -77,3 +77,34 @@ Methods
With no arguments the pulse width in nanoseconds is returned.
With a single *value* argument the pulse width is set to that value.
+
+Limitations of PWM
+------------------
+
+* Not all frequencies can be generated with absolute accuracy due to
+ the discrete nature of the computing hardware. Typically the PWM frequency
+ is obtained by dividing some integer base frequency by an integer divider.
+ For example, if the base frequency is 80MHz and the required PWM frequency is
+ 300kHz the divider must be a non-integer number 80000000 / 300000 = 266.67.
+ After rounding the divider is set to 267 and the PWM frequency will be
+ 80000000 / 267 = 299625.5 Hz, not 300kHz. If the divider is set to 266 then
+ the PWM frequency will be 80000000 / 266 = 300751.9 Hz, but again not 300kHz.
+
+* The duty cycle has the same discrete nature and its absolute accuracy is not
+ achievable. On most hardware platforms the duty will be applied at the next
+ frequency period. Therefore, you should wait more than "1/frequency" before
+ measuring the duty.
+
+* The frequency and the duty cycle resolution are usually interdependent.
+ The higher the PWM frequency the lower the duty resolution which is available,
+ and vice versa. For example, a 300kHz PWM frequency can have a duty cycle
+ resolution of 8 bit, not 16-bit as may be expected. In this case, the lowest
+ 8 bits of *duty_u16* are insignificant. So::
+
+ pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2)
+
+ and::
+
+ pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2 + 255)
+
+ will generate PWM with the same 50% duty cycle.
diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c
index 41b8dbcbf..1cf3bc033 100644
--- a/ports/esp32/machine_pwm.c
+++ b/ports/esp32/machine_pwm.c
@@ -27,6 +27,8 @@
* THE SOFTWARE.
*/
+#include <math.h>
+
#include "py/runtime.h"
#include "py/mphal.h"
@@ -34,10 +36,11 @@
#include "esp_err.h"
#define PWM_DBG(...)
-// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__)
+// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n");
// 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)
@@ -46,6 +49,7 @@ typedef struct _chan_t {
// (-1 if not assigned)
int timer_idx;
} chan_t;
+
// List of PWM channels
STATIC chan_t chans[PWM_CHANNEL_MAX];
@@ -57,6 +61,7 @@ STATIC chan_t chans[PWM_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];
@@ -73,6 +78,28 @@ STATIC ledc_timer_config_t timers[PWM_TIMER_MAX];
// 10-bit resolution (compatible with esp8266 PWM)
#define PWRES (LEDC_TIMER_10_BIT)
+// Maximum duty value on 10-bit resolution
+#define MAX_DUTY_U10 ((1 << PWRES) - 1)
+// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html#supported-range-of-frequency-and-duty-resolutions
+// duty() uses 10-bit resolution or less
+// duty_u16() and duty_ns() use 16-bit resolution or less
+
+// Possible highest resolution in device
+#if CONFIG_IDF_TARGET_ESP32
+#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit in fact, but 16 bit is used
+#else
+#define HIGHEST_PWM_RES (LEDC_TIMER_14_BIT)
+#endif
+// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer
+#define UI_RES_16_BIT (16)
+// Maximum duty value on highest user interface resolution
+#define UI_MAX_DUTY ((1 << UI_RES_16_BIT) - 1)
+// How much to shift from the HIGHEST_PWM_RES duty resolution to the user interface duty resolution UI_RES_16_BIT
+#define UI_RES_SHIFT (16 - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3
+
+// 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
+
// Config of timer upon which we run all PWM'ed GPIO pins
STATIC bool pwm_inited = false;
@@ -84,8 +111,17 @@ typedef struct _machine_pwm_obj_t {
int mode;
int channel;
int timer;
+ int duty_x; // PWRES if duty(), HIGHEST_PWM_RES if duty_u16(), -HIGHEST_PWM_RES if duty_ns()
+ int duty_u10; // stored values from previous duty setters
+ int duty_u16; // - / -
+ int duty_ns; // - / -
} machine_pwm_obj_t;
+STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx);
+STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty);
+STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty);
+STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns);
+
STATIC void pwm_init(void) {
// Initial condition: no channels assigned
for (int i = 0; i < PWM_CHANNEL_MAX; ++i) {
@@ -96,12 +132,61 @@ STATIC void pwm_init(void) {
// Prepare all timers config
// Initial condition: no timers assigned
for (int i = 0; i < PWM_TIMER_MAX; ++i) {
- timers[i].duty_resolution = PWRES;
+ timers[i].duty_resolution = HIGHEST_PWM_RES;
// 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;
+ timers[i].clk_cfg = LEDC_AUTO_CLK; // will reinstall later according to the EMPIRIC_FREQ
+ }
+}
+
+// Deinit channel and timer if the timer is unused
+STATIC void pwm_deinit(int channel_idx) {
+ // Valid channel?
+ if ((channel_idx >= 0) && (channel_idx < PWM_CHANNEL_MAX)) {
+ // Clean up timer if necessary
+ int timer_idx = chans[channel_idx].timer_idx;
+ if (timer_idx != -1) {
+ if (!is_timer_in_use(channel_idx, timer_idx)) {
+ check_esp_err(ledc_timer_rst(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx)));
+ // Flag it unused
+ timers[chans[channel_idx].timer_idx].freq_hz = -1;
+ }
+ }
+
+ int pin = chans[channel_idx].pin;
+ if (pin != -1) {
+ int mode = CHANNEL_IDX_TO_MODE(channel_idx);
+ int channel = CHANNEL_IDX_TO_CHANNEL(channel_idx);
+ // Mark it unused, and tell the hardware to stop routing
+ check_esp_err(ledc_stop(mode, channel, 0));
+ // Disable ledc signal for the pin
+ // gpio_matrix_out(pin, SIG_GPIO_OUT_IDX, false, false);
+ if (mode == LEDC_LOW_SPEED_MODE) {
+ gpio_matrix_out(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true);
+ } else {
+ #if LEDC_SPEED_MODE_MAX > 1
+ #if CONFIG_IDF_TARGET_ESP32
+ gpio_matrix_out(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, true);
+ #else
+ #error Add supported CONFIG_IDF_TARGET_ESP32_xxx
+ #endif
+ #endif
+ }
+ }
+ chans[channel_idx].pin = -1;
+ chans[channel_idx].timer_idx = -1;
+ }
+}
+
+// This called from Ctrl-D soft reboot
+void machine_pwm_deinit_all(void) {
+ if (pwm_inited) {
+ for (int channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) {
+ pwm_deinit(channel_idx);
+ }
+ pwm_inited = false;
}
}
@@ -119,74 +204,169 @@ STATIC void configure_channel(machine_pwm_obj_t *self) {
}
}
-STATIC void set_freq(int newval, ledc_timer_config_t *timer) {
- // If already set, do nothing
- if (newval == timer->freq_hz) {
- return;
- }
+STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) {
+ // Even if the timer frequency is already set,
+ // the set_duty_x() is required to reconfigure the channel duty anyway
+ if (freq != timer->freq_hz) {
+ PWM_DBG("set_freq(%d)", freq)
- // Find the highest bit resolution for the requested frequency
- if (newval <= 0) {
- newval = 1;
- }
- unsigned int res = 0;
- for (unsigned int i = LEDC_APB_CLK_HZ / newval; i > 1; i >>= 1) {
- ++res;
- }
- if (res == 0) {
- res = 1;
- } else if (res > PWRES) {
- // Limit resolution to PWRES to match units of our duty
- res = PWRES;
- }
+ // Find the highest bit resolution for the requested frequency
+ unsigned int i = LEDC_APB_CLK_HZ; // 80 MHz
+ if (freq < EMPIRIC_FREQ) {
+ i = LEDC_REF_CLK_HZ; // 1 MHz
+ }
- // Configure the new resolution and frequency
- timer->duty_resolution = res;
- timer->freq_hz = newval;
+ #if 1
+ // original code
+ i /= freq;
+ #else
+ // See https://github.com/espressif/esp-idf/issues/7722
+ unsigned int divider = i / freq; // truncated
+ // int divider = (i + freq / 2) / freq; // rounded
+ if (divider == 0) {
+ divider = 1;
+ }
+ float f = (float)i / divider; // actual frequency
+ if (f <= 1.0) {
+ f = 1.0;
+ }
+ i = (unsigned int)roundf((float)i / f);
+ #endif
- // 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);
+ unsigned int res = 0;
+ for (; i > 1; i >>= 1) {
+ ++res;
+ }
+ if (res == 0) {
+ res = 1;
+ } else if (res > HIGHEST_PWM_RES) {
+ // Limit resolution to HIGHEST_PWM_RES to match units of our duty
+ res = HIGHEST_PWM_RES;
+ }
+
+ // Configure the new resolution and frequency
+ timer->duty_resolution = res;
+ timer->freq_hz = freq;
+ timer->clk_cfg = LEDC_USE_APB_CLK;
+ if (freq < EMPIRIC_FREQ) {
+ timer->clk_cfg = LEDC_USE_REF_TICK;
+ }
+
+ // Set frequency
+ 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("unreachable frequency %d"), freq);
+ } else {
+ check_esp_err(err);
+ }
+ }
+ // Reset the timer if low speed
+ if (self->mode == LEDC_LOW_SPEED_MODE) {
+ check_esp_err(ledc_timer_rst(self->mode, self->timer));
}
}
+
+ // Save the same duty cycle when frequency or channel are changed
+ if (self->duty_x == HIGHEST_PWM_RES) {
+ set_duty_u16(self, self->duty_u16);
+ } else if (self->duty_x == PWRES) {
+ set_duty_u10(self, self->duty_u10);
+ } else if (self->duty_x == -HIGHEST_PWM_RES) {
+ set_duty_ns(self, self->duty_ns);
+ }
}
-STATIC int get_duty(machine_pwm_obj_t *self) {
- uint32_t duty = ledc_get_duty(self->mode, self->channel);
- duty <<= PWRES - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
+// Calculate the duty parameters based on an ns value
+STATIC int ns_to_duty(machine_pwm_obj_t *self, int ns) {
+ ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)];
+ int64_t duty = ((int64_t)ns * UI_MAX_DUTY * timer.freq_hz + 500000000LL) / 1000000000LL;
+ if ((ns > 0) && (duty == 0)) {
+ duty = 1;
+ } else if (duty > UI_MAX_DUTY) {
+ duty = UI_MAX_DUTY;
+ }
+ // PWM_DBG(" ns_to_duty(UI_MAX_DUTY=%d freq_hz=%d duty=%d=%f <- ns=%d) ", UI_MAX_DUTY, timer.freq_hz, duty, (float)ns * UI_MAX_DUTY * timer.freq_hz / 1000000000.0, ns);
return duty;
}
-STATIC void set_duty(machine_pwm_obj_t *self, int duty) {
- if ((duty < 0) || (duty > (1 << PWRES) - 1)) {
- mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be between 0 and %u"), (1 << PWRES) - 1);
+STATIC int duty_to_ns(machine_pwm_obj_t *self, int duty) {
+ ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)];
+ int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY);
+ // PWM_DBG(" duty_to_ns(UI_MAX_DUTY=%d freq_hz=%d duty=%d -> ns=%f=%d) ", UI_MAX_DUTY, timer.freq_hz, duty, (float)duty * 1000000000.0 / ((float)timer.freq_hz * UI_MAX_DUTY), ns);
+ return ns;
+}
+
+#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel)
+
+STATIC uint32_t get_duty_u16(machine_pwm_obj_t *self) {
+ return ledc_get_duty(self->mode, self->channel) << (HIGHEST_PWM_RES + UI_RES_SHIFT - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution);
+}
+
+STATIC uint32_t get_duty_u10(machine_pwm_obj_t *self) {
+ return get_duty_u16(self) >> (HIGHEST_PWM_RES - PWRES);
+}
+
+STATIC uint32_t get_duty_ns(machine_pwm_obj_t *self) {
+ return duty_to_ns(self, get_duty_u16(self));
+}
+
+STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty) {
+ if ((duty < 0) || (duty > UI_MAX_DUTY)) {
+ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY);
+ }
+ duty >>= HIGHEST_PWM_RES + UI_RES_SHIFT - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
+ int max_duty = (1 << timers[TIMER_IDX(self->mode, self->timer)].duty_resolution) - 1;
+ if (duty < 0) {
+ duty = 0;
+ } else if (duty > max_duty) {
+ duty = max_duty;
}
- duty &= (1 << PWRES) - 1;
- duty >>= PWRES - 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);
+ if (duty != get_duty_u16(self)) {
+ ets_delay_us(100);
+ if (duty != get_duty_u16(self)) {
+ PWM_DBG(" (set_duty_u16(%u) get_duty_u16()=%u duty_resolution=%d) ", duty, get_duty_u16(self), timers[TIMER_IDX(self->mode, self->timer)].duty_resolution);
+ }
}
*/
+
+ self->duty_x = HIGHEST_PWM_RES;
+ self->duty_u16 = duty;
+}
+
+STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty) {
+ if ((duty < 0) || (duty > MAX_DUTY_U10)) {
+ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_U10);
+ }
+ set_duty_u16(self, duty << (HIGHEST_PWM_RES + UI_RES_SHIFT - PWRES));
+ self->duty_x = PWRES;
+ self->duty_u10 = duty;
+}
+
+STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns) {
+ if ((ns < 0) || (ns > duty_to_ns(self, UI_MAX_DUTY))) {
+ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI_MAX_DUTY));
+ }
+ set_duty_u16(self, ns_to_duty(self, ns));
+ self->duty_x = -HIGHEST_PWM_RES;
+ self->duty_ns = ns;
}
/******************************************************************************/
+
#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) {
+STATIC int find_timer(unsigned 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) {
@@ -242,22 +422,36 @@ STATIC int find_channel(int pin, int mode) {
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);
+ 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, ", freq=%u", ledc_get_freq(self->mode, self->timer));
+
+ if (self->duty_x == PWRES) {
+ mp_printf(print, ", duty=%d", get_duty_u10(self));
+ } else if (self->duty_x == -HIGHEST_PWM_RES) {
+ mp_printf(print, ", duty_ns=%d", get_duty_ns(self));
+ } else {
+ mp_printf(print, ", duty_u16=%d", get_duty_u16(self));
+ }
+ int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
+ mp_printf(print, ", resolution=%d", resolution);
+
+ mp_printf(print, ", (duty=%.2f%%, resolution=%.3f%%)", 100.0 * get_duty_raw(self) / (1 << resolution), 100.0 * 1 / (1 << resolution)); // percents
+
mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer);
}
mp_printf(print, ")");
}
+// This called from pwm.init() method
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 };
+ enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns };
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, 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_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args,
@@ -268,25 +462,40 @@ STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self,
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;
- if ((freq < -1) || (freq > 40000000)) {
- mp_raise_ValueError(MP_ERROR_TEXT("freqency must be between 1Hz and 40MHz"));
+ 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 != -1) && (duty_u16 != -1)) || ((duty != -1) && (duty_ns != -1)) || ((duty_u16 != -1) && (duty_ns != -1))) {
+ mp_raise_ValueError(MP_ERROR_TEXT("only one of parameters 'duty', 'duty_u16' or 'duty_ns' is allowed"));
}
+
+ int freq = args[ARG_freq].u_int;
// Check if freq wasn't passed as an argument
if (freq == -1) {
// Check if already set, otherwise use the default freq.
- // Possible case:
+ // It is possible in 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) {
+ if (freq <= 0) {
freq = PWFREQ;
}
}
+ if ((freq <= 0) || (freq > 40000000)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("freqency must be from 1Hz to 40MHz"));
+ }
+
+ int timer_idx;
+ int current_timer_idx = chans[channel_idx].timer_idx;
+ bool current_in_use = is_timer_in_use(channel_idx, current_timer_idx);
+ if (current_in_use) {
+ timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx));
+ } else {
+ timer_idx = chans[channel_idx].timer_idx;
+ }
- 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);
}
@@ -318,23 +527,24 @@ STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self,
self->active = true;
// Set timer frequency
- set_freq(freq, &timers[timer_idx]);
+ set_freq(self, freq, &timers[timer_idx]);
// 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));
+ if (duty_u16 != -1) {
+ set_duty_u16(self, duty_u16);
+ } else if (duty_ns != -1) {
+ set_duty_ns(self, duty_ns);
+ } else if (duty != -1) {
+ set_duty_u10(self, duty);
+ } else if (self->duty_x == 0) {
+ set_duty_u10(self, (1 << PWRES) / 2); // 50%
}
}
+// 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, MP_OBJ_FUN_ARGS_MAX, true);
+ mp_arg_check_num(n_args, n_kw, 1, 2, true);
gpio_num_t pin_id = machine_pin_get_id(args[0]);
// create PWM object from the given pin
@@ -345,6 +555,7 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type,
self->mode = -1;
self->channel = -1;
self->timer = -1;
+ self->duty_x = 0;
// start the PWM subsystem if it's not already running
if (!pwm_inited) {
@@ -360,47 +571,27 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type,
return MP_OBJ_FROM_PTR(self);
}
+// This called from pwm.deinit() method
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;
- }
+ int channel_idx = CHANNEL_IDX(self->mode, self->channel);
+ pwm_deinit(channel_idx);
+ self->active = false;
+ self->mode = -1;
+ self->channel = -1;
+ self->timer = -1;
+ self->duty_x = 0;
}
+// Set's and get's methods of PWM class
+
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(machine_pwm_obj_t *self, mp_int_t freq) {
+ if ((freq <= 0) || (freq > 40000000)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("freqency must be from 1Hz to 40MHz"));
+ }
if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) {
return;
}
@@ -441,19 +632,30 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
self->mode = TIMER_IDX_TO_MODE(current_timer_idx);
self->timer = TIMER_IDX_TO_TIMER(current_timer_idx);
- // Set the freq
- set_freq(freq, &timers[current_timer_idx]);
-
- // Reset the timer if low speed
- if (self->mode == LEDC_LOW_SPEED_MODE) {
- check_esp_err(ledc_timer_rst(self->mode, self->timer));
- }
+ // Set the frequency
+ set_freq(self, freq, &timers[current_timer_idx]);
}
STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) {
- return MP_OBJ_NEW_SMALL_INT(get_duty(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(self, 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);
}
diff --git a/ports/esp32/main.c b/ports/esp32/main.c
index c1728e318..4920180b2 100644
--- a/ports/esp32/main.c
+++ b/ports/esp32/main.c
@@ -193,6 +193,8 @@ soft_reset_exit:
mp_hal_stdout_tx_str("MPY: soft reboot\r\n");
// deinitialise peripherals
+ machine_pwm_deinit_all();
+ // TODO: machine_rmt_deinit_all();
machine_pins_deinit();
machine_deinit();
usocket_events_deinit();
diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h
index afc2ab07f..c773f8dbc 100644
--- a/ports/esp32/modmachine.h
+++ b/ports/esp32/modmachine.h
@@ -26,6 +26,8 @@ void machine_init(void);
void machine_deinit(void);
void machine_pins_init(void);
void machine_pins_deinit(void);
+void machine_pwm_deinit_all(void);
+// TODO: void machine_rmt_deinit_all(void);
void machine_timer_deinit_all(void);
void machine_i2s_init0();
diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h
index 52949c534..e49c97ab1 100644
--- a/ports/esp32/mpconfigport.h
+++ b/ports/esp32/mpconfigport.h
@@ -165,6 +165,7 @@
#define MICROPY_PY_MACHINE_PWM (1)
#define MICROPY_PY_MACHINE_PWM_INIT (1)
#define MICROPY_PY_MACHINE_PWM_DUTY (1)
+#define MICROPY_PY_MACHINE_PWM_DUTY_U16_NS (1)
#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/esp32/machine_pwm.c"
#define MICROPY_PY_MACHINE_I2C (1)
#define MICROPY_PY_MACHINE_SOFTI2C (1)