In this guide, you’ll learn how to control hobby servo motors with the Raspberry Pi Pico programmed with MicroPython. Servo motors can be controlled using PWM signals to move with precision to a certain angle. The most common models are the SG90 and the S0009.
New to the Raspberry Pi Pico? Read the following guide: Getting Started with Raspberry Pi Pico (and Pico W).
Table of Contents:
Throughout this tutorial, we’ll cover the following contents:
- Introducing Servo Motors
- Wiring a Servo Motor to the Raspberry Pi Pico
- Controlling a Servo Motor with PWM – MicroPython
- The servo.py MicroPython Module
- Controlling a Servo Motor with a Library
Prerequisites
Before continuing, make sure you follow the next prerequisites.
MicroPython Firmware
To follow this tutorial you need MicroPython firmware installed in your Raspberry Pi Pico board. You also need an IDE to write and upload the code to your board.
The recommended MicroPython IDE for the Raspberry Pi Pico is Thonny IDE. Follow the next tutorial to learn how to install Thonny IDE, flash MicroPython firmware, and upload code to the board.
Alternatively, if you like programming using VS Code, you can start with the following tutorial:
Parts Required
You’ll also need the following parts:
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
How to Control a Servo Motor?
You can position the servo’s shaft at various angles from 0 to 180º (depending on the model). Servos are controlled using a pulse width modulation (PWM) signal. The PWM signal sent to the motor will determine the shaft’s position.
To control the motor you can simply use the PWM capabilities of the Raspberry Pi Pico by sending a 50Hz signal with the appropriate pulse width.
Usually, the S0009 or SG90 servo motors (capable of 180º), the one we’re using in this tutorial, used in most microcontroller projects, has a pulse width range of around 550 to 2400 microseconds:
- Minimum Pulse Width (0º): around 550 microseconds
- Maximum Pulse Width (180º): around 2400 microseconds
For servo motors that only travel in the 90º range, these are usually the pulse width values:
- Minimum Pulse Width (0º): around 1000 microseconds
- Maximum Pulse Width (90º): around 200 microseconds
These values can slightly vary between different servo models. Try to search for your servo datasheet or test these values and then adjust accordingly.
In my case, I haven’t found the datasheet for my specific servo, but using the default pulse width values used in most Arduino libraries (550 to 2400 microseconds) works fine for the one we’re using.
Calculating the Duty Cycle
From those pulse width values, we can calculate the duty cycle to position the motor’s shaft at a certain angle. The duty cycle is the ratio of the pulse-width to the total period of the signal. We can use the following formula:
Duty cycle (%) = (pulse-width/period)x100
The period is the inverse of the frequency. Our frequency is 50Hz, which corresponds to 1/50 = 0.02 seconds (20000 microseconds).
So, to put the motor in 0º position, we need a 2.75% duty cycle:
Duty cycle = (550/20000)x100 = 2.75%
With the Raspberry Pi Pico, 100% duty cycle value is represented by a digital value of 65535, and 0% is represented by 0 (learn more about PWM with the Raspberry Pi Pico here). So, we can map the duty cycle values to the Raspberry Pi Pico 0-65535 range as follows:
Mapped value = Duty cycle x (maximum value/100)
So, the 0º position, would correspond to:
Mapped value = 2.75 x (65535/100) = 1802
Doing the same procedure for the other values, we get:
- Minimum Pulse Width (0º) corresponds to 1802
- Maximum Pulse Width (180º) corresponds to 7864
Connecting the Servo Motor to the Raspberry Pi Pico
Servo motors have three wires: power, ground, and signal. The power is usually red, the GND is black or brown, and the signal wire is usually yellow, orange, or white.
When using a small servo like the S0009 (or SG90), you can power it directly from the Raspberry Pi Pico power pin. However, for other models, you may need to apply an external power supply (check your servo datasheet).
Wire | Color | Raspberry Pi Pico |
Power/VCC | Red | 5V (VBUS) |
GND | Black, or brown | GND |
Signal | Yellow, orange, or white | GPIO 0 (or any other PWM Pin) |
To control the servo motor, you can connect the signal (data) pin to any GPIO that can produce PWM signals. All pins marked on the pinout with a light green color can produce PWM signals (which are basically all GPIOs). We’ll connect it to GPIO 0, but you can use any other GPIO as long as you specify the correct pin on the code.
Controlling the Servo Motor with PWM
The servo motor shaft’s position can be controlled by sending a PWM signal with a determined pulse width. We’ve seen previously that:
- Minimum Pulse Width (0º): around 550 microseconds
- Maximum Pulse Width (180º): around 2400 microseconds
The following code moves the servo motor to the 0º position, then to 90º and finally to 180º. Then, it repeats and moves again to the 0º, 90º, and so on. This is just a simple example for you to understand how to position the servo at specific angles.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-servo-motor-micropython/
from machine import Pin, PWM
from time import sleep
# Set up PWM Pin for servo control
servo_pin = machine.Pin(0)
servo = PWM(servo_pin)
# Set Duty Cycle for Different Angles
max_duty = 7864
min_duty = 1802
half_duty = int(max_duty/2)
#Set PWM frequency
frequency = 50
servo.freq (frequency)
try:
while True:
#Servo at 0 degrees
servo.duty_u16(min_duty)
sleep(2)
#Servo at 90 degrees
servo.duty_u16(half_duty)
sleep(2)
#Servo at 180 degrees
servo.duty_u16(max_duty)
sleep(2)
except KeyboardInterrupt:
print("Keyboard interrupt")
# Turn off PWM
servo.deinit()
How the Code Works
We start by including the required libraries, including the PWM class from the machine module to control the servo motor using PWM.
from machine import Pin, PWM
from time import sleep
We initialize PWM on GPIO 0 to control our servo. We call it servo.
# Set up PWM Pin for servo control
servo_pin = machine.Pin(0)
servo = PWM(servo_pin)
We define the minimum, maximum and half-duty cycles to control the servo motor. We calculated these values in the previous section.
# Set Duty Cycle for Different Angles
max_duty = 7864
min_duty = 1802
half_duty = int(max_duty/2)
Then, we set the PWM frequency to control the servo. As we’ve seen previously, we need a 50Hz frequency. We use the freq() method on the servo object to set the frequency.
#Set PWM frequency
frequency = 50
servo.freq (frequency)
In the while loop, we use the duty_u16() method to set the PWM duty cycle. We first put the servo at the 0º angle:
#Servo at 0 degrees
servo.duty_u16(min_duty)
sleep(2)
Then, at 90º and finally at 180º.
#Servo at 90 degrees
servo.duty_u16(half_duty)
sleep(2)
#Servo at 180 degrees
servo.duty_u16(max_duty)
sleep(2)
When the program is stopped by the user, we turn off PWM using the deinit() method.
# Turn off PWM
servo.deinit()
Testing the Code
Run the previous code on your Raspberry Pi Pico. If you’re using Thonny IDE, click on the green Run icon.
The motor should go to the 0º position and stay there for two seconds. Then, it will go to 90º for another two seconds and finally to the 180º position for two seconds. This will be repeated indefinitely until you stop the program.
If you hear a weird buzz sound at the 0 or 180º angles, it means that you’re trying to control the servo outside its pulse width limits. In that case, you should try to adjust the minimum and/or maximum values until it stops making noise. Those values work well for my specific servo, but some adjustments might need to be done for other models.
Controlling the Servo Motor using a Library
Having to calculate the specific duty cycle values for a specific angle might be a tedious task and might cause errors and typos when writing the code. To make things easier we can create a function, or even better, use a library.
Uploading the servo.py library
We’re using the library found at the following link:
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-servo-motor-micropython/
from machine import Pin, PWM
class Servo:
__servo_pwm_freq = 50
__min_u16_duty = 1802
__max_u16_duty = 7864
min_angle = 0
max_angle = 180
current_angle = 0.001
def __init__(self, pin):
self.__initialise(pin)
def update_settings(self, servo_pwm_freq, min_u16_duty, max_u16_duty, min_angle, max_angle, pin):
self.__servo_pwm_freq = servo_pwm_freq
self.__min_u16_duty = min_u16_duty
self.__max_u16_duty = max_u16_duty
self.min_angle = min_angle
self.max_angle = max_angle
self.__initialise(pin)
def move(self, angle):
# round to 2 decimal places, so we have a chance of reducing unwanted servo adjustments
angle = round(angle, 2)
# do we need to move?
if angle == self.current_angle:
return
self.current_angle = angle
# calculate the new duty cycle and move the motor
duty_u16 = self.__angle_to_u16_duty(angle)
self.__motor.duty_u16(duty_u16)
def stop(self):
self.__motor.deinit()
def get_current_angle(self):
return self.current_angle
def __angle_to_u16_duty(self, angle):
return int((angle - self.min_angle) * self.__angle_conversion_factor) + self.__min_u16_duty
def __initialise(self, pin):
self.current_angle = -0.001
self.__angle_conversion_factor = (self.__max_u16_duty - self.__min_u16_duty) / (self.max_angle - self.min_angle)
self.__motor = PWM(Pin(pin))
self.__motor.freq(self.__servo_pwm_freq)
This library assumes your servo motor rotates within the 0 to 180º range and that the minimum and maximum duty cycle values are the ones we calculated previously (maximum:7864, and minimum:1802). If you have different values, you should adjust them on the library file right on the following lines.
from machine import Pin, PWM
class Servo:
__servo_pwm_freq = 50
__min_u16_duty = 1802
__max_u16_duty = 7864
min_angle = 0
max_angle = 180
current_angle = 0.001
Save the library file on your Raspberry Pi Pico with the name servo.py. You can follow the next steps:
- Download the library code. You can find the servo.py file here.
- Copy the code to a file on Thonny IDE;
- Go to File > Save as… and select Raspberry Pi Pico;
- Save the file with the name servo.py (don’t change the name)
Now, that you’ve uploaded the library to the Raspberry Pi Pico, we can use the library functionalities in our code.
Controlling the Servo Motor – MicroPython
The following code does the same thing as the previous example but uses the library methods that are more intuitive to use. Additionally, you can use the specific angle value to where you want to move the motor.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-servo-motor-micropython/
from servo import Servo
from time import sleep
# Create a Servo object on pin 0
servo=Servo(pin=0)
try:
while True:
#Servo at 0 degrees
servo.move(0)
sleep(2)
#Servo at 90 degrees
servo.move(90)
sleep(2)
#Servo at 180 degrees
servo.move(180)
sleep(2)
except KeyboardInterrupt:
print("Keyboard interrupt")
# Turn off PWM
servo.stop()
How the Code Works
You start by importing the Servo class from the servo library.
from servo import Servo
Then, you create a Servo object on GPIO 0. If you’re using a different GPIO, you just need to modify the following line.
# Create a Servo object on pin 0
servo=Servo(pin=0)
Then, in the loop, we just need to use the move() method on the servo object and pass as argument the position to where we want to move the motor in degrees. For example:
#Servo at 0 degrees
servo.move(0)
sleep(2)
#Servo at 90 degrees
servo.move(90)
sleep(2)
#Servo at 180 degrees
servo.move(180)
sleep(2)
Adjust the code and try with different angle values.
Finally, when the user interrupts the code, we stop the motor using the stop() method. This will stop PWM on the pin connected to the motor.
except KeyboardInterrupt:
print("Keyboard interrupt")
# Turn off PWM
servo.stop()
Testing the Code
Upload or run the previous code to the Raspberry Pi Pico. The servo motor should behave exactly like in the previous example. But this time, it is more intuitive to move the servo to a certain position in our code.
If you want to run this code on your Raspberry Pi Pico when it is not connected to your computer, you must save the code on a file called main.py and upload it to your board (File > Save as... > Raspberry Pi Pico).
Wrapping Up
In this guide, you learned how to control a hobby servo motor with the Raspberry Pi Pico programmed with MicroPython. We’ve shown you how to control it by simply using PWM signals with the right pulse length and using a library.
If you want to interface other motors with the Raspberry Pi Pico, check the tutorials below:
- Raspberry Pi Pico: Control DC Motor with L298N Motor Driver (MicroPython)
- Raspberry Pi Pico: Control a Stepper Motor (MicroPython)
We hope you’ve found this tutorial useful. If you would like to learn more about the Raspberry Pi Pico, make sure you check all our guides:
Thanks for reading.