From 6a4185e3540589d97bff278095d81904cb3a2bef Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 4 Nov 2024 16:55:16 +1100 Subject: [PATCH 01/30] tests/extmod_hardware: Add a test for machine.PWM freq and duty. Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 108 +++++++++++++++++++++++ tests/extmod_hardware/machine_pwm.py.exp | 49 ++++++++++ 2 files changed, 157 insertions(+) create mode 100644 tests/extmod_hardware/machine_pwm.py create mode 100644 tests/extmod_hardware/machine_pwm.py.exp diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py new file mode 100644 index 0000000000000..a87bd0a92d44c --- /dev/null +++ b/tests/extmod_hardware/machine_pwm.py @@ -0,0 +1,108 @@ +# Test machine.PWM, frequncy and duty cycle (using machine.time_pulse_us). +# +# IMPORTANT: This test requires hardware connections: the PWM-output and pulse-input +# pins must be wired together. + +import sys + +try: + from machine import time_pulse_us, Pin, PWM +except ImportError: + print("SKIP") + raise SystemExit + +freq_margin_per_thousand = 0 +duty_margin_per_thousand = 0 +timing_margin_us = 5 + +# Configure pins based on the target. +if "esp32" in sys.platform: + pwm_pin = 4 + pulse_pin = 5 + freq_margin_per_thousand = 2 + duty_margin_per_thousand = 1 + timing_margin_us = 20 +elif "esp8266" in sys.platform: + pwm_pin = 4 + pulse_pin = 5 + duty_margin_per_thousand = 3 + timing_margin_us = 50 +elif "mimxrt" in sys.platform: + pwm_pin = "D0" + pulse_pin = "D1" +elif "rp2" in sys.platform: + pwm_pin = "GPIO0" + pulse_pin = "GPIO1" +elif "samd" in sys.platform: + pwm_pin = "D0" + pulse_pin = "D1" +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def test_freq_duty(pulse_in, pwm, freq, duty_u16): + print("freq={:<5} duty_u16={:<5} :".format(freq, duty_u16), end="") + + # Check configured freq/duty_u16 is within error bound. + freq_error = abs(pwm.freq() - freq) * 1000 // freq + duty_error = abs(pwm.duty_u16() - duty_u16) * 1000 // (duty_u16 or 1) + print("", freq_error <= freq_margin_per_thousand or (freq, pwm.freq()), end="") + print("", duty_error <= duty_margin_per_thousand or (duty_u16, pwm.duty_u16()), end="") + print(" :", end="") + + # Calculate expected timing. + expected_total_us = 1_000_000 // freq + expected_high_us = expected_total_us * duty_u16 // 65535 + expected_low_us = expected_total_us - expected_high_us + expected_us = (expected_low_us, expected_high_us) + timeout = 2 * expected_total_us + + # Wait for output to settle. + time_pulse_us(pulse_in, 0, timeout) + time_pulse_us(pulse_in, 1, timeout) + + if duty_u16 == 0 or duty_u16 == 65535: + # Expect a constant output level. + no_pulse = ( + time_pulse_us(pulse_in, 0, timeout) < 0 and time_pulse_us(pulse_in, 1, timeout) < 0 + ) + if expected_high_us == 0: + # Expect a constant low level. + print("", 0, no_pulse and pulse_in() == 0) + else: + # Expect a constant high level. + print(" ", 1, no_pulse and pulse_in() == 1) + else: + # Test timing of low and high pulse. + n_averaging = 10 + for level in (0, 1): + t = 0 + time_pulse_us(pulse_in, level, timeout) + for _ in range(n_averaging): + t += time_pulse_us(pulse_in, level, timeout) + t //= n_averaging + expected = expected_us[level] + print("", level, abs(t - expected) <= timing_margin_us or (expected, t), end="") + print() + + +def test(): + pulse_in = Pin(pulse_pin, Pin.IN) + pwm = PWM(pwm_pin) + + for freq in (50, 100, 500, 1_000, 2_000, 5_000, 10_000): + pwm.freq(freq) + for duty in (0, 10, 25, 50, 75, 90, 100): + duty_u16 = duty * 65535 // 100 + if sys.platform == "esp32": + # TODO why is this bit needed to get it working on esp32? + import time + + pwm.init(freq=freq, duty_u16=duty_u16) + time.sleep(0.1) + pwm.duty_u16(duty_u16) + test_freq_duty(pulse_in, pwm, freq, duty_u16) + + +test() diff --git a/tests/extmod_hardware/machine_pwm.py.exp b/tests/extmod_hardware/machine_pwm.py.exp new file mode 100644 index 0000000000000..e9e2e5490a216 --- /dev/null +++ b/tests/extmod_hardware/machine_pwm.py.exp @@ -0,0 +1,49 @@ +freq=50 duty_u16=0 : True True : 0 True +freq=50 duty_u16=6553 : True True : 0 True 1 True +freq=50 duty_u16=16383 : True True : 0 True 1 True +freq=50 duty_u16=32767 : True True : 0 True 1 True +freq=50 duty_u16=49151 : True True : 0 True 1 True +freq=50 duty_u16=58981 : True True : 0 True 1 True +freq=50 duty_u16=65535 : True True : 1 True +freq=100 duty_u16=0 : True True : 0 True +freq=100 duty_u16=6553 : True True : 0 True 1 True +freq=100 duty_u16=16383 : True True : 0 True 1 True +freq=100 duty_u16=32767 : True True : 0 True 1 True +freq=100 duty_u16=49151 : True True : 0 True 1 True +freq=100 duty_u16=58981 : True True : 0 True 1 True +freq=100 duty_u16=65535 : True True : 1 True +freq=500 duty_u16=0 : True True : 0 True +freq=500 duty_u16=6553 : True True : 0 True 1 True +freq=500 duty_u16=16383 : True True : 0 True 1 True +freq=500 duty_u16=32767 : True True : 0 True 1 True +freq=500 duty_u16=49151 : True True : 0 True 1 True +freq=500 duty_u16=58981 : True True : 0 True 1 True +freq=500 duty_u16=65535 : True True : 1 True +freq=1000 duty_u16=0 : True True : 0 True +freq=1000 duty_u16=6553 : True True : 0 True 1 True +freq=1000 duty_u16=16383 : True True : 0 True 1 True +freq=1000 duty_u16=32767 : True True : 0 True 1 True +freq=1000 duty_u16=49151 : True True : 0 True 1 True +freq=1000 duty_u16=58981 : True True : 0 True 1 True +freq=1000 duty_u16=65535 : True True : 1 True +freq=2000 duty_u16=0 : True True : 0 True +freq=2000 duty_u16=6553 : True True : 0 True 1 True +freq=2000 duty_u16=16383 : True True : 0 True 1 True +freq=2000 duty_u16=32767 : True True : 0 True 1 True +freq=2000 duty_u16=49151 : True True : 0 True 1 True +freq=2000 duty_u16=58981 : True True : 0 True 1 True +freq=2000 duty_u16=65535 : True True : 1 True +freq=5000 duty_u16=0 : True True : 0 True +freq=5000 duty_u16=6553 : True True : 0 True 1 True +freq=5000 duty_u16=16383 : True True : 0 True 1 True +freq=5000 duty_u16=32767 : True True : 0 True 1 True +freq=5000 duty_u16=49151 : True True : 0 True 1 True +freq=5000 duty_u16=58981 : True True : 0 True 1 True +freq=5000 duty_u16=65535 : True True : 1 True +freq=10000 duty_u16=0 : True True : 0 True +freq=10000 duty_u16=6553 : True True : 0 True 1 True +freq=10000 duty_u16=16383 : True True : 0 True 1 True +freq=10000 duty_u16=32767 : True True : 0 True 1 True +freq=10000 duty_u16=49151 : True True : 0 True 1 True +freq=10000 duty_u16=58981 : True True : 0 True 1 True +freq=10000 duty_u16=65535 : True True : 1 True From ab6a9957f8e474fd2774313348b23d807ddbb925 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 5 Jan 2024 13:05:25 +0200 Subject: [PATCH 02/30] 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 | 791 +++++++++++++++++++---------------- 4 files changed, 634 insertions(+), 408 deletions(-) mode change 100644 => 100755 ports/esp32/machine_pwm.c 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 old mode 100644 new mode 100755 index 0134fa2cc8c89..d945f25286a2a --- 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,11 +41,9 @@ #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, "\n"); typedef struct _chan_t { // Which channel has which GPIO pin assigned? @@ -52,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 @@ -84,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; @@ -114,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; } @@ -200,15 +208,38 @@ 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 pin output as input/output + 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 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; } // Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 @@ -218,11 +249,7 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 // Find the highest bit resolution for the requested frequency unsigned int freq = src_clk_freq; - - int divider = (freq + timer_freq / 2) / timer_freq; // rounded - if (divider == 0) { - divider = 1; - } + unsigned int divider = calc_divider(freq, timer_freq); float f = (float)freq / divider; // actual frequency if (f <= 1.0) { f = 1.0; @@ -239,78 +266,21 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 // Limit resolution to HIGHEST_PWM_RES to match units of our duty res = HIGHEST_PWM_RES; } - return res; } #endif -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; - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 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); - } - #else - // Simplified fallback logic for IDF V5.0.x, for targets with APB only. - src_clk_freq = APB_CLK_FREQ; // 80 MHz - #if SOC_LEDC_SUPPORT_REF_TICK - if (timer->clk_cfg == LEDC_USE_REF_TICK) { - src_clk_freq = REF_CLK_FREQ; // 1 MHz - } - #endif // SOC_LEDC_SUPPORT_REF_TICK - #endif // ESP_IDF_VERSION - - 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); + 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) { @@ -320,54 +290,56 @@ 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); + pwm_is_active(self); + 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")); - } -} - 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) { +/* +static void apply_duty(machine_pwm_obj_t *self) { 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[self->mode][self->timer]; + int duty = 0; + if (self->duty_x == HIGHEST_PWM_RES) { + duty = self->duty; + } else if (self->duty_x == PWM_RES_10_BIT) { + duty = self->duty << (UI_RES_16_BIT - PWM_RES_10_BIT); + } else if (self->duty_x == -HIGHEST_PWM_RES) { + duty = ns_to_duty(self, self->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); + 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) { @@ -375,75 +347,128 @@ 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)); + self->channel_duty = channel_duty; +} +*/ +static void set_duty_u16(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > UI_MAX_DUTY)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY); + } - /* - // 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); - } + 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); + } else { + channel_duty = duty << (timer->duty_resolution - UI_RES_16_BIT); } - */ + int max_duty = 1 << timer->duty_resolution; + 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)); 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 + + // 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)); } - } - return free_timer_idx_found; + // 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; } } @@ -451,25 +476,105 @@ 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) { + // 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; } /******************************************************************************/ @@ -478,7 +583,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) { @@ -488,128 +593,145 @@ 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; + if (freq != -1) { + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); + } + } // 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; + register_channel(self->mode, self->channel, self->pin, self->timer); } - 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->mode = -1; + self->channel = -1; + self->timer = -1; + self->duty_x = 0; + self->duty = 0; + self->channel_duty = 0; + 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]); + gpio_num_t pin = 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; + self->pin = pin; + self_reset(self); // start the PWM subsystem if it's not already running if (!pwm_inited) { @@ -627,69 +749,32 @@ 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_reset(self); } // 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) { + pwm_is_active(self); + 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) { From 457bad930bcda6cece087dc9a7594118dc1dfad4 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 10:56:41 +0200 Subject: [PATCH 03/30] machine: Add time_hardware_pulse_us function. The difference from `time_pulse_us` is that if the pin is initially equal to *pulse_level* then `time_pulse_us` counts the pulse duration immediately, but `time_hardware_pulse_us` first waits the different *pulse_level*, then waits the equal to *pulse_level* and then counts pulse duration. Signed-off-by: IhorNehrutsa --- docs/library/machine.rst | 23 +++++++++++++++++++++++ extmod/machine_pulse.c | 38 ++++++++++++++++++++++++++++++++++++++ extmod/modmachine.c | 1 + extmod/modmachine.h | 1 + 4 files changed, 63 insertions(+) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 7d2eb26a7ea34..330e608f9a2f2 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -198,6 +198,29 @@ Miscellaneous functions above. The timeout is the same for both cases and given by *timeout_us* (which is in microseconds). +.. function:: time_hardware_pulse_us(pin, pulse_level, timeout_us=1000000, /) + + Time a pulse on the given *pin*, and return the duration of the pulse in + microseconds. The *pulse_level* argument should be 0 to time a low pulse + or 1 to time a high pulse. + + If the pin is initially equal to *pulse_level* then first waits until + the pin input becomes different from *pulse_level* (***). + Then the function waits until the pin input becomes equal to *pulse_level* (**), + then the function counts the duration that the pin is equal to *pulse_level* (*). + + The function returns -3 if there was timeout waiting for condition marked (***) above. + The function will return -2 if there was timeout waiting for condition marked + (**) above, and -1 if there was timeout during the main measurement, marked (*) + above. The timeout is the same for all cases and given by *timeout_us* (which + is in microseconds). + + The difference from `time_pulse_us` is that if the pin is initially equal to *pulse_level* + then `time_pulse_us` counts the pulse duration immediately, + but `time_hardware_pulse_us` first waits the different *pulse_level*, + then waits the equal to *pulse_level* and then counts pulse duration. + A little bit longer, but higher accuracy. + .. function:: bitstream(pin, encoding, timing, data, /) Transmits *data* by bit-banging the specified *pin*. The *encoding* argument diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 85dba86d9b5ad..8515a5ad2937d 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -62,4 +62,42 @@ static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_time_pulse_us_); +MP_WEAK mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { + mp_uint_t start = mp_hal_ticks_us(); + while (mp_hal_pin_read(pin) == pulse_level) { + if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + return (mp_uint_t)-3; + } + } + start = mp_hal_ticks_us(); + while (mp_hal_pin_read(pin) != pulse_level) { + if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + return (mp_uint_t)-2; + } + } + start = mp_hal_ticks_us(); + while (mp_hal_pin_read(pin) == pulse_level) { + if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + return (mp_uint_t)-1; + } + } + return mp_hal_ticks_us() - start; +} + +static mp_obj_t machine_time_hardware_pulse_us_(size_t n_args, const mp_obj_t *args) { + mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[0]); + int level = 0; + if (mp_obj_is_true(args[1])) { + level = 1; + } + mp_uint_t timeout_us = 1000000; + if (n_args > 2) { + timeout_us = mp_obj_get_int(args[2]); + } + mp_uint_t us = machine_time_hardware_pulse_us(pin, level, timeout_us); + // May return -1 or -2 or -3 in case of timeout + return mp_obj_new_int(us); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_hardware_pulse_us_obj, 2, 3, machine_time_hardware_pulse_us_); + #endif diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 5906835949861..5441f378d2dcf 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -188,6 +188,7 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #endif #if MICROPY_PY_MACHINE_PULSE { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_hardware_pulse_us), MP_ROM_PTR(&machine_time_hardware_pulse_us_obj) }, #endif // Classes for PinBase and Signal. diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 7c16ed302ee2f..41aab2aaddcec 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -255,6 +255,7 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bootloader_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bitstream_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_hardware_pulse_us_obj); #if MICROPY_PY_MACHINE_I2C int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); From eaccd2a3948e8a6ef611f0534f452133968091a5 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 15:47:13 +0200 Subject: [PATCH 04/30] Update machine_pulse.c --- extmod/machine_pulse.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 8515a5ad2937d..33b206431ddf2 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -69,18 +69,27 @@ MP_WEAK mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse return (mp_uint_t)-3; } } + //mp_uint_t delta1 = mp_hal_ticks_us() - start; start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) != pulse_level) { if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { return (mp_uint_t)-2; } } + //mp_uint_t delta2 = mp_hal_ticks_us() - start; start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) == pulse_level) { if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { return (mp_uint_t)-1; } } + /* + #define min(a,b) (((a)<(b))?(a):(b)) + #include "py/mpprint.h" + #define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); + debug_printf("\n delta1=%d %d %d \n", delta1, delta2, min(delta1, delta2)); + return min(delta1, delta2); // alhorithm delay + */ return mp_hal_ticks_us() - start; } From 22fc61908b43c463cb0a2cdd235aedfcfc8c3bb3 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 17:12:29 +0200 Subject: [PATCH 05/30] Update tests/extmod_hardware/machine_pwm.py --- tests/extmod_hardware/machine_pwm.py | 60 +++++++++++++++++----------- 1 file changed, 37 insertions(+), 23 deletions(-) mode change 100644 => 100755 tests/extmod_hardware/machine_pwm.py diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py old mode 100644 new mode 100755 index a87bd0a92d44c..7e9fd266e3f8d --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -1,24 +1,35 @@ -# Test machine.PWM, frequncy and duty cycle (using machine.time_pulse_us). +# Test machine.PWM, frequncy and duty cycle (using machine.time_hardware_pulse_us). # # IMPORTANT: This test requires hardware connections: the PWM-output and pulse-input # pins must be wired together. -import sys +import sys, time try: - from machine import time_pulse_us, Pin, PWM + from machine import time_hardware_pulse_us, Pin, PWM except ImportError: print("SKIP") raise SystemExit +FREQS1 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +FREQS2 = (50, 100, 500, 1_000, 2_000, 5_000, 10_000) +FREQS3 = (100_000, 1_000_000, 10_000_000, 20_000_000, 40_000_000) +FREQS = FREQS1 + FREQS2 + FREQS3 +FREQS = FREQS2 + +print("Test frequencies:", FREQS) + +DUTY_MAX = 65536 +DUTY_LEVELS = 8 # 4 # 16 # + freq_margin_per_thousand = 0 duty_margin_per_thousand = 0 timing_margin_us = 5 # Configure pins based on the target. if "esp32" in sys.platform: - pwm_pin = 4 - pulse_pin = 5 + pwm_pin = 26 # 4 # 2 # + pulse_pin = 27 # 5 # 2 # freq_margin_per_thousand = 2 duty_margin_per_thousand = 1 timing_margin_us = 20 @@ -42,30 +53,29 @@ def test_freq_duty(pulse_in, pwm, freq, duty_u16): - print("freq={:<5} duty_u16={:<5} :".format(freq, duty_u16), end="") + print("freq= {:<8} duty_u16 = {:<5} = {:6.2f}% :".format(freq, duty_u16, duty_u16 * 100 / DUTY_MAX), end="") + # Keep values ​​stable during calculations and prints + pwm_freq = pwm.freq() + pwm_duty_u16 = pwm.duty_u16() # Check configured freq/duty_u16 is within error bound. - freq_error = abs(pwm.freq() - freq) * 1000 // freq - duty_error = abs(pwm.duty_u16() - duty_u16) * 1000 // (duty_u16 or 1) - print("", freq_error <= freq_margin_per_thousand or (freq, pwm.freq()), end="") - print("", duty_error <= duty_margin_per_thousand or (duty_u16, pwm.duty_u16()), end="") + freq_error = abs(pwm_freq - freq) * 1000 // freq + duty_error = abs(pwm_duty_u16 - duty_u16) * 1000 // (duty_u16 or 1) + print("", freq_error <= freq_margin_per_thousand or (freq, pwm_freq), end="") + print("", duty_error <= duty_margin_per_thousand or (duty_u16, pwm_duty_u16), end="") print(" :", end="") # Calculate expected timing. expected_total_us = 1_000_000 // freq - expected_high_us = expected_total_us * duty_u16 // 65535 + expected_high_us = expected_total_us * duty_u16 // DUTY_MAX expected_low_us = expected_total_us - expected_high_us expected_us = (expected_low_us, expected_high_us) timeout = 2 * expected_total_us - # Wait for output to settle. - time_pulse_us(pulse_in, 0, timeout) - time_pulse_us(pulse_in, 1, timeout) - - if duty_u16 == 0 or duty_u16 == 65535: + if duty_u16 == 0 or duty_u16 == DUTY_MAX: # Expect a constant output level. no_pulse = ( - time_pulse_us(pulse_in, 0, timeout) < 0 and time_pulse_us(pulse_in, 1, timeout) < 0 + time_hardware_pulse_us(pulse_in, 0, timeout) < 0 and time_hardware_pulse_us(pulse_in, 1, timeout) < 0 ) if expected_high_us == 0: # Expect a constant low level. @@ -78,9 +88,8 @@ def test_freq_duty(pulse_in, pwm, freq, duty_u16): n_averaging = 10 for level in (0, 1): t = 0 - time_pulse_us(pulse_in, level, timeout) for _ in range(n_averaging): - t += time_pulse_us(pulse_in, level, timeout) + t += time_hardware_pulse_us(pulse_in, level, timeout) t //= n_averaging expected = expected_us[level] print("", level, abs(t - expected) <= timing_margin_us or (expected, t), end="") @@ -91,18 +100,23 @@ def test(): pulse_in = Pin(pulse_pin, Pin.IN) pwm = PWM(pwm_pin) - for freq in (50, 100, 500, 1_000, 2_000, 5_000, 10_000): + for freq in FREQS: pwm.freq(freq) - for duty in (0, 10, 25, 50, 75, 90, 100): - duty_u16 = duty * 65535 // 100 - if sys.platform == "esp32": + for duty in range(0, DUTY_LEVELS + 1): + duty_u16 = DUTY_MAX * duty // DUTY_LEVELS + if 0 and sys.platform == "esp32": # TODO why is this bit needed to get it working on esp32? + # Note: do not need in #10854 import time pwm.init(freq=freq, duty_u16=duty_u16) time.sleep(0.1) pwm.duty_u16(duty_u16) + if sys.platform == "esp32": + time.sleep(1/freq) # wait for duty stabilization during one period of the PWM signal test_freq_duty(pulse_in, pwm, freq, duty_u16) + print() + pwm.deinit() test() From 9b7a224bbc0f6fdc11b8ece94d05917641c5e1f5 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 17:13:19 +0200 Subject: [PATCH 06/30] Create machine_pwm_esp32_1_40M.exp --- .../machine_pwm_esp32_1_40M.exp | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100755 tests/extmod_hardware/machine_pwm_esp32_1_40M.exp diff --git a/tests/extmod_hardware/machine_pwm_esp32_1_40M.exp b/tests/extmod_hardware/machine_pwm_esp32_1_40M.exp new file mode 100755 index 0000000000000..70afe070a5a37 --- /dev/null +++ b/tests/extmod_hardware/machine_pwm_esp32_1_40M.exp @@ -0,0 +1,132 @@ +Test frequencies: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 50, 100, 500, 1000, 2000, 5000, 10000, 100000, 1000000, 10000000, 20000000, 40000000) +freq= 1 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 1 duty_u16 = 16384 = 25.00% : True True : 0 (750000, 749567) 1 (250000, 249856) +freq= 1 duty_u16 = 32768 = 50.00% : True True : 0 (500000, 499712) 1 (500000, 499712) +freq= 1 duty_u16 = 49152 = 75.00% : True True : 0 (250000, 249856) 1 (750000, 749568) +freq= 1 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 2 duty_u16 = 0 = 0.00% : True (0, 131072) : 0 True +freq= 2 duty_u16 = 16384 = 25.00% : True True : 0 (375000, 374783) 1 True +freq= 2 duty_u16 = 32768 = 50.00% : True True : 0 (250000, 249856) 1 (250000, 249855) +freq= 2 duty_u16 = 49152 = 75.00% : True True : 0 True 1 (375000, 374784) +freq= 2 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 3 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 3 duty_u16 = 16384 = 25.00% : True True : 0 (250000, 250367) 1 (83333, 83456) +freq= 3 duty_u16 = 32768 = 50.00% : True True : 0 (166667, 166912) 1 (166666, 166912) +freq= 3 duty_u16 = 49152 = 75.00% : True True : 0 (83334, 83456) 1 (249999, 250367) +freq= 3 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 4 duty_u16 = 0 = 0.00% : True (0, 131072) : 0 True +freq= 4 duty_u16 = 16384 = 25.00% : True True : 0 (187500, 187391) 1 True +freq= 4 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 4 duty_u16 = 49152 = 75.00% : True True : 0 True 1 (187500, 187392) +freq= 4 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 5 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 5 duty_u16 = 16384 = 25.00% : True True : 0 (150000, 150144) 1 True +freq= 5 duty_u16 = 32768 = 50.00% : True True : 0 (100000, 100095) 1 (100000, 100096) +freq= 5 duty_u16 = 49152 = 75.00% : True True : 0 True 1 (150000, 150143) +freq= 5 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 6 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 6 duty_u16 = 16384 = 25.00% : True True : 0 (125000, 125184) 1 True +freq= 6 duty_u16 = 32768 = 50.00% : True True : 0 (83333, 83455) 1 (83333, 83456) +freq= 6 duty_u16 = 49152 = 75.00% : True True : 0 True 1 (124999, 125183) +freq= 6 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 7 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 7 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 7 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 7 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 7 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 8 duty_u16 = 0 = 0.00% : True (0, 131072) : 0 True +freq= 8 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 8 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 8 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 8 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 9 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 9 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 9 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 9 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 9 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 10 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 10 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 10 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 10 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 10 duty_u16 = 65536 = 100.00% : True True : 1 False + +freq= 50 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 50 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 50 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 50 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 50 duty_u16 = 65536 = 100.00% : True True : 1 False + +freq= 100 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 100 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 100 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 100 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 100 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 500 duty_u16 = 0 = 0.00% : True (0, 262144) : 0 True +freq= 500 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 500 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 500 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 500 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 1000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 1000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 1000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 1000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 1000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 2000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 2000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 2000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 2000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 2000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 5000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 5000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 5000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 5000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 5000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 10000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 10000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 10000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 10000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 10000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 100000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 100000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 100000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 100000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 100000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 1000000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 1000000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 1000000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 1000000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 1000000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 10000000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 10000000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 10000000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 10000000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 10000000 duty_u16 = 65536 = 100.00% : True True : 0 False + +freq= 20000000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 20000000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 20000000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 20000000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 20000000 duty_u16 = 65536 = 100.00% : True True : 0 False + +freq= 40000000 duty_u16 = 0 = 0.00% : True (0, 98304) : 0 True +freq= 40000000 duty_u16 = 16384 = 25.00% : True (16384, 98304) : 0 True 1 True +freq= 40000000 duty_u16 = 32768 = 50.00% : True (32768, 98304) : 0 True 1 True +freq= 40000000 duty_u16 = 49152 = 75.00% : True (49152, 98304) : 0 True 1 True +freq= 40000000 duty_u16 = 65536 = 100.00% : True True : 0 True From 9f75598bb548ac3bae55a6681597c94a2f73956c Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 17:13:41 +0200 Subject: [PATCH 07/30] Create machine_pwm_esp32_50_10k.exp --- .../machine_pwm_esp32_50_10k.exp | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100755 tests/extmod_hardware/machine_pwm_esp32_50_10k.exp diff --git a/tests/extmod_hardware/machine_pwm_esp32_50_10k.exp b/tests/extmod_hardware/machine_pwm_esp32_50_10k.exp new file mode 100755 index 0000000000000..bcc9452ece34c --- /dev/null +++ b/tests/extmod_hardware/machine_pwm_esp32_50_10k.exp @@ -0,0 +1,70 @@ +Test frequencies: (50, 100, 500, 1000, 2000, 5000, 10000) +freq= 50 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 50 duty_u16 = 8192 = 12.50% : True True : 0 True 1 True +freq= 50 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 50 duty_u16 = 24576 = 37.50% : True True : 0 True 1 True +freq= 50 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 50 duty_u16 = 40960 = 62.50% : True True : 0 True 1 True +freq= 50 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 50 duty_u16 = 57344 = 87.50% : True True : 0 True 1 True +freq= 50 duty_u16 = 65536 = 100.00% : True True : 1 False + +freq= 100 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 100 duty_u16 = 8192 = 12.50% : True True : 0 True 1 True +freq= 100 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 100 duty_u16 = 24576 = 37.50% : True True : 0 True 1 True +freq= 100 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 100 duty_u16 = 40960 = 62.50% : True True : 0 True 1 True +freq= 100 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 100 duty_u16 = 57344 = 87.50% : True True : 0 True 1 True +freq= 100 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 500 duty_u16 = 0 = 0.00% : True (0, 262144) : 0 True +freq= 500 duty_u16 = 8192 = 12.50% : True True : 0 True 1 True +freq= 500 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 500 duty_u16 = 24576 = 37.50% : True True : 0 True 1 True +freq= 500 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 500 duty_u16 = 40960 = 62.50% : True True : 0 True 1 True +freq= 500 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 500 duty_u16 = 57344 = 87.50% : True True : 0 True 1 True +freq= 500 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 1000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 1000 duty_u16 = 8192 = 12.50% : True True : 0 True 1 True +freq= 1000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 1000 duty_u16 = 24576 = 37.50% : True True : 0 True 1 True +freq= 1000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 1000 duty_u16 = 40960 = 62.50% : True True : 0 True 1 True +freq= 1000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 1000 duty_u16 = 57344 = 87.50% : True True : 0 True 1 True +freq= 1000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 2000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 2000 duty_u16 = 8192 = 12.50% : True True : 0 True 1 True +freq= 2000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 2000 duty_u16 = 24576 = 37.50% : True True : 0 True 1 True +freq= 2000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 2000 duty_u16 = 40960 = 62.50% : True True : 0 True 1 True +freq= 2000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 2000 duty_u16 = 57344 = 87.50% : True True : 0 True 1 True +freq= 2000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 5000 duty_u16 = 0 = 0.00% : True True : 0 True +freq= 5000 duty_u16 = 8192 = 12.50% : True True : 0 True 1 True +freq= 5000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 5000 duty_u16 = 24576 = 37.50% : True True : 0 True 1 True +freq= 5000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 5000 duty_u16 = 40960 = 62.50% : True True : 0 True 1 True +freq= 5000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 5000 duty_u16 = 57344 = 87.50% : True True : 0 True 1 True +freq= 5000 duty_u16 = 65536 = 100.00% : True True : 1 True + +freq= 10000 duty_u16 = 0 = 0.00% : True (0, 65536) : 0 False +freq= 10000 duty_u16 = 8192 = 12.50% : True True : 0 True 1 True +freq= 10000 duty_u16 = 16384 = 25.00% : True True : 0 True 1 True +freq= 10000 duty_u16 = 24576 = 37.50% : True True : 0 True 1 True +freq= 10000 duty_u16 = 32768 = 50.00% : True True : 0 True 1 True +freq= 10000 duty_u16 = 40960 = 62.50% : True True : 0 True 1 True +freq= 10000 duty_u16 = 49152 = 75.00% : True True : 0 True 1 True +freq= 10000 duty_u16 = 57344 = 87.50% : True True : 0 True 1 True +freq= 10000 duty_u16 = 65536 = 100.00% : True True : 1 True From e7f20c3755233c00a6830aabde3e11b1730789df Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 6 Nov 2024 00:06:04 +0200 Subject: [PATCH 08/30] Update machine_pulse.c #define TEST_JITTER 0 --- extmod/machine_pulse.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 33b206431ddf2..0f94722087953 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -64,6 +64,16 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_tim MP_WEAK mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { mp_uint_t start = mp_hal_ticks_us(); + #define TEST_JITTER 0 + #if TEST_JITTER + while (mp_hal_pin_read(pin) == pulse_level) { + if ((mp_uint_t)(mp_hal_ticks_us() - start) >= start) { + break; + } + break; + } + return mp_hal_ticks_us() - start; + #endif while (mp_hal_pin_read(pin) == pulse_level) { if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { return (mp_uint_t)-3; From ac1af7af970fd6d74df69ddb549dd01939b0dccb Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 6 Nov 2024 10:57:10 +0200 Subject: [PATCH 09/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index d945f25286a2a..97c5adc659c20 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -260,6 +260,12 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 for (; freq > 1; freq >>= 1) { ++res; } + return res; +} +#endif + +static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + unsigned int res = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); if (res == 0) { res = 1; } else if (res > HIGHEST_PWM_RES) { @@ -268,7 +274,6 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 } return res; } -#endif static void pwm_is_active(machine_pwm_obj_t *self) { if (self->timer < 0) { @@ -449,7 +454,7 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { #endif // Configure the new resolution and frequency - timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); + timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); // Configure timer - Set frequency if (ESP_OK != ledc_timer_config(timer)) { From be8f719a74258baf83fff89b345d7bc98b66b0fe Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 6 Nov 2024 20:56:31 +0200 Subject: [PATCH 10/30] Update quickref.rst --- docs/esp32/quickref.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 2906ead522de5..6d4b14aef42d2 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -346,6 +346,18 @@ but only 8 different PWM frequencies are available, the remaining 8 channels mus have the same frequency. On the other hand, 16 independent PWM duty cycles are possible at the same frequency. +Note: New PWM parameters take effect in the next PWM cycle. + + pwm = PWM(2, duty=512) + print(pwm) + >>>PWM(Pin(2), freq=5000, duty=1024) # 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 + See more examples in the :ref:`esp32_pwm` tutorial. DAC (digital to analog conversion) From 451b02f4b25316eeb32df38072b39c19cf9e2999 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 6 Nov 2024 20:57:15 +0200 Subject: [PATCH 11/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 140 +++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 62 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 97c5adc659c20..53808c5d1bdc5 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -64,20 +64,22 @@ static ledc_timer_config_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; // 5khz is default frequency #define PWM_FREQ (5000) -// 10-bit resolution (compatible with esp8266 PWM) -#define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) +// 10-bit resolution compatible with esp8266 PWM.duty(0..1024) +#define UI_RES_10_BIT (10) // Maximum duty value on 10-bit resolution -#define MAX_DUTY_U10 (1 << PWM_RES_10_BIT) +#define MAX_DUTY_UI10 (1 << UI_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 -// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer +// Duty resolution of user interface in `duty_u16(0..2^16)` 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) +#define MAX_DUTY_UI16 (1 << UI_RES_16_BIT) +#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) +/* #if defined(SOC_LEDC_TIMER_BIT_WIDTH) #if SOC_LEDC_TIMER_BIT_WIDTH < 16 #define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) @@ -93,6 +95,9 @@ static ledc_timer_config_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; #else #error HIGHEST_PWM_RES #endif +*/ + +#define MAX_TIMER_DUTY (1 << timer->duty_resolution) // 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 @@ -109,9 +114,9 @@ typedef struct _machine_pwm_obj_t { 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; // saved values from previous duty setters - int channel_duty; // saved values from previous duty setters calculated to raw channel->duty + int 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; @@ -216,8 +221,7 @@ static void configure_channel(machine_pwm_obj_t *self) { .flags.output_invert = self->output_invert, }; ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - int max_duty = 1 << timer->duty_resolution; - if (self->channel_duty == max_duty) { + if (self->channel_duty == MAX_TIMER_DUTY) { cfg.duty = 0; cfg.flags.output_invert = self->output_invert ^ 1; } @@ -266,6 +270,7 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { unsigned int res = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + debug_printf("ledc_find_suitable_duty_resolution=%d", res) if (res == 0) { res = 1; } else if (res > HIGHEST_PWM_RES) { @@ -285,11 +290,11 @@ static void pwm_is_active(machine_pwm_obj_t *self) { static int ns_to_duty(machine_pwm_obj_t *self, int ns) { pwm_is_active(self); ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - int64_t duty = ((int64_t)ns * UI_MAX_DUTY * timer->freq_hz + 500000000LL) / 1000000000LL; + int64_t duty = ((int64_t)ns * MAX_DUTY_UI16 * timer->freq_hz + 500000000LL) / 1000000000LL; if ((ns > 0) && (duty == 0)) { duty = 1; - } else if (duty > UI_MAX_DUTY) { - duty = UI_MAX_DUTY; + } else if (duty > MAX_DUTY_UI16) { + duty = MAX_DUTY_UI16; } return duty; } @@ -297,7 +302,7 @@ static int ns_to_duty(machine_pwm_obj_t *self, int ns) { static int duty_to_ns(machine_pwm_obj_t *self, int duty) { pwm_is_active(self); 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); + int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer->freq_hz * MAX_DUTY_UI16 / 2) / ((int64_t)timer->freq_hz * MAX_DUTY_UI16); return ns; } @@ -306,11 +311,13 @@ static int duty_to_ns(machine_pwm_obj_t *self, int duty) { static uint32_t get_duty_u16(machine_pwm_obj_t *self) { pwm_is_active(self); 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; + debug_printf("MAX_TIMER_DUTY=%d timer->duty_resolution=%d self->channel_duty=%d get_duty_raw()=%d", MAX_TIMER_DUTY, timer->duty_resolution, self->channel_duty, get_duty_raw(self)) + + if ((self->channel_duty == MAX_TIMER_DUTY) || (get_duty_raw(self) >= MAX_TIMER_DUTY)) { + return MAX_DUTY_UI16; } else { int resolution = timers[self->mode][self->timer].duty_resolution; + debug_printf("resolution=%d get_duty_raw()=%d", resolution, get_duty_raw(self)); if (resolution <= UI_RES_16_BIT) { return get_duty_raw(self) << (UI_RES_16_BIT - resolution); } else { @@ -320,7 +327,7 @@ static uint32_t get_duty_u16(machine_pwm_obj_t *self) { } static uint32_t get_duty_u10(machine_pwm_obj_t *self) { - return get_duty_u16(self) >> (UI_RES_16_BIT - PWM_RES_10_BIT); // Scale down from 16 bit to 10 bit resolution + return get_duty_u16(self) >> (UI_RES_16_BIT - UI_RES_10_BIT); // Scale down from 16 bit to 10 bit resolution } static uint32_t get_duty_ns(machine_pwm_obj_t *self) { @@ -331,12 +338,12 @@ static void apply_duty(machine_pwm_obj_t *self) { pwm_is_active(self); ledc_timer_config_t *timer = &timers[self->mode][self->timer]; int duty = 0; - if (self->duty_x == HIGHEST_PWM_RES) { - duty = self->duty; - } else if (self->duty_x == PWM_RES_10_BIT) { - duty = self->duty << (UI_RES_16_BIT - PWM_RES_10_BIT); - } else if (self->duty_x == -HIGHEST_PWM_RES) { - duty = ns_to_duty(self, self->duty); + 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 channel_duty; if (timer->duty_resolution <= UI_RES_16_BIT) { @@ -344,11 +351,10 @@ static void apply_duty(machine_pwm_obj_t *self) { } else { channel_duty = duty << (timer->duty_resolution - UI_RES_16_BIT); } - int max_duty = 1 << timer->duty_resolution; if (channel_duty < 0) { channel_duty = 0; - } else if (channel_duty > max_duty) { - channel_duty = max_duty; + } else if (channel_duty > MAX_TIMER_DUTY) { + channel_duty = MAX_TIMER_DUTY; } check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); check_esp_err(ledc_update_duty(self->mode, self->channel)); @@ -356,8 +362,8 @@ static void apply_duty(machine_pwm_obj_t *self) { } */ static void set_duty_u16(machine_pwm_obj_t *self, int duty) { - if ((duty < 0) || (duty > UI_MAX_DUTY)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY); + if ((duty < 0) || (duty > MAX_DUTY_UI16)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), MAX_DUTY_UI16); } ledc_timer_config_t *timer = &timers[self->mode][self->timer]; @@ -367,45 +373,52 @@ static void set_duty_u16(machine_pwm_obj_t *self, int duty) { } else { channel_duty = duty << (timer->duty_resolution - UI_RES_16_BIT); } - int max_duty = 1 << timer->duty_resolution; if (channel_duty < 0) { channel_duty = 0; - } else if (channel_duty > max_duty) { - channel_duty = max_duty; - } + } else if (channel_duty > MAX_TIMER_DUTY) { + channel_duty = MAX_TIMER_DUTY; + } + debug_printf("\nself->mode=%d, self->channel=%d, channel_duty=%d, MAX_TIMER_DUTY=%d, timer->duty_resolution=%d", self->mode, self->channel, channel_duty, MAX_TIMER_DUTY, timer->duty_resolution) + #define USE_THREAD_SAFE_LEDC_API 0 + #if USE_THREAD_SAFE_LEDC_API + esp_err_t err = ledc_set_duty_and_update(self->mode, self->channel, channel_duty, channel_duty); + if (err != ESP_OK) + #endif + { check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); check_esp_err(ledc_update_duty(self->mode, self->channel)); + } - self->duty_x = HIGHEST_PWM_RES; - self->duty = duty; + self->duty_x = UI_RES_16_BIT; + self->duty_ui = duty; self->channel_duty = channel_duty; } static void set_duty_u10(machine_pwm_obj_t *self, int duty) { - if ((duty < 0) || (duty > MAX_DUTY_U10)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_U10); + if ((duty < 0) || (duty > MAX_DUTY_UI10)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_UI10); } - set_duty_u16(self, duty << (UI_RES_16_BIT - PWM_RES_10_BIT)); - self->duty_x = PWM_RES_10_BIT; - self->duty = duty; + set_duty_u16(self, duty << (UI_RES_16_BIT - UI_RES_10_BIT)); + self->duty_x = UI_RES_10_BIT; + self->duty_ui = duty; } static void set_duty_ns(machine_pwm_obj_t *self, int ns) { - if ((ns < 0) || (ns > duty_to_ns(self, UI_MAX_DUTY))) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI_MAX_DUTY)); + if ((ns < 0) || (ns > duty_to_ns(self, MAX_DUTY_UI16))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, MAX_DUTY_UI16)); } set_duty_u16(self, ns_to_duty(self, ns)); - self->duty_x = -HIGHEST_PWM_RES; - self->duty = ns; + self->duty_x = -UI_RES_16_BIT; + self->duty_ui = 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); + 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); } } @@ -591,9 +604,9 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p 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)); @@ -602,7 +615,7 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p mp_printf(print, ", invert=%d", self->output_invert); } mp_printf(print, ")"); - #if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL + #if 1//MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL int resolution = timers[self->mode][self->timer].duty_resolution; mp_printf(print, " # resolution=%d", resolution); @@ -652,17 +665,17 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, } */ if (duty_u16 >= 0) { - self->duty_x = HIGHEST_PWM_RES; - self->duty = duty_u16; + self->duty_x = UI_RES_16_BIT; + self->duty_ui = duty_u16; } else if (duty_ns >= 0) { - self->duty_x = -HIGHEST_PWM_RES; - self->duty = duty_ns; + self->duty_x = -UI_RES_16_BIT; + self->duty_ui = duty_ns; } else if (duty >= 0) { - self->duty_x = PWM_RES_10_BIT; - self->duty = duty; + self->duty_x = UI_RES_10_BIT; + self->duty_ui = duty; } else if (self->duty_x == 0) { - self->duty_x = HIGHEST_PWM_RES; - self->duty = (1 << HIGHEST_PWM_RES) / 2; // 50% + self->duty_x = UI_RES_16_BIT; + self->duty_ui = (1 << UI_RES_16_BIT) / 2; // default 50% } self->output_invert = args[ARG_invert].u_int == 0 ? 0 : 1; @@ -722,7 +735,7 @@ static void self_reset(machine_pwm_obj_t *self) { self->channel = -1; self->timer = -1; self->duty_x = 0; - self->duty = 0; + self->duty_ui = 0; self->channel_duty = 0; self->output_invert = 0; } @@ -740,6 +753,9 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, // start the PWM subsystem if it's not already running if (!pwm_inited) { + #if USE_THREAD_SAFE_LEDC_API + ledc_fade_func_install(ESP_INTR_FLAG_INTRDISABLED); + #endif pwm_init(); pwm_inited = true; } From e5cf7e8f6620518568832e4f0df409aabdea0e22 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 6 Nov 2024 20:58:13 +0200 Subject: [PATCH 12/30] Update mphalport.h --- ports/esp32/mphalport.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/esp32/mphalport.h b/ports/esp32/mphalport.h index a908d784d7f61..1d6e9cff9e150 100644 --- a/ports/esp32/mphalport.h +++ b/ports/esp32/mphalport.h @@ -57,10 +57,11 @@ extern ringbuf_t stdin_ringbuf; extern portMUX_TYPE mp_atomic_mux; // Check the ESP-IDF error code and raise an OSError if it's not ESP_OK. -#if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_NORMAL +#if 0//MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_NORMAL #define check_esp_err(code) check_esp_err_(code) void check_esp_err_(esp_err_t code); #else +#define MICROPY_ERROR_REPORTING_NORMAL_PLUS #define check_esp_err(code) check_esp_err_(code, __FUNCTION__, __LINE__, __FILE__) void check_esp_err_(esp_err_t code, const char *func, const int line, const char *file); #endif From 1f30f850d407fd4ec2938848d1164c3eb9f20c84 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 6 Nov 2024 20:58:34 +0200 Subject: [PATCH 13/30] Update mphalport.c --- ports/esp32/mphalport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c index 1cd99f4ae287b..4bce46dffd063 100644 --- a/ports/esp32/mphalport.c +++ b/ports/esp32/mphalport.c @@ -60,7 +60,7 @@ ringbuf_t stdin_ringbuf = {stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, portMUX_TYPE mp_atomic_mux = portMUX_INITIALIZER_UNLOCKED; // Check the ESP-IDF error code and raise an OSError if it's not ESP_OK. -#if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_NORMAL +#ifndef MICROPY_ERROR_REPORTING_NORMAL_PLUS void check_esp_err_(esp_err_t code) #else void check_esp_err_(esp_err_t code, const char *func, const int line, const char *file) @@ -87,7 +87,7 @@ void check_esp_err_(esp_err_t code, const char *func, const int line, const char return; } o_str->base.type = &mp_type_str; - #if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL + #ifdef MICROPY_ERROR_REPORTING_NORMAL_PLUS char err_msg[64]; esp_err_to_name_r(code, err_msg, sizeof(err_msg)); vstr_t vstr; From aaf7a22539cf3f56dfd878733afb93a0d2b763f7 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Thu, 7 Nov 2024 00:02:29 +0200 Subject: [PATCH 14/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 53808c5d1bdc5..05250a7848504 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -385,8 +385,8 @@ static void set_duty_u16(machine_pwm_obj_t *self, int duty) { if (err != ESP_OK) #endif { - check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); - check_esp_err(ledc_update_duty(self->mode, self->channel)); + check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); } self->duty_x = UI_RES_16_BIT; From 9d9471f6590f2d921cded953986b6899345d5f28 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 5 Jan 2024 13:05:25 +0200 Subject: [PATCH 15/30] esp32\machine_pwm: PWM reduce inconsist. Signed-off-by: IhorNehrutsa --- docs/esp32/quickref.rst | 48 +- docs/esp32/tutorial/pwm.rst | 197 ++++++-- docs/library/machine.PWM.rst | 18 +- ports/esp32/machine_pwm.c | 844 ++++++++++++++++++++--------------- 4 files changed, 681 insertions(+), 426 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 3ab4e8f5ecd96..6d4b14aef42d2 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,25 +319,45 @@ 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=1024) # 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 + 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 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 0134fa2cc8c89..c75f2bec5627c 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,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,11 +41,9 @@ #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, "\n"); typedef struct _chan_t { // Which channel has which GPIO pin assigned? @@ -52,61 +51,59 @@ 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 #define PWM_FREQ (5000) -// 10-bit resolution (compatible with esp8266 PWM) -#define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) +// 10-bit resolution compatible with esp8266 PWM.duty(0..1024) +#define UI_RES_10_BIT (10) // Maximum duty value on 10-bit resolution -#define MAX_DUTY_U10 ((1 << PWM_RES_10_BIT) - 1) +#define MAX_DUTY_UI10 (1 << UI_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(0..2^16)` and `duty_u16` parameter in constructor/initializer +#define UI_RES_16_BIT (16) +// Maximum duty value on highest user interface resolution +#define MAX_DUTY_UI16 (1 << UI_RES_16_BIT) + +#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) +/* +#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 +#define MAX_TIMER_DUTY (1 << timer->duty_resolution) + +// 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; @@ -114,84 +111,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; // 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); +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; } @@ -200,15 +213,37 @@ 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]; + if (self->channel_duty == MAX_TIMER_DUTY) { + cfg.duty = 0; + cfg.flags.output_invert = self->output_invert ^ 1; + } + check_esp_err(ledc_channel_config(&cfg)); + + // reconfigure PWM pin output as input/output + 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 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; } // Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 @@ -218,11 +253,7 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 // Find the highest bit resolution for the requested frequency unsigned int freq = src_clk_freq; - - int divider = (freq + timer_freq / 2) / timer_freq; // rounded - if (divider == 0) { - divider = 1; - } + unsigned int divider = calc_divider(freq, timer_freq); float f = (float)freq / divider; // actual frequency if (f <= 1.0) { f = 1.0; @@ -233,217 +264,225 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 for (; freq > 1; freq >>= 1) { ++res; } + return res; +} +#endif + +static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + unsigned int res = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); 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 - -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; - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 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); - } - #else - // Simplified fallback logic for IDF V5.0.x, for targets with APB only. - src_clk_freq = APB_CLK_FREQ; // 80 MHz - #if SOC_LEDC_SUPPORT_REF_TICK - if (timer->clk_cfg == LEDC_USE_REF_TICK) { - src_clk_freq = REF_CLK_FREQ; // 1 MHz - } - #endif // SOC_LEDC_SUPPORT_REF_TICK - #endif // ESP_IDF_VERSION - - 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); + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + int64_t duty = ((int64_t)ns * MAX_DUTY_UI16 * timer->freq_hz + 500000000LL) / 1000000000LL; if ((ns > 0) && (duty == 0)) { duty = 1; - } else if (duty > UI_MAX_DUTY) { - duty = UI_MAX_DUTY; + } else if (duty > MAX_DUTY_UI16) { + duty = MAX_DUTY_UI16; } 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); + pwm_is_active(self); + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer->freq_hz * MAX_DUTY_UI16 / 2) / ((int64_t)timer->freq_hz * MAX_DUTY_UI16); 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")); - } -} - 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]; + + if ((self->channel_duty == MAX_TIMER_DUTY) || (get_duty_raw(self) >= MAX_TIMER_DUTY)) { + return MAX_DUTY_UI16; } 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 - UI_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) { +/* +static void apply_duty(machine_pwm_obj_t *self) { 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[self->mode][self->timer]; + 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); } - 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); + 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; if (channel_duty < 0) { channel_duty = 0; - } else if (channel_duty > max_duty) { - channel_duty = max_duty; + } else if (channel_duty > MAX_TIMER_DUTY) { + channel_duty = MAX_TIMER_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->channel_duty = channel_duty; +} +*/ +static void set_duty_u16(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > MAX_DUTY_UI16)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), MAX_DUTY_UI16); } - */ - self->duty_x = HIGHEST_PWM_RES; - self->duty_u16 = duty; + 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); + } else { + channel_duty = duty << (timer->duty_resolution - UI_RES_16_BIT); + } + if (channel_duty < 0) { + channel_duty = 0; + } else if (channel_duty > MAX_TIMER_DUTY) { + channel_duty = MAX_TIMER_DUTY; + } + #define USE_THREAD_SAFE_LEDC_API 0 + #if USE_THREAD_SAFE_LEDC_API + esp_err_t err = ledc_set_duty_and_update(self->mode, self->channel, channel_duty, channel_duty); + if (err != ESP_OK) + #endif + { + check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); + } + + self->duty_x = UI_RES_16_BIT; + self->duty_ui = 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); + if ((duty < 0) || (duty > MAX_DUTY_UI10)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_UI10); } - set_duty_u16(self, duty << (UI_RES_16_BIT - PWM_RES_10_BIT)); - self->duty_x = PWM_RES_10_BIT; - self->duty_u10 = duty; + set_duty_u16(self, duty << (UI_RES_16_BIT - UI_RES_10_BIT)); + self->duty_x = UI_RES_10_BIT; + self->duty_ui = 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)); + if ((ns < 0) || (ns > duty_to_ns(self, MAX_DUTY_UI16))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, MAX_DUTY_UI16)); } 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; } -/******************************************************************************/ +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. - } +// 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 + + // Configure the new resolution and frequency + timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); - return free_timer_idx_found; + // 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; } } @@ -451,25 +490,105 @@ 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) { + // 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; } /******************************************************************************/ @@ -478,141 +597,161 @@ 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); + 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 = 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 = (1 << UI_RES_16_BIT) / 2; // default 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; + if (freq != -1) { + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); + } + } // 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; + register_channel(self->mode, self->channel, self->pin, self->timer); } - 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->mode = -1; + self->channel = -1; + self->timer = -1; + self->duty_x = 0; + self->duty_ui = 0; + self->channel_duty = 0; + 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]); + gpio_num_t pin = 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; + self->pin = pin; + self_reset(self); // start the PWM subsystem if it's not already running if (!pwm_inited) { + #if USE_THREAD_SAFE_LEDC_API + ledc_fade_func_install(ESP_INTR_FLAG_INTRDISABLED); + #endif pwm_init(); pwm_inited = true; } @@ -627,69 +766,32 @@ 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_reset(self); } // 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) { + pwm_is_active(self); + 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) { From b9d6cf01c1a916beac3836149245587e60d57ae1 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 10:56:41 +0200 Subject: [PATCH 16/30] machine: Add time_hardware_pulse_us function. The difference from `time_pulse_us` is that if the pin is initially equal to *pulse_level* then `time_pulse_us` counts the pulse duration immediately, but `time_hardware_pulse_us` first waits for the different *pulse_level*, then waits the equal to *pulse_level* and then counts pulse duration. Signed-off-by: IhorNehrutsa Co-Authored-By: Robert Hammelrath <12476868+robert-hh@users.noreply.github.com> --- docs/library/machine.rst | 23 +++++++++++++++++++++++ extmod/machine_pulse.c | 38 ++++++++++++++++++++++++++++++++++++++ extmod/modmachine.c | 1 + extmod/modmachine.h | 2 ++ 4 files changed, 64 insertions(+) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 7d2eb26a7ea34..770e51c87d106 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -198,6 +198,29 @@ Miscellaneous functions above. The timeout is the same for both cases and given by *timeout_us* (which is in microseconds). +.. function:: time_hardware_pulse_us(pin, pulse_level, timeout_us=1000000, /) + + Time a pulse on the given *pin*, and return the duration of the pulse in + microseconds. The *pulse_level* argument should be 0 to time a low pulse + or 1 to time a high pulse. + + If the pin is initially equal to *pulse_level* then first waits until + the pin input becomes different from *pulse_level* (***). + Then the function waits until the pin input becomes equal to *pulse_level* (**), + then the function counts the duration that the pin is equal to *pulse_level* (*). + + The function returns -3 if there was timeout waiting for condition marked (***) above. + The function will return -2 if there was timeout waiting for condition marked + (**) above, and -1 if there was timeout during the main measurement, marked (*) + above. The timeout is the same for all cases and given by *timeout_us* (which + is in microseconds). + + The difference from `time_pulse_us` is that if the pin is initially equal to *pulse_level* + then `time_pulse_us` counts the pulse duration immediately, + but `time_hardware_pulse_us` first waits for the different *pulse_level*, + then waits the equal to *pulse_level* and then counts pulse duration. + A little bit longer, but with higher accuracy. + .. function:: bitstream(pin, encoding, timing, data, /) Transmits *data* by bit-banging the specified *pin*. The *encoding* argument diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 85dba86d9b5ad..8515a5ad2937d 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -62,4 +62,42 @@ static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_time_pulse_us_); +MP_WEAK mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { + mp_uint_t start = mp_hal_ticks_us(); + while (mp_hal_pin_read(pin) == pulse_level) { + if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + return (mp_uint_t)-3; + } + } + start = mp_hal_ticks_us(); + while (mp_hal_pin_read(pin) != pulse_level) { + if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + return (mp_uint_t)-2; + } + } + start = mp_hal_ticks_us(); + while (mp_hal_pin_read(pin) == pulse_level) { + if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + return (mp_uint_t)-1; + } + } + return mp_hal_ticks_us() - start; +} + +static mp_obj_t machine_time_hardware_pulse_us_(size_t n_args, const mp_obj_t *args) { + mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[0]); + int level = 0; + if (mp_obj_is_true(args[1])) { + level = 1; + } + mp_uint_t timeout_us = 1000000; + if (n_args > 2) { + timeout_us = mp_obj_get_int(args[2]); + } + mp_uint_t us = machine_time_hardware_pulse_us(pin, level, timeout_us); + // May return -1 or -2 or -3 in case of timeout + return mp_obj_new_int(us); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_hardware_pulse_us_obj, 2, 3, machine_time_hardware_pulse_us_); + #endif diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 5906835949861..5441f378d2dcf 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -188,6 +188,7 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #endif #if MICROPY_PY_MACHINE_PULSE { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_hardware_pulse_us), MP_ROM_PTR(&machine_time_hardware_pulse_us_obj) }, #endif // Classes for PinBase and Signal. diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 7c16ed302ee2f..a33925a49e352 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -245,6 +245,7 @@ uintptr_t MICROPY_MACHINE_MEM_GET_WRITE_ADDR(mp_obj_t addr_o, uint align); NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len); mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); +mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj); MP_DECLARE_CONST_FUN_OBJ_0(machine_reset_obj); @@ -255,6 +256,7 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bootloader_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bitstream_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_hardware_pulse_us_obj); #if MICROPY_PY_MACHINE_I2C int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); From 0f3dd7895214f92d06226adbe33c5f621a79df29 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 8 Nov 2024 17:54:27 +0200 Subject: [PATCH 17/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 308 +++++++++++++++++++------------------- 1 file changed, 158 insertions(+), 150 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 05250a7848504..ec706195c4f04 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -43,7 +43,11 @@ #include "py/mpprint.h" -#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); +#define debug_printf(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf0(...)// mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf2(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf3(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf4(...)// 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? @@ -64,40 +68,21 @@ static ledc_timer_config_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; // 5khz is default frequency #define PWM_FREQ (5000) -// 10-bit resolution compatible with esp8266 PWM.duty(0..1024) +// 10-bit user interface resolution compatible with esp8266 PWM.duty(0..1024) #define UI_RES_10_BIT (10) // Maximum duty value on 10-bit resolution -#define MAX_DUTY_UI10 (1 << UI_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 +#define UI10_DUTY (1 << UI_RES_10_BIT) // Duty resolution of user interface in `duty_u16(0..2^16)` and `duty_u16` parameter in constructor/initializer #define UI_RES_16_BIT (16) // Maximum duty value on highest user interface resolution -#define MAX_DUTY_UI16 (1 << UI_RES_16_BIT) +#define UI16_DUTY (1 << UI_RES_16_BIT) #define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) -/* -#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 -#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 -*/ -#define MAX_TIMER_DUTY (1 << timer->duty_resolution) +// timer_duty is the MAX value of a channel_duty +#define timer_duty (1 << timer->duty_resolution) // 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 @@ -145,7 +130,7 @@ static void pwm_init(void) { // 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].freq_hz = -1; // unset timer is -1 timers[mode][timer].speed_mode = mode; timers[mode][timer].timer_num = timer; timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; // will reinstall later @@ -173,7 +158,7 @@ static void pwm_deinit(int mode, int channel) { if (!is_timer_in_use(mode, channel, timer)) { check_esp_err(ledc_timer_rst(mode, timer)); // Flag it unused - timers[mode][timer].freq_hz = 0; + timers[mode][timer].freq_hz = -1; } } @@ -210,7 +195,74 @@ void machine_pwm_deinit_all(void) { } } -static void configure_channel(machine_pwm_obj_t *self) { +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) { + pwm_is_active(self); + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + int64_t duty = ((int64_t)ns * UI16_DUTY * timer->freq_hz + 500000000LL) / 1000000000LL; + if ((ns > 0) && (duty == 0)) { + duty = 1; + } else if (duty > UI16_DUTY) { + duty = UI16_DUTY; + } + return duty; +} + +static int duty_to_ns(machine_pwm_obj_t *self, int duty) { + pwm_is_active(self); + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer->freq_hz * UI16_DUTY / 2) / ((int64_t)timer->freq_hz * UI16_DUTY); + return ns; +} + +static void configure_pin(machine_pwm_obj_t *self) { + // reconfigure PWM pin output as input/output + 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 + } +} + +#define apply_duty(self) debug_printf0("apply_duty(self->channel=%d,self->channel_duty=%d,self->pin=%d,self->mode=%d,self->timer=%d)", self->channel,self->channel_duty,self->pin,self->mode,self->timer); _apply_duty(self) ; +static void _apply_duty(machine_pwm_obj_t *self) { + ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + + 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 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); + } + /* + if (channel_duty < 0) { + channel_duty = 0; + } else if (channel_duty >= timer_duty) { + // When channel's binded timer selects its maximum duty resolution, + // the duty cycle value cannot be set to (2 ** duty_resolution) + channel_duty = timer_duty - 1; // Ok! + } + */ + self->channel_duty = channel_duty; + ledc_channel_config_t cfg = { .channel = self->channel, .duty = self->channel_duty, @@ -220,22 +272,15 @@ static void configure_channel(machine_pwm_obj_t *self) { .timer_sel = self->timer, .flags.output_invert = self->output_invert, }; - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - if (self->channel_duty == MAX_TIMER_DUTY) { + debug_printf4("apply_duty():self->channel_duty=%d, timer_duty=%d, cfg.flags.output_invert=%d", self->channel_duty, timer_duty, cfg.flags.output_invert) + if (self->channel_duty == timer_duty) { cfg.duty = 0; cfg.flags.output_invert = self->output_invert ^ 1; } check_esp_err(ledc_channel_config(&cfg)); - - // reconfigure PWM pin output as input/output - 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 - } +// 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 unsigned int calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { @@ -247,8 +292,8 @@ static unsigned int calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { } // 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) { +#if 1//ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) +static uint32_t Qledc_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 // Find the highest bit resolution for the requested frequency @@ -269,8 +314,9 @@ static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32 #endif static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { - unsigned int res = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - debug_printf("ledc_find_suitable_duty_resolution=%d", res) + unsigned int res0 = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + unsigned int res = Qledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + debug_printf3("res=%d, res0=%d", res, res0) if (res == 0) { res = 1; } else if (res > HIGHEST_PWM_RES) { @@ -280,32 +326,6 @@ static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t ti return res; } -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) { - pwm_is_active(self); - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - int64_t duty = ((int64_t)ns * MAX_DUTY_UI16 * timer->freq_hz + 500000000LL) / 1000000000LL; - if ((ns > 0) && (duty == 0)) { - duty = 1; - } else if (duty > MAX_DUTY_UI16) { - duty = MAX_DUTY_UI16; - } - return duty; -} - -static int duty_to_ns(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer->freq_hz * MAX_DUTY_UI16 / 2) / ((int64_t)timer->freq_hz * MAX_DUTY_UI16); - return ns; -} - #define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) static uint32_t get_duty_u16(machine_pwm_obj_t *self) { @@ -313,15 +333,24 @@ static uint32_t get_duty_u16(machine_pwm_obj_t *self) { ledc_timer_config_t *timer = &timers[self->mode][self->timer]; debug_printf("MAX_TIMER_DUTY=%d timer->duty_resolution=%d self->channel_duty=%d get_duty_raw()=%d", MAX_TIMER_DUTY, timer->duty_resolution, self->channel_duty, get_duty_raw(self)) - if ((self->channel_duty == MAX_TIMER_DUTY) || (get_duty_raw(self) >= MAX_TIMER_DUTY)) { - return MAX_DUTY_UI16; + debug_printf2("get_duty_u16():self->output_invert=%d, get_duty_raw(self)=%d", self->output_invert, get_duty_raw(self)) + //if ((self->channel_duty == timer_duty) || (get_duty_raw(self) >= timer_duty)) { + if (self->channel_duty == timer_duty) { + return UI16_DUTY; + //return self->output_invert == 0 ? UI16_DUTY : 0; + //return HIGHEST_PWM_RES; } else { - int resolution = timers[self->mode][self->timer].duty_resolution; - debug_printf("resolution=%d get_duty_raw()=%d", resolution, get_duty_raw(self)); - if (resolution <= UI_RES_16_BIT) { - return get_duty_raw(self) << (UI_RES_16_BIT - resolution); + /* + if ((self->output_invert) && (get_duty_raw(self) == 0)) { + return UI16_DUTY; + } + */ + int duty_resolution = timers[self->mode][self->timer].duty_resolution; + debug_printf2("duty_resolution=%d", duty_resolution) + if (duty_resolution <= UI_RES_16_BIT) { + return get_duty_raw(self) << (UI_RES_16_BIT - duty_resolution); } else { - return get_duty_raw(self) >> (resolution - UI_RES_16_BIT); + return get_duty_raw(self) >> (duty_resolution - UI_RES_16_BIT); } } } @@ -333,83 +362,47 @@ static uint32_t get_duty_u10(machine_pwm_obj_t *self) { static uint32_t get_duty_ns(machine_pwm_obj_t *self) { return duty_to_ns(self, get_duty_u16(self)); } -/* -static void apply_duty(machine_pwm_obj_t *self) { - pwm_is_active(self); - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - 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 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); - } - if (channel_duty < 0) { - channel_duty = 0; - } else if (channel_duty > MAX_TIMER_DUTY) { - channel_duty = MAX_TIMER_DUTY; - } - check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); - check_esp_err(ledc_update_duty(self->mode, self->channel)); - self->channel_duty = channel_duty; -} -*/ + static void set_duty_u16(machine_pwm_obj_t *self, int duty) { - if ((duty < 0) || (duty > MAX_DUTY_UI16)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), MAX_DUTY_UI16); + 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[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); - } - if (channel_duty < 0) { - channel_duty = 0; - } else if (channel_duty > MAX_TIMER_DUTY) { - channel_duty = MAX_TIMER_DUTY; - } - debug_printf("\nself->mode=%d, self->channel=%d, channel_duty=%d, MAX_TIMER_DUTY=%d, timer->duty_resolution=%d", self->mode, self->channel, channel_duty, MAX_TIMER_DUTY, timer->duty_resolution) + self->duty_x = UI_RES_16_BIT; + self->duty_ui = duty; + apply_duty(self); + /* #define USE_THREAD_SAFE_LEDC_API 0 #if USE_THREAD_SAFE_LEDC_API - esp_err_t err = ledc_set_duty_and_update(self->mode, self->channel, channel_duty, channel_duty); + check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1)); + esp_err_t err = ESP_OK; // ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1); if (err != ESP_OK) #endif { check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); check_esp_err(ledc_update_duty(self->mode, self->channel)); } - - self->duty_x = UI_RES_16_BIT; - self->duty_ui = duty; - self->channel_duty = channel_duty; + */ } static void set_duty_u10(machine_pwm_obj_t *self, int duty) { - if ((duty < 0) || (duty > MAX_DUTY_UI10)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_UI10); + 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 - UI_RES_10_BIT)); + // set_duty_u16(self, duty << (UI_RES_16_BIT - UI_RES_10_BIT)); 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) { - if ((ns < 0) || (ns > duty_to_ns(self, MAX_DUTY_UI16))) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, MAX_DUTY_UI16)); + 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)); + // set_duty_u16(self, ns_to_duty(self, ns)); self->duty_x = -UI_RES_16_BIT; self->duty_ui = ns; + apply_duty(self); } static void set_duty(machine_pwm_obj_t *self) { @@ -459,6 +452,7 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { } #endif + debug_printf4("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg) #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) { @@ -466,6 +460,7 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { } #endif + debug_printf4("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg) // Configure the new resolution and frequency timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); @@ -508,8 +503,9 @@ static int find_channel(int mode, int pin) { 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) { +// Returns timer with the same mode and frequency, freq == -1 means free timer +#define find_timer(mode, freq) _find_timer(mode, freq); debug_printf0("find_timer(mode=%d, freq=%d)", mode, freq); +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; @@ -521,7 +517,8 @@ static int find_timer(int mode, unsigned int freq) { // 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) { +#define select_a_timer(self, freq) debug_printf0("select_a_timer(freq=%d)", freq); _select_a_timer(self, freq); +static void _select_a_timer(machine_pwm_obj_t *self, int freq) { // mode, channel, timer may be -1(not defined) or actual values int save_mode = self->mode; int save_channel = self->channel; @@ -551,6 +548,7 @@ static void select_a_timer(machine_pwm_obj_t *self, int freq) { } } // If the timer is found, then bind and set the duty + debug_printf0("timer=%d, timers[mode][timer].freq_hz=%d, self->channel=%d, self->mode=%d", timer, timers[mode][timer].freq_hz, self->channel, self->mode); if ((timer >= 0) && (timers[mode][timer].freq_hz != 0) && (timers[mode][timer].freq_hz != freq) @@ -561,9 +559,12 @@ static void select_a_timer(machine_pwm_obj_t *self, int freq) { 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; + ////// timer = -1; + } + if (timer >= 0) { + self->timer = timer; + set_duty(self); } if (timer < 0) { @@ -580,7 +581,7 @@ static void select_a_timer(machine_pwm_obj_t *self, int freq) { 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 + timer = find_timer(mode, -1); // find free timer } } if (timer < 0) { @@ -616,10 +617,10 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p } mp_printf(print, ")"); #if 1//MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL - int resolution = timers[self->mode][self->timer].duty_resolution; - mp_printf(print, " # resolution=%d", resolution); + int duty_resolution = timers[self->mode][self->timer].duty_resolution; + mp_printf(print, " # duty_resolution=%d, get_duty_raw(self)=%d", duty_resolution, get_duty_raw(self)); - mp_printf(print, ", (duty=%.2f%%, resolution=%.6f%%)", 100.0 * get_duty_raw(self) / (1 << resolution), 100.0 * 1 / (1 << resolution)); // percents + mp_printf(print, ", (duty=%.2f%%, duty_resolution=%.6f%%)", 100.0 * get_duty_u16(self) / UI16_DUTY, 100.0 * 1 / (1 << duty_resolution)); // percents mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); #endif @@ -645,12 +646,19 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, { 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_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 freq = args[ARG_freq].u_int; + if (freq != -1) { + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); + } + } + int duty = args[ARG_duty].u_int; int duty_u16 = args[ARG_duty_u16].u_int; int duty_ns = args[ARG_duty_ns].u_int; @@ -678,7 +686,10 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, self->duty_ui = (1 << UI_RES_16_BIT) / 2; // default 50% } - self->output_invert = args[ARG_invert].u_int == 0 ? 0 : 1; + int output_invert = args[ARG_invert].u_int; + if (output_invert >= 0) { + self->output_invert = output_invert == 0 ? 0 : 1; + } int save_mode = self->mode; int save_channel = self->channel; @@ -697,12 +708,6 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, self->mode = mode; self->channel = channel; - int freq = args[ARG_freq].u_int; - if (freq != -1) { - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - } // Check if freq wasn't passed as an argument if ((freq == -1) && (mode >= 0) && (channel >= 0)) { // Check if already set, otherwise use the default freq. @@ -718,15 +723,18 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, } select_a_timer(self, freq); - set_freq(self, freq); // New PWM assignment if ((chans[mode][channel].pin < 0) || ((save_mode != self->mode)) || ((save_channel != self->channel)) || ((save_timer != self->timer))) { - configure_channel(self); + debug_printf("mp_machine_pwm_init_helper()") + apply_duty(self); + configure_pin(self); register_channel(self->mode, self->channel, self->pin, self->timer); + } else { + set_freq(self, freq); } } From 83b221728b3115565beda5f11f0cd8b88a4b8be0 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Sat, 9 Nov 2024 19:51:55 +0200 Subject: [PATCH 18/30] BAD --- extmod/machine_pulse.c | 16 +++--- ports/esp32/machine_pwm.c | 102 +++++++++++++++++++++----------------- ports/esp32/mphalport.c | 2 + ports/esp32/mphalport.h | 5 ++ 4 files changed, 72 insertions(+), 53 deletions(-) diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 767111f6ffaf1..1a34d4bc427f4 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -62,12 +62,12 @@ static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_time_pulse_us_); -MP_WEAK mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { - mp_uint_t start = mp_hal_ticks_us(); - #define TEST_JITTER 0 +MP_WEAK mp_uint_t/*uint64_t*/ machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { + /*mp_uint_t*/uint64_t start = mp_hal_ticks_us(); + #define TEST_JITTER 1//0 #if TEST_JITTER while (mp_hal_pin_read(pin) == pulse_level) { - if ((mp_uint_t)(mp_hal_ticks_us() - start) >= start) { + if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)start) { break; } break; @@ -75,21 +75,23 @@ MP_WEAK mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse return mp_hal_ticks_us() - start; #endif while (mp_hal_pin_read(pin) == pulse_level) { - if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { return (mp_uint_t)-3; } } start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) != pulse_level) { - if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { return (mp_uint_t)-2; } } start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) == pulse_level) { - if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { + /* + if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { return (mp_uint_t)-1; } + */ } return mp_hal_ticks_us() - start; } diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index ec706195c4f04..f8581c28a9b5b 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -48,6 +48,7 @@ #define debug_printf2(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf3(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf4(...)// mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf5(...) 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? @@ -283,74 +284,78 @@ static void _apply_duty(machine_pwm_obj_t *self) { //} } -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; +static uint32_t calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { + uint32_t divider = (uint32_t)(((uint64_t)src_clk_freq + (uint64_t)timer_freq / 2ULL) / (uint64_t)timer_freq); // rounded + return divider == 0 ? 1 : divider; +} +/* +static uint32_t calc_divider(uint64_t src_clk_freq, uint64_t timer_freq) { + uint32_t divider = (uint32_t)((src_clk_freq + timer_freq / 2ULL) / timer_freq); // rounded + return divider == 0 ? 1 : divider; } +*/ // Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 #if 1//ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) -static uint32_t Qledc_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 - +static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { // 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); + uint32_t divider = calc_divider(src_clk_freq, timer_freq); + // actual frequency + uint32_t freq = (uint32_t)((uint64_t)src_clk_freq * (uint64_t)divider / (uint64_t)src_clk_freq); - unsigned int res = 0; + uint32_t resolution = 0; for (; freq > 1; freq >>= 1) { - ++res; + ++resolution; } - return res; + debug_printf5("\nsrc_clk_freq=%d, timer_freq=%d, divider=%d, freq=%d, resolution=%d", src_clk_freq, timer_freq, divider, freq, resolution); + return resolution; } #endif static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { - unsigned int res0 = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - unsigned int res = Qledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - debug_printf3("res=%d, res0=%d", res, res0) - if (res == 0) { - res = 1; - } else if (res > HIGHEST_PWM_RES) { + unsigned int resolution; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + //resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + //if (timer_freq < 10) + #endif + { + // magic second calculation + resolution = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + } + if (resolution == 0) { + //resolution = 1; + } else if (resolution > HIGHEST_PWM_RES) { // Limit resolution to HIGHEST_PWM_RES to match units of our duty - res = HIGHEST_PWM_RES; + resolution = HIGHEST_PWM_RES; } - return res; + return resolution; } -#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) +#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); ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - debug_printf("MAX_TIMER_DUTY=%d timer->duty_resolution=%d self->channel_duty=%d get_duty_raw()=%d", MAX_TIMER_DUTY, timer->duty_resolution, self->channel_duty, get_duty_raw(self)) + debug_printf("MAX_TIMER_DUTY=%d timer->duty_resolution=%d self->channel_duty=%d ledc_duty()=%d", MAX_TIMER_DUTY, timer->duty_resolution, self->channel_duty, ledc_duty()) - debug_printf2("get_duty_u16():self->output_invert=%d, get_duty_raw(self)=%d", self->output_invert, get_duty_raw(self)) - //if ((self->channel_duty == timer_duty) || (get_duty_raw(self) >= timer_duty)) { + debug_printf2("get_duty_u16():self->output_invert=%d, ledc_duty()=%d", self->output_invert, ledc_duty()) + //if ((self->channel_duty == timer_duty) || (ledc_duty() >= timer_duty)) { if (self->channel_duty == timer_duty) { return UI16_DUTY; //return self->output_invert == 0 ? UI16_DUTY : 0; //return HIGHEST_PWM_RES; } else { /* - if ((self->output_invert) && (get_duty_raw(self) == 0)) { + if ((self->output_invert) && (ledc_duty() == 0)) { return UI16_DUTY; } */ int duty_resolution = timers[self->mode][self->timer].duty_resolution; debug_printf2("duty_resolution=%d", duty_resolution) if (duty_resolution <= UI_RES_16_BIT) { - return get_duty_raw(self) << (UI_RES_16_BIT - duty_resolution); + return ledc_duty() << (UI_RES_16_BIT - duty_resolution); } else { - return get_duty_raw(self) >> (duty_resolution - UI_RES_16_BIT); + return ledc_duty() >> (duty_resolution - UI_RES_16_BIT); } } } @@ -420,11 +425,11 @@ 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; - + timer->clk_cfg = LEDC_USE_APB_CLK; // LEDC_AUTO_CLK; + uint32_t src_clk_freq = APB_CLK_FREQ; +/* #if SOC_LEDC_SUPPORT_APB_CLOCK - timer->clk_cfg = LEDC_USE_APB_CLK; + timer->clk_cfg = LEDC_APB_CLK; // 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; @@ -451,24 +456,30 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { #endif } #endif - +*/ debug_printf4("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg) #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); + esp_err_t err = ESP_OK; + //esp_err_t err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); +// esp_err_t err = esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_APB, 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); } #endif - debug_printf4("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg) // Configure the new resolution and frequency timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); + debug_printf5("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d, timer->duty_resolution=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg, timer->duty_resolution) + // Configure timer - Set frequency + check_esp_err(ledc_timer_config(timer)); + /* if (ESP_OK != ledc_timer_config(timer)) { - unsigned int divider = calc_divider(src_clk_freq, timer->freq_hz); + uint32_t 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)); @@ -618,10 +629,9 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p mp_printf(print, ")"); #if 1//MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL int duty_resolution = timers[self->mode][self->timer].duty_resolution; - mp_printf(print, " # duty_resolution=%d, get_duty_raw(self)=%d", duty_resolution, get_duty_raw(self)); - - mp_printf(print, ", (duty=%.2f%%, duty_resolution=%.6f%%)", 100.0 * get_duty_u16(self) / UI16_DUTY, 100.0 * 1 / (1 << duty_resolution)); // percents - + mp_printf(print, " #"); + mp_printf(print, " duty=%.2f%%", 100.0 * get_duty_u16(self) / UI16_DUTY); // percents + mp_printf(print, ", raw_duty()=%d, duty_resolution=%d", ledc_duty(), duty_resolution); mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); #endif } else { diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c index 4bce46dffd063..62543156d2fc5 100644 --- a/ports/esp32/mphalport.c +++ b/ports/esp32/mphalport.c @@ -188,9 +188,11 @@ uint32_t mp_hal_ticks_ms(void) { return esp_timer_get_time() / 1000; } +#ifndef mp_hal_ticks_us uint32_t mp_hal_ticks_us(void) { return esp_timer_get_time(); } +#endif void mp_hal_delay_ms(uint32_t ms) { uint64_t us = (uint64_t)ms * 1000ULL; diff --git a/ports/esp32/mphalport.h b/ports/esp32/mphalport.h index 1d6e9cff9e150..c97bb3783fe8b 100644 --- a/ports/esp32/mphalport.h +++ b/ports/esp32/mphalport.h @@ -29,6 +29,8 @@ #ifndef INCLUDED_MPHALPORT_H #define INCLUDED_MPHALPORT_H +#include "esp_timer.h" + #include "py/ringbuf.h" #include "shared/runtime/interrupt_char.h" @@ -82,7 +84,10 @@ static inline void mp_end_atomic_section(mp_uint_t state) { #define MICROPY_BEGIN_ATOMIC_SECTION() mp_begin_atomic_section() #define MICROPY_END_ATOMIC_SECTION(state) mp_end_atomic_section(state) +#define mp_hal_ticks_us esp_timer_get_time +#ifndef mp_hal_ticks_us uint32_t mp_hal_ticks_us(void); +#endif __attribute__((always_inline)) static inline uint32_t mp_hal_ticks_cpu(void) { uint32_t ccount; #if CONFIG_IDF_TARGET_ARCH_RISCV From 8e731f163546391b6d3b8f1e63d668d2ad2eacd8 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Sun, 10 Nov 2024 23:25:36 +0200 Subject: [PATCH 19/30] ok --- extmod/machine_pulse.c | 4 +- ports/esp32/machine_pwm.c | 99 +++++++++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 1a34d4bc427f4..7ffe78481b2b4 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -64,7 +64,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_tim MP_WEAK mp_uint_t/*uint64_t*/ machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { /*mp_uint_t*/uint64_t start = mp_hal_ticks_us(); - #define TEST_JITTER 1//0 + #define TEST_JITTER 0 #if TEST_JITTER while (mp_hal_pin_read(pin) == pulse_level) { if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)start) { @@ -93,7 +93,7 @@ MP_WEAK mp_uint_t/*uint64_t*/ machine_time_hardware_pulse_us(mp_hal_pin_obj_t pi } */ } - return mp_hal_ticks_us() - start; + return (mp_uint_t)(mp_hal_ticks_us() - start); } static mp_obj_t machine_time_hardware_pulse_us_(size_t n_args, const mp_obj_t *args) { diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index f8581c28a9b5b..175987b80f175 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -48,7 +48,7 @@ #define debug_printf2(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf3(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf4(...)// mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -#define debug_printf5(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf5(...) //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? @@ -283,23 +283,52 @@ static void _apply_duty(machine_pwm_obj_t *self) { // mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin); //} } - +/* +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; +} +*/ +/* static uint32_t calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { uint32_t divider = (uint32_t)(((uint64_t)src_clk_freq + (uint64_t)timer_freq / 2ULL) / (uint64_t)timer_freq); // rounded return divider == 0 ? 1 : divider; } -/* -static uint32_t calc_divider(uint64_t src_clk_freq, uint64_t timer_freq) { - uint32_t divider = (uint32_t)((src_clk_freq + timer_freq / 2ULL) / timer_freq); // rounded +*/ +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; } -*/ // Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 #if 1//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 + + // 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); + + unsigned int res = 0; + for (; freq > 1; freq >>= 1) { + ++res; + } + return res; +} +*/ static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { // Find the highest bit resolution for the requested frequency uint32_t divider = calc_divider(src_clk_freq, timer_freq); + //uint32_t freq = src_clk_freq; // actual frequency uint32_t freq = (uint32_t)((uint64_t)src_clk_freq * (uint64_t)divider / (uint64_t)src_clk_freq); @@ -313,17 +342,25 @@ static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint3 #endif static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { - unsigned int resolution; + unsigned int resolution = 0; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) - //resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + unsigned int resolution1 = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + //if (resolution == 0) + //{ + unsigned int resolution2 = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + //} //if (timer_freq < 10) #endif { // magic second calculation - resolution = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + // resolution = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); } + if (resolution1 != resolution2) { + debug_printf5("resolution1=%d, resolution2=%d", resolution1, resolution2); + } + resolution = resolution2; if (resolution == 0) { - //resolution = 1; + resolution = 1; } else if (resolution > HIGHEST_PWM_RES) { // Limit resolution to HIGHEST_PWM_RES to match units of our duty resolution = HIGHEST_PWM_RES; @@ -424,12 +461,13 @@ static void set_duty(machine_pwm_obj_t *self) { 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) { + //int timer_freq_hz = timer->freq_hz; timer->freq_hz = freq; - timer->clk_cfg = LEDC_USE_APB_CLK; // LEDC_AUTO_CLK; - uint32_t src_clk_freq = APB_CLK_FREQ; -/* + timer->clk_cfg = LEDC_AUTO_CLK; + uint32_t src_clk_freq = 0; + #if SOC_LEDC_SUPPORT_APB_CLOCK - timer->clk_cfg = LEDC_APB_CLK; // LEDC_USE_APB_CLK; + 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; @@ -440,28 +478,16 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { #error No supported PWM / LEDC clocks. #endif - #ifdef EMPIRIC_FREQ + #ifdef EMPIRIC_FREQ // ESP32 and ESP32S2 only 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 -*/ - debug_printf4("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg) + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) esp_err_t err = ESP_OK; - //esp_err_t err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); -// esp_err_t err = esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_APB, ESP_CLK_TREE_SRC_FREQ_PRECISION_EXACT, &src_clk_freq); + 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); } @@ -469,17 +495,22 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { // Configure the new resolution and frequency timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); - + //debug_printf5("SOC_MOD_CLK_APB=%d, LEDC_USE_APB_CLK=%d, LEDC_AUTO_CLK=%d", SOC_MOD_CLK_APB, LEDC_USE_APB_CLK, LEDC_AUTO_CLK); debug_printf5("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d, timer->duty_resolution=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg, timer->duty_resolution) // Configure timer - Set frequency - check_esp_err(ledc_timer_config(timer)); - /* - if (ESP_OK != ledc_timer_config(timer)) { + err = ESP_FAIL; + //if (timer_freq_hz == -1) + { + //check_esp_err(ledc_timer_config(timer)); + err = ledc_timer_config(timer); + } + if (err != ESP_OK) + { uint32_t divider = calc_divider(src_clk_freq, timer->freq_hz); + debug_printf5("divider=%d, timer->clk_cfg=%d, err=0x%X", divider, timer->clk_cfg, err); 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)); From fcd22814e4e8d02c6162ead5d404a310e4309299 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 12 Nov 2024 20:28:18 +0200 Subject: [PATCH 20/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 73 +++++++++++---------------------------- 1 file changed, 21 insertions(+), 52 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 175987b80f175..32676e645bd3c 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -43,7 +43,7 @@ #include "py/mpprint.h" -#define debug_printf(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf0(...)// mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf2(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf3(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); @@ -234,7 +234,7 @@ static void configure_pin(machine_pwm_obj_t *self) { } } -#define apply_duty(self) debug_printf0("apply_duty(self->channel=%d,self->channel_duty=%d,self->pin=%d,self->mode=%d,self->timer=%d)", self->channel,self->channel_duty,self->pin,self->mode,self->timer); _apply_duty(self) ; +#define apply_duty(self) debug_printf0("apply_duty(self->channel=%d,self->channel_duty=%d,self->pin=%d,self->mode=%d,self->timer=%d)", self->channel, self->channel_duty, self->pin, self->mode, self->timer); _apply_duty(self); static void _apply_duty(machine_pwm_obj_t *self) { ledc_timer_config_t *timer = &timers[self->mode][self->timer]; @@ -279,9 +279,6 @@ static void _apply_duty(machine_pwm_obj_t *self) { cfg.flags.output_invert = self->output_invert ^ 1; } check_esp_err(ledc_channel_config(&cfg)); -// 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 unsigned int calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { @@ -304,7 +301,7 @@ static uint32_t calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { } // Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 -#if 1//ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) +#if 1 // 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 @@ -328,7 +325,6 @@ static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint3 static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { // Find the highest bit resolution for the requested frequency uint32_t divider = calc_divider(src_clk_freq, timer_freq); - //uint32_t freq = src_clk_freq; // actual frequency uint32_t freq = (uint32_t)((uint64_t)src_clk_freq * (uint64_t)divider / (uint64_t)src_clk_freq); @@ -342,23 +338,13 @@ static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint3 #endif static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { - unsigned int resolution = 0; - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) - unsigned int resolution1 = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - //if (resolution == 0) - //{ - unsigned int resolution2 = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - //} - //if (timer_freq < 10) - #endif - { - // magic second calculation - // resolution = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - } + unsigned int resolution = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + #if 0 // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + unsigned int resolution2 = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); if (resolution1 != resolution2) { - debug_printf5("resolution1=%d, resolution2=%d", resolution1, resolution2); + debug_printf("resolution1=%d, resolution2=%d", resolution1, resolution2); } - resolution = resolution2; + #endif if (resolution == 0) { resolution = 1; } else if (resolution > HIGHEST_PWM_RES) { @@ -376,17 +362,9 @@ static uint32_t get_duty_u16(machine_pwm_obj_t *self) { debug_printf("MAX_TIMER_DUTY=%d timer->duty_resolution=%d self->channel_duty=%d ledc_duty()=%d", MAX_TIMER_DUTY, timer->duty_resolution, self->channel_duty, ledc_duty()) debug_printf2("get_duty_u16():self->output_invert=%d, ledc_duty()=%d", self->output_invert, ledc_duty()) - //if ((self->channel_duty == timer_duty) || (ledc_duty() >= timer_duty)) { if (self->channel_duty == timer_duty) { return UI16_DUTY; - //return self->output_invert == 0 ? UI16_DUTY : 0; - //return HIGHEST_PWM_RES; } else { - /* - if ((self->output_invert) && (ledc_duty() == 0)) { - return UI16_DUTY; - } - */ int duty_resolution = timers[self->mode][self->timer].duty_resolution; debug_printf2("duty_resolution=%d", duty_resolution) if (duty_resolution <= UI_RES_16_BIT) { @@ -431,7 +409,6 @@ static void set_duty_u10(machine_pwm_obj_t *self, int duty) { 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 - UI_RES_10_BIT)); self->duty_x = UI_RES_10_BIT; self->duty_ui = duty; apply_duty(self); @@ -441,7 +418,6 @@ static void set_duty_ns(machine_pwm_obj_t *self, int ns) { 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 = -UI_RES_16_BIT; self->duty_ui = ns; apply_duty(self); @@ -457,12 +433,16 @@ static void set_duty(machine_pwm_obj_t *self) { } } +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) { ledc_timer_config_t *timer = &timers[self->mode][self->timer]; if (timer->freq_hz != freq) { - //int timer_freq_hz = timer->freq_hz; - timer->freq_hz = freq; timer->clk_cfg = LEDC_AUTO_CLK; uint32_t src_clk_freq = 0; @@ -486,12 +466,14 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { #endif #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) - esp_err_t err = ESP_OK; - err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_EXACT, &src_clk_freq); + 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 + timer->freq_hz = freq; // Configure the new resolution and frequency timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); @@ -499,14 +481,7 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { debug_printf5("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d, timer->duty_resolution=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg, timer->duty_resolution) // Configure timer - Set frequency - err = ESP_FAIL; - //if (timer_freq_hz == -1) - { - //check_esp_err(ledc_timer_config(timer)); - err = ledc_timer_config(timer); - } - if (err != ESP_OK) - { + if (ESP_OK != ledc_timer_config(timer)) { uint32_t divider = calc_divider(src_clk_freq, timer->freq_hz); debug_printf5("divider=%d, timer->clk_cfg=%d, err=0x%X", divider, timer->clk_cfg, err); check_esp_err(ledc_timer_set(timer->speed_mode, timer->timer_num, divider, timer->duty_resolution, timer->clk_cfg)); @@ -601,8 +576,6 @@ static void _select_a_timer(machine_pwm_obj_t *self, int freq) { 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); - } else { - ////// timer = -1; } if (timer >= 0) { self->timer = timer; @@ -695,9 +668,7 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, int freq = args[ARG_freq].u_int; if (freq != -1) { - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } + check_freq_ranges(freq, 40000000); } int duty = args[ARG_duty].u_int; @@ -834,10 +805,8 @@ 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) { - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } pwm_is_active(self); + check_freq_ranges(freq, 40000000); if (freq == timers[self->mode][self->timer].freq_hz) { return; } From e0ddfff47f43bb400d542daf48e2458aa1955ae7 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Thu, 14 Nov 2024 13:46:39 +0200 Subject: [PATCH 21/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 98 ++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 32676e645bd3c..1471d4b287d36 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -48,7 +48,7 @@ #define debug_printf2(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf3(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); #define debug_printf4(...)// mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -#define debug_printf5(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf5(...) //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? @@ -73,22 +73,23 @@ static ledc_timer_config_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; #define UI_RES_10_BIT (10) // Maximum duty value on 10-bit resolution -#define UI10_DUTY (1 << UI_RES_10_BIT) +#define UI10_DUTY ((1 << UI_RES_10_BIT) - 1) // Duty resolution of user interface in `duty_u16(0..2^16)` and `duty_u16` parameter in constructor/initializer #define UI_RES_16_BIT (16) // Maximum duty value on highest user interface resolution -#define UI16_DUTY (1 << UI_RES_16_BIT) +#define UI16_DUTY ((1 << UI_RES_16_BIT) - 1) #define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) +//#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1 - 1) // timer_duty is the MAX value of a channel_duty -#define timer_duty (1 << timer->duty_resolution) +#define timer_duty ((1 << timer->duty_resolution) - 1) // 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 static bool pwm_inited = false; @@ -135,6 +136,7 @@ static void pwm_init(void) { timers[mode][timer].speed_mode = mode; timers[mode][timer].timer_num = timer; timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; // will reinstall later + timers[mode][timer].deconfigure = false; } } } @@ -274,11 +276,25 @@ static void _apply_duty(machine_pwm_obj_t *self) { .flags.output_invert = self->output_invert, }; debug_printf4("apply_duty():self->channel_duty=%d, timer_duty=%d, cfg.flags.output_invert=%d", self->channel_duty, timer_duty, cfg.flags.output_invert) + /* if (self->channel_duty == timer_duty) { cfg.duty = 0; cfg.flags.output_invert = self->output_invert ^ 1; } + */ check_esp_err(ledc_channel_config(&cfg)); + /* + #define USE_THREAD_SAFE_LEDC_API 0 + #if USE_THREAD_SAFE_LEDC_API + check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1)); + esp_err_t err = ESP_OK; // ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1); + if (err != ESP_OK) + #endif + { + check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); + } + */ } /* static unsigned int calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { @@ -297,6 +313,12 @@ static uint32_t calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { */ 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 +/* + uint32_t divider2 = (uint32_t)((uint64_t)src_clk_freq / timer_freq); // tracate + if (divider != divider2) { + debug_printf5("divider=%d, divider2=%d", divider, divider2); + } +*/ return divider == 0 ? 1 : divider; } @@ -322,31 +344,44 @@ static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint3 return res; } */ +/* + unsigned int freq = src_clk_freq; + unsigned int divider = calc_divider(src_clk_freq, timer_freq); + float f = (float)src_clk_freq / divider; // actual frequency + if (f <= 1.0) { + f = 1.0; + } + freq = (unsigned int)roundf((float)src_clk_freq + (src_clk_freq / divider /2) / (src_clk_freq / divider)); +*/ static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { // Find the highest bit resolution for the requested frequency uint32_t divider = calc_divider(src_clk_freq, timer_freq); - // actual frequency - uint32_t freq = (uint32_t)((uint64_t)src_clk_freq * (uint64_t)divider / (uint64_t)src_clk_freq); + uint32_t f = src_clk_freq / divider; // actual frequency + uint32_t freq = src_clk_freq + (f / 2) / f; // rounded + uint32_t freq1 = freq; uint32_t resolution = 0; for (; freq > 1; freq >>= 1) { ++resolution; } - debug_printf5("\nsrc_clk_freq=%d, timer_freq=%d, divider=%d, freq=%d, resolution=%d", src_clk_freq, timer_freq, divider, freq, resolution); + //if (src_clk_freq != freq1) { + if (1) { + debug_printf5("\nsrc_clk_freq=%d, timer_freq=%d, divider=%d, freq=%d, resolution=%d", src_clk_freq, timer_freq, divider, freq1, 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 0 // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) - unsigned int resolution2 = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - if (resolution1 != resolution2) { - debug_printf("resolution1=%d, resolution2=%d", resolution1, resolution2); - } + unsigned int ledc_resolution = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + unsigned int resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + //if (resolution != resolution2) { + debug_printf5("resolution=%d, ledc_resolution=%d", resolution, ledc_resolution); + //} #endif if (resolution == 0) { - resolution = 1; + //resolution = 1; } else if (resolution > HIGHEST_PWM_RES) { // Limit resolution to HIGHEST_PWM_RES to match units of our duty resolution = HIGHEST_PWM_RES; @@ -358,13 +393,16 @@ static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t ti static uint32_t get_duty_u16(machine_pwm_obj_t *self) { pwm_is_active(self); - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; + //ledc_timer_config_t *timer = &timers[self->mode][self->timer]; debug_printf("MAX_TIMER_DUTY=%d timer->duty_resolution=%d self->channel_duty=%d ledc_duty()=%d", MAX_TIMER_DUTY, timer->duty_resolution, self->channel_duty, ledc_duty()) debug_printf2("get_duty_u16():self->output_invert=%d, ledc_duty()=%d", self->output_invert, ledc_duty()) + #if 0 // UI16_DUTY == 65536 if (self->channel_duty == timer_duty) { return UI16_DUTY; - } else { + } else + #endif + { int duty_resolution = timers[self->mode][self->timer].duty_resolution; debug_printf2("duty_resolution=%d", duty_resolution) if (duty_resolution <= UI_RES_16_BIT) { @@ -391,18 +429,6 @@ static void set_duty_u16(machine_pwm_obj_t *self, int duty) { self->duty_x = UI_RES_16_BIT; self->duty_ui = duty; apply_duty(self); - /* - #define USE_THREAD_SAFE_LEDC_API 0 - #if USE_THREAD_SAFE_LEDC_API - check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1)); - esp_err_t err = ESP_OK; // ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1); - if (err != ESP_OK) - #endif - { - check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); - check_esp_err(ledc_update_duty(self->mode, self->channel)); - } - */ } static void set_duty_u10(machine_pwm_obj_t *self, int duty) { @@ -477,15 +503,23 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { // Configure the new resolution and frequency timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); - //debug_printf5("SOC_MOD_CLK_APB=%d, LEDC_USE_APB_CLK=%d, LEDC_AUTO_CLK=%d", SOC_MOD_CLK_APB, LEDC_USE_APB_CLK, LEDC_AUTO_CLK); - debug_printf5("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d, timer->duty_resolution=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg, timer->duty_resolution) + debug_printf5("SOC_MOD_CLK_APB=%d, LEDC_USE_APB_CLK=%d, LEDC_AUTO_CLK=%d, LEDC_USE_REF_TICK=%d", SOC_MOD_CLK_APB, LEDC_USE_APB_CLK, LEDC_AUTO_CLK, LEDC_USE_REF_TICK); + debug_printf5("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d, timer->duty_resolution=%d, timer->speed_mode=%d, timer->timer_num=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg, timer->duty_resolution, timer->speed_mode, timer->timer_num) // Configure timer - Set frequency - if (ESP_OK != ledc_timer_config(timer)) { + check_esp_err(ledc_timer_config(timer)); + /* + err = ledc_timer_config(timer); + if (1) { + //if (ESP_OK != (err = ledc_timer_config(timer))) { uint32_t divider = calc_divider(src_clk_freq, timer->freq_hz); debug_printf5("divider=%d, timer->clk_cfg=%d, err=0x%X", divider, timer->clk_cfg, err); check_esp_err(ledc_timer_set(timer->speed_mode, timer->timer_num, divider, timer->duty_resolution, timer->clk_cfg)); + if (freq >= EMPIRIC_FREQ) { + check_esp_err(ledc_set_freq(timer->speed_mode, timer->timer_num,timer->freq_hz)); + } } + */ // Reset the timer if low speed if (self->mode == LEDC_LOW_SPEED_MODE) { check_esp_err(ledc_timer_rst(self->mode, self->timer)); From 81a29d09ff3c45ecb81ca6c5e0a7a167c7a7dcc9 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Thu, 5 Dec 2024 23:58:23 +0200 Subject: [PATCH 22/30] Ok? --- docs/esp32/quickref.rst | 10 +- docs/esp32/tutorial/pwm.rst | 183 ++++++------ drivers/dht/dht.c | 5 +- extmod/machine_pulse.c | 20 +- extmod/modmachine.h | 2 +- ports/esp32/machine_pwm.c | 536 ++++++++++++------------------------ 6 files changed, 293 insertions(+), 463 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index d170402771674..18fcd99867124 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -307,11 +307,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-1024 (default 512, 50%) - pwm0.duty(256) # set duty cycle from 0 to 1024 as a ratio duty/1024, (now 25%) + 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_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_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_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%) @@ -351,7 +351,7 @@ Note: New PWM parameters take effect in the next PWM cycle. pwm = PWM(2, duty=512) print(pwm) - >>>PWM(Pin(2), freq=5000, duty=1024) # the duty is not relevant + >>>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 diff --git a/docs/esp32/tutorial/pwm.rst b/docs/esp32/tutorial/pwm.rst index 2e1b82a585108..9fa8553bdf70f 100644 --- a/docs/esp32/tutorial/pwm.rst +++ b/docs/esp32/tutorial/pwm.rst @@ -16,14 +16,16 @@ low all of the time. from time import sleep from machine import Pin, PWM try: - f = 100 # Hz - d = 2**16 // 16 # 6.25% + 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_u16=min(2**16 - 1, 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]) - sleep(60) finally: for pwm in pwms: try: @@ -33,22 +35,22 @@ low all of the time. Output is:: - 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 + 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**:: @@ -56,17 +58,18 @@ low all of the time. from time import sleep from machine import Pin, PWM - F_MIN = 100 - F_MAX = 1000 + F_MIN = 1000 + F_MAX = 10000 f = F_MIN - delta_f = 100 + delta_f = F_MAX // 50 - p = PWM(Pin(27), f) + pwm = PWM(Pin(27), f) while True: - p.freq(f) - print(p) + pwm.freq(f) + sleep(1/f) + print(pwm) sleep(.2) @@ -83,28 +86,25 @@ low all of the time. Output is:: - 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 - ... +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=65536) # duty=100.00%, raw_duty=32768, 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=16384) # duty=25.00%, raw_duty=16384, 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**:: @@ -117,59 +117,54 @@ low all of the time. duty_u16 = 0 delta_d = 256 - pwm = PWM(Pin(27), 1000, duty_u16=duty_u16) - print(pwm) + pwm = PWM(Pin(27), freq=1000, duty_u16=duty_u16) while True: pwm.duty_u16(duty_u16) - - sleep(.001) - + sleep(2/pwm.freq()) print(pwm) + if duty_u16 >= DUTY_MAX: + print() + sleep(2) + elif duty_u16 <= 0: + print() + sleep(2) + 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=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=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=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=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 + 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**:: @@ -183,8 +178,8 @@ low all of the time. duty_u16 = 0 delta_d = 2**16 // 32 - pwm = PWM(Pin(27), 5000) - pwmi = PWM(Pin(32), 5000, invert=1) + pwm = PWM(Pin(27)) + pwmi = PWM(Pin(32), invert=1) while True: pwm.duty_u16(duty_u16) @@ -214,19 +209,23 @@ low all of the time. 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 ... ... - 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. -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/drivers/dht/dht.c b/drivers/dht/dht.c index e6b71542dabc9..47d114fed784e 100644 --- a/drivers/dht/dht.c +++ b/drivers/dht/dht.c @@ -71,8 +71,9 @@ static mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) { } } + #define WAIT_OPPOSITE false // time pulse, should be 80us - ticks = machine_time_pulse_us(pin, 1, 150); + ticks = machine_time_pulse_us(pin, 1, 150, WAIT_OPPOSITE); if ((mp_int_t)ticks < 0) { goto timeout; } @@ -80,7 +81,7 @@ static mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) { // time 40 pulses for data (either 26us or 70us) uint8_t *buf = bufinfo.buf; for (int i = 0; i < 40; ++i) { - ticks = machine_time_pulse_us(pin, 1, 100); + ticks = machine_time_pulse_us(pin, 1, 100, WAIT_OPPOSITE); if ((mp_int_t)ticks < 0) { goto timeout; } diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 7ffe78481b2b4..6077ca3b166a7 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -30,8 +30,14 @@ #if MICROPY_PY_MACHINE_PULSE -MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { +MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite) { mp_uint_t start = mp_hal_ticks_us(); + while (wait_opposite && (mp_hal_pin_read(pin) == pulse_level)) { + if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { + return (mp_uint_t)-3; + } + } + start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) != pulse_level) { if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { return (mp_uint_t)-2; @@ -56,11 +62,15 @@ static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { if (n_args > 2) { timeout_us = mp_obj_get_int(args[2]); } - mp_uint_t us = machine_time_pulse_us(pin, level, timeout_us); - // May return -1 or -2 in case of timeout + bool wait_opposite = false; + if (n_args > 3) { + wait_opposite = mp_obj_is_true(args[3]); + } + mp_uint_t us = machine_time_pulse_us(pin, level, timeout_us, wait_opposite); + // May return -1 or -2 or -3 in case of timeout return mp_obj_new_int(us); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_time_pulse_us_); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 4, machine_time_pulse_us_); MP_WEAK mp_uint_t/*uint64_t*/ machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { /*mp_uint_t*/uint64_t start = mp_hal_ticks_us(); @@ -87,11 +97,9 @@ MP_WEAK mp_uint_t/*uint64_t*/ machine_time_hardware_pulse_us(mp_hal_pin_obj_t pi } start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) == pulse_level) { - /* if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { return (mp_uint_t)-1; } - */ } return (mp_uint_t)(mp_hal_ticks_us() - start); } diff --git a/extmod/modmachine.h b/extmod/modmachine.h index a33925a49e352..7825ff0b6428f 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -244,7 +244,7 @@ uintptr_t MICROPY_MACHINE_MEM_GET_WRITE_ADDR(mp_obj_t addr_o, uint align); NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len); -mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); +mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite); mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj); diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 1471d4b287d36..d78cbd10df7e8 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -44,99 +44,84 @@ #include "py/mpprint.h" #define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -#define debug_printf0(...)// mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -#define debug_printf2(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -#define debug_printf3(...) //mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -#define debug_printf4(...)// mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); -#define debug_printf5(...) //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; -} chan_t; -// List of PWM channels -static chan_t chans[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_MAX]; - -// List of timer configs -static ledc_timer_config_t timers[LEDC_SPEED_MODE_MAX][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 user interface resolution compatible with esp8266 PWM.duty(0..1024) +// 10-bit user interface resolution compatible with esp8266 PWM.duty() #define UI_RES_10_BIT (10) - // Maximum duty value on 10-bit resolution -#define UI10_DUTY ((1 << UI_RES_10_BIT) - 1) +#define UI10_DUTY ((1UL << UI_RES_10_BIT) - 1) -// Duty resolution of user interface in `duty_u16(0..2^16)` and `duty_u16` parameter in constructor/initializer +// 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 UI16_DUTY ((1 << UI_RES_16_BIT) - 1) - -#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) -//#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1 - 1) +// 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 ((1 << timer->duty_resolution) - 1) +#define timer_duty ((1UL << timers[self->mode][self->timer].duty_resolution) - 1) // 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; // MicroPython PWM object struct typedef struct _machine_pwm_obj_t { mp_obj_base_t base; - gpio_num_t pin; - int mode; - int channel; - int timer; - int duty_x; // UI_RES_10_BIT if duty(), UI_RES_16_BIT if duty_u16(), -UI_RES_16_BIT if 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 void register_channel(int mode, int channel, int pin, int timer) { +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 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 mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + // Initial condition: no channels assigned for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { - unregister_channel(mode, channel); + set_channel(mode, channel, -1, -1); // unregister } - - // 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 = -1; // unset timer is -1 - timers[mode][timer].speed_mode = mode; - timers[mode][timer].timer_num = timer; - timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; // will reinstall later - timers[mode][timer].deconfigure = false; + timers[mode][timer].freq = -1; // unset timer is -1 + timers[mode][timer].duty_resolution = 0; } } } @@ -151,8 +136,8 @@ static bool is_timer_in_use(int mode, int current_channel, int timer) { return false; } -// Deinit channel and timer if the timer is unused, detach pin -static void pwm_deinit(int mode, int channel) { +// Deinit channel and timer if the timer is unused +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 @@ -161,28 +146,17 @@ static void pwm_deinit(int mode, int channel) { if (!is_timer_in_use(mode, channel, timer)) { check_esp_err(ledc_timer_rst(mode, timer)); // Flag it unused - timers[mode][timer].freq_hz = -1; + timers[mode][timer].freq = -1; + timers[mode][timer].duty_resolution = 0; } } 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 - if (mode == LEDC_LOW_SPEED_MODE) { - esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, false); - } else { - #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); - */ + // Disable LEDC output, and set idle level + check_esp_err(ledc_stop(mode, channel, level)); } - unregister_channel(mode, channel); + set_channel(mode, channel, -1, -1); // unregister } } @@ -191,7 +165,7 @@ void machine_pwm_deinit_all(void) { if (pwm_inited) { for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { - pwm_deinit(mode, channel); + pwm_deinit(mode, channel, 0); } } pwm_inited = false; @@ -207,8 +181,7 @@ static void pwm_is_active(machine_pwm_obj_t *self) { // Calculate the duty parameters based on an ns value static int ns_to_duty(machine_pwm_obj_t *self, int ns) { pwm_is_active(self); - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - int64_t duty = ((int64_t)ns * UI16_DUTY * timer->freq_hz + 500000000LL) / 1000000000LL; + int64_t duty = ((int64_t)ns * UI16_DUTY * self->freq + 500000000LL) / 1000000000LL; if ((ns > 0) && (duty == 0)) { duty = 1; } else if (duty > UI16_DUTY) { @@ -219,26 +192,27 @@ static int ns_to_duty(machine_pwm_obj_t *self, int ns) { static int duty_to_ns(machine_pwm_obj_t *self, int duty) { pwm_is_active(self); - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer->freq_hz * UI16_DUTY / 2) / ((int64_t)timer->freq_hz * UI16_DUTY); - return ns; + return ((int64_t)duty * 1000000000LL + (int64_t)self->freq * UI16_DUTY / 2) / ((int64_t)self->freq * UI16_DUTY); } -static void configure_pin(machine_pwm_obj_t *self) { - // reconfigure PWM pin output as input/output +// 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, self->output_invert, false); + 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, self->output_invert, false); + esp_rom_gpio_connect_out_signal(self->pin, LEDC_HS_SIG_OUT0_IDX + self->channel, invert, false); #endif } } -#define apply_duty(self) debug_printf0("apply_duty(self->channel=%d,self->channel_duty=%d,self->pin=%d,self->mode=%d,self->timer=%d)", self->channel, self->channel_duty, self->pin, self->mode, self->timer); _apply_duty(self); -static void _apply_duty(machine_pwm_obj_t *self) { - ledc_timer_config_t *timer = &timers[self->mode][self->timer]; +static void apply_duty(machine_pwm_obj_t *self) { + pwm_is_active(self); int duty = 0; if (self->duty_x == UI_RES_16_BIT) { @@ -249,21 +223,14 @@ static void _apply_duty(machine_pwm_obj_t *self) { duty = ns_to_duty(self, self->duty_ui); } + int duty_resolution = timers[self->mode][self->timer].duty_resolution; int channel_duty; - if (timer->duty_resolution <= UI_RES_16_BIT) { - channel_duty = duty >> (UI_RES_16_BIT - timer->duty_resolution); + if (duty_resolution <= UI_RES_16_BIT) { + channel_duty = duty >> (UI_RES_16_BIT - duty_resolution); } else { - channel_duty = duty << (timer->duty_resolution - UI_RES_16_BIT); - } - /* - if (channel_duty < 0) { - channel_duty = 0; - } else if (channel_duty >= timer_duty) { - // When channel's binded timer selects its maximum duty resolution, - // the duty cycle value cannot be set to (2 ** duty_resolution) - channel_duty = timer_duty - 1; // Ok! + channel_duty = duty << (duty_resolution - UI_RES_16_BIT); } - */ + self->channel_duty = channel_duty; ledc_channel_config_t cfg = { @@ -275,116 +242,46 @@ static void _apply_duty(machine_pwm_obj_t *self) { .timer_sel = self->timer, .flags.output_invert = self->output_invert, }; - debug_printf4("apply_duty():self->channel_duty=%d, timer_duty=%d, cfg.flags.output_invert=%d", self->channel_duty, timer_duty, cfg.flags.output_invert) - /* - if (self->channel_duty == timer_duty) { + if (self->channel_duty && (self->channel_duty == timer_duty)) { cfg.duty = 0; cfg.flags.output_invert = self->output_invert ^ 1; } - */ + debug_printf("ledc_channel_config() self->pin=%d, self->channel=%d, self->mode=%d, self->timer=%d, cfg.duty=%d, cfg.flags.output_invert=%d", + self->pin, self->channel, self->mode, self->timer, cfg.duty, cfg.flags.output_invert); check_esp_err(ledc_channel_config(&cfg)); - /* - #define USE_THREAD_SAFE_LEDC_API 0 - #if USE_THREAD_SAFE_LEDC_API - check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1)); - esp_err_t err = ESP_OK; // ledc_set_duty_and_update(self->mode, self->channel, channel_duty, timer_duty - 1); - if (err != ESP_OK) - #endif - { - check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); - check_esp_err(ledc_update_duty(self->mode, self->channel)); - } - */ -} -/* -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; -} -*/ -/* -static uint32_t calc_divider(uint32_t src_clk_freq, uint32_t timer_freq) { - uint32_t divider = (uint32_t)(((uint64_t)src_clk_freq + (uint64_t)timer_freq / 2ULL) / (uint64_t)timer_freq); // rounded - return divider == 0 ? 1 : divider; + debug_printf("bind self->mode=%d, self->channel=%d, self->timer=%d", self->mode, self->channel, self->timer); + 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 -/* - uint32_t divider2 = (uint32_t)((uint64_t)src_clk_freq / timer_freq); // tracate - if (divider != divider2) { - debug_printf5("divider=%d, divider2=%d", divider, divider2); - } -*/ return divider == 0 ? 1 : divider; } -// Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 -#if 1 // 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 - - // 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); - - unsigned int res = 0; - for (; freq > 1; freq >>= 1) { - ++res; - } - return res; -} -*/ -/* - unsigned int freq = src_clk_freq; - unsigned int divider = calc_divider(src_clk_freq, timer_freq); - float f = (float)src_clk_freq / divider; // actual frequency - if (f <= 1.0) { - f = 1.0; - } - freq = (unsigned int)roundf((float)src_clk_freq + (src_clk_freq / divider /2) / (src_clk_freq / divider)); -*/ -static uint32_t _ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { - // Find the highest bit resolution for the requested frequency +// 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 - uint32_t freq1 = freq; - + uint32_t freq = src_clk_freq + (f / 2) / f; // rounded frequency uint32_t resolution = 0; for (; freq > 1; freq >>= 1) { ++resolution; } - //if (src_clk_freq != freq1) { - if (1) { - debug_printf5("\nsrc_clk_freq=%d, timer_freq=%d, divider=%d, freq=%d, resolution=%d", src_clk_freq, timer_freq, divider, freq1, resolution); - } return resolution; } #endif static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { - unsigned int ledc_resolution = _ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) unsigned int resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); - //if (resolution != resolution2) { - debug_printf5("resolution=%d, ledc_resolution=%d", resolution, ledc_resolution); - //} - #endif if (resolution == 0) { - //resolution = 1; - } else if (resolution > HIGHEST_PWM_RES) { - // Limit resolution to HIGHEST_PWM_RES to match units of our duty - resolution = HIGHEST_PWM_RES; + resolution = 1; + } else if (resolution > UI_RES_16_BIT) { + // limit resolution to user interface + resolution = UI_RES_16_BIT; } return resolution; } @@ -393,18 +290,10 @@ static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t ti static uint32_t get_duty_u16(machine_pwm_obj_t *self) { pwm_is_active(self); - //ledc_timer_config_t *timer = &timers[self->mode][self->timer]; - debug_printf("MAX_TIMER_DUTY=%d timer->duty_resolution=%d self->channel_duty=%d ledc_duty()=%d", MAX_TIMER_DUTY, timer->duty_resolution, self->channel_duty, ledc_duty()) - - debug_printf2("get_duty_u16():self->output_invert=%d, ledc_duty()=%d", self->output_invert, ledc_duty()) - #if 0 // UI16_DUTY == 65536 if (self->channel_duty == timer_duty) { return UI16_DUTY; - } else - #endif - { + } else { int duty_resolution = timers[self->mode][self->timer].duty_resolution; - debug_printf2("duty_resolution=%d", duty_resolution) if (duty_resolution <= UI_RES_16_BIT) { return ledc_duty() << (UI_RES_16_BIT - duty_resolution); } else { @@ -414,7 +303,8 @@ static uint32_t get_duty_u16(machine_pwm_obj_t *self) { } static uint32_t get_duty_u10(machine_pwm_obj_t *self) { - return get_duty_u16(self) >> (UI_RES_16_BIT - UI_RES_10_BIT); // 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) { @@ -467,18 +357,26 @@ static void check_freq_ranges(int freq, int upper_freq) { // 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->clk_cfg = LEDC_AUTO_CLK; + 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; + 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; + timer.clk_cfg = LEDC_USE_PLL_DIV_CLK; #elif SOC_LEDC_SUPPORT_XTAL_CLOCK - timer->clk_cfg = LEDC_USE_XTAL_CLK; + timer.clk_cfg = LEDC_USE_XTAL_CLK; src_clk_freq = XTAL_CLK_FREQ; // 40 MHz #else #error No supported PWM / LEDC clocks. @@ -486,47 +384,33 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { #ifdef EMPIRIC_FREQ // ESP32 and ESP32S2 only if (freq < EMPIRIC_FREQ) { - timer->clk_cfg = LEDC_USE_REF_TICK; + 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); + 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); + 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 - timer->freq_hz = freq; - // Configure the new resolution and frequency - timer->duty_resolution = find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); - debug_printf5("SOC_MOD_CLK_APB=%d, LEDC_USE_APB_CLK=%d, LEDC_AUTO_CLK=%d, LEDC_USE_REF_TICK=%d", SOC_MOD_CLK_APB, LEDC_USE_APB_CLK, LEDC_AUTO_CLK, LEDC_USE_REF_TICK); - debug_printf5("src_clk_freq=%d, timer->freq_hz=%d, timer->clk_cfg=%d, timer->duty_resolution=%d, timer->speed_mode=%d, timer->timer_num=%d", src_clk_freq, timer->freq_hz, timer->clk_cfg, timer->duty_resolution, timer->speed_mode, timer->timer_num) + // 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; + + debug_printf("ledc_timer_config() timer.freq_hz=%d, timer.clk_cfg=%d, timer.duty_resolution=%d, timer.speed_mode=%d, timer.timer_num=%d", + timer.freq_hz, timer.clk_cfg, timer.duty_resolution, timer.speed_mode, timer.timer_num); // Configure timer - Set frequency - check_esp_err(ledc_timer_config(timer)); - /* - err = ledc_timer_config(timer); - if (1) { - //if (ESP_OK != (err = ledc_timer_config(timer))) { - uint32_t divider = calc_divider(src_clk_freq, timer->freq_hz); - debug_printf5("divider=%d, timer->clk_cfg=%d, err=0x%X", divider, timer->clk_cfg, err); - check_esp_err(ledc_timer_set(timer->speed_mode, timer->timer_num, divider, timer->duty_resolution, timer->clk_cfg)); - if (freq >= EMPIRIC_FREQ) { - check_esp_err(ledc_set_freq(timer->speed_mode, timer->timer_num,timer->freq_hz)); - } - } - */ + 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)); } - - // Save the same duty cycle when frequency is changed - set_duty(self); } } @@ -536,112 +420,83 @@ static bool is_free_channels(int mode, int pin) { return true; } } - return false; } -// 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; +// 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_channel < 0) && (chans[mode][channel].pin < 0)) { - avail_channel = channel; + } + // 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; + } } } - return avail_channel; } -// Returns timer with the same mode and frequency, freq == -1 means free timer -#define find_timer(mode, freq) _find_timer(mode, freq); debug_printf0("find_timer(mode=%d, freq=%d)", mode, freq); -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; +// 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; + } + } } } - 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. -#define select_a_timer(self, freq) debug_printf0("select_a_timer(freq=%d)", freq); _select_a_timer(self, freq); -static void _select_a_timer(machine_pwm_obj_t *self, int freq) { +static void select_timer(machine_pwm_obj_t *self, int freq) { // 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 mode = -1; 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 the timer is found, then bind and set the duty - debug_printf0("timer=%d, timers[mode][timer].freq_hz=%d, self->channel=%d, self->mode=%d", timer, timers[mode][timer].freq_hz, self->channel, self->mode); - 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); - } - if (timer >= 0) { - self->timer = timer; - set_duty(self); - } - + // Check if an already running timer with the required frequency is running + find_tmr(self->pin, freq, &mode, &timer); + debug_printf("1. freq=%d, mode=%d, timer=%d", 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; + debug_printf("2. freq=%d, mode=%d, timer=%d", freq, mode, 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, -1); // 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); - } + find_tmr(self->pin, -1, &mode, &timer); + debug_printf("3. freq=%d, mode=%d, timer=%d", freq, mode, 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); + } + // 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; - } - if ((save_mode != self->mode) || (save_channel != self->channel)) { - unregister_channel(save_mode, save_channel); + set_channel(self->mode, self->channel, self->pin, self->timer); // register } } @@ -666,10 +521,10 @@ static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_p } mp_printf(print, ")"); #if 1//MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL - int duty_resolution = timers[self->mode][self->timer].duty_resolution; mp_printf(print, " #"); mp_printf(print, " duty=%.2f%%", 100.0 * get_duty_u16(self) / UI16_DUTY); // percents - mp_printf(print, ", raw_duty()=%d, duty_resolution=%d", ledc_duty(), duty_resolution); + 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 { @@ -708,16 +563,6 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, 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 = UI_RES_16_BIT; self->duty_ui = duty_u16; @@ -729,7 +574,7 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, self->duty_ui = duty; } else if (self->duty_x == 0) { self->duty_x = UI_RES_16_BIT; - self->duty_ui = (1 << UI_RES_16_BIT) / 2; // default 50% + self->duty_ui = PWM_DUTY; } int output_invert = args[ARG_invert].u_int; @@ -737,17 +582,10 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, self->output_invert = output_invert == 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); - } + 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 } @@ -761,36 +599,27 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, // pwm = PWM(pin, freq=1000, duty=256) // pwm = PWM(pin, duty=128) if (chans[mode][channel].timer >= 0) { - freq = timers[mode][chans[mode][channel].timer].freq_hz; + freq = timers[mode][chans[mode][channel].timer].freq; } if (freq <= 0) { freq = PWM_FREQ; } } - select_a_timer(self, freq); - - // New PWM assignment - if ((chans[mode][channel].pin < 0) - || ((save_mode != self->mode)) - || ((save_channel != self->channel)) - || ((save_timer != self->timer))) { - debug_printf("mp_machine_pwm_init_helper()") - apply_duty(self); - configure_pin(self); - register_channel(self->mode, self->channel, self->pin, self->timer); - } else { - set_freq(self, freq); - } + select_timer(self, freq); + set_freq(self, freq); + set_duty(self); } 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 = 0; + self->channel_duty = -1; self->output_invert = 0; } @@ -798,23 +627,20 @@ static void self_reset(machine_pwm_obj_t *self) { 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 = 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; - self_reset(self); // start the PWM subsystem if it's not already running if (!pwm_inited) { - #if USE_THREAD_SAFE_LEDC_API - ledc_fade_func_install(ESP_INTR_FLAG_INTRDISABLED); - #endif pwm_init(); 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); @@ -824,30 +650,26 @@ 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) { - pwm_deinit(self->mode, self->channel); + pwm_deinit(self->mode, self->channel, self->output_invert ? 1 : 0); self_reset(self); } // Set and get methods of PWM class static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { - 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)); - } + pwm_is_active(self); + return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); } static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { pwm_is_active(self); check_freq_ranges(freq, 40000000); - if (freq == timers[self->mode][self->timer].freq_hz) { + if (freq == timers[self->mode][self->timer].freq) { return; } - // Set new PWM frequency - select_a_timer(self, freq); - // Set the frequency + 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) { From 6e5ca0067478a0884261f0ec7dea98a4e9684c76 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 6 Dec 2024 01:37:46 +0200 Subject: [PATCH 23/30] remove time_hardware_pulse_us --- docs/library/machine.rst | 23 ------------------- extmod/machine_pulse.c | 48 ---------------------------------------- extmod/modmachine.c | 1 - extmod/modmachine.h | 2 -- 4 files changed, 74 deletions(-) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 97533c254813e..76d111f11ef3d 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -197,29 +197,6 @@ Miscellaneous functions above. The timeout is the same for both cases and given by *timeout_us* (which is in microseconds). -.. function:: time_hardware_pulse_us(pin, pulse_level, timeout_us=1000000, /) - - Time a pulse on the given *pin*, and return the duration of the pulse in - microseconds. The *pulse_level* argument should be 0 to time a low pulse - or 1 to time a high pulse. - - If the pin is initially equal to *pulse_level* then first waits until - the pin input becomes different from *pulse_level* (***). - Then the function waits until the pin input becomes equal to *pulse_level* (**), - then the function counts the duration that the pin is equal to *pulse_level* (*). - - The function returns -3 if there was timeout waiting for condition marked (***) above. - The function will return -2 if there was timeout waiting for condition marked - (**) above, and -1 if there was timeout during the main measurement, marked (*) - above. The timeout is the same for all cases and given by *timeout_us* (which - is in microseconds). - - The difference from `time_pulse_us` is that if the pin is initially equal to *pulse_level* - then `time_pulse_us` counts the pulse duration immediately, - but `time_hardware_pulse_us` first waits for the different *pulse_level*, - then waits the equal to *pulse_level* and then counts pulse duration. - A little bit longer, but with higher accuracy. - .. function:: bitstream(pin, encoding, timing, data, /) Transmits *data* by bit-banging the specified *pin*. The *encoding* argument diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 6077ca3b166a7..60edc3ce9e4c4 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -72,52 +72,4 @@ static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 4, machine_time_pulse_us_); -MP_WEAK mp_uint_t/*uint64_t*/ machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { - /*mp_uint_t*/uint64_t start = mp_hal_ticks_us(); - #define TEST_JITTER 0 - #if TEST_JITTER - while (mp_hal_pin_read(pin) == pulse_level) { - if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)start) { - break; - } - break; - } - return mp_hal_ticks_us() - start; - #endif - while (mp_hal_pin_read(pin) == pulse_level) { - if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { - return (mp_uint_t)-3; - } - } - start = mp_hal_ticks_us(); - while (mp_hal_pin_read(pin) != pulse_level) { - if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { - return (mp_uint_t)-2; - } - } - start = mp_hal_ticks_us(); - while (mp_hal_pin_read(pin) == pulse_level) { - if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { - return (mp_uint_t)-1; - } - } - return (mp_uint_t)(mp_hal_ticks_us() - start); -} - -static mp_obj_t machine_time_hardware_pulse_us_(size_t n_args, const mp_obj_t *args) { - mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[0]); - int level = 0; - if (mp_obj_is_true(args[1])) { - level = 1; - } - mp_uint_t timeout_us = 1000000; - if (n_args > 2) { - timeout_us = mp_obj_get_int(args[2]); - } - mp_uint_t us = machine_time_hardware_pulse_us(pin, level, timeout_us); - // May return -1 or -2 or -3 in case of timeout - return mp_obj_new_int(us); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_hardware_pulse_us_obj, 2, 3, machine_time_hardware_pulse_us_); - #endif diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 5441f378d2dcf..5906835949861 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -188,7 +188,6 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #endif #if MICROPY_PY_MACHINE_PULSE { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, - { MP_ROM_QSTR(MP_QSTR_time_hardware_pulse_us), MP_ROM_PTR(&machine_time_hardware_pulse_us_obj) }, #endif // Classes for PinBase and Signal. diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 7825ff0b6428f..c3a1e61da4d4d 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -245,7 +245,6 @@ uintptr_t MICROPY_MACHINE_MEM_GET_WRITE_ADDR(mp_obj_t addr_o, uint align); NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len); mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite); -mp_uint_t machine_time_hardware_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj); MP_DECLARE_CONST_FUN_OBJ_0(machine_reset_obj); @@ -256,7 +255,6 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bootloader_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bitstream_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj); -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_hardware_pulse_us_obj); #if MICROPY_PY_MACHINE_I2C int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); From 69fe420521563fe86e0936d26a03a78263e08266 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Mon, 16 Dec 2024 14:10:45 +0200 Subject: [PATCH 24/30] Update pwm.rst --- docs/esp32/tutorial/pwm.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/esp32/tutorial/pwm.rst b/docs/esp32/tutorial/pwm.rst index 9fa8553bdf70f..07adaedba158e 100644 --- a/docs/esp32/tutorial/pwm.rst +++ b/docs/esp32/tutorial/pwm.rst @@ -69,10 +69,9 @@ low all of the time. while True: pwm.freq(f) sleep(1/f) + sleep(.1) print(pwm) - sleep(.2) - f += delta_f if f > F_MAX or f < F_MIN: delta_f = -delta_f @@ -88,7 +87,7 @@ low all of the time. 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=65536) # duty=100.00%, raw_duty=32768, 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=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 @@ -103,7 +102,7 @@ PWM(Pin(27), freq=9398, duty_u16=32768) # duty=50.00%, raw_duty=4096, resolutio ... 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=16384) # duty=25.00%, raw_duty=16384, 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=998, duty_u16=32768) # duty=50.00%, raw_duty=32768, resolution=16, mode=0, channel=0, timer=2 From 824b5ee078ce5b124cb3f6dc6a2cd321eb6db8b7 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Mon, 16 Dec 2024 14:18:45 +0200 Subject: [PATCH 25/30] Update quickref.rst --- docs/esp32/quickref.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 18fcd99867124..3fb0ed48ae9c7 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -359,6 +359,8 @@ Note: New PWM parameters take effect in the next PWM cycle. 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) From c050f054638d1577b46eccacb041297b17068dbc Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Mon, 16 Dec 2024 14:44:37 +0200 Subject: [PATCH 26/30] esp32/PWM: Reduce inconsitencies between ports. Signed-off-by: IhorNehrutsa Co-Authored-By: Robert Hammelrath <12476868+robert-hh@users.noreply.github.com> Co-Authored-By: Andrew Leech <3318786+andrewleech@users.noreply.github.com> --- docs/esp32/quickref.rst | 47 +- docs/esp32/tutorial/pwm.rst | 205 ++++++-- docs/library/machine.PWM.rst | 18 +- ports/esp32/machine_pwm.c | 904 +++++++++++++++++++---------------- 4 files changed, 700 insertions(+), 474 deletions(-) mode change 100644 => 100755 docs/esp32/quickref.rst mode change 100644 => 100755 docs/esp32/tutorial/pwm.rst mode change 100644 => 100755 docs/library/machine.PWM.rst mode change 100644 => 100755 ports/esp32/machine_pwm.c diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst old mode 100644 new mode 100755 index 5cce96d687875..9bb26c3e0f69f --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -320,25 +320,52 @@ 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. + + pwm4 = PWM(Pin(4), light_sleep_enable=True) # Allow PWM during light sleep mode 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 +======================================================= ======== ========= ========== + +In light sleep, the ESP32 PWM can only operate in low speed mode, so only 4 timers and +8 channels are available. 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..854d98ab12468 --- 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(2/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..4d97131bb7ea2 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -6,7 +6,8 @@ * 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 + * Copyright (c) 2024 Yoann Darche * * 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,77 +33,42 @@ #include #include "py/mphal.h" +#include "hal/ledc_hal.h" #include "driver/ledc.h" #include "esp_err.h" +#include "esp_sleep.h" #include "soc/gpio_sig_map.h" - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) #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"); - -// Total number of channels -#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX) - -typedef struct _chan_t { - // Which channel has which GPIO pin assigned? - // (-1 if not assigned) - gpio_num_t pin; - // Which channel has which timer assigned? - // (-1 if not assigned) - int timer_idx; -} chan_t; - -// List of PWM channels -static chan_t chans[PWM_CHANNEL_MAX]; -// channel_idx is an index (end-to-end sequential numbering) for all channels -// available on the chip and described in chans[] -#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel) -#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX) -#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX) +#include "py/mpprint.h" -// Total number of timers -#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX) +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +/* +Set in micropython/ports/esp32/mpconfigport.h +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL + 1) +to view __FUNCTION__, __LINE__, __FILE__ in exception +*/ -// List of timer configs -static ledc_timer_config_t timers[PWM_TIMER_MAX]; +// 10-bit user interface resolution compatible with esp8266 PWM.duty() +#define UI_RES_10_BIT (10) +// Maximum duty value on 10-bit resolution +#define UI10_DUTY ((1UL << UI_RES_10_BIT) - 1) -// 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) +// 16-bit user interface resolution used in PWM.duty_u16() +#define UI_RES_16_BIT (16) +// Maximum duty value on 16-bit resolution +#define UI16_DUTY ((1UL << UI_RES_16_BIT) - 1) -// 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) - -// 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 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 +// max_timer_duty is the MAX value of a channel_duty +#define max_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 #endif @@ -113,320 +79,462 @@ 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; + bool light_sleep_enable; + 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; + // Is light sleep enable has been set for this pin + bool light_sleep_enable; +} 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; + ledc_clk_cfg_t clk_cfg; +} timers_t; + +// List of PWM timers +static timers_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; + +// register-unregister channel +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; + } +} +#define unregister_channel(mode, channel) register_channel(mode, channel, -1, -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) { + // Initial condition: no channels assigned + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + unregister_channel(mode, channel); + chans[mode][channel].light_sleep_enable = false; + } + // 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; + timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; + } } +} - // 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; + timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; } } - 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)); + if (chans[mode][channel].light_sleep_enable) { + // Enable SLP_SEL to change GPIO status automantically in lightsleep. + check_esp_err(gpio_sleep_sel_en(pin)); + chans[mode][channel].light_sleep_enable = false; } } - 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, 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 == max_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); + } + self->channel_duty = duty >> (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); + + 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 == max_timer_duty)) { + cfg.duty = 0; + cfg.flags.output_invert = self->output_invert ^ 1; + } + check_esp_err(ledc_channel_config(&cfg)); + if (self->light_sleep_enable) { + // Disable SLP_SEL to change GPIO status automantically in lightsleep. + check_esp_err(gpio_sleep_sel_dis(self->pin)); + chans[self->mode][self->channel].light_sleep_enable = true; + } + check_esp_err(ledc_bind_channel_timer(self->mode, self->channel, self->timer)); + reconfigure_pin(self); + register_channel(self->mode, self->channel, self->pin, self->timer); +} + +#if CALC_RESOLUTION +// Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 +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 > 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 == max_timer_duty) { + return UI16_DUTY; } else { - duty >>= (resolution - UI_RES_16_BIT); + return ledc_duty() << (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); } - 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); - } - 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); - } + 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); } - */ - 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. +#if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) +// This check if a clock is already set in the timer list, if yes, return the LEDC_XXX value +static ledc_clk_cfg_t find_clock_in_use() { + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if (timers[mode][timer].clk_cfg != LEDC_AUTO_CLK) { + return timers[mode][timer].clk_cfg; } } } + return LEDC_AUTO_CLK; +} +#endif - return free_timer_idx_found; +static void check_freq_ranges(machine_pwm_obj_t *self, 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); + } } -// Return true if the timer is in use in addition to current channel -static bool is_timer_in_use(int current_channel_idx, int timer_idx) { - for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { - if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) { - return true; +// 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; + + if (self->light_sleep_enable) { + timer.clk_cfg = LEDC_USE_RC_FAST_CLK; // 8 or 20 MHz + } else { + #if SOC_LEDC_SUPPORT_APB_CLOCK + timer.clk_cfg = LEDC_USE_APB_CLK; // 80 MHz + #elif SOC_LEDC_SUPPORT_PLL_DIV_CLOCK + timer.clk_cfg = LEDC_USE_PLL_DIV_CLK; // 60 or 80 or 96 MHz + #elif SOC_LEDC_SUPPORT_XTAL_CLOCK + timer.clk_cfg = LEDC_USE_XTAL_CLK; // 40 MHz + #else + #error No supported PWM / LEDC clocks. + #endif + + #ifdef EMPIRIC_FREQ // ESP32 and ESP32S2 only + if (freq < EMPIRIC_FREQ) { + timer.clk_cfg = LEDC_USE_REF_TICK; // 1 MHz + } + #endif + + } + #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) + // Check for clock source conflic + ledc_clk_cfg_t pwm_clk = find_clock_in_use(); + if ((pwm_clk != LEDC_AUTO_CLK) && (pwm_clk != timer.clk_cfg)) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("one or more active timers use a different clock source, not supported by the current SoC.")); + } + #endif + + uint32_t src_clk_freq = 0; + check_esp_err(esp_clk_tree_src_get_freq_hz(timer.clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &src_clk_freq)); + // machine.freq(20_000_000) reduces APB_CLK_FREQ to 20MHz and the highest PWM frequency to 10MHz + check_freq_ranges(self, freq, src_clk_freq / 2); + + // 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; + timers[self->mode][self->timer].clk_cfg = timer.clk_cfg; + + // 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)); } } +} +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(machine_pwm_obj_t *self, int *ret_mode, int *ret_channel) { + // Try to find self channel first + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->light_sleep_enable && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if (chans[mode][channel].pin == self->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) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->light_sleep_enable && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if (chans[mode][channel].pin < 0) { + *ret_mode = mode; + *ret_channel = channel; + return; } } } - if (channel_idx >= PWM_CHANNEL_MAX) { - channel_idx = avail_idx; +} + +// Returns timer with the same frequency, freq == -1 means free timer +static void find_timer(machine_pwm_obj_t *self, int freq, int *ret_mode, int *ret_timer) { + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->light_sleep_enable && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + if (is_free_channels(mode, self->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_timer(self, 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_timer(self, -1, &mode, &timer); + } + } + if (timer < 0) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of PWM timers:%d"), self->light_sleep_enable ? LEDC_TIMER_MAX : LEDC_SPEED_MODE_MAX *LEDC_TIMER_MAX); + } + // If the timer is found, then bind + if (self->timer != timer) { + unregister_channel(self->mode, self->channel); + // Bind the channel to the timer + self->mode = mode; + self->timer = timer; + register_channel(self->mode, self->channel, self->pin, self->timer); } - return channel_idx; } /******************************************************************************/ @@ -435,138 +543,135 @@ 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); + } + if (self->light_sleep_enable) { + mp_printf(print, ", light_sleep_enable=True"); + } + mp_printf(print, ")"); - mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); + #if 1 // MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL + mp_printf(print, " # duty=%.2f%%", 100.0 * get_duty_u16(self) / UI16_DUTY); + mp_printf(print, ", raw_duty=%d, resolution=%d", ledc_duty(), timers[self->mode][self->timer].duty_resolution); + mp_printf(print, ", mode=%d, timer=%d, channel=%d", self->mode, self->timer, self->channel); + #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, ARG_light_sleep_enable }; 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_QSTR_light_sleep_enable, 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 + if (args[ARG_light_sleep_enable].u_int > 0) { + self->light_sleep_enable = true; + } else if (args[ARG_light_sleep_enable].u_int == 0) { + self->light_sleep_enable = false; + } + + int freq = args[ARG_freq].u_int; + if (freq != -1) { + check_freq_ranges(self, 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, &mode, &channel); + if (channel < 0) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of PWM channels:%d"), self->light_sleep_enable ? LEDC_CHANNEL_MAX : 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->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; + self->light_sleep_enable = false; } // 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 +679,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 +695,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 +708,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(self, 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) { From b6bcf72dfbb3c95607bc58dfa1aceacf05b2c571 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 10:56:41 +0200 Subject: [PATCH 27/30] machine: Add wait_opposite to time_pulse_us func. If the pin is initially equal to *pulse_level* then first waits until the pin input becomes different from *pulse_level*. Then the function waits until the pin input becomes equal to *pulse_level*, then the func counts the duration that the pin is equal to *pulse_level*. Signed-off-by: IhorNehrutsa Co-Authored-By: Robert Hammelrath <12476868+robert-hh@users.noreply.github.com> --- docs/library/machine.rst | 13 ++++++++----- drivers/dht/dht.c | 5 +++-- extmod/machine_pulse.c | 18 ++++++++++++++---- extmod/modmachine.h | 2 +- ports/esp8266/modmachine.c | 2 +- tests/extmod_hardware/machine_pwm.py | 9 ++------- 6 files changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 76d111f11ef3d..77a7637ed4025 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -181,19 +181,22 @@ Miscellaneous functions varies by hardware (so use substring of a full value if you expect a short ID). In some MicroPython ports, ID corresponds to the network MAC address. -.. function:: time_pulse_us(pin, pulse_level, timeout_us=1000000, /) +.. function:: time_pulse_us(pin, pulse_level, timeout_us=1000000, wait_opposite=false, /) Time a pulse on the given *pin*, and return the duration of the pulse in microseconds. The *pulse_level* argument should be 0 to time a low pulse or 1 to time a high pulse. - If the current input value of the pin is different to *pulse_level*, - the function first (*) waits until the pin input becomes equal to *pulse_level*, - then (**) times the duration that the pin is equal to *pulse_level*. + If *wait_opposite* is true, if the pin is initially equal to *pulse_level* then first + waits until the pin input becomes different from *pulse_level* (***). + Then if the current input value of the pin is different to *pulse_level*, + the function first (**) waits until the pin input becomes equal to *pulse_level*, + then (*) times the duration that the pin is equal to *pulse_level*. If the pin is already equal to *pulse_level* then timing starts straight away. + The function returns -3 if there was timeout waiting for condition marked (***) above. The function will return -2 if there was timeout waiting for condition marked - (*) above, and -1 if there was timeout during the main measurement, marked (**) + (**) above, and -1 if there was timeout during the main measurement, marked (*) above. The timeout is the same for both cases and given by *timeout_us* (which is in microseconds). diff --git a/drivers/dht/dht.c b/drivers/dht/dht.c index e6b71542dabc9..47d114fed784e 100644 --- a/drivers/dht/dht.c +++ b/drivers/dht/dht.c @@ -71,8 +71,9 @@ static mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) { } } + #define WAIT_OPPOSITE false // time pulse, should be 80us - ticks = machine_time_pulse_us(pin, 1, 150); + ticks = machine_time_pulse_us(pin, 1, 150, WAIT_OPPOSITE); if ((mp_int_t)ticks < 0) { goto timeout; } @@ -80,7 +81,7 @@ static mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) { // time 40 pulses for data (either 26us or 70us) uint8_t *buf = bufinfo.buf; for (int i = 0; i < 40; ++i) { - ticks = machine_time_pulse_us(pin, 1, 100); + ticks = machine_time_pulse_us(pin, 1, 100, WAIT_OPPOSITE); if ((mp_int_t)ticks < 0) { goto timeout; } diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 85dba86d9b5ad..60edc3ce9e4c4 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -30,8 +30,14 @@ #if MICROPY_PY_MACHINE_PULSE -MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { +MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite) { mp_uint_t start = mp_hal_ticks_us(); + while (wait_opposite && (mp_hal_pin_read(pin) == pulse_level)) { + if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { + return (mp_uint_t)-3; + } + } + start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) != pulse_level) { if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { return (mp_uint_t)-2; @@ -56,10 +62,14 @@ static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { if (n_args > 2) { timeout_us = mp_obj_get_int(args[2]); } - mp_uint_t us = machine_time_pulse_us(pin, level, timeout_us); - // May return -1 or -2 in case of timeout + bool wait_opposite = false; + if (n_args > 3) { + wait_opposite = mp_obj_is_true(args[3]); + } + mp_uint_t us = machine_time_pulse_us(pin, level, timeout_us, wait_opposite); + // May return -1 or -2 or -3 in case of timeout return mp_obj_new_int(us); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_time_pulse_us_); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 4, machine_time_pulse_us_); #endif diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 7c16ed302ee2f..c3a1e61da4d4d 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -244,7 +244,7 @@ uintptr_t MICROPY_MACHINE_MEM_GET_WRITE_ADDR(mp_obj_t addr_o, uint align); NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len); -mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); +mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite); MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj); MP_DECLARE_CONST_FUN_OBJ_0(machine_reset_obj); diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index 23ccf8cebfb0d..b994bf813382e 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -329,7 +329,7 @@ MP_DEFINE_CONST_OBJ_TYPE( ); // Custom version of this function that feeds system WDT if necessary -mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { +mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite) { int nchanges = 2; uint32_t start = system_get_time(); // in microseconds for (;;) { diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index 014030be5c601..0ba2308d35dbc 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -72,14 +72,10 @@ def _test_freq_duty(self, pulse_in, pwm, freq, duty_u16): expected_us = (expected_low_us, expected_high_us) timeout = 2 * expected_total_us - # Wait for output to settle. - time_pulse_us(pulse_in, 0, timeout) - time_pulse_us(pulse_in, 1, timeout) - if duty_u16 == 0 or duty_u16 == 65535: # Expect a constant output level. no_pulse = ( - time_pulse_us(pulse_in, 0, timeout) < 0 and time_pulse_us(pulse_in, 1, timeout) < 0 + time_pulse_us(pulse_in, 0, timeout, True) < 0 and time_pulse_us(pulse_in, 1, timeout, True) < 0 ) self.assertTrue(no_pulse) if expected_high_us == 0: @@ -93,9 +89,8 @@ def _test_freq_duty(self, pulse_in, pwm, freq, duty_u16): n_averaging = 10 for level in (0, 1): t = 0 - time_pulse_us(pulse_in, level, timeout) for _ in range(n_averaging): - t += time_pulse_us(pulse_in, level, timeout) + t += time_pulse_us(pulse_in, level, timeout, True) t //= n_averaging expected = expected_us[level] print(" level={} timing_er={}".format(level, abs(t - expected)), end="") From e2a1c3abe979db9081965df563088a2714f44533 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 5 Nov 2024 10:56:41 +0200 Subject: [PATCH 28/30] machine: Add wait_opposite to time_pulse_us func. If the pin is initially equal to *pulse_level* then first waits until the pin input becomes different from *pulse_level*. Then the function waits until the pin input becomes equal to *pulse_level*, then the func counts the duration that the pin is equal to *pulse_level*. Signed-off-by: IhorNehrutsa Co-Authored-By: Robert Hammelrath <12476868+robert-hh@users.noreply.github.com> --- docs/library/machine.rst | 13 ++++++++----- drivers/dht/dht.c | 5 +++-- extmod/machine_pulse.c | 18 ++++++++++++++---- extmod/modmachine.h | 2 +- ports/esp8266/modmachine.c | 2 +- tests/extmod_hardware/machine_pwm.py | 10 +++------- 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 76d111f11ef3d..77a7637ed4025 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -181,19 +181,22 @@ Miscellaneous functions varies by hardware (so use substring of a full value if you expect a short ID). In some MicroPython ports, ID corresponds to the network MAC address. -.. function:: time_pulse_us(pin, pulse_level, timeout_us=1000000, /) +.. function:: time_pulse_us(pin, pulse_level, timeout_us=1000000, wait_opposite=false, /) Time a pulse on the given *pin*, and return the duration of the pulse in microseconds. The *pulse_level* argument should be 0 to time a low pulse or 1 to time a high pulse. - If the current input value of the pin is different to *pulse_level*, - the function first (*) waits until the pin input becomes equal to *pulse_level*, - then (**) times the duration that the pin is equal to *pulse_level*. + If *wait_opposite* is true, if the pin is initially equal to *pulse_level* then first + waits until the pin input becomes different from *pulse_level* (***). + Then if the current input value of the pin is different to *pulse_level*, + the function first (**) waits until the pin input becomes equal to *pulse_level*, + then (*) times the duration that the pin is equal to *pulse_level*. If the pin is already equal to *pulse_level* then timing starts straight away. + The function returns -3 if there was timeout waiting for condition marked (***) above. The function will return -2 if there was timeout waiting for condition marked - (*) above, and -1 if there was timeout during the main measurement, marked (**) + (**) above, and -1 if there was timeout during the main measurement, marked (*) above. The timeout is the same for both cases and given by *timeout_us* (which is in microseconds). diff --git a/drivers/dht/dht.c b/drivers/dht/dht.c index e6b71542dabc9..47d114fed784e 100644 --- a/drivers/dht/dht.c +++ b/drivers/dht/dht.c @@ -71,8 +71,9 @@ static mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) { } } + #define WAIT_OPPOSITE false // time pulse, should be 80us - ticks = machine_time_pulse_us(pin, 1, 150); + ticks = machine_time_pulse_us(pin, 1, 150, WAIT_OPPOSITE); if ((mp_int_t)ticks < 0) { goto timeout; } @@ -80,7 +81,7 @@ static mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) { // time 40 pulses for data (either 26us or 70us) uint8_t *buf = bufinfo.buf; for (int i = 0; i < 40; ++i) { - ticks = machine_time_pulse_us(pin, 1, 100); + ticks = machine_time_pulse_us(pin, 1, 100, WAIT_OPPOSITE); if ((mp_int_t)ticks < 0) { goto timeout; } diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 85dba86d9b5ad..60edc3ce9e4c4 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -30,8 +30,14 @@ #if MICROPY_PY_MACHINE_PULSE -MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { +MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite) { mp_uint_t start = mp_hal_ticks_us(); + while (wait_opposite && (mp_hal_pin_read(pin) == pulse_level)) { + if ((uint64_t)(mp_hal_ticks_us() - start) >= (uint64_t)timeout_us) { + return (mp_uint_t)-3; + } + } + start = mp_hal_ticks_us(); while (mp_hal_pin_read(pin) != pulse_level) { if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { return (mp_uint_t)-2; @@ -56,10 +62,14 @@ static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { if (n_args > 2) { timeout_us = mp_obj_get_int(args[2]); } - mp_uint_t us = machine_time_pulse_us(pin, level, timeout_us); - // May return -1 or -2 in case of timeout + bool wait_opposite = false; + if (n_args > 3) { + wait_opposite = mp_obj_is_true(args[3]); + } + mp_uint_t us = machine_time_pulse_us(pin, level, timeout_us, wait_opposite); + // May return -1 or -2 or -3 in case of timeout return mp_obj_new_int(us); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 3, machine_time_pulse_us_); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_time_pulse_us_obj, 2, 4, machine_time_pulse_us_); #endif diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 7c16ed302ee2f..c3a1e61da4d4d 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -244,7 +244,7 @@ uintptr_t MICROPY_MACHINE_MEM_GET_WRITE_ADDR(mp_obj_t addr_o, uint align); NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len); -mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); +mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite); MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj); MP_DECLARE_CONST_FUN_OBJ_0(machine_reset_obj); diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index 23ccf8cebfb0d..b994bf813382e 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -329,7 +329,7 @@ MP_DEFINE_CONST_OBJ_TYPE( ); // Custom version of this function that feeds system WDT if necessary -mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { +mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us, bool wait_opposite) { int nchanges = 2; uint32_t start = system_get_time(); // in microseconds for (;;) { diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index 014030be5c601..de394674b1f35 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -72,14 +72,11 @@ def _test_freq_duty(self, pulse_in, pwm, freq, duty_u16): expected_us = (expected_low_us, expected_high_us) timeout = 2 * expected_total_us - # Wait for output to settle. - time_pulse_us(pulse_in, 0, timeout) - time_pulse_us(pulse_in, 1, timeout) - if duty_u16 == 0 or duty_u16 == 65535: # Expect a constant output level. no_pulse = ( - time_pulse_us(pulse_in, 0, timeout) < 0 and time_pulse_us(pulse_in, 1, timeout) < 0 + time_pulse_us(pulse_in, 0, timeout, True) < 0 + and time_pulse_us(pulse_in, 1, timeout, True) < 0 ) self.assertTrue(no_pulse) if expected_high_us == 0: @@ -93,9 +90,8 @@ def _test_freq_duty(self, pulse_in, pwm, freq, duty_u16): n_averaging = 10 for level in (0, 1): t = 0 - time_pulse_us(pulse_in, level, timeout) for _ in range(n_averaging): - t += time_pulse_us(pulse_in, level, timeout) + t += time_pulse_us(pulse_in, level, timeout, True) t //= n_averaging expected = expected_us[level] print(" level={} timing_er={}".format(level, abs(t - expected)), end="") From d85a120ad13beac6070ec50f1a3aa9c56a9f02c0 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 20 Dec 2024 15:33:06 +0200 Subject: [PATCH 29/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 52 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 4d97131bb7ea2..0cb4930d30e5c 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -42,7 +42,7 @@ #include "py/mpprint.h" -#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); /* Set in micropython/ports/esp32/mpconfigport.h #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL + 1) @@ -247,27 +247,35 @@ static void apply_duty(machine_pwm_obj_t *self) { } self->channel_duty = duty >> (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); - 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 == max_timer_duty)) { - cfg.duty = 0; - cfg.flags.output_invert = self->output_invert ^ 1; - } - check_esp_err(ledc_channel_config(&cfg)); - if (self->light_sleep_enable) { - // Disable SLP_SEL to change GPIO status automantically in lightsleep. - check_esp_err(gpio_sleep_sel_dis(self->pin)); - chans[self->mode][self->channel].light_sleep_enable = true; + if ((chans[self->mode][self->channel].pin == -1) || (self->channel_duty == max_timer_duty)) { + // New PWM assignment + 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 == max_timer_duty)) { + cfg.duty = 0; + cfg.flags.output_invert = self->output_invert ^ 1; + } + check_esp_err(ledc_channel_config(&cfg)); + if (self->light_sleep_enable) { + // Disable SLP_SEL to change GPIO status automantically in lightsleep. + check_esp_err(gpio_sleep_sel_dis(self->pin)); + chans[self->mode][self->channel].light_sleep_enable = true; + } + check_esp_err(ledc_bind_channel_timer(self->mode, self->channel, self->timer)); + reconfigure_pin(self); + debug_printf("AAA self->mode=%d, self->channel=%d, self->channel_duty=%d", self->mode, self->channel, self->channel_duty) + } else { + debug_printf("BBB self->mode=%d, self->channel=%d, self->channel_duty=%d", self->mode, self->channel, self->channel_duty) + check_esp_err(ledc_set_duty(self->mode, self->channel, self->channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); } - check_esp_err(ledc_bind_channel_timer(self->mode, self->channel, self->timer)); - reconfigure_pin(self); register_channel(self->mode, self->channel, self->pin, self->timer); } @@ -533,7 +541,7 @@ static void select_timer(machine_pwm_obj_t *self, int freq) { // Bind the channel to the timer self->mode = mode; self->timer = timer; - register_channel(self->mode, self->channel, self->pin, self->timer); + register_channel(self->mode, self->channel, /*self->pin*/-1, self->timer); } } From fba7b3b2801d1781e353bbe82d229484d750169e Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 20 Dec 2024 17:20:37 +0200 Subject: [PATCH 30/30] Update machine_pwm.c --- ports/esp32/machine_pwm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 0cb4930d30e5c..d574ed2bcd4b4 100755 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -42,7 +42,7 @@ #include "py/mpprint.h" -#define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, ", LINE=%d\n", __LINE__); /* Set in micropython/ports/esp32/mpconfigport.h #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL + 1) @@ -247,7 +247,7 @@ static void apply_duty(machine_pwm_obj_t *self) { } self->channel_duty = duty >> (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); - if ((chans[self->mode][self->channel].pin == -1) || (self->channel_duty == max_timer_duty)) { + if ((chans[self->mode][self->channel].pin == -1) || (self->channel_duty == 0) || (self->channel_duty == max_timer_duty)) { // New PWM assignment ledc_channel_config_t cfg = { .channel = self->channel, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy