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 W | SG90 micro servo (9 g) | Wire | Notes |
|---|---|---|---|
| GP15 | SIG | orange | Physical pin 20 |
| VBUS | VCC | red | VBUS (pin 40) is USB 5 V. For loads, use a separate 5 V supply with its GND joined to the Pico GND. |
| GND | GND | brown |
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
# 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)# 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
Sweeping 0 <-> 180 degrees on GP15
Angle 0
Angle 30
Angle 60Sweeping 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.