diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst old mode 100644 new mode 100755 index 5cce96d687875..3fb0ed48ae9c7 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -320,25 +320,47 @@ 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 have the same frequency. On the other hand, 16 independent PWM duty cycles are possible at the same frequency. +Note: New PWM parameters take effect in the next PWM cycle. + + pwm = PWM(2, duty=512) + print(pwm) + >>>PWM(Pin(2), freq=5000, duty=1023) # the duty is not relevant + pwm.init(freq=2, duty=64) + print(pwm) + >>>PWM(Pin(2), freq=2, duty=16) # the duty is not relevant + time.sleep(1 / 2) # wait one PWM period + print(pwm) + >>>PWM(Pin(2), freq=2, duty=64) # the duty is actual + +Note: machine.freq(20_000_000) reduces the highest PWM frequency to 10 MHz. + See more examples in the :ref:`esp32_pwm` tutorial. DAC (digital to analog conversion) diff --git a/docs/esp32/tutorial/pwm.rst b/docs/esp32/tutorial/pwm.rst old mode 100644 new mode 100755 index 2650284d35f41..07adaedba158e --- a/docs/esp32/tutorial/pwm.rst +++ b/docs/esp32/tutorial/pwm.rst @@ -11,16 +11,20 @@ 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) + F = 10000 # Hz + 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))) + f = F * (i // 2 + 1) + d = min(2**16 - 1, D * (i + 1)) + pwms.append(PWM(pin, freq=f, duty_u16=d)) + sleep(1/f) print(pwms[i]) finally: for pwm in pwms: @@ -31,49 +35,78 @@ 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=10000, duty_u16=4096) # duty=6.25%, raw_duty=256, resolution=12, mode=0, channel=0, timer=0 + PWM(Pin(4), freq=10000, duty_u16=8192) # duty=12.50%, raw_duty=512, resolution=12, mode=0, channel=1, timer=0 + PWM(Pin(12), freq=20000, duty_u16=12288) # duty=18.75%, raw_duty=384, resolution=11, mode=0, channel=2, timer=1 + PWM(Pin(13), freq=20000, duty_u16=16384) # duty=25.00%, raw_duty=512, resolution=11, mode=0, channel=3, timer=1 + PWM(Pin(14), freq=30030, duty_u16=20480) # duty=31.25%, raw_duty=640, resolution=11, mode=0, channel=4, timer=2 + PWM(Pin(15), freq=30030, duty_u16=24576) # duty=37.50%, raw_duty=768, resolution=11, mode=0, channel=5, timer=2 + PWM(Pin(16), freq=40000, duty_u16=28672) # duty=43.75%, raw_duty=448, resolution=10, mode=0, channel=6, timer=3 + PWM(Pin(18), freq=40000, duty_u16=32768) # duty=50.00%, raw_duty=512, resolution=10, mode=0, channel=7, timer=3 + PWM(Pin(19), freq=50000, duty_u16=36864) # duty=56.25%, raw_duty=576, resolution=10, mode=1, channel=0, timer=0 + PWM(Pin(22), freq=50000, duty_u16=40960) # duty=62.50%, raw_duty=640, resolution=10, mode=1, channel=1, timer=0 + PWM(Pin(23), freq=60060, duty_u16=45056) # duty=68.75%, raw_duty=704, resolution=10, mode=1, channel=2, timer=1 + PWM(Pin(25), freq=60060, duty_u16=49152) # duty=75.00%, raw_duty=768, resolution=10, mode=1, channel=3, timer=1 + PWM(Pin(26), freq=69930, duty_u16=53248) # duty=81.25%, raw_duty=832, resolution=10, mode=1, channel=4, timer=2 + PWM(Pin(27), freq=69930, duty_u16=57344) # duty=87.50%, raw_duty=896, resolution=10, mode=1, channel=5, timer=2 + PWM(Pin(32), freq=80000, duty_u16=61440) # duty=93.75%, raw_duty=480, resolution=9, mode=1, channel=6, timer=3 + PWM(Pin(33), freq=80000, duty_u16=65535) # duty=100.00%, raw_duty=0, resolution=9, 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_MAX = 1000 + F_MIN = 1000 + F_MAX = 10000 f = F_MIN - delta_f = 1 + delta_f = F_MAX // 50 - p = PWM(Pin(5), f) - print(p) + pwm = PWM(Pin(27), f) while True: - p.freq(f) - - sleep(10 / F_MIN) + pwm.freq(f) + sleep(1/f) + sleep(.1) + print(pwm) 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 at Pin(5) with an oscilloscope. + `See PWM wave on Pin(27) with an oscilloscope. `_ + + Output is:: -* Example of a smooth duty change:: +PWM(Pin(27), freq=998, duty_u16=32768) # duty=50.00%, raw_duty=32768, resolution=16, mode=0, channel=0, timer=2 +PWM(Pin(27), freq=1202, duty_u16=32768) # duty=50.00%, raw_duty=32768, resolution=16, mode=0, channel=0, timer=2 +PWM(Pin(27), freq=1401, duty_u16=32768) # duty=50.00%, raw_duty=16384, resolution=15, mode=0, channel=0, timer=2 +PWM(Pin(27), freq=1598, duty_u16=32768) # duty=50.00%, raw_duty=16384, resolution=15, mode=0, channel=0, timer=2 +... +PWM(Pin(27), freq=9398, duty_u16=32768) # duty=50.00%, raw_duty=4096, resolution=13, mode=0, channel=0, timer=0 +PWM(Pin(27), freq=9615, duty_u16=32768) # duty=50.00%, raw_duty=4096, resolution=13, mode=0, channel=0, timer=0 +PWM(Pin(27), freq=9804, duty_u16=32768) # duty=50.00%, raw_duty=2048, resolution=12, mode=0, channel=0, timer=0 +PWM(Pin(27), freq=10000, duty_u16=32768) # duty=50.00%, raw_duty=2048, resolution=12, mode=0, channel=0, timer=1 + +PWM(Pin(27), freq=10000, duty_u16=32768) # duty=50.00%, raw_duty=2048, resolution=12, mode=0, channel=0, timer=1 +PWM(Pin(27), freq=9804, duty_u16=32768) # duty=50.00%, raw_duty=2048, resolution=12, mode=0, channel=0, timer=0 +PWM(Pin(27), freq=9615, duty_u16=32768) # duty=50.00%, raw_duty=4096, resolution=13, mode=0, channel=0, timer=0 +PWM(Pin(27), freq=9398, duty_u16=32768) # duty=50.00%, raw_duty=4096, resolution=13, mode=0, channel=0, timer=0 +... +PWM(Pin(27), freq=1598, duty_u16=32768) # duty=50.00%, raw_duty=16384, resolution=15, mode=0, channel=0, timer=2 +PWM(Pin(27), freq=1401, duty_u16=32768) # duty=50.00%, raw_duty=16384, resolution=15, mode=0, channel=0, timer=2 +PWM(Pin(27), freq=1202, duty_u16=32768) # duty=50.00%, raw_duty=32768, resolution=16, mode=0, channel=0, timer=2 +PWM(Pin(27), freq=998, duty_u16=32768) # duty=50.00%, raw_duty=32768, resolution=16, mode=0, channel=0, timer=2 + + +* Example of a **smooth duty change**:: from time import sleep from machine import Pin, PWM @@ -81,15 +114,21 @@ 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), freq=1000, duty_u16=duty_u16) while True: - p.duty_u16(duty_u16) + pwm.duty_u16(duty_u16) + sleep(2/pwm.freq()) + print(pwm) - sleep(1 / 1000) + if duty_u16 >= DUTY_MAX: + print() + sleep(2) + elif duty_u16 <= 0: + print() + sleep(2) duty_u16 += delta_d if duty_u16 >= DUTY_MAX: @@ -99,9 +138,93 @@ low all of the time. duty_u16 = 0 delta_d = -delta_d - See PWM wave at Pin(5) with an oscilloscope. + See `PWM wave on Pin(27) with an oscilloscope. `_ + + Output is:: + + PWM(Pin(27), freq=998, duty_u16=0) # duty=0.00%, raw_duty=0, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=256) # duty=0.39%, raw_duty=256, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=512) # duty=0.78%, raw_duty=512, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=768) # duty=1.17%, raw_duty=768, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=1024) # duty=1.56%, raw_duty=1024, resolution=16, mode=0, channel=0, timer=0 + ... + PWM(Pin(27), freq=998, duty_u16=64512) # duty=98.44%, raw_duty=64512, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=64768) # duty=98.83%, raw_duty=64768, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65024) # duty=99.22%, raw_duty=65024, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65280) # duty=99.61%, raw_duty=65280, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65535) # duty=100.00%, raw_duty=0, resolution=16, mode=0, channel=0, timer=0 + + PWM(Pin(27), freq=998, duty_u16=65279) # duty=99.61%, raw_duty=65279, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=65023) # duty=99.22%, raw_duty=65023, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=64767) # duty=98.83%, raw_duty=64767, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=64511) # duty=98.44%, raw_duty=64511, resolution=16, mode=0, channel=0, timer=0 + ... + PWM(Pin(27), freq=998, duty_u16=1023) # duty=1.56%, raw_duty=1023, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=767) # duty=1.17%, raw_duty=767, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=511) # duty=0.78%, raw_duty=511, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=255) # duty=0.39%, raw_duty=255, resolution=16, mode=0, channel=0, timer=0 + PWM(Pin(27), freq=998, duty_u16=0) # duty=0.00%, raw_duty=0, resolution=16, 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)) + pwmi = PWM(Pin(32), 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=0) # duty=0.00%, raw_duty=0, resolution=13, mode=0, channel=0, timer=3 + PWM(Pin(32), freq=5000, duty_u16=32768, invert=1) # duty=50.00%, raw_duty=4096, resolution=13, mode=0, channel=1, timer=3 + PWM(Pin(27), freq=5000, duty_u16=2048) # duty=3.13%, raw_duty=256, resolution=13, mode=0, channel=0, timer=3 + PWM(Pin(32), freq=5000, duty_u16=2048, invert=1) # duty=3.13%, raw_duty=256, resolution=13, mode=0, channel=1, timer=3 + PWM(Pin(27), freq=5000, duty_u16=4096) # duty=6.25%, raw_duty=512, resolution=13, mode=0, channel=0, timer=3 + PWM(Pin(32), freq=5000, duty_u16=4096, invert=1) # duty=6.25%, raw_duty=512, resolution=13, mode=0, channel=1, timer=3 + PWM(Pin(27), freq=5000, duty_u16=6144) # duty=9.38%, raw_duty=768, resolution=13, mode=0, channel=0, timer=3 + PWM(Pin(32), freq=5000, duty_u16=6144, invert=1) # duty=9.38%, raw_duty=768, resolution=13, mode=0, channel=1, timer=3 + PWM(Pin(27), freq=5000, duty_u16=8192) # duty=12.50%, raw_duty=1024, resolution=13, mode=0, channel=0, timer=3 + PWM(Pin(32), freq=5000, duty_u16=8192, invert=1) # duty=12.50%, raw_duty=1024, resolution=13, mode=0, channel=1, timer=3 ... + ... + + + See `PWM waves on Pin(27) and Pin(32) `_ with an oscilloscope. + -Note: the Pin.OUT mode does not need to be specified. The channel is initialized +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:: diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst old mode 100644 new mode 100755 index 5f592b8dff593..dde8175de70b8 --- 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 old mode 100644 new mode 100755 index 6e3610b156679..9161d5fc04e52 --- 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,6 +32,7 @@ #include #include "py/mphal.h" +#include "hal/ledc_hal.h" #include "driver/ledc.h" #include "esp_err.h" #include "soc/gpio_sig_map.h" @@ -40,71 +41,32 @@ #include "esp_clk_tree.h" #endif -#define PWM_DBG(...) -// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); +#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__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -typedef struct _chan_t { - // Which channel has which GPIO pin assigned? - // (-1 if not assigned) - gpio_num_t pin; - // Which channel has which timer assigned? - // (-1 if not assigned) - int timer_idx; -} chan_t; - -// List of PWM channels -static chan_t chans[PWM_CHANNEL_MAX]; - -// channel_idx is an index (end-to-end sequential numbering) for all channels -// available on the chip and described in chans[] -#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel) -#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX) -#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX) - -// Total number of timers -#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX) - -// List of timer configs -static ledc_timer_config_t timers[PWM_TIMER_MAX]; - -// timer_idx is an index (end-to-end sequential numbering) for all timers -// available on the chip and configured in timers[] -#define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer) -#define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX) -#define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX) - -// Params for PWM operation // 5khz is default frequency #define PWM_FREQ (5000) +// default duty 50% +#define PWM_DUTY ((1UL << UI_RES_16_BIT) / 2) -// 10-bit resolution (compatible with esp8266 PWM) -#define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) - +// 10-bit user interface resolution compatible with esp8266 PWM.duty() +#define UI_RES_10_BIT (10) // Maximum duty value on 10-bit resolution -#define MAX_DUTY_U10 ((1 << PWM_RES_10_BIT) - 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 (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT -#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 UI10_DUTY ((1UL << UI_RES_10_BIT) - 1) + +// 16-bit user interface resolution used in PWM.duty_u16() #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 +// Maximum duty value on 16-bit resolution +#define UI16_DUTY ((1UL << UI_RES_16_BIT) - 1) + +// timer_duty is the MAX value of a channel_duty +#define timer_duty ((1UL << timers[self->mode][self->timer].duty_resolution) - 1) -#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 +// #define EMPIRIC_FREQ (10) // Hz #endif // Config of timer upon which we run all PWM'ed GPIO pins @@ -113,320 +75,426 @@ static bool pwm_inited = false; // MicroPython PWM object struct typedef struct _machine_pwm_obj_t { mp_obj_base_t base; - gpio_num_t pin; - bool active; - int mode; - int channel; - int timer; - 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; // - / - + int8_t pin; + int8_t mode; + int8_t channel; + int8_t timer; + int32_t freq; + int8_t duty_x; // UI_RES_10_BIT if duty(), UI_RES_16_BIT if duty_u16(), -UI_RES_16_BIT if duty_ns() + int duty_ui; // saved values of UI duty + int channel_duty; // saved values of UI duty, 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); +typedef struct _chans_t { + // Which channel has which GPIO pin assigned? (-1 if not assigned) + int8_t pin; + // Which channel has which timer assigned? (-1 if not assigned) + int8_t timer; +} chans_t; + +// List of PWM channels +static chans_t chans[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_MAX]; + +typedef struct _timers_t { + int32_t freq; + int8_t duty_resolution; +} timers_t; + +// List of PWM timers +static timers_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; + +// register-unregister channel +static void set_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 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) { + // Initial condition: no channels assigned + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + set_channel(mode, channel, -1, -1); // unregister + } + // Initial condition: no timers assigned + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + timers[mode][timer].freq = -1; // unset timer is -1 + timers[mode][timer].duty_resolution = 0; + } } +} - // 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)) { +static void pwm_deinit(int mode, int channel, int level) { + // Is valid channel? + if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_CHANNEL_MAX)) { // Clean up timer if necessary - int timer_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_pause(mode, timer)); + ledc_timer_config_t ledc_timer = { + .deconfigure = true, + .speed_mode = mode, + .timer_num = timer, + }; + check_esp_err(ledc_timer_config(&ledc_timer)); // Flag it unused - timers[chans[channel_idx].timer_idx].freq_hz = -1; + timers[mode][timer].freq = -1; + timers[mode][timer].duty_resolution = 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); - // 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); - } 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 - #endif - } + int pin = chans[mode][channel].pin; + if (pin >= 0) { + // Disable LEDC output, and set idle level + check_esp_err(ledc_stop(mode, channel, level)); } - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; + set_channel(mode, channel, -1, -1); // unregister } } // 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, 0); + } } pwm_inited = false; } } -static void configure_channel(machine_pwm_obj_t *self) { - ledc_channel_config_t cfg = { - .channel = self->channel, - .duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2, - .gpio_num = self->pin, - .intr_type = LEDC_INTR_DISABLE, - .speed_mode = self->mode, - .timer_sel = self->timer, - }; - if (ledc_channel_config(&cfg) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin); - } -} - -static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { - esp_err_t err; - if (freq != timer->freq_hz) { - // Configure the new frequency and resolution - timer->freq_hz = freq; - - #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; - 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); - - // 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)); - } - } - - // 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); +static void pwm_is_active(machine_pwm_obj_t *self) { + if (self->timer < 0) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM is inactive")); } } // Calculate the duty parameters based on an ns value static int ns_to_duty(machine_pwm_obj_t *self, int ns) { - 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; + pwm_is_active(self); + int64_t duty = ((int64_t)ns * UI16_DUTY * self->freq + 500000000LL) / 1000000000LL; if ((ns > 0) && (duty == 0)) { duty = 1; - } else if (duty > UI_MAX_DUTY) { - duty = UI_MAX_DUTY; + } else if (duty > UI16_DUTY) { + duty = UI16_DUTY; } return duty; } 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); - return ns; + pwm_is_active(self); + return ((int64_t)duty * 1000000000LL + (int64_t)self->freq * UI16_DUTY / 2) / ((int64_t)self->freq * UI16_DUTY); } -#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) +// Reconfigure PWM pin output as input/output. This allows to read the pin level. +static void reconfigure_pin(machine_pwm_obj_t *self) { + int invert = self->output_invert; + if (self->channel_duty && (self->channel_duty == timer_duty)) { + invert = invert ^ 1; + } + 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, 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, invert, false); + #endif + } +} -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")); +static void apply_duty(machine_pwm_obj_t *self) { + pwm_is_active(self); + + int duty = 0; + if (self->duty_x == UI_RES_16_BIT) { + duty = self->duty_ui; + } else if (self->duty_x == UI_RES_10_BIT) { + duty = self->duty_ui << (UI_RES_16_BIT - UI_RES_10_BIT); + } else if (self->duty_x == -UI_RES_16_BIT) { + duty = ns_to_duty(self, self->duty_ui); + } + + int duty_resolution = timers[self->mode][self->timer].duty_resolution; + int channel_duty; + if (duty_resolution <= UI_RES_16_BIT) { + channel_duty = duty >> (UI_RES_16_BIT - duty_resolution); + } else { + channel_duty = duty << (duty_resolution - UI_RES_16_BIT); + } + + self->channel_duty = channel_duty; + + ledc_channel_config_t cfg = { + .channel = self->channel, + .duty = self->channel_duty, + .gpio_num = self->pin, + .intr_type = LEDC_INTR_DISABLE, + .speed_mode = self->mode, + .timer_sel = self->timer, + .flags.output_invert = self->output_invert, + }; + if (self->channel_duty && (self->channel_duty == timer_duty)) { + cfg.duty = 0; + cfg.flags.output_invert = self->output_invert ^ 1; + } + check_esp_err(ledc_channel_config(&cfg)); + check_esp_err(ledc_bind_channel_timer(self->mode, self->channel, self->timer)); + reconfigure_pin(self); + set_channel(self->mode, self->channel, self->pin, self->timer); // register +} + +// 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 calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { + uint32_t divider = (uint32_t)(((uint64_t)src_clk_freq + timer_freq / 2) / timer_freq); // rounded + return divider == 0 ? 1 : divider; +} + +// Find the highest bit resolution for the requested frequency +static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + uint32_t divider = calc_divider(src_clk_freq, timer_freq); + uint32_t f = src_clk_freq / divider; // actual frequency + uint32_t freq = src_clk_freq + (f / 2) / f; // rounded frequency + uint32_t resolution = 0; + for (; freq > 1; freq >>= 1) { + ++resolution; } + return resolution; } +#endif + +static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + unsigned int resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + if (resolution == 0) { + resolution = 1; + } else if (resolution > UI_RES_16_BIT) { + // limit resolution to user interface + resolution = UI_RES_16_BIT; + } + return resolution; +} + +#define ledc_duty() ledc_get_duty(self->mode, self->channel) 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); + if (self->channel_duty == timer_duty) { + return UI16_DUTY; } else { - duty >>= (resolution - UI_RES_16_BIT); + int duty_resolution = timers[self->mode][self->timer].duty_resolution; + if (duty_resolution <= UI_RES_16_BIT) { + return ledc_duty() << (UI_RES_16_BIT - duty_resolution); + } else { + return ledc_duty() >> (duty_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 + // Scale down from 16 bit to 10 bit resolution + return get_duty_u16(self) >> (UI_RES_16_BIT - UI_RES_10_BIT); } static uint32_t get_duty_ns(machine_pwm_obj_t *self) { - 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); + if ((duty < 0) || (duty > UI16_DUTY)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI16_DUTY); } - ledc_timer_config_t timer = timers[TIMER_IDX(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); - } else { - channel_duty = duty << (timer.duty_resolution - UI_RES_16_BIT); - } - int max_duty = (1 << timer.duty_resolution) - 1; - if (channel_duty < 0) { - channel_duty = 0; - } else if (channel_duty > max_duty) { - channel_duty = max_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_x = UI_RES_16_BIT; + self->duty_ui = duty; + apply_duty(self); } 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); + if ((duty < 0) || (duty > UI10_DUTY)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), UI10_DUTY); } - 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_x = UI_RES_10_BIT; + self->duty_ui = duty; + apply_duty(self); } 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)); + if ((ns < 0) || (ns > duty_to_ns(self, UI16_DUTY))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI16_DUTY)); } - set_duty_u16(self, ns_to_duty(self, ns)); - self->duty_x = -HIGHEST_PWM_RES; - self->duty_ns = ns; + self->duty_x = -UI_RES_16_BIT; + self->duty_ui = ns; + apply_duty(self); } -/******************************************************************************/ +static void set_duty(machine_pwm_obj_t *self) { + if (self->duty_x == UI_RES_16_BIT) { + set_duty_u16(self, self->duty_ui); + } else if (self->duty_x == UI_RES_10_BIT) { + set_duty_u10(self, self->duty_ui); + } else if (self->duty_x == -UI_RES_16_BIT) { + set_duty_ns(self, self->duty_ui); + } +} -#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. - } - } +static void check_freq_ranges(int freq, int upper_freq) { + if ((freq <= 0) || (freq > upper_freq)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("frequency must be from 1Hz to %dMHz"), upper_freq / 1000000); } +} + +// Set timer frequency +static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { + self->freq = freq; + if (timers[self->mode][self->timer].freq != freq) { + timers[self->mode][self->timer].freq = freq; + + ledc_timer_config_t timer = {}; + timer.speed_mode = self->mode; + timer.timer_num = self->timer; + timer.freq_hz = freq; + timer.deconfigure = false; + + timer.clk_cfg = LEDC_AUTO_CLK; + 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 - return free_timer_idx_found; + #ifdef EMPIRIC_FREQ // ESP32 and ESP32S2 only + if (freq < EMPIRIC_FREQ) { + timer.clk_cfg = LEDC_USE_REF_TICK; + src_clk_freq = REF_CLK_FREQ; // 1 MHz + } + #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_EXACT, &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); + } + // machine.freq(20_000_000) reduces APB_CLK_FREQ to 20MHz and the highest PWM frequency to 10MHz + check_freq_ranges(freq, src_clk_freq / 2); + #endif + + // Configure the new resolution + timer.duty_resolution = find_suitable_duty_resolution(src_clk_freq, self->freq); + timers[self->mode][self->timer].duty_resolution = timer.duty_resolution; + + // Configure timer - Set frequency + check_esp_err(ledc_timer_config(&timer)); + // Reset the timer if low speed + if (self->mode == LEDC_LOW_SPEED_MODE) { + check_esp_err(ledc_timer_rst(self->mode, self->timer)); + } + } } -// 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; } } - 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 +static void find_channel(int pin, int *ret_mode, int *ret_channel) { + // Try to find self channel first + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if (chans[mode][channel].pin == pin) { + *ret_mode = mode; + *ret_channel = channel; + return; } - if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) { - avail_idx = channel_idx; + } + } + // Find free channel + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if (chans[mode][channel].pin < 0) { + *ret_mode = mode; + *ret_channel = channel; + return; + } + } + } +} + +// Returns timer with the same frequency, freq == -1 means free timer +static void find_tmr(int pin, int freq, int *ret_mode, int *ret_timer) { + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + if (is_free_channels(mode, pin)) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if (timers[mode][timer].freq == freq) { + *ret_mode = mode; + *ret_timer = timer; + return; + } + } + } + } +} + +// Try to find a timer with the same frequency in the current mode, otherwise in the next mode. +// If no existing timer and channel was found, then try to find free timer in any mode. +// If the mode or channel is changed, release the channel and select(bind) a new channel in the next mode. +static void select_timer(machine_pwm_obj_t *self, int freq) { + // mode, channel, timer may be -1(not defined) or actual values + int mode = -1; + int timer = -1; + // Check if an already running timer with the required frequency is running + find_tmr(self->pin, freq, &mode, &timer); + if (timer < 0) { + // Try to reuse self timer + if ((self->mode >= 0) && (self->channel >= 0)) { + if (!is_timer_in_use(self->mode, self->channel, self->timer)) { + mode = self->mode; + timer = self->timer; } } + // If no existing timer and channel was found, then try to find free timer in any mode + if (timer < 0) { + find_tmr(self->pin, -1, &mode, &timer); + } } - if (channel_idx >= PWM_CHANNEL_MAX) { - channel_idx = avail_idx; + 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); + } + // If the timer is found, then bind + if (self->timer != timer) { + set_channel(self->mode, self->channel, -1, -1); // unregister + // Bind the channel to the timer + self->mode = mode; + self->timer = timer; + set_channel(self->mode, self->channel, self->pin, self->timer); // register } - return channel_idx; } /******************************************************************************/ @@ -435,138 +503,127 @@ 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) { + if (self->duty_x == UI_RES_10_BIT) { mp_printf(print, ", duty=%d", get_duty_u10(self)); - } else if (self->duty_x == -HIGHEST_PWM_RES) { + } else if (self->duty_x == -UI_RES_16_BIT) { 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 - + if (self->output_invert) { + mp_printf(print, ", invert=%d", self->output_invert); + } + mp_printf(print, ")"); + #if 1 // MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL + mp_printf(print, " #"); + mp_printf(print, " duty=%.2f%%", 100.0 * get_duty_u16(self) / UI16_DUTY); // percents + int duty_resolution = timers[self->mode][self->timer].duty_resolution; + mp_printf(print, ", raw_duty=%d, resolution=%d", ledc_duty(), duty_resolution); 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 = -1}}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - int channel_idx = find_channel(self->pin, ANY_MODE); - if (channel_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes + int freq = args[ARG_freq].u_int; + if (freq != -1) { + check_freq_ranges(freq, 40000000); } int duty = args[ARG_duty].u_int; int duty_u16 = args[ARG_duty_u16].u_int; int duty_ns = args[ARG_duty_ns].u_int; - if (((duty != -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_u16 >= 0) { + self->duty_x = UI_RES_16_BIT; + self->duty_ui = duty_u16; + } else if (duty_ns >= 0) { + self->duty_x = -UI_RES_16_BIT; + self->duty_ui = duty_ns; + } else if (duty >= 0) { + self->duty_x = UI_RES_10_BIT; + self->duty_ui = duty; + } else if (self->duty_x == 0) { + self->duty_x = UI_RES_16_BIT; + self->duty_ui = PWM_DUTY; } - int freq = args[ARG_freq].u_int; + int output_invert = args[ARG_invert].u_int; + if (output_invert >= 0) { + self->output_invert = output_invert == 0 ? 0 : 1; + } + + // Check the current mode and channel + int mode = -1; + int channel = -1; + find_channel(self->pin, &mode, &channel); + 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; + // 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; } 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 - } + select_timer(self, freq); + set_freq(self, freq); + set_duty(self); +} - int mode = TIMER_IDX_TO_MODE(timer_idx); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - // unregister old channel - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; - // find new channel - channel_idx = find_channel(self->pin, mode); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode - } - } - self->mode = mode; - self->timer = TIMER_IDX_TO_TIMER(timer_idx); - self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); - - // New PWM assignment - if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) { - configure_channel(self); - chans[channel_idx].pin = self->pin; - } - chans[channel_idx].timer_idx = timer_idx; - self->active = true; - - // Set timer frequency - 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% - } +static void self_reset(machine_pwm_obj_t *self) { + self->pin = -1; + self->mode = -1; + self->channel = -1; + self->timer = -1; + self->freq = -1; + self->duty_x = 0; + self->duty_ui = 0; + self->channel_duty = -1; + self->output_invert = 0; } // This called from PWM() constructor static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 2, true); - gpio_num_t pin_id = machine_pin_get_id(args[0]); - - // 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; // start the PWM subsystem if it's not already running if (!pwm_inited) { @@ -574,7 +631,13 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, pwm_inited = true; } - // start the PWM running for this channel + // create PWM object from the given pin + machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + self_reset(self); + + self->pin = machine_pin_get_id(args[0]); + + // Process the remaining parameters. mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); @@ -584,13 +647,8 @@ 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; - self->mode = -1; - self->channel = -1; - self->timer = -1; - self->duty_x = 0; + pwm_deinit(self->mode, self->channel, self->output_invert ? 1 : 0); + self_reset(self); } // Set and get methods of PWM class @@ -602,51 +660,13 @@ static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { 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) { + check_freq_ranges(freq, 40000000); + if (freq == timers[self->mode][self->timer].freq) { 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 the frequency - set_freq(self, freq, &timers[current_timer_idx]); + select_timer(self, freq); + set_freq(self, freq); + set_duty(self); } static mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) {