Estimated Time: 45 Minutes
Target Board Variants: Raspberry Pi 4 Model B (1GB/2GB/4GB/8GB) and Raspberry Pi 5 (4GB/8GB) with the 40-pin header.
To get true, jitter-free PWM in Raspberry Pi for precision motor or servo control, you must use GPIO 18 (Physical Pin 12) and drive it via the pigpio daemon. The Raspberry Pi runs a standard Linux kernel, which is not a real-time operating system. If you rely on software PWM, background OS tasks will interrupt the pulse train, causing microsecond jitter that makes servos buzz, overheat, and drift. By routing your signal through the Pi's dedicated hardware PWM peripheral via DMA (Direct Memory Access), you bypass the CPU entirely and get a rock-solid signal.
The Core Decision: Hardware vs. Software PWM
Before wiring anything, you need to decide which PWM method fits your component. The Pi's hardware PWM is limited to specific pins, while software PWM can run on any GPIO but suffers from timing noise.
| Criteria | Hardware PWM (pigpio) | Software PWM (gpiozero default) |
|---|---|---|
| Signal Stability | Perfect (DMA driven) | Jittery (CPU interrupt driven) |
| Available Pins | GPIO 18 (Primary), 12, 13, 19 | All 26 usable GPIO pins |
| Best Use Case | Servos, ESCs, stepper drivers | LED dimming, simple heaters |
| Daemon Required? | Yes (pigpiod) | No |
Parts List and Pin Mapping (Pi 4 & Pi 5)
The Raspberry Pi 5 introduced the RP1 southbridge chip, which changed how GPIO is addressed at the silicon level, but the gpiozero and pigpio libraries abstract this perfectly. The physical pinout for the 40-pin header remains identical for our purposes.
Required Components
- Microcontroller: Raspberry Pi 4 Model B (4GB) or Raspberry Pi 5 (4GB) running Raspberry Pi OS (Bookworm or newer).
- Actuator: MG996R High-Torque Servo (or standard SG90 for low-load testing).
- Power Supply: 5V 3A USB-C Power Supply (Do not power high-torque servos directly from the Pi's 5V rail; it will brown out the board).
- Protection: 10kΩ pull-down resistor (prevents servo flutter during Pi boot sequence).
- Wiring: 22 AWG silicone jumper wires.
Pin Mapping Table
| Signal | BCM GPIO | Physical Pin | Wire Color (Standard) |
|---|---|---|---|
| PWM Control | GPIO 18 | Pin 12 | Orange / Yellow |
| 5V Power (External) | N/A | N/A | Red |
| Ground | GND | Pin 14 | Brown / Black |
Step-by-Step Wiring and Jitter-Free Python Code
This build targets the gpiozero library but forces it to use the pigpio pin factory, ensuring the hardware PWM peripheral handles the pulses.
Wiring Steps
- De-energize: Unplug the Raspberry Pi and the external servo power supply.
- Install Resistor: Solder or connect a 10kΩ resistor between the servo signal wire and the servo ground wire. This pulls the line low while the Pi's GPIO 18 is initializing during boot, preventing the servo from violently jerking to a random position.
- Connect Signal: Connect the servo signal wire to Physical Pin 12 (GPIO 18).
- Connect Power: Connect the servo VCC (Red) to your external 5V supply, and servo GND (Brown) to the external supply GND.
- Bond Grounds: Run a jumper wire from the external supply GND to Raspberry Pi Physical Pin 14 (GND).
- Verify: Use a multimeter in continuity mode to verify that the Pi's GND and the external supply GND are bonded (should read < 1 ohm).
Pre-requisites: Enable the pigpio Daemon
Open your Pi's terminal and enable the daemon so it starts on boot:
sudo apt update
sudo apt install pigpio python3-pigpio
sudo systemctl enable pigpiod
sudo systemctl start pigpiod
Complete Python Implementation
Save the following code as servo_pwm.py. This script includes explicit pin definitions, hardware factory enforcement, and robust error handling.
import sys
import time
from gpiozero import Servo
from gpiozero.pins.pigpio import PiGPIOFactory
from gpiozero.exc import BadPinFactory, GPIODeviceError
# --- PIN DEFINITIONS ---
# GPIO 18 (Physical Pin 12) is the primary Hardware PWM0 channel
SERVO_PIN = 18
def main():
servo = None
try:
# Force hardware PWM via the pigpio daemon
factory = PiGPIOFactory()
# Initialize servo with custom pulse widths for MG996R
# Standard servos usually expect 1ms to 2ms, but MG996R can handle 0.5ms to 2.5ms
servo = Servo(
SERVO_PIN,
pin_factory=factory,
min_pulse_width=0.5/1000,
max_pulse_width=2.5/1000
)
print(f"Hardware PWM active on GPIO {SERVO_PIN}. Sweeping...")
print("Press Ctrl+C to exit safely.")
while True:
servo.min()
time.sleep(1.5)
servo.mid()
time.sleep(1.5)
servo.max()
time.sleep(1.5)
except BadPinFactory as e:
print(f"[FATAL] Pin Factory Error: {e}")
print("Fix: Ensure pigpiod is running (sudo systemctl start pigpiod)")
sys.exit(1)
except GPIODeviceError as e:
print(f"[FATAL] GPIO Device Error: {e}")
print("Fix: Check if GPIO 18 is physically damaged or shorted.")
sys.exit(1)
except KeyboardInterrupt:
print("\n[INFO] Interrupt received. Stopping PWM signal.")
except Exception as e:
print(f"[ERROR] Unexpected failure: {e}")
sys.exit(1)
finally:
# Cleanup: Detach stops the pulse train, preventing servo buzz on exit
if servo is not None:
servo.detach()
servo.close()
print("[INFO] Servo detached and GPIO resources released.")
if __name__ == "__main__":
main()
Debugging: First Three Things to Check When PWM Fails
When working with gpiozero and hardware PWM, failures usually stem from daemon states or pin numbering confusion. If your servo is dead or throwing exceptions, check these three things in order.
1. The Daemon Connection Error
Exact Error String: ConnectionError: Can't connect to pigpio at localhost(8888)
Cause: The pigpiod background service is not running, or it crashed due to an I2C/SPI conflict. Without it, the PiGPIOFactory cannot access the DMA hardware registers.
Fix: Run sudo systemctl status pigpiod. If it is dead, restart it with sudo systemctl restart pigpiod. If it fails to start, check sudo raspi-config and ensure I2C and Serial interfaces are disabled if you aren't using them, as they can sometimes hog DMA channels on older Pi OS versions.
2. The Pin Numbering Trap
Exact Error String: ValueError: Pin 12 is not a valid GPIO pin
Cause: You passed the physical pin number (12) instead of the BCM GPIO number (18) into the code, or vice versa. gpiozero defaults to BCM numbering.
Fix: Verify your code uses SERVO_PIN = 18. If you absolutely must use physical board numbering, you have to initialize the factory with factory = PiGPIOFactory(pin_factory='gpio') and change your pin definition, but sticking to BCM is the industry standard for Pi development.
3. The Audio DMA Conflict
Exact Error String: OSError: [Errno 12] Cannot allocate memory (often seen in the pigpiod logs or as a silent failure where the servo simply never moves).
Cause: The Raspberry Pi's onboard analog audio jack uses the exact same hardware PWM/DMA channel (PWM0) that GPIO 18 uses. If the audio driver claims the DMA channel first, hardware PWM is blocked.
Fix: Edit your /boot/firmware/config.txt (or /boot/config.txt on older OS) and add or uncomment the line dtparam=audio=off. Reboot the Pi. This frees the DMA channel for your servo. For a deeper dive into Pi peripheral conflicts, refer to the official Raspberry Pi GPIO documentation.
Extending and Simplifying the Build
Depending on your project's end goal, you may need to scale this setup up for robotics, or dumb it down for a simple indicator light.
How to Simplify (LED Dimming)
If you are just dimming an LED or driving a MOSFET for a heating pad, you do not need the pigpio daemon or hardware PWM. Human eyes cannot perceive the 60Hz jitter introduced by the Linux kernel.
The Simplified Code: Drop the PiGPIOFactory entirely. Just use from gpiozero import PWMLED and assign it to any GPIO pin (e.g., GPIO 17). It will use software PWM out of the box with zero daemon configuration.
How to Extend (Multi-Axis Robotics)
The Pi only has one primary hardware PWM channel (PWM0 on GPIO 18) that is completely safe and easy to use. While GPIO 12, 13, and 19 share PWM1, multiplexing them often causes phase interference. If you need to drive 4, 8, or 16 servos for a robotic arm or hexapod, do not try to bit-bang software PWM on multiple pins.
The Extension Pick: Buy a PCA9685 16-Channel I2C PWM Driver board (approx. $6 on Amazon/AliExpress). This chip connects to the Pi's I2C bus (GPIO 2/SDA and GPIO 3/SCL) and generates 16 independent, hardware-timed PWM signals. You control it using the adafruit-circuitpython-pca9685 library, completely bypassing the Pi's internal DMA limitations.
By anchoring your precision loads to GPIO 18 via pigpio, or offloading to an I2C driver for scale, you eliminate the single biggest point of failure in Pi-based embedded motor control: OS-level timing jitter.






