PWM Servo Control with Raspberry Pi

Pulse-Width Modulation (PWM) is a highly efficient technique used to control the precise position of servo motors by varying the width of electrical pulses. This article provides a comprehensive guide on how to utilize a Raspberry Pi to generate PWM signals, wire the hardware safely, and write Python code using libraries like gpiozero to command a servo motor to specific angles.

Understanding PWM and Servo Motors

Servo motors rely on a specific PWM frequency, typically 50Hz (which translates to a 20ms period), to determine their rotational position. The duration of the “on” time—known as the pulse width—tells the servo where to move.

The ratio of the pulse width to the total period is called the Duty Cycle. For a 50Hz signal, a 1.5ms pulse translates to a 7.5% duty cycle.

Hardware Wiring and Power Requirements

Standard servo motors can draw more current than the Raspberry Pi’s 5V pins can safely supply, especially under load. It is crucial to use an external 5V power supply to prevent your Raspberry Pi from crashing or suffering permanent damage.

Servo Wire Color Connection Target Notes
Ground (Brown/Black) External Power Supply (-) AND Raspberry Pi GND Must share a common ground
Power (Red) External Power Supply (+) Typically 5V to 6V
Signal (Yellow/Orange) Raspberry Pi GPIO Pin (e.g., GPIO 18) Delivers the PWM command

Important Note: Always connect the ground (GND) pin of the Raspberry Pi to the negative terminal of the external power supply. Without a common ground, the PWM signal will lack a stable reference point, causing the servo to jitter violently or fail to move.

Python Implementation using GPIO Zero

The gpiozero library is the most modern and straightforward tool for controlling servos on a Raspberry Pi. It automatically handles the underlying duty cycle calculations behind the scenes.

First, ensure your system is updated and the library is installed:

sudo apt update
sudo apt install python3-gpiozero

You can then use the following Python script to sweep the servo back and forth:

from gpiozero import Servo
from time import sleep

# Class default uses a standard pulse width range (1ms to 2ms)
# Connected to Raspberry Pi GPIO pin 18
servo = Servo(18)

try:
    while True:
        print("Setting servo to minimum position")
        servo.min()
        sleep(2)
        
        print("Setting servo to mid position")
        servo.mid()
        sleep(2)
        
        print("Setting servo to maximum position")
        servo.max()
        sleep(2)

except KeyboardInterrupt:
    print("Program stopped by user")

Fine-Tuning the Pulse Width

Not all servo motors are calibrated identically. If your servo is hitting its physical limits or not rotating a full 180 degrees, you can fine-tune the minimum and maximum pulse widths by using the Servo class parameters.

from gpiozero import Servo

# Fine-tuning for a servo that expects 0.5ms to 2.5ms pulses
my_factory_target = Servo(18, min_pulse_width=0.5/1000, max_pulse_width=2.5/1000)

By adjusting these thresholds and utilizing the gpiozero library, the Raspberry Pi can achieve reliable, hardware-timed PWM control over any standard hobby servo.