Pulse-width Modulation (Outputs)

Another way to generate analog signals is with a technique called pulse-width modulation (or PWM). Like using a digital to analog converter you can use PWM to control the voltage output by a pin. However PWM is actually using a very high speed digital signal (either on or off, never in-between) to approximate an analog value. Not every device can work with or ‘see’ the varying voltages output with PWM, but many devices like LEDs and servos work great with PWM. The advantage of using PWM is that it typically doesn’t need special hardware from the microprocessor like with a digital to analog converter. In many cases you can use any digital output as a PWM output!

To explore PWM outputs we’ll use one to dim and brighten a LED, just like with using the digital to analog converter above. You’ll need the same components and setup as with the DAC:

  • A single color LED. You want a simple single color LED and not a fancier multi-color LED or NeoPixel. Look for a LED that has two legs, a short one and long one. Check out the Adafruit LED guide for more details on LEDs.
  • A resistor in the range of 300-1,000 ohms. You must use a resistor when wiring up a LED to your board or else you might damage the digital output on the board. The resistor limits the amount of current to the LED and prevents damage to the board or LED. The exact value of the resistor isn’t super important for this demonstration–pick any resistor in the 300-1,000 ohm range.
  • A breadboard and wires to connect the components and board together.

Connect the components to your board as follows:

  • The short leg (cathode) of the LED connects to one end of the resistor.
  • The other end of the resistor connects to the ground or GND pin of the board.
  • The long leg (anode) of the LED connects to a PWM output of your board. You might need to check your board’s documentation to find these pins, but typically any digital output capable pin will work. Note that on Circuit Playground Express and Metro M0 Express you can’t use pin A0 and need to switch to pin A1!

Now at the REPL import the pulseio and board module to create an instance of the pulseio.PWMOut class:

Download: file
>>> import board
>>> import pulseio
>>> led = pulseio.PWMOut(board.A1)

Just like with using analog inputs and outputs you need to specify the board pin as a parameter to the pulseio.PWMOut class initializer. However there are more optional parameters which you might later choose to specify:

frequency

Specify the frequency of the PWM signal in hertz. The default is 500 hz, or 500 times a second.

duty_cycle

Specify the duty cycle of the signal, or percent of time that it’s held at a high vs. low signal. The default is 0, or a completely low / off signal, and can be any 16-bit unsigned value. You’ll learn more about duty cycle values further below.

variable_frequency

This boolean indicates if the frequency of the PWM output can be changed. By default this is false which means the frequency can’t be changed (but the duty cycle still can be changed). It doesn’t hurt to enable this boolean but there are a limited number of internal timers to support different variable frequency PWM outputs. If you’re not planning to change the frequency of the output leave this disabled.

For now stick with the defaults for frequency and duty cycle (500hz and 0% respectively)–these values will work great to control the brightness of a LED.

Before you control the PWM output you’ll want to understand how frequency and duty cycle affect the output signal. As mentioned earlier a PWM output isn’t actually an analog signal in the truest sense of the word–at no point is a PWM output any voltage in-between fully on (~3.3V) or off (0V / ground). However a PWM output can appear to many devices to be an in-between voltage by very quickly turning itself on and off.

Imagine flicking a light switch on and off very quickly, like 30 times a second. The light bulb would be changing so quickly your eyes might not even see the change from on to off and back on again–it would appear to be solidly lit at a moderate brightness. Now imagine as you’re quickly turning the light on and off you hold it on slightly longer than you hold it off. The light would appear to be brighter because it’s turned on more often than it’s turned off! Likewise if you hold the switch off very slightly longer than on you would see the light grow dimmer. Your eyes are effectively ‘averaging out’ the fast changes and seeing the light’s overall brightness change. Remember at no point is the light actually in-between fully on or off–if your eyes were fast enough they would actually see the light as flickering on and off!

With a PWM output the frequency is the rate at which the signal turns on and off. Typically you set this to a high value that’s much faster than the device you’re connected to can see or measure. For a LED any value greater than about 60-100hz is enough to appear to the human eye as unchanging. For other devices like servos they might expect a very specific PWM frequency like 50hz.

Duty cycle is the percent of time that a part of the PWM signal is fully on vs. fully off. Think of duty cycle like a knob you can twist from 0 to 100%, where at 0% the signal is always turned off and never turns on, at 50% the signal is on for exactly as much time as it’s off, and at 100% it’s always turned on. You can adjust the duty cycle to any in-between value, like 33.33%, to have the signal turned on for 1/3 of the time and turned off for the remaining 2/3 of the time. By manipulating the duty cycle you have similar control as if you were adjusting the voltage output by the pin!

To further illustrate how PWM is different from true analog output, look at the image below which shows oscilloscope output of a PWM signal at different duty cycles (0%, 25%, 50%, 75%, and 100%). Notice how as the duty cycle increases the amount of time the signal is at a high logic level (3.3V) gets longer. At 50% duty cycle the signal is high for twice as long as at 25% duty cycle (compare how long the tops of each wave are to check for yourself). At the extremes of 0% and 100% you can also see the signal never changes and is always at a high or low level!

Back to controlling the LED, you can change the duty cycle by modifying thepulseio.PWMOut.duty_cycle attribute. Try setting the duty cycle to a 100% or fully on value with:

Download: file
>>> led.duty_cycle = 65535

Notice the LED turns on fully bright! Now set the duty cycle to 0% or fully off with:

Download: file
>>> led.duty_cycle = 0

The LED turns off! Try an in-between value like:

Download: file
>>> led.duty_cycle = 32767

You should see the LED turn on at a moderate or half brightness. Experiment with setting different duty cycle values between 0% and 100% (or 0 and 65535 values) to see the LED brighten and dim.

The duty cycle value is a 16-bit unsigned number just like the value used by digital to analog and analog to digital converters in CircuitPython. You can convert from a percentage value, like 66%, to a 16-bit duty cycle value with an equation like:

Download: file
>>> int(66 / 100 * 65535)
43253

Notice you can set any value in-between 0 to 65535 for the duty cycle and the LED appears to brighten and dim, even all the way down to very low duty cycle values like 1000 or less. This is in contrast to the digital to analog converter output where you saw below a large threshold there wasn’t enough voltage to turn on the LED. Remember a PWM output is always either fully on or fully off, it’s never in-between. As a result the PWM output will always be able to light the LED, even at very low duty cycle values. It’s only the amount of time the LED is turned on vs. off that changes–the less the LED is turned on the less bright it appears to your eyes!

Try using a loop to go through all 0-100% duty cycle values and back:

Download: file
>>> import time
>>> while True:
...    for i in range(100):
...        led.duty_cycle = int(i / 100 * 65535)
...        time.sleep(0.01)
...    for i in range(100, -1, -1):
...        led.duty_cycle = int(i / 100 * 65535)
...        time.sleep(0.01)
>>>

You should see the LED fade from off to fully on and back down to off repeatedly. Press Ctrl-C to stop the loop and get back to the serial REPL.

And remember you can make a handy Python function to more easily set PWM duty cycle. For example given a value from 0.0 to 1.0 (0 to 100%) it can compute the necessary duty cycle value for you:

Download: file
>>> def duty_cycle_value(percent):
...     return int(percent * 65535)
>>> led.duty_cycle = duty_cycle_value(0.5)  # Set 50% duty cycle!
This guide was first published on Sep 02, 2017. It was last updated on Sep 02, 2017. This page (Pulse-width Modulation (Outputs)) was last updated on Sep 21, 2019.