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 a technique in electronics that allows us to control electrical signals by generating a “pseudo-analog” signal.
If you want to learn more about PWM outputs, check out,
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 that a signal is in a high state (ON) versus the time it is in a low state (OFF) within a cycle.
Duty Cycle: It is the percentage of time that the signal is in a high state (ON) during a complete cycle. For example, a 50% duty cycle means that the signal is high half the time and low the other half.
Frequency: It is the number of complete cycles per second. It is measured in Hertz (Hz). A high frequency means that the cycles repeat quickly, while a low frequency means that the cycles repeat more slowly.
For some devices, a PWM signal works “more or less the same” as a real analog signal. For example, an LED will give us virtually the same performance.
However, a PWM signal is not an analog signal. You are actually applying pulses of 3.3V. If you connect it to a device that does not support that voltage, you may 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 (like controlling servos or motors).
Let’s see an example where we will use pin GPIO18 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 pin GPIO18 as an output.
- We create a PWM instance with a frequency of 100 Hz.
- We start PWM with a duty cycle of 0% (LED off).
- We use a loop to gradually increase and decrease the brightness of the LED.
Software PWM
If we need more PWM pins or do not have access to the hardware pins, we can use software PWM.
Software PWM is less accurate and may consume more CPU resources.
In this example, we will use pin GPIO17.
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 pin GPIO17 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 the brightness of the LED.
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 energy 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 an axis. PWM is essential for controlling servos, as the position of the servo 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 pin GPIO18 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°).