Raspberry Pi PWM Basics

Learn how Pulse Width Modulation (PWM) lets a purely digital GPIO pin fake an in-between, analog-like voltage by switching on and off very quickly, and control it with gpiozero's PWMLED and duty cycle.

The Problem: GPIO Pins Are Digital

A standard GPIO pin only really knows two states: fully on (about 3.3V) or fully off (0V). That's fine for an LED you just want to switch on or off, but plenty of things you'd want to control - an LED's brightness, a motor's speed, a servo's position - call for something in between full power and none at all. A plain digital pin has no native way to output, say, '40% of full brightness.'

What PWM Actually Does

Pulse Width Modulation solves this without ever producing a true in-between voltage. Instead, it switches the pin fully on and fully off very rapidly - hundreds or thousands of times per second - and varies how much of each cycle is spent on versus off. If the switching is fast enough, an LED can't visibly flicker at that speed, and a motor's inertia can't respond to individual pulses either; both effectively respond to the average power delivered over each cycle, which reads as a steady, intermediate level rather than a series of blinks.

Duty Cycle Defined

The fraction of each cycle that the signal spends HIGH is called the duty cycle, usually expressed as a percentage (or, in gpiozero, as a float from 0.0 to 1.0). A 25% duty cycle means the pin is on for one quarter of each cycle and off for the other three quarters, averaging out to roughly a quarter of full power. A 100% duty cycle is indistinguishable from a plain 'on' signal, and 0% is indistinguishable from 'off' - PWM's plain digital on/off states are really just the two extremes of the same duty cycle scale.

Duty Cyclegpiozero valueApproximate LED Brightness
0%0.0Fully off
25%0.25Dim
50%0.5Half brightness
75%0.75Bright
100%1.0Fully on, same as a plain LED

Setting brightness directly with PWMLED

from gpiozero import PWMLED
from time import sleep

led = PWMLED(17)

led.value = 0.0   # off
sleep(1)
led.value = 0.25  # dim - 25% duty cycle
sleep(1)
led.value = 1.0   # full brightness - 100% duty cycle
sleep(1)
led.off()

Fading Smoothly with pulse()

Instead of jumping between fixed brightness levels, `PWMLED` also has a `.pulse()` method that continuously ramps the duty cycle up and down over time, producing a smooth fade in and out - a common effect for 'breathing' status LEDs. Under the hood it's still just PWM: gpiozero is rapidly adjusting the duty cycle itself, many times per second, to create the illusion of a continuously changing brightness.

A smooth breathing fade

from gpiozero import PWMLED
from signal import pause

led = PWMLED(17)
led.pulse(fade_in_time=2, fade_out_time=2, n=None, background=True)
# fades from off to full brightness and back over 2s each way, repeating forever (n=None)

pause()
Note: By default, gpiozero drives PWM in software at a modest frequency (100Hz), which is plenty for LED brightness and simple motor speed control. GPIO12, GPIO13, GPIO18, and GPIO19 additionally support hardware PWM on the Raspberry Pi, which offers steadier timing and higher frequencies - worth switching to if you notice flicker or need very precise control, such as driving a servo.