Skip to content

Commit

Permalink
esp32/PWM: Reduce inconsitencies between ports.
Browse files Browse the repository at this point in the history
Signed-off-by: IhorNehrutsa <[email protected]>
  • Loading branch information
IhorNehrutsa committed Dec 16, 2024
1 parent ee8d8b3 commit 1cdfc1a
Show file tree
Hide file tree
Showing 4 changed files with 647 additions and 474 deletions.
42 changes: 32 additions & 10 deletions docs/esp32/quickref.rst
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -320,25 +320,47 @@ 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.

ESP chips have different hardware peripherals:

===================================================== ======== ======== ========
Hardware specification ESP32 ESP32-S2 ESP32-C3
----------------------------------------------------- -------- -------- --------
Number of groups (speed modes) 2 1 1
Number of timers per group 4 4 4
Number of channels per group 8 8 6
----------------------------------------------------- -------- -------- --------
Different PWM frequencies (groups * timers) 8 4 4
Total PWM channels (Pins, duties) (groups * channels) 16 8 6
===================================================== ======== ======== ========
======================================================= ======== ========= ==========
Hardware specification ESP32 ESP32-S2, ESP32-C2,
ESP32-S3, ESP32-C3,
ESP32-P2 ESP32-C5,
ESP32-C6,
ESP32-H2
------------------------------------------------------- -------- --------- ----------
Number of groups (speed modes) 2 1 1
Number of timers per group 4 4 4
Number of channels per group 8 8 6
------------------------------------------------------- -------- --------- ----------
Different PWM frequencies = (groups * timers) 8 4 4
Total PWM channels (Pins, duties) = (groups * channels) 16 8 6
======================================================= ======== ========= ==========

A maximum number of PWM channels (Pins) are available on the ESP32 - 16 channels,
but only 8 different PWM frequencies are available, the remaining 8 channels must
have the same frequency. On the other hand, 16 independent PWM duty cycles are
possible at the same frequency.

Note: New PWM parameters take effect in the next PWM cycle.

pwm = PWM(2, duty=512)
print(pwm)
>>>PWM(Pin(2), freq=5000, duty=1023) # the duty is not relevant
pwm.init(freq=2, duty=64)
print(pwm)
>>>PWM(Pin(2), freq=2, duty=16) # the duty is not relevant
time.sleep(1 / 2) # wait one PWM period
print(pwm)
>>>PWM(Pin(2), freq=2, duty=64) # the duty is actual

Note: machine.freq(20_000_000) reduces the highest PWM frequency to 10 MHz.

See more examples in the :ref:`esp32_pwm` tutorial.

DAC (digital to analog conversion)
Expand Down
205 changes: 164 additions & 41 deletions docs/esp32/tutorial/pwm.rst
100644 → 100755
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 = 2**16 // 16 # 6.25%
pins = (2, 4, 12, 13, 14, 15, 16, 18, 19, 22, 23, 25, 26, 27, 32, 33)
pwms = []
for i, pin in enumerate(pins):
pwms.append(PWM(Pin(pin), freq=f * (i // 2 + 1), duty= 1023 if i==15 else d * (i + 1)))
f = F * (i // 2 + 1)
d = min(2**16 - 1, D * (i + 1))
pwms.append(PWM(pin, freq=f, duty_u16=d))
sleep(1/f)
print(pwms[i])
finally:
for pwm in pwms:
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) # 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. <https://user-images.githubusercontent.com/70886343/224013926-73953f7b-9b75-4e32-9595-83236c76ca1f.mp4>`_

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

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:
Expand All @@ -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. <https://user-images.githubusercontent.com/70886343/224020123-1c958e85-0c91-4ca6-8b4c-b3bb956892b1.mp4>`_

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) <https://user-images.githubusercontent.com/70886343/222743883-dca25aa8-681d-471c-933a-6f9beacb6eee.mp4>`_ 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::
Expand Down
18 changes: 13 additions & 5 deletions docs/library/machine.PWM.rst
100644 → 100755
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 at RP2, i.MXRT, SAMD, nRF, ESP32 ports.

Methods
-------
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 1cdfc1a

Please sign in to comment.