Hardware GPIO 12 → Physical Pin 32
PWM Channel PWM0_CHAN0 → chip=2, channel=0
Frequency 50 Hz (standard servo)
01 Wire the Servo
Servo WireConnect to
Signal (orange / yellow)GPIO 12 — Physical Pin 32
Power (red)5V — Pin 2 or 4 (use external supply for metal-gear servos)
Ground (brown / black)GND — Pin 6 or 9
Important
Share a common ground between the Pi and any external servo power supply. Without a shared ground, the PWM signal has no reference voltage and the servo will not respond.
02 Edit config.txt

Open the boot configuration file:

sudo nano /boot/firmware/config.txt

Add this line to the [all] section (or at the bottom of the file):

dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4

Save and reboot:

sudo reboot

Verify the overlay loaded

After rebooting, confirm the PWM chip is visible:

ls /sys/class/pwm/
# Expected output: pwmchip0  pwmchip2

pinctrl get 12
# Expected: 12: a4 pd | lo // GPIO12 = PWM0_CHAN0
Pi 5 Note
lsmod | grep pwm returns nothing on the Pi 5 — this is normal. The RP1 southbridge uses a device-tree driver that doesn't appear in lsmod. Use ls /sys/class/pwm/ to verify instead.
03 Add User to gpio Group
sudo usermod -aG gpio $USER

Log out and back in (or reboot) for the group change to take effect, then verify:

groups
# output should include: gpio
04 Set Up PWM Permissions

The Pi 5 RP1 driver doesn't reliably fire udev events on channel export, so a systemd oneshot service is the most robust approach.

Create the helper script

sudo nano /usr/local/bin/pwm-permissions.sh

Paste the following contents:

#!/bin/bash

chown -R root:gpio /sys/class/pwm
chmod -R 770 /sys/class/pwm

# Export channel 0 on pwmchip2 (GPIO 12)
if [ ! -d /sys/class/pwm/pwmchip2/pwm0 ]; then
    echo 0 > /sys/class/pwm/pwmchip2/export
    sleep 0.1
fi

chown -R root:gpio /sys/class/pwm
chmod -R 770 /sys/class/pwm

Make it executable:

sudo chmod +x /usr/local/bin/pwm-permissions.sh

Create the systemd service

sudo nano /etc/systemd/system/pwm-permissions.service

Paste the following:

[Unit]
Description=Hardware PWM permissions for gpio group
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/pwm-permissions.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable pwm-permissions.service
sudo systemctl start pwm-permissions.service
05 Install rpi-hardware-pwm
pip install rpi-hardware-pwm --break-system-packages
Why --break-system-packages?
Trixie (Debian 13) uses an externally-managed Python environment by default. The --break-system-packages flag tells pip to install into the system site-packages anyway. This is the simplest approach for our single-purpose Pi 5 setups.
06 Control the Servo
Critical Pi 5 Parameter
Always pass chip=2 when creating the HardwarePWM object. The RP1 controller registers as pwmchip2, not pwmchip0 as on older Pi models.
from rpi_hardware_pwm import HardwarePWM
import time

# Pi 5: chip=2 always. GPIO 12 = channel 0.
pwm = HardwarePWM(pwm_channel=0, hz=50, chip=2)
pwm.start(7.5)  # Start at centre (90°)

def set_angle(degrees):
    """Map 0-180 degrees to duty cycle (2.5%-12.5%)"""
    duty = 2.5 + (degrees / 180.0) * 10.0
    pwm.change_duty_cycle(duty)

set_angle(0)    # Full left
time.sleep(1)

set_angle(90)   # Centre
time.sleep(1)

set_angle(180)  # Full right
time.sleep(1)

set_angle(90)   # Return to centre
time.sleep(0.5)

pwm.stop()

Understanding the Duty Cycle Math

AngleDuty CyclePulse WidthServo Position
2.5%0.5 msFull left
90°7.5%1.5 msCentre
180°12.5%2.5 msFull right

At 50 Hz the period is 20 ms. A 2.5% duty cycle means the signal is HIGH for 0.5 ms out of every 20 ms cycle — that's what tells the servo to go to 0°.

Troubleshooting
SymptomFix
PermissionError on export Re-run the systemd service; confirm groups includes gpio; log out and back in
FileNotFoundError on pwmchip2 Overlay didn't load — check config.txt spelling, reboot, then run ls /sys/class/pwm/
Servo jitters / vibrates Use a separate 5V supply for servo power; share GND with Pi. Never power servos from the Pi's GPIO header.
Servo doesn't reach full range Adjust the 2.5 / 12.5 duty cycle limits in set_angle() — exact range varies by servo model
chip=0 works but chip=2 doesn't Your overlay may have loaded on a different chip number. Run ls /sys/class/pwm/ and check which pwmchip directories exist.