Skip to content

Commit

Permalink
esp32\machine_pwm: PWM reduce inconsist.
Browse files Browse the repository at this point in the history
Signed-off-by: Ihor Nehrutsa <[email protected]>
  • Loading branch information
IhorNehrutsa committed Nov 20, 2023
1 parent fce8d9f commit 93a7733
Show file tree
Hide file tree
Showing 4 changed files with 603 additions and 408 deletions.
35 changes: 20 additions & 15 deletions docs/esp32/quickref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ These are working configurations for LAN interfaces of popular boards::
# Olimex ESP32-GATEWAY: power controlled by Pin(5)
# Olimex ESP32 PoE and ESP32-PoE ISO: power controlled by Pin(12)

lan = network.LAN(mdc=machine.Pin(23), mdio=machine.Pin(18), power=machine.Pin(5),
lan = network.LAN(mdc=machine.Pin(23), mdio=machine.Pin(18), power=machine.Pin(5),
phy_type=network.PHY_LAN8720, phy_addr=0,
ref_clk=machine.Pin(17), ref_clk_mode=machine.Pin.OUT)

Expand Down Expand Up @@ -296,11 +296,11 @@ Use the :ref:`machine.PWM <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%)
Expand All @@ -309,19 +309,24 @@ 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-C3
ESP32-S3 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
Expand Down
197 changes: 161 additions & 36 deletions docs/esp32/tutorial/pwm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -31,75 +33,198 @@ 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. <https://user-images.githubusercontent.com/70886343/224013926-73953f7b-9b75-4e32-9595-83236c76ca1f.mp4>`_

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

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

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) <https://user-images.githubusercontent.com/70886343/222743883-dca25aa8-681d-471c-933a-6f9beacb6eee.mp4>`_ 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.
Expand Down
Loading

0 comments on commit 93a7733

Please sign in to comment.