From c2faa6b7ff4d161c7fc9362778b59b6e812baa19 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 5 Jan 2024 13:05:25 +0200 Subject: [PATCH] esp32\machine_pwm: PWM reduce inconsist. Signed-off-by: IhorNehrutsa --- docs/esp32/quickref.rst | 36 +- docs/esp32/tutorial/pwm.rst | 197 +++++++-- docs/library/machine.PWM.rst | 18 +- ports/esp32/machine_pwm.c | 765 ++++++++++++++++++++--------------- 4 files changed, 637 insertions(+), 379 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 3ab4e8f5ecd96..2906ead522de5 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -306,11 +306,11 @@ Use the :ref:`machine.PWM ` class:: freq = pwm0.freq() # get current frequency pwm0.freq(1000) # set PWM frequency from 1Hz to 40MHz - duty = 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%) + duty = pwm0.duty() # get current duty cycle, range 0-1024 (default 512, 50%) + pwm0.duty(256) # set duty cycle from 0 to 1024 as a ratio duty/1024, (now 25%) - duty_u16 = pwm0.duty_u16() # get current duty cycle, range 0-65535 - pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%) + duty_u16 = pwm0.duty_u16() # get current duty cycle, range 0-65536 + pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65536 as a ratio duty_u16/65536, (now 75%) duty_ns = pwm0.duty_ns() # get current pulse width in ns pwm0.duty_ns(250_000) # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%) @@ -319,19 +319,27 @@ Use the :ref:`machine.PWM ` class:: pwm2 = PWM(Pin(2), freq=20000, duty=512) # create and configure in one go print(pwm2) # view PWM settings + pwm2.deinit() # turn off PWM on the pin + + pwm0 = PWM(Pin(0), duty_u16=16384) # The output is at a high level 25% of the time. + pwm2 = PWM(Pin(2), duty_u16=16384, invert=1) # The output is at a low level 25% of the time. ESP chips have different hardware peripherals: -===================================================== ======== ======== ======== -Hardware specification ESP32 ESP32-S2 ESP32-C3 ------------------------------------------------------ -------- -------- -------- -Number of groups (speed modes) 2 1 1 -Number of timers per group 4 4 4 -Number of channels per group 8 8 6 ------------------------------------------------------ -------- -------- -------- -Different PWM frequencies (groups * timers) 8 4 4 -Total PWM channels (Pins, duties) (groups * channels) 16 8 6 -===================================================== ======== ======== ======== +======================================================= ======== ========= ========== +Hardware specification ESP32 ESP32-S2, ESP32-C2, + ESP32-S3, ESP32-C3, + ESP32-P2 ESP32-C5, + ESP32-C6, + ESP32-H2 +------------------------------------------------------- -------- --------- ---------- +Number of groups (speed modes) 2 1 1 +Number of timers per group 4 4 4 +Number of channels per group 8 8 6 +------------------------------------------------------- -------- --------- ---------- +Different PWM frequencies = (groups * timers) 8 4 4 +Total PWM channels (Pins, duties) = (groups * channels) 16 8 6 +======================================================= ======== ========= ========== A maximum number of PWM channels (Pins) are available on the ESP32 - 16 channels, but only 8 different PWM frequencies are available, the remaining 8 channels must diff --git a/docs/esp32/tutorial/pwm.rst b/docs/esp32/tutorial/pwm.rst index 2650284d35f41..2e1b82a585108 100644 --- a/docs/esp32/tutorial/pwm.rst +++ b/docs/esp32/tutorial/pwm.rst @@ -11,17 +11,19 @@ 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 time import sleep from machine import Pin, PWM try: f = 100 # Hz - d = 1024 // 16 # 6.25% - pins = (15, 2, 4, 16, 18, 19, 22, 23, 25, 26, 27, 14 , 12, 13, 32, 33) + d = 2**16 // 16 # 6.25% + pins = (2, 4, 12, 13, 14, 15, 16, 18, 19, 22, 23, 25, 26, 27, 32, 33) pwms = [] for i, pin in enumerate(pins): - pwms.append(PWM(Pin(pin), freq=f * (i // 2 + 1), duty= 1023 if i==15 else d * (i + 1))) + pwms.append(PWM(Pin(pin), freq=f * (i // 2 + 1), duty_u16=min(2**16 - 1, d * (i + 1)))) print(pwms[i]) + sleep(60) finally: for pwm in pwms: try: @@ -31,49 +33,81 @@ low all of the time. 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:: + PWM(Pin(2), freq=100, duty_u16=4096) # resolution=16, (duty=6.25%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(4), freq=100, duty_u16=8192) # resolution=16, (duty=12.50%, resolution=0.002%), mode=0, channel=1, timer=0 + PWM(Pin(12), freq=199, duty_u16=12288) # resolution=16, (duty=18.75%, resolution=0.002%), mode=0, channel=2, timer=1 + PWM(Pin(13), freq=199, duty_u16=16384) # resolution=16, (duty=25.00%, resolution=0.002%), mode=0, channel=3, timer=1 + PWM(Pin(14), freq=299, duty_u16=20480) # resolution=16, (duty=31.25%, resolution=0.002%), mode=0, channel=4, timer=2 + PWM(Pin(15), freq=299, duty_u16=24576) # resolution=16, (duty=37.50%, resolution=0.002%), mode=0, channel=5, timer=2 + PWM(Pin(16), freq=400, duty_u16=28672) # resolution=16, (duty=43.75%, resolution=0.002%), mode=0, channel=6, timer=3 + PWM(Pin(18), freq=400, duty_u16=32768) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=7, timer=3 + PWM(Pin(19), freq=500, duty_u16=36864) # resolution=16, (duty=56.25%, resolution=0.002%), mode=1, channel=0, timer=0 + PWM(Pin(22), freq=500, duty_u16=40960) # resolution=16, (duty=62.50%, resolution=0.002%), mode=1, channel=1, timer=0 + PWM(Pin(23), freq=599, duty_u16=45056) # resolution=16, (duty=68.75%, resolution=0.002%), mode=1, channel=2, timer=1 + PWM(Pin(25), freq=599, duty_u16=49152) # resolution=16, (duty=75.00%, resolution=0.002%), mode=1, channel=3, timer=1 + PWM(Pin(26), freq=700, duty_u16=53248) # resolution=16, (duty=81.25%, resolution=0.002%), mode=1, channel=4, timer=2 + PWM(Pin(27), freq=700, duty_u16=57344) # resolution=16, (duty=87.50%, resolution=0.002%), mode=1, channel=5, timer=2 + PWM(Pin(32), freq=799, duty_u16=61440) # resolution=16, (duty=93.75%, resolution=0.002%), mode=1, channel=6, timer=3 + PWM(Pin(33), freq=799, duty_u16=65536) # resolution=16, (duty=100.00%, resolution=0.002%), mode=1, channel=7, timer=3 + + +* Example of a **smooth frequency change**:: from time import sleep from machine import Pin, PWM - F_MIN = 500 + F_MIN = 100 F_MAX = 1000 f = F_MIN - delta_f = 1 + delta_f = 100 - p = PWM(Pin(5), f) - print(p) + p = PWM(Pin(27), f) while True: p.freq(f) + print(p) - sleep(10 / F_MIN) + sleep(.2) f += delta_f - if f >= F_MAX or f <= F_MIN: + if f > F_MAX or f < F_MIN: delta_f = -delta_f + print() + if f > F_MAX: + f = F_MAX + elif f < F_MIN: + f = F_MIN + + `See PWM wave on Pin(27) with an oscilloscope. `_ - See PWM wave at Pin(5) with an oscilloscope. + Output is:: -* Example of a smooth duty change:: + PWM(Pin(27), freq=100, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=199, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=299, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=400, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=500, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=599, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=700, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=799, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=900, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + + PWM(Pin(27), freq=998, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=900, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=799, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=700, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=599, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=500, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=400, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=299, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=199, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=100, duty=512) # resolution=16, (duty=50.00%, resolution=0.002%), mode=0, channel=0, timer=0 + ... + + +* Example of a **smooth duty change**:: from time import sleep from machine import Pin, PWM @@ -81,25 +115,116 @@ low all of the time. DUTY_MAX = 2**16 - 1 duty_u16 = 0 - delta_d = 16 + delta_d = 256 - p = PWM(Pin(5), 1000, duty_u16=duty_u16) - print(p) + pwm = PWM(Pin(27), 1000, duty_u16=duty_u16) + print(pwm) while True: - p.duty_u16(duty_u16) + pwm.duty_u16(duty_u16) - sleep(1 / 1000) + sleep(.001) + + print(pwm) duty_u16 += delta_d if duty_u16 >= DUTY_MAX: duty_u16 = DUTY_MAX delta_d = -delta_d + print() elif duty_u16 <= 0: duty_u16 = 0 delta_d = -delta_d + print() + + See `PWM wave on Pin(27) with an oscilloscope. `_ + + Output is:: + + PWM(Pin(27), freq=998, duty_u16=0) # resolution=16, (duty=0.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=256) # resolution=16, (duty=0.39%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=512) # resolution=16, (duty=0.78%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=768) # resolution=16, (duty=1.17%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=1024) # resolution=16, (duty=1.56%, resolution=0.002%), mode=0, channel=0, timer=0 + ... + PWM(Pin(27), freq=998, duty_u16=64256) # resolution=16, (duty=98.05%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=64512) # resolution=16, (duty=98.44%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=64768) # resolution=16, (duty=98.83%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65024) # resolution=16, (duty=99.22%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65280) # resolution=16, (duty=99.61%, resolution=0.002%), mode=0, channel=0, timer=0 + + PWM(Pin(27), freq=998, duty_u16=65536) # resolution=16, (duty=100.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65279) # resolution=16, (duty=99.61%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65023) # resolution=16, (duty=99.22%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=64767) # resolution=16, (duty=98.83%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=64511) # resolution=16, (duty=98.44%, resolution=0.002%), mode=0, channel=0, timer=0 + ... + PWM(Pin(27), freq=998, duty_u16=1279) # resolution=16, (duty=1.95%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=1024) # resolution=16, (duty=1.56%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=767) # resolution=16, (duty=1.17%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=511) # resolution=16, (duty=0.78%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=255) # resolution=16, (duty=0.39%, resolution=0.002%), mode=0, channel=0, timer=0 + + PWM(Pin(27), freq=998, duty_u16=0) # resolution=16, (duty=0.00%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=256) # resolution=16, (duty=0.39%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=512) # resolution=16, (duty=0.78%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=768) # resolution=16, (duty=1.17%, resolution=0.002%), mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=1024) # resolution=16, (duty=1.56%, resolution=0.002%), mode=0, channel=0, timer=0 + + +* Example of a **smooth duty change and PWM output inversion**:: + + from utime import sleep + from machine import Pin, PWM + + try: + DUTY_MAX = 2**16 - 1 + + duty_u16 = 0 + delta_d = 2**16 // 32 + + pwm = PWM(Pin(27), 5000) + pwmi = PWM(Pin(32), 5000, invert=1) + + while True: + pwm.duty_u16(duty_u16) + pwmi.duty_u16(duty_u16) + + 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 + + sleep(.01) + print(pwm) + print(pwmi) + + finally: + try: + pwm.deinit() + except: + pass + try: + pwmi.deinit() + except: + pass + + Output is:: + + ... + PWM(Pin(27), freq=5000, duty_u16=24576) # resolution=13, (duty=37.50%, resolution=0.012%), mode=0, channel=0, timer=0 + PWM(Pin(32), freq=5000, duty_u16=24576, invert=1) # resolution=13, (duty=37.50%, resolution=0.012%), mode=0, channel=1, timer=0 + PWM(Pin(27), freq=5000, duty_u16=26624) # resolution=13, (duty=40.63%, resolution=0.012%), mode=0, channel=0, timer=0 + PWM(Pin(32), freq=5000, duty_u16=26624, invert=1) # resolution=13, (duty=40.63%, resolution=0.012%), mode=0, channel=1, timer=0 + PWM(Pin(27), freq=5000, duty_u16=28672) # resolution=13, (duty=43.75%, resolution=0.012%), mode=0, channel=0, timer=0 + PWM(Pin(32), freq=5000, duty_u16=28672, invert=1) # resolution=13, (duty=43.75%, resolution=0.012%), mode=0, channel=1, timer=0 + ... + + See `PWM waves on Pin(27) and Pin(32) `_ with an oscilloscope. - 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. diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst index 5f592b8dff593..dde8175de70b8 100644 --- a/docs/library/machine.PWM.rst +++ b/docs/library/machine.PWM.rst @@ -11,20 +11,20 @@ Example usage:: from machine import PWM pwm = PWM(pin, freq=50, duty_u16=8192) # create a PWM object on a pin - # and set freq and duty - pwm.duty_u16(32768) # set duty to 50% + # and set freq 50 Hz and duty 12.5% + pwm.duty_u16(32768) # set duty to 50% # reinitialise with a period of 200us, duty of 5us pwm.init(freq=5000, duty_ns=5000) - pwm.duty_ns(3000) # set pulse width to 3us + pwm.duty_ns(3000) # set pulse width to 3us pwm.deinit() Constructors ------------ -.. class:: PWM(dest, *, freq, duty_u16, duty_ns, invert) +.. class:: PWM(dest, *, freq, duty_u16, duty_ns, invert=False) Construct and return a new PWM object using the following parameters: @@ -40,7 +40,7 @@ Constructors Setting *freq* may affect other PWM objects if the objects share the same underlying PWM generator (this is hardware specific). Only one of *duty_u16* and *duty_ns* should be specified at a time. - *invert* is not available at all ports. + *invert* is available at RP2, i.MXRT, SAMD, nRF, ESP32 ports. Methods ------- @@ -73,6 +73,14 @@ Methods With a single *value* argument the duty cycle is set to that value, measured as the ratio ``value / 65535``. + Use functions like these to convert percentages to u16 and back:: + + def percents_to_u16(percents:int)->int: + return (percents * 2**16 + 50) // 100 + + def u16_to_percents(u16:int)->int: + return (u16 * 100 + 2**15) // 2**16 + .. method:: PWM.duty_ns([value]) Get or set the current pulse width of the PWM output, as a value in nanoseconds. diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 61efdfa18548b..b964998e6277e 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -6,7 +6,7 @@ * Copyright (c) 2016-2021 Damien P. George * Copyright (c) 2018 Alan Dragomirecky * Copyright (c) 2020 Antoine Aubert - * Copyright (c) 2021 Ihor Nehrutsa + * Copyright (c) 2021, 2023, 2024 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 @@ -32,16 +32,18 @@ #include #include "py/mphal.h" +#include "hal/ledc_hal.h" #include "driver/ledc.h" #include "esp_err.h" -#include "esp_clk_tree.h" #include "soc/gpio_sig_map.h" -#define PWM_DBG(...) -// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) +#include "esp_clk_tree.h" +#endif + +#include "py/mpprint.h" -// Total number of channels -#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX) +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) typedef struct _chan_t { // Which channel has which GPIO pin assigned? @@ -49,29 +51,14 @@ typedef struct _chan_t { gpio_num_t pin; // Which channel has which timer assigned? // (-1 if not assigned) - int timer_idx; + int timer; } 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) +static chan_t chans[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_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) +static ledc_timer_config_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; // Params for PWM operation // 5khz is default frequency @@ -81,29 +68,37 @@ static ledc_timer_config_t timers[PWM_TIMER_MAX]; #define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) // Maximum duty value on 10-bit resolution -#define MAX_DUTY_U10 ((1 << PWM_RES_10_BIT) - 1) +#define MAX_DUTY_U10 (1 << PWM_RES_10_BIT) // 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 (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT +// 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) + +#if defined(SOC_LEDC_TIMER_BIT_WIDTH) +#if SOC_LEDC_TIMER_BIT_WIDTH < 16 #define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) #else #define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used #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 (UI_RES_16_BIT - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3 +#elif defined(SOC_LEDC_TIMER_BIT_WIDE_NUM) +#if SOC_LEDC_TIMER_BIT_WIDE_NUM < 16 +#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) +#else +#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used +#endif +#else +#error HIGHEST_PWM_RES +#endif -#if SOC_LEDC_SUPPORT_REF_TICK +// All chips except esp32 and esp32s2 do not have timer-specific clock sources, which means clock source for all timers must be the same one. +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 // If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used #define EMPIRIC_FREQ (10) // Hz #endif - // Config of timer upon which we run all PWM'ed GPIO pins static bool pwm_inited = false; @@ -111,84 +106,100 @@ static bool pwm_inited = false; typedef struct _machine_pwm_obj_t { mp_obj_base_t base; gpio_num_t pin; - bool active; int mode; int channel; int timer; - int duty_x; // PWM_RES_10_BIT 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; // - / - + int duty_x; // PWM_RES_10_BIT if duty(), HIGHEST_PWM_RES if duty_u16(), -HIGHEST_PWM_RES if duty_ns() + int duty; // saved values from previous duty setters + int channel_duty; // saved values from previous duty setters calculated to raw channel_duty + uint8_t output_invert; } 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 register_channel(int mode, int channel, int pin, int timer) { + if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_CHANNEL_MAX)) { + chans[mode][channel].pin = pin; + chans[mode][channel].timer = timer; + } +} + +static void unregister_channel(int mode, int channel) { + if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_CHANNEL_MAX)) { + chans[mode][channel].pin = -1; + chans[mode][channel].timer = -1; + } +} 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; + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + unregister_channel(mode, channel); + } + + // Prepare all timers config + // Initial condition: no timers assigned + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + timers[mode][timer].duty_resolution = HIGHEST_PWM_RES; + timers[mode][timer].freq_hz = 0; // unset timer is 0 + timers[mode][timer].speed_mode = mode; + timers[mode][timer].timer_num = timer; + timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; // will reinstall later + } } +} - // Prepare all timers config - // Initial condition: no timers assigned - for (int i = 0; i < PWM_TIMER_MAX; ++i) { - 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; // will reinstall later according to the EMPIRIC_FREQ +// Returns true if the timer is in use in addition to current channel +static bool is_timer_in_use(int mode, int current_channel, int timer) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((channel != current_channel) && (chans[mode][channel].timer == timer)) { + return true; + } } + return false; } -// 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)) { +// Deinit channel and timer if the timer is unused, detach pin +static void pwm_deinit(int mode, int channel) { + // Is valid channel? + if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_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))); + int timer = chans[mode][channel].timer; + if (timer >= 0) { + if (!is_timer_in_use(mode, channel, timer)) { + check_esp_err(ledc_timer_rst(mode, timer)); // Flag it unused - timers[chans[channel_idx].timer_idx].freq_hz = -1; + timers[mode][timer].freq_hz = 0; } } - 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); + int pin = chans[mode][channel].pin; + if (pin >= 0) { // Mark it unused, and tell the hardware to stop routing check_esp_err(ledc_stop(mode, channel, 0)); + /* // Disable ledc signal for the pin - // esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); if (mode == LEDC_LOW_SPEED_MODE) { - esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true); + esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, false); } else { - #if LEDC_SPEED_MODE_MAX > 1 - #if CONFIG_IDF_TARGET_ESP32 - esp_rom_gpio_connect_out_signal(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, true); - #else - #error Add supported CONFIG_IDF_TARGET_ESP32_xxx - #endif + #if SOC_LEDC_SUPPORT_HS_MODE + esp_rom_gpio_connect_out_signal(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, false); #endif } + // reconfigure as GPIO + //gpio_set_direction(pin, GPIO_MODE_INPUT_OUTPUT); + */ } - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; + unregister_channel(mode, channel); } } // 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); + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + pwm_deinit(mode, channel); + } } pwm_inited = false; } @@ -197,72 +208,74 @@ void machine_pwm_deinit_all(void) { 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, + .duty = self->channel_duty, .gpio_num = self->pin, .intr_type = LEDC_INTR_DISABLE, .speed_mode = self->mode, .timer_sel = self->timer, + .flags.output_invert = self->output_invert, }; - 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); + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + int max_duty = 1 << timer->duty_resolution; + if (self->channel_duty == max_duty) { + cfg.duty = 0; + cfg.flags.output_invert = self->output_invert ^ 1; + } + check_esp_err(ledc_channel_config(&cfg)); + + // reconfigure PWM output for Counter input + gpio_set_direction(self->pin, GPIO_MODE_INPUT_OUTPUT); + if (self->mode == LEDC_LOW_SPEED_MODE) { + esp_rom_gpio_connect_out_signal(self->pin, LEDC_LS_SIG_OUT0_IDX + self->channel, self->output_invert, false); + #if SOC_LEDC_SUPPORT_HS_MODE + } else if (self->mode == LEDC_HIGH_SPEED_MODE) { + esp_rom_gpio_connect_out_signal(self->pin, LEDC_HS_SIG_OUT0_IDX + self->channel, self->output_invert, false); + #endif } } -static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { - if (freq != timer->freq_hz) { - // Configure the new frequency and resolution - timer->freq_hz = freq; +static unsigned int calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { + unsigned int divider = (src_clk_freq + timer_freq / 2) / timer_freq; // rounded + if (divider == 0) { + divider = 1; + } + return divider; +} - #if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK - timer->clk_cfg = LEDC_USE_PLL_DIV_CLK; - #elif SOC_LEDC_SUPPORT_APB_CLOCK - timer->clk_cfg = LEDC_USE_APB_CLK; - #elif SOC_LEDC_SUPPORT_XTAL_CLOCK - timer->clk_cfg = LEDC_USE_XTAL_CLK; - #else - #error No supported PWM / LEDC clocks. - #endif - #if SOC_LEDC_SUPPORT_REF_TICK - if (freq < EMPIRIC_FREQ) { - timer->clk_cfg = LEDC_USE_REF_TICK; - } - #endif - uint32_t src_clk_freq = 0; - esp_err_t err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); - if (err != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); - } - timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); +// Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) +static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + // This implementation is based on the one used in Micropython v1.23 - // Set frequency - err = ledc_timer_config(timer); - if (err != ESP_OK) { - if (err == ESP_FAIL) { - 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)); - } + // Find the highest bit resolution for the requested frequency + unsigned int freq = src_clk_freq; + unsigned int divider = calc_divider(freq, timer_freq); + float f = (float)freq / divider; // actual frequency + if (f <= 1.0) { + f = 1.0; } + freq = (unsigned int)roundf((float)freq / f); - // Save the same duty cycle when frequency is changed - if (self->duty_x == HIGHEST_PWM_RES) { - set_duty_u16(self, self->duty_u16); - } else if (self->duty_x == PWM_RES_10_BIT) { - set_duty_u10(self, self->duty_u10); - } else if (self->duty_x == -HIGHEST_PWM_RES) { - set_duty_ns(self, self->duty_ns); + unsigned int res = 0; + for (; freq > 1; freq >>= 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; + } + + return res; } +#endif // 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; + ledc_timer_config_t *timer = &timers[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) { @@ -272,54 +285,55 @@ static int ns_to_duty(machine_pwm_obj_t *self, int ns) { } 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); + ledc_timer_config_t *timer = &timers[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); return ns; } #define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) static void pwm_is_active(machine_pwm_obj_t *self) { - if (self->active == false) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM inactive")); + if (self->timer < 0) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM is inactive")); } } static uint32_t get_duty_u16(machine_pwm_obj_t *self) { - pwm_is_active(self); - int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; - int duty = ledc_get_duty(self->mode, self->channel); - if (resolution <= UI_RES_16_BIT) { - duty <<= (UI_RES_16_BIT - resolution); + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + int max_duty = 1 << timer->duty_resolution; + if (self->channel_duty == max_duty) { + return UI_MAX_DUTY; } else { - duty >>= (resolution - UI_RES_16_BIT); + int resolution = timers[self->mode][self->timer].duty_resolution; + if (resolution <= UI_RES_16_BIT) { + return get_duty_raw(self) << (UI_RES_16_BIT - resolution); + } else { + return get_duty_raw(self) >> (resolution - UI_RES_16_BIT); + } } - return duty; } static uint32_t get_duty_u10(machine_pwm_obj_t *self) { - pwm_is_active(self); - return get_duty_u16(self) >> 6; // Scale down from 16 bit to 10 bit resolution + return get_duty_u16(self) >> (UI_RES_16_BIT - PWM_RES_10_BIT); // Scale down from 16 bit to 10 bit resolution } static uint32_t get_duty_ns(machine_pwm_obj_t *self) { - pwm_is_active(self); return duty_to_ns(self, get_duty_u16(self)); } static void set_duty_u16(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); 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); } - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; + + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; int channel_duty; - if (timer.duty_resolution <= UI_RES_16_BIT) { - channel_duty = duty >> (UI_RES_16_BIT - timer.duty_resolution); + if (timer->duty_resolution <= UI_RES_16_BIT) { + channel_duty = duty >> (UI_RES_16_BIT - timer->duty_resolution); } else { - channel_duty = duty << (timer.duty_resolution - UI_RES_16_BIT); + channel_duty = duty << (timer->duty_resolution - UI_RES_16_BIT); } - int max_duty = (1 << timer.duty_resolution) - 1; + int max_duty = 1 << timer->duty_resolution; if (channel_duty < 0) { channel_duty = 0; } else if (channel_duty > max_duty) { @@ -328,74 +342,104 @@ static void set_duty_u16(machine_pwm_obj_t *self, int duty) { check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); check_esp_err(ledc_update_duty(self->mode, self->channel)); - /* - // Bug: Sometimes duty is not set right now. - // Not a bug. It's a feature. The duty is applied at the beginning of the next signal period. - // Bug: It has been experimentally established that the duty is set during 2 signal periods, but 1 period is expected. - // See https://github.com/espressif/esp-idf/issues/7288 - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - esp_rom_delay_us(2 * 1000000 / timer.freq_hz); - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - } - } - */ - self->duty_x = HIGHEST_PWM_RES; - self->duty_u16 = duty; + self->duty = duty; + self->channel_duty = channel_duty; } static void set_duty_u10(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); 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 << (UI_RES_16_BIT - PWM_RES_10_BIT)); self->duty_x = PWM_RES_10_BIT; - self->duty_u10 = duty; + self->duty = duty; } static void set_duty_ns(machine_pwm_obj_t *self, int ns) { - pwm_is_active(self); 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; + self->duty = ns; } -/******************************************************************************/ +static void set_duty(machine_pwm_obj_t *self) { + if (self->duty_x == HIGHEST_PWM_RES) { + set_duty_u16(self, self->duty); + } else if (self->duty_x == PWM_RES_10_BIT) { + set_duty_u10(self, self->duty); + } else if (self->duty_x == -HIGHEST_PWM_RES) { + set_duty_ns(self, self->duty); + } +} -#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(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) { - 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. - } +// Set timer frequency +static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + if (timer->freq_hz != freq) { + timer->freq_hz = freq; + timer->clk_cfg = LEDC_AUTO_CLK; + uint32_t src_clk_freq = 0; + + #if SOC_LEDC_SUPPORT_APB_CLOCK + timer->clk_cfg = LEDC_USE_APB_CLK; + src_clk_freq = APB_CLK_FREQ; // 80 MHz + #elif SOC_LEDC_SUPPORT_PLL_DIV_CLOCK + timer->clk_cfg = LEDC_USE_PLL_DIV_CLK; + #elif SOC_LEDC_SUPPORT_XTAL_CLOCK + timer->clk_cfg = LEDC_USE_XTAL_CLK; + src_clk_freq = XTAL_CLK_FREQ; // 40 MHz + #else + #error No supported PWM / LEDC clocks. + #endif + + #ifdef EMPIRIC_FREQ + if (freq < EMPIRIC_FREQ) { + #if SOC_LEDC_SUPPORT_REF_TICK + timer->clk_cfg = LEDC_USE_REF_TICK; + src_clk_freq = REF_CLK_FREQ; // 1 MHz + #else + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) + timer->clk_cfg = LEDC_USE_RC_FAST_CLK; + src_clk_freq = SOC_CLK_RC_FAST_FREQ_APPROX; // 8.5 or 17.5 MHz + #elif defined(XTAL_CLK_FREQ) + timer->clk_cfg = LEDC_USE_XTAL_CLK; + src_clk_freq = XTAL_CLK_FREQ; // 40MHz // 32MHz + #endif + #endif } - } + #endif + + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) + esp_err_t err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); + if (err != ESP_OK) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); + } + #endif - return free_timer_idx_found; + // Configure the new resolution and frequency + timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); + + // Configure timer - Set frequency + if (ESP_OK != ledc_timer_config(timer)) { + unsigned int divider = calc_divider(src_clk_freq, timer->freq_hz); + check_esp_err(ledc_timer_set(timer->speed_mode, timer->timer_num, divider, timer->duty_resolution, timer->clk_cfg)); + } + // 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 is changed + set_duty(self); + } } -// 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)) { +static bool is_free_channels(int mode, int pin) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((chans[mode][channel].pin < 0) || (chans[mode][channel].pin == pin)) { return true; } } @@ -403,25 +447,109 @@ static bool is_timer_in_use(int current_channel_idx, int timer_idx) { 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; +// Find self channel or free channel in the mode +static int find_channel(int mode, int pin) { + int avail_channel = -1; + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if (chans[mode][channel].pin == pin) { + return channel; + } + if ((avail_channel < 0) && (chans[mode][channel].pin < 0)) { + avail_channel = channel; + } + } + return avail_channel; +} + +// Returns timer with the same mode and frequency, freq == 0 means free timer +static int find_timer(int mode, unsigned int freq) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if (timers[mode][timer].freq_hz == freq) { + return timer; + } + } + return -1; +} + +// Try to find a timer with the same frequency in the current mode, otherwise in the next mode. +// If no existing timer and channel was found, then try to find free timer in any mode. +// If the mode or channel is changed, release the channel and select(bind) a new channel in the next mode. +static void select_a_timer(machine_pwm_obj_t *self, int freq) { + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); + } + + // mode, channel, timer may be -1(not defined) or actual values + int save_mode = self->mode; + int save_channel = self->channel; + // int save_timer = self->timer; + + int mode = MAX(self->mode, 0); + + // Check if an already running timer with the required frequency is running in the current mode + int timer = -1; + if (is_free_channels(mode, self->pin)) { + timer = find_timer(mode, freq); + } + // If no existing timer and channel was found in the current mode, then find a new one in another mode + if (timer < 0) { + // Calc next mode + int mode_ = mode; + if (mode > 0) { + --mode; + } else if (mode < (LEDC_SPEED_MODE_MAX - 1)) { + ++mode; + } + + if (mode_ != mode) { + if (is_free_channels(mode, self->pin)) { + timer = find_timer(mode, freq); } - if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) { - avail_idx = channel_idx; + } + } + // If the timer is found, then bind and set the duty + if ((timer >= 0) + && (timers[mode][timer].freq_hz != 0) + && (timers[mode][timer].freq_hz != freq) + && (self->channel >= 0) + && (self->mode >= 0)) { + // Bind the channel to the timer + self->mode = mode; + self->timer = timer; + check_esp_err(ledc_bind_channel_timer(self->mode, self->channel, self->timer)); + register_channel(self->mode, self->channel, self->pin, self->timer); + set_duty(self); + } else { + timer = -1; + } + + if (timer < 0) { + // Try to reuse self timer + if ((self->mode >= 0) && (self->channel >= 0)) { + if (!is_timer_in_use(self->mode, self->channel, self->timer)) { + mode = self->mode; + timer = self->timer; } } + // If no existing timer and channel was found, then try to find free timer in any mode + if (timer < 0) { + mode = -1; + while ((timer < 0) && (mode < (LEDC_SPEED_MODE_MAX - 1))) { + ++mode; + if (is_free_channels(mode, self->pin)) { + timer = find_timer(mode, 0); // find free timer + } + } + if (timer < 0) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX); + } + } + self->mode = mode; + self->timer = timer; } - if (channel_idx >= PWM_CHANNEL_MAX) { - channel_idx = avail_idx; + if ((save_mode != self->mode) || (save_channel != self->channel)) { + unregister_channel(save_mode, save_channel); } - return channel_idx; } /******************************************************************************/ @@ -430,7 +558,7 @@ 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); - if (self->active) { + if (self->timer >= 0) { mp_printf(print, ", freq=%u", ledc_get_freq(self->mode, self->timer)); if (self->duty_x == PWM_RES_10_BIT) { @@ -440,111 +568,117 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p } 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); + if (self->output_invert) { + mp_printf(print, ", invert=%d", self->output_invert); + } + mp_printf(print, ")"); + #if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL + int resolution = timers[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, ", (duty=%.2f%%, resolution=%.6f%%)", 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); + #endif + } else { + mp_printf(print, ")"); } - mp_printf(print, ")"); } // This called from pwm.init() method +/* +Check the current mode. +If the frequency is changed, try to find a timer with the same frequency +in the current mode, otherwise in the new mode. +If the mode is changed, release the channel and select a new channel in the new mode. +Then set the frequency with the same duty. +*/ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns }; + + enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert }; static const mp_arg_t allowed_args[] = { { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0}}, }; 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 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")); } + */ + /* + if ((duty < 0) && (duty_u16 < 0) && (duty_ns < 0)) { + mp_raise_ValueError(MP_ERROR_TEXT("one of parameters 'duty', 'duty_u16', or 'duty_ns' is required")); + } + */ + if (duty_u16 >= 0) { + self->duty_x = HIGHEST_PWM_RES; + self->duty = duty_u16; + } else if (duty_ns >= 0) { + self->duty_x = -HIGHEST_PWM_RES; + self->duty = duty_ns; + } else if (duty >= 0) { + self->duty_x = PWM_RES_10_BIT; + self->duty = duty; + } else if (self->duty_x == 0) { + self->duty_x = HIGHEST_PWM_RES; + self->duty = (1 << HIGHEST_PWM_RES) / 2; // 50% + } + + self->output_invert = args[ARG_invert].u_int == 0 ? 0 : 1; + + int save_mode = self->mode; + int save_channel = self->channel; + int save_timer = self->timer; + + // Check the current mode and channel + int mode = -1; + int channel = -1; + while ((channel < 0) && (mode < (LEDC_SPEED_MODE_MAX - 1))) { + ++mode; + channel = find_channel(mode, self->pin); + } + if (channel < 0) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX); // in all modes + } + self->mode = mode; + self->channel = channel; int freq = args[ARG_freq].u_int; // Check if freq wasn't passed as an argument - if (freq == -1) { + if ((freq == -1) && (mode >= 0) && (channel >= 0)) { // Check if already set, otherwise use the default freq. // It is possible in case: // pwm = PWM(pin, freq=1000, duty=256) // pwm = PWM(pin, duty=128) - if (chans[channel_idx].timer_idx != -1) { - freq = timers[chans[channel_idx].timer_idx].freq_hz; + if (chans[mode][channel].timer >= 0) { + freq = timers[mode][chans[mode][channel].timer].freq_hz; } if (freq <= 0) { freq = PWM_FREQ; } } - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency 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; - } - - 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); + select_a_timer(self, freq); + set_freq(self, freq); // New PWM assignment - if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) { + if ((chans[mode][channel].pin < 0) + || ((save_mode != self->mode)) + || ((save_channel != self->channel)) + || ((save_timer != self->timer))) { configure_channel(self); - chans[channel_idx].pin = self->pin; - } - chans[channel_idx].timer_idx = timer_idx; - self->active = true; - - // Set timer frequency - set_freq(self, freq, &timers[timer_idx]); - - // Set duty cycle? - 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 << PWM_RES_10_BIT) / 2); // 50% + register_channel(self->mode, self->channel, self->pin, self->timer); } } @@ -557,11 +691,13 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, // create PWM object from the given pin machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); self->pin = pin_id; - self->active = false; self->mode = -1; self->channel = -1; self->timer = -1; self->duty_x = 0; + self->duty = 0; + self->channel_duty = 0; + self->output_invert = 0; // start the PWM subsystem if it's not already running if (!pwm_inited) { @@ -579,91 +715,72 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, // This called from pwm.deinit() method static void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { - int channel_idx = CHANNEL_IDX(self->mode, self->channel); - pwm_deinit(channel_idx); - self->active = false; + pwm_deinit(self->mode, self->channel); self->mode = -1; self->channel = -1; self->timer = -1; self->duty_x = 0; + self->duty = 0; + self->channel_duty = 0; + self->output_invert = 0; } // Set and get methods of PWM class static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { - pwm_is_active(self); - return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); + if (self->timer < 0) { + return MP_OBJ_NEW_SMALL_INT(0); + } else { + return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); + } } static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { pwm_is_active(self); - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) { + if (freq == timers[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); - + // Set new PWM frequency + select_a_timer(self, freq); // Set the frequency - set_freq(self, freq, &timers[current_timer_idx]); + set_freq(self, freq); } static mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { - return MP_OBJ_NEW_SMALL_INT(get_duty_u10(self)); + if (self->timer < 0) { + return MP_OBJ_NEW_SMALL_INT(0); + } else { + 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) { + pwm_is_active(self); 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)); + if (self->timer < 0) { + return MP_OBJ_NEW_SMALL_INT(0); + } else { + 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) { + pwm_is_active(self); 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)); + if (self->timer < 0) { + return MP_OBJ_NEW_SMALL_INT(0); + } else { + 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) { + pwm_is_active(self); set_duty_ns(self, duty_ns); }