Raspberry Pi Servo Motor

Hobby servo motors are positioned to a specific angle by the width of a repeating electrical pulse, and gpiozero's Servo class lets you set that angle directly with simple method calls or a value between -1 and 1.

Position, Not Speed: How a Servo Differs from a DC Motor

A plain DC motor just spins in whichever direction current flows through it, at a speed roughly proportional to voltage - it has no idea what angle it is at. A hobby servo is a small, self-contained unit: inside its case is a DC motor, a reduction gearbox, a potentiometer that reports the output shaft's current angle, and a tiny control circuit that compares the requested angle to the actual angle and drives the motor until they match. The result is a shaft that moves to, and holds, an absolute position rather than spinning continuously.

Position Is Encoded in Pulse Width

A servo's control wire expects a pulse roughly every 20 milliseconds (about 50 times a second). What matters is not whether the pulse is on or off overall, but exactly how long the high portion of each pulse lasts. By convention a pulse of about 1 millisecond drives the shaft to one extreme, about 1.5 milliseconds centres it, and about 2 milliseconds drives it to the other extreme - with everything in between mapping smoothly to an angle across the servo's usable range, commonly around 180 degrees.

  • Signal wire (often orange or yellow) connects to a GPIO pin.
  • Power wire (usually red) needs 4.5 to 6V - for anything beyond a single small servo, use an external supply rather than the Pi's own 5V pin.
  • Ground wire (usually brown or black) must be connected to a Pi GND pin, even when the servo is powered externally, so both circuits share a reference voltage.

Example

from gpiozero import Servo
from time import sleep

servo = Servo(17)

servo.min()   # rotate to one end (~1ms pulse)
sleep(1)
servo.mid()   # move to centre (~1.5ms pulse)
sleep(1)
servo.max()   # rotate to the other end (~2ms pulse)
sleep(1)

Example

from gpiozero import Servo
from time import sleep

# Correct for a servo whose usable pulse range is narrower than gpiozero's default
servo = Servo(17, min_pulse_width=1/1000, max_pulse_width=2/1000)

for position in [-1, -0.5, 0, 0.5, 1]:
    servo.value = position
    sleep(0.8)
Note: Powering a servo, especially one under load, directly from the Raspberry Pi's 5V pin can pull the supply voltage down far enough to reboot or crash the Pi. Use a separate 5V/6V supply for the servo's power wire and simply share ground with the Pi.
gpiozero valueApprox. pulse widthApprox. shaft angle
-1 (servo.min())~1.0 ms0 degrees
0 (servo.mid())~1.5 ms90 degrees
1 (servo.max())~2.0 ms180 degrees
Note: Not every servo agrees on exactly 1ms-2ms for its full range - some need a slightly wider or narrower pulse to reach their true end stops. Passing min_pulse_width and max_pulse_width to Servo() lets you calibrate gpiozero's -1 to 1 scale to match the specific servo you have.

Continuous-Rotation Servos Are a Different Beast

Some servos sold in the same style of case are modified internally to remove the position feedback entirely, turning them into a geared motor that spins continuously. With those, gpiozero's Servo class still works and still reads pulse widths, but the value now maps to spin speed and direction rather than an angle - value 0 stops it, positive values spin one way, negative values spin the other. Everything above about angles assumes a standard positional servo.

Exercise: Raspberry Pi PWM

What does PWM stand for and fundamentally do?