Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

SG90 servo with Raspberry Pi Pico W — MicroPython PWM code

Drive an SG90 servo from a Pico W on GP15 with MicroPython hardware PWM at 50 Hz, using duty_ns for precise pulse widths.

MicroPythonVerified — micropython (mpy-cross v1.29.0), 3 Nov 2026Compile-only

Community draft — report a mistake

The Pico W has hardware PWM on every GPIO, so a servo needs no library at all in MicroPython: set the frequency to 50 Hz and the pulse width with duty_ns().

Wire the signal to GP15 and power from VBUS for a quick test; give the servo its own 5 V supply once it moves anything.

Wiring

Raspberry Pi Pico WOnly the pins used are shown · 3.3 V logicSG90 micro servo (9 g)GNDGP15VBUSSIGVCCGND
Every connection
Raspberry Pi Pico WSG90 micro servo (9 g)WireNotes
GP15SIGorangePhysical pin 20
VBUSVCCredVBUS (pin 40) is USB 5 V. For loads, use a separate 5 V supply with its GND joined to the Pico GND.
GNDGNDbrown

4.8–6 V (several hundred mA while moving or stalled) part, 3.3 V board. The signal input is happy with 3.3 V logic, so ESP32 and Pico drive it directly. On ESP32 use the ESP32Servo library — the standard Servo library is for AVR and RP2040.

Board quirks

  • duty_ns() sets the pulse width directly in nanoseconds — clearer than duty fractions for servos.
  • Never power a servo from 3V3: it is too low a voltage and the current spikes would reset the Pico.
  • Any GPIO can do PWM on the RP2040; GP15 sits next to a GND pin, which keeps the servo cable tidy.

Code

main.pyMicroPython
# SG90 micro servo on a Raspberry Pi Pico W (MicroPython, hardware PWM).
# Signal (orange) -> GP15 (pin 20), red -> VBUS (pin 40, 5 V from USB), brown -> GND.
from machine import Pin, PWM
import time

servo = PWM(Pin(15))
servo.freq(50)  # one 20 ms frame per pulse


def angle(deg):
    """0-180 degrees -> 500-2400 us pulse (SG90 range)."""
    deg = max(0, min(180, deg))
    servo.duty_ns(int((500 + deg * 1900 / 180) * 1000))


print("Sweeping 0 <-> 180 degrees on GP15")
while True:
    for deg in range(0, 181, 30):
        angle(deg)
        print("Angle", deg)
        time.sleep(0.5)
    for deg in range(180, -1, -30):
        angle(deg)
        print("Angle", deg)
        time.sleep(0.5)

Expected serial output

REPL / Thonny shellOutput
Sweeping 0 <-> 180 degrees on GP15
Angle 0
Angle 30
Angle 60

Common errors

Servo buzzes at the ends of travel

Your servo’s range is narrower than 500–2400 µs. Change the 500 and 1900 constants to 600 and 1700.

Pico restarts when the servo moves

USB can’t supply the current spike. Use a separate 5 V supply with a shared ground.

↑↓ to move · Enter to open