In this post, we will see how to use PWM outputs on Raspberry Pi to control devices like LEDs, motors, and servos.
PWM outputs (Pulse Width Modulation) are an electronics technique that allows us to control electrical signals by generating a “pseudo-analog” signal.
Key PWM Concepts
A PWM output is a simple (and cheap) way to achieve a “more or less analog” signal.
Instead of varying the voltage directly, PWM modifies the time a signal is in a high state (ON) versus the time it is in a low state (OFF) within a cycle.
Duty Cycle: This is the percentage of time the signal is in a high state (ON) during a complete cycle. For example, a 50% duty cycle means the signal is high half the time and low the other half.
Frequency: This is the number of complete cycles per second. It is measured in Hertz (Hz). A high frequency means cycles repeat quickly, while a low frequency means cycles repeat more slowly.
For some machines, a PWM signal works “more or less the same” as a real analog signal. For example, an LED will give us practically the same operation.
However, a PWM signal is not an analog signal. You are actually applying 3V3 pulses. If you connect it to a device that does not support that voltage, you could damage it.
Hardware PWM Outputs
The Raspberry Pi has two pins that support hardware PWM: GPIO18 and GPIO19. These pins are suitable for applications that require high precision and stability (such as controlling servos or motors).
Let’s see an example where we will use the GPIO18 pin to control the brightness of an LED using PWM.
import RPi.GPIO as GPIO
import time
# Pin configuration
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
# Create a PWM instance with a frequency of 100 Hz
pwm = GPIO.PWM(18, 100)
# Start PWM with a duty cycle of 0% (LED off)
pwm.start(0)
try:
while True:
# Gradually increase the brightness of the LED
for duty_cycle in range(0, 101, 5):
pwm.ChangeDutyCycle(duty_cycle)
time.sleep(0.1)
# Gradually decrease the brightness of the LED
for duty_cycle in range(100, -1, -5):
pwm.ChangeDutyCycle(duty_cycle)
time.sleep(0.1)
except KeyboardInterrupt:
# Stop PWM and clean up GPIO pins
pwm.stop()
GPIO.cleanup()
In this code:
- We configure the GPIO18 pin as an output.
- We create a PWM instance with a frequency of 100 Hz.
- We start PWM with a 0% duty cycle (LED off).
- We use a loop to gradually increase and decrease the LED brightness.
Software PWM
If we need more PWM pins or don’t have access to the hardware pins, we can use software PWM.
Software PWM is less precise and may consume more CPU resources.
In this example, we will use the GPIO17 pin.
import RPi.GPIO as GPIO
import time
# Pin configuration
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)
# PWM frequency (in Hz)
frequency = 100
# Initial duty cycle (0%)
duty_cycle = 0
# Cycle time (1 / frequency)
cycle_time = 1.0 / frequency
try:
while True:
# Calculate the high and low times
on_time = cycle_time * (duty_cycle / 100.0)
off_time = cycle_time - on_time
# Turn on the LED
GPIO.output(17, GPIO.HIGH)
time.sleep(on_time)
# Turn off the LED
GPIO.output(17, GPIO.LOW)
time.sleep(off_time)
# Increase the duty cycle
duty_cycle += 5
if duty_cycle > 100:
duty_cycle = 0
except KeyboardInterrupt:
# Clean up GPIO pins
GPIO.cleanup()
In this code:
- We configure the GPIO17 pin as an output.
- We implement software PWM using a loop that alternates between turning the LED on and off.
- We gradually increase the duty cycle to simulate an increase in LED brightness.
Practical Examples
Controlling a DC Motor
PWM is also used to control the speed of a DC motor. By varying the duty cycle, we can control the amount of power delivered to the motor, which in turn controls its speed.
import RPi.GPIO as GPIO
import time
# Pin configuration
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
# Create a PWM instance with a frequency of 100 Hz
pwm = GPIO.PWM(18, 100)
# Start PWM with a duty cycle of 0% (motor stopped)
pwm.start(0)
try:
while True:
# Gradually increase the speed of the motor
for duty_cycle in range(0, 101, 5):
pwm.ChangeDutyCycle(duty_cycle)
time.sleep(0.5)
# Gradually decrease the speed of the motor
for duty_cycle in range(100, -1, -5):
pwm.ChangeDutyCycle(duty_cycle)
time.sleep(0.5)
except KeyboardInterrupt:
# Stop PWM and clean up GPIO pins
pwm.stop()
GPIO.cleanup()
Controlling a Servo
Servos are devices used to control the position of a shaft. PWM is essential for controlling servos, as the servo’s position depends on the duty cycle of the PWM signal.
import RPi.GPIO as GPIO
import time
# Pin configuration
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
# Create a PWM instance with a frequency of 50 Hz (typical for servos)
pwm = GPIO.PWM(18, 50)
# Start PWM with a duty cycle of 7.5% (center position)
pwm.start(7.5)
try:
while True:
# Move the servo to the 0-degree position
pwm.ChangeDutyCycle(2.5)
time.sleep(1)
# Move the servo to the 90-degree position
pwm.ChangeDutyCycle(7.5)
time.sleep(1)
# Move the servo to the 180-degree position
pwm.ChangeDutyCycle(12.5)
time.sleep(1)
except KeyboardInterrupt:
# Stop PWM and clean up GPIO pins
pwm.stop()
GPIO.cleanup()
In this code:
- We configure the GPIO18 pin as an output.
- We create a PWM instance with a frequency of 50 Hz, which is typical for servos.
- We use different duty cycles to move the servo to different positions (0°, 90°, 180°).
