ESP32 In MicroPython: PWM And The Duty Cycle
Written by Harry Fairhead & Mike James   
Monday, 21 August 2023
Article Index
ESP32 In MicroPython: PWM And The Duty Cycle
Changing The Duty Cycle

For reasons that will be discussed later, most applications of PWM vary the duty cycle or the period of the pulse train while the frequency remains fixed. This raises the next question, how fast can you change the duty cycle? There is no easy way to give an exact answer and, in most cases, an exact answer isn't of much value. The reason is that for a PWM signal to convey information it generally has to deliver a number of complete cycles with a given duty cycle. This is because of the way pulses are often averaged in applications. We also have another problem – synchronization. This is more subtle than it first seems. The hardware won't change the duty cycle until the current pulse is complete. You might think that the following program works to switch between two duty cycles on a per pulse basis:

import time
from machine import Pin, PWM
pwm1 = PWM(Pin(4),freq = 50)
while True:
    pwm1.duty_u16(65535//2)
    time.sleep_ms(19)
    pwm1.duty_u16(65535//4)
    time.sleep_ms(19)

However, if you try this out the result isn’t what you might expect on a first analysis:

esp32pwm2

You don’t get one 25% pulse followed by one 50% pulse, but a varying number of each in turn. The reason is, of course, that the duty cycle is being set asynchronously. The time that the duty cycle changes drifts in time and produces an interference pattern. If you want to create a PWM bitstream that varies duty cycle accurately on a per pulse basis then you need to use RMT, the remote controller introduced in Chapter 4.

Pulse Coding Using RMT

RMT can be used to send a bitstream with accurately specified duty cycles. All you have to do is construct a list with the correct timings for the high and low portions of each pulse:

import esp32
from machine import Pin
import machine
pin=Pin(4,Pin.OUT,value=0)
rmt=esp32.RMT(0,pin=pin)
t=20000000 # pulse repetition in us
d=[10,20,30,40,50] #duty cycles in percent
times=[]
for p in d:
    times.append(int(t*p/100))
    times.append(int((t*(100-p)/100)))
rmt.loop(True)
rmt.write_pulses(times,1)

The frequency is specified as a time in µs to make the math easier. All we have to do is multiply up the list of duty cycles to get the high and low times for each pulse and then use this to send a bitstream from GPIO4. Notice that the loop method is used to repeat the same duty cycle over and over.


The logic analyzer trace reveals that the pulses really do have duty cycles of 10,20,30,40 and 50%. This technique can be used to create precise codes like PCM, Pulse Code Modulation.

esp32pwm3

Duty Cycle Resolution

It seems as if you can set the duty cycle to a 16-bit value giving you 65535 different settings, however, this is only true in a few cases.

The PWM hardware implements the following algorithm (in pseudo code):

wrap = clock_frequency/PWM_frequency
level = wrap*duty%/100
set GPIO line high
for count in range(wrap):
	wait one clock cycle
	if count > level:
		set GPIO line low

In other words, the frequency of the PWM signal depends on the value of wrap and the duty cycle depends on level. For example, if clock_frequency is 100MHz, PWM_frequency is 10MHz and duty% is 50% then:

wrap = 10

level = 5

The for loop runs for 10 clock pulses and the line is high for 5 clock pulses. You can see that this does produce the required frequency and duty cycle.

When you select a PWM_frequency this gives the total number of clock pulses counted in a pulse width modulation cycle. Clearly you can only set the level and hence the duty cycle between 0 and wrap and this can limit the precision that you can set. For example, as the clock frequency is set to 40MHz setting a PWM_frequency of 20MHz gives wrap = 2 and, given the duty cycle resolution is just two bits, level can only be 0, 1, 2, 3 which is a duty cycle of 0%, 25%, 75% and 100%.

 

In other words, the PWM_frequency you select affects the accuracy of the setting of the duty cycle. In most cases the wrap value is large enough to ignore the problem, but sometimes it is important. You can see that this is the case in the following example:

import time
from machine import Pin, PWM
pwm1 = PWM(Pin(4),freq = 20000000)
pwm1.duty_u16(65535//2+1)

This produces a duty cycle of 50% as you would expect, but if you change the duty cycle to 65535//4*n+1 for n = 0, 1, 2, 3 you get duty cycles of 0%, 25%, 50%, and 75% and these are the only duty cycles you can get, no matter what you try to set it to.

You can discover the duty cycle resolution for any given frequency by printing the PWM instance:

print(pwm1) 

which displays:

PWM(Pin(4), freq=20000000, duty_u16=16384, resolution=2,
(duty=25.00%, resolution=25.000%), mode=0, channel=0, timer=0)

You can see that the resolution is reported as 2 bits or 25%.

At lower frequencies the duty cycle precision is usually closer or greater than 16-bits and we tend to ignore any restrictions. For example, at 50Hz the frequency used for controlling servo motors the resolution is 20 bits which is more than you can specify using a 16-bit parameter!

In chapter but not in this extract

  • Uses of PWM – Digital to Analog
  • Frequency Modulation
  • Controlling an LED
  • What Else Can You Use PWM For?

Summary

  • PWM, Pulse Width Modulation, has a fixed repetition rate but a variable duty cycle, i.e. the amount of time the signal is high or low changes.

  • PWM can be generated by software simply by changing the state of a GPIO line correctly, but it can also be generated in hardware so relieving the processor of some work.

  • As well as being a way of signaling, PWM can also be used to vary the amount of power or voltage transferred. The higher the duty cycle, the more power/voltage.

  • The ESP32 has 16 hardware PWM generators and these can be used with any of the GPIO lines capable of output.

  • There are only eight timers which determine the frequency that the PWM lines work at. These are allocated as needed as you setup PWM lines.

  • You cannot change the duty cycle of a PWM signal in a precisely timed way unless you use the RMT hardware instead.

  • The higher the frequency the lower the duty cycle resolution.

  • You can find the frequency and resolution by printing the PWM object.

  • PWM can be used to implement digital to analog conversion simply by varying the duty cycle. In the same way, by varying the duty cycle, you can dim an LED.

esp32sbc

Programming the ESP32in MicroPython

By Harry Fairhead & Mike James

esppython360

Buy from Amazon.

Contents

       Preface

  1. The ESP32 – Before We Begin
  2. Getting Started
  3. Getting Started With The GPIO 
  4. Simple Output
  5. Some Electronics
  6. Simple Input
  7. Advanced Input – Interrupts
  8. Pulse Width Modulation
       Extract:
    PWM And The Duty Cycle
  9. Controlling Motors And Servos
  10. Getting Started With The SPI Bus
  11. Using Analog Sensors
       Extract:
    Analog Input
  12. Using The I2C Bus
       Extract
    : I2C, HTU21D And Slow Reading 
  13. One-Wire Protocols
  14. The Serial Port
  15. Using WiFi
     Extract:
    WiFi 
  16. Sockets
     Extract:
    Client Sockets
     Extract:
    SSL Client Sockets
  17. Asyncio And Servers
     Extract:
    Asyncio ***NEW!
  18. Direct To The Hardware
     Extract:
    Using Hardware Registers 

<ASIN:187196282X>

espbook

 

Comments




or email your comment to: comments@i-programmer.info

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner



Last Updated ( Monday, 21 August 2023 )