8000 esp32/PWM: Reduce inconsitencies between ports. by IhorNehrutsa · Pull Request #10854 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

esp32/PWM: Reduce inconsitencies between ports. #10854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions docs/esp32/quickref.rst
< 8000 template class="js-line-alert-template">
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ for more details.

Use the :ref:`machine.PWM <machine.PWM>` class::

from machine import Pin, PWM
from machine import Pin, PWM, lightsleep

pwm0 = PWM(Pin(0), freq=5000, duty_u16=32768) # create PWM object from a pin
freq = pwm0.freq() # get current frequency
Expand All @@ -393,7 +393,7 @@ Use the :ref:`machine.PWM <machine.PWM>` class::
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-65535
pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%)
pwm0.duty_u16(65536*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%)
Expand All @@ -402,19 +402,35 @@ Use the :ref:`machine.PWM <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), lightsleep=True) # Allow PWM during light sleep mode

lightsleep(10*1000) # pwm0, pwm2 goes off, pwm4 stays on during 10s light sleep
# pwm0, pwm2, pwm4 on after 10s light sleep

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-P4 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
Expand Down
220 changes: 178 additions & 42 deletions docs/esp32/tutorial/pwm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 65536 // 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(65535, D * (i + 1))
pwms.append(PWM(pin, freq=f, duty_u16=d))
sleep(2 / f)
print(pwms[i])
finally:
for pwm in pwms:
Expand All @@ -31,65 +35,100 @@ 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)
PWM(Pin(4), freq=10000, duty_u16=8192)
PWM(Pin(12), freq=20000, duty_u16=12288)
PWM(Pin(13), freq=20000, duty_u16=16384)
PWM(Pin(14), freq=30030, duty_u16=20480)
PWM(Pin(15), freq=30030, duty_u16=24576)
PWM(Pin(16), freq=40000, duty_u16=28672)
PWM(Pin(18), freq=40000, duty_u16=32768)
PWM(Pin(19), freq=50000, duty_u16=36864)
PWM(Pin(22), freq=50000, duty_u16=40960)
PWM(Pin(23), freq=60060, duty_u16=45056)
PWM(Pin(25), freq=60060, duty_u16=49152)
PWM(Pin(26), freq=69930, duty_u16=53248)
PWM(Pin(27), freq=69930, duty_u16=57344)
PWM(Pin(32), freq=80000, duty_u16=61440)
PWM(Pin(33), freq=80000, duty_u16=65535)


* 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(0.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)
PWM(Pin(27), freq=1202, duty_u16=32768)
PWM(Pin(27), freq=1401, duty_u16=32768)
PWM(Pin(27), freq=1598, duty_u16=32768)
...
PWM(Pin(27), freq=9398, duty_u16=32768)
PWM(Pin(27), freq=9615, duty_u16=32768)
PWM(Pin(27), freq=9804, duty_u16=32768)
PWM(Pin(27), freq=10000, duty_u16=32768)

PWM(Pin(27), freq=10000, duty_u16=32768)
PWM(Pin(27), freq=9804, duty_u16=32768)
PWM(Pin(27), freq=9615, duty_u16=32768)
PWM(Pin(27), freq=9398, duty_u16=32768)
...
PWM(Pin(27), freq=1598, duty_u16=32768)
PWM(Pin(27), freq=1401, duty_u16=32768)
PWM(Pin(27), freq=1202, duty_u16=32768)
PWM(Pin(27), freq=998, duty_u16=32768)


* Example of a **smooth duty change**::

from time import sleep
from machine import Pin, PWM

DUTY_MAX = 2**16 - 1
DUTY_MAX = 65535

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:
Expand All @@ -99,9 +138,106 @@ low all of the time.
duty_u16 = 0
delta_d = -delta_d

See PWM wave at Pin(5) with an oscilloscope.
PWM wave on Pin(27) with an oscilloscope.

Output is::

PWM(Pin(27), freq=998, duty_u16=0)
PWM(Pin(27), freq=998, duty_u16=256)
PWM(Pin(27), freq=998, duty_u16=512)
PWM(Pin(27), freq=998, duty_u16=768)
PWM(Pin(27), freq=998, duty_u16=1024)
...
PWM(Pin(27), freq=998, duty_u16=64512)
PWM(Pin(27), freq=998, duty_u16=64768)
PWM(Pin(27), freq=998, duty_u16=65024)
PWM(Pin(27), freq=998, duty_u16=65280)
PWM(Pin(27), freq=998, duty_u16=65535)

PWM(Pin(27), freq=998, duty_u16=65279)
PWM(Pin(27), freq=998, duty_u16=65023)
PWM(Pin(27), freq=998, duty_u16=64767)
PWM(Pin(27), freq=998, duty_u16=64511)
...
PWM(Pin(27), freq=998, duty_u16=1023)
PWM(Pin(27), freq=998, duty_u16=767)
PWM(Pin(27), freq=998, duty_u16=511)
PWM(Pin(27), freq=998, duty_u16=255)
PWM(Pin(27), freq=998, duty_u16=0)


* Example of a **smooth duty change and PWM output inversion**::

from utime import sleep
from machine import Pin, PWM

try:
DUTY_MAX = 65535

duty_u16 = 0
delta_d = 65536 // 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)
PWM(Pin(32), freq=5000, duty_u16=32768, invert=1)
PWM(Pin(27), freq=5000, duty_u16=2048)
PWM(Pin(32), freq=5000, duty_u16=2048, invert=1)
PWM(Pin(27), freq=5000, duty_u16=4096)
PWM(Pin(32), freq=5000, duty_u16=4096, invert=1)
PWM(Pin(27), freq=5000, duty_u16=6144)
PWM(Pin(32), freq=5000, duty_u16=6144, invert=1)
PWM(Pin(27), freq=5000, duty_u16=8192)
PWM(Pin(32), freq=5000, duty_u16=8192, invert=1)
...


See PWM waves on Pin(27) and Pin(32) with an oscilloscope.

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.

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::
Expand Down
14 changes: 7 additions & 7 deletions docs/library/machine.PWM.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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 only on the esp32, mimxrt, nrf, rp2, samd and zephyr ports.

Methods
-------
Expand Down Expand Up @@ -116,10 +116,10 @@ Limitations of PWM
resolution of 8 bit, not 16-bit as may be expected. In this case, the lowest
8 bits of *duty_u16* are insignificant. So::

pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2)
pwm=PWM(Pin(13), freq=300_000, duty_u16=65536//2)

and::

pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2 + 255)
pwm=PWM(Pin(13), freq=300_000, duty_u16=65536//2 + 255)

will generate PWM with the same 50% duty cycle.
Loading
Loading
0