Controlling Servos Using Raspberry Pi & Python
Jan 24 2021 · 5 min read
Servos have been around for a long time and used in various applications, ranging from toys to robotics. Do you know how does a servo work? Let's learn how to control one using Raspberry Pi and Python!
What You Will Need
- 1 Raspberry Pi
- 1 Servo (I’m using SG90)
- Some jumper wires
Build the Circuit
This is going to be a simple demonstration, so the wiring is pretty straightforward, here’s the diagram to help you out:
From the diagram above:
- Connect the servo’s Vin to the Pi’s 5V
- Connect the servo’s GND to the Pi’s GND
- Connect the servo’s SIGNAL to the Pi’s to GPIO13
If you don’t know what a GPIO13 is, here’s a diagram of Raspberry Pi 3B’s header pins (also check the official documentation out):
In our case, GPIO13 is also labelled as number 33. It’s the fourth last pin on the left side.
After connecting all of the wires, it’s time to get started in the coding!
How Servos Work
Sorry I lied, before we get coding, we have to understand how a servo works under the hood.
Servo motors are usually made in a closed-loop system that receives a single input to determine where and the motor should move. A closed-loop system means that the system will tune itself to get the right output for the given input. Here’s a diagram on how a closed-loop servo works:
Note that the servo receives a constant feedback loop to correct its position with respect to the given input.
Now how do we send this input to the servo? We can’t simply give them a number, right? This is where we use Pulse-Width Modulation (PWM). PWM, as the name suggests, adjusts the widths of the pulses being sent over a period T. So for example, a signal with 50% duty cycle will turn on and off at every half period.
We can use PWM as an input signal for our servos, thus indicating the desired output that we want to achieve. For the servo, here’s PWM signal required to send to the motor:
From the diagram above, we can that the signal should be in 50Hz and the range needed to be sent to the servo should be within 1-2ms; where 1ms means the servo will go all the way to the left (0 degrees), and 2ms will go all the way to the right (180 degrees). Translating the duty cycle from milliseconds to percentages will give us a range of 5%-10%.
Theoretically, that’s the number that we need, but do try calibrating the numbers yourself!
Now on to Coding!
Now that we know what we need to do in the Raspberry Pi, we can get straight into coding! In this example I will be controlling the servo using Python 3.7, so make sure you have Python installed. I will write out the code and explain it by segments.
import RPi.GPIO as GPIO
# duty cycle, calibrate if needed
MIN_DUTY = 5
MAX_DUTY = 10
servo_signal_pin = 13
def deg_to_duty(deg):
return (deg - 0) * (MAX_DUTY- MIN_DUTY) / 180 + MIN_DUTY
if __name__ == "__main__":
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(servo_signal_pin, GPIO.OUT)
# set pwm signal to 50Hz
servo = GPIO.pwm(servo_signal_pin, 50)
servo.start(0)
# loop from 0 to 180
for deg in range(181):
duty_cycle = deg_to_duty(deg)
servo.ChangeDutyCycle(duty_cycle)
# cleanup the gpio pins
GPIO.cleanup()
First things first, on the first line:
import RPi.GPIO as GPIO
We are using the library RPi_GPIO
from Raspberry Pi, it will handle our communication with the GPIO pins.
Next up, we have some variable declarations:
# duty cycle, calibrate if needed
MIN_DUTY = 5
MAX_DUTY = 10
servo_signal_pin = 13
Remember that the servo accepts a 5-10% duty cycle as the signal, you can adjust this if needed. Then, we declare GPIO13 as our servo signal pin.
def deg_to_duty(deg):
return (deg - 0) * (MAX_DUTY- MIN_DUTY) / 180 + MIN_DUTY
deg_to_duty
is a function that takes in a degree number as inputs and converts it into duty cycle, with respect to the MAX_DUTY
and MIN_DUTY
, this is much like how you would convert Celcius to Fahrenheit, or vice versa.
After that is all done, we come into Python’s main
function
if __name__ == "__main__":
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
In this section, we disable GPIO warnings if the pins aren’t flushed yet with GPIO.cleanup()
using GPIO.setwarnings(False)
. After that, we set the GPIO mode into BCM using GPIO.setmode(GPIO.BCM)
. By doing so, we are telling the Raspberry Pi that we will refer to the pins using “Broadcom SOC Channel” numbers. Remember that we can either refer the pin we are using with GPIO13
or 33
.
GPIO.setup(servo_signal_pin, GPIO.OUT)
# set pwm signal to 50Hz
servo = GPIO.pwm(servo_signal_pin, 50)
servo.start(0)
In this section, we set the GPIO13
pin as an output and set the PWM signal to 50Hz. We then start the servo by sending 0% duty cycle, essentially saying that the servo is not taking any commands just yet. We store a reference to the PWM signal in a variable called servo
, we can use this variable later.
# loop from 0 to 180
for deg in range(181):
duty_cycle = deg_to_duty(deg)
servo.ChangeDutyCycle(duty_cycle)
# cleanup the gpio pins
GPIO.cleanup()
Afterwards, we will go from 0 to 180 degrees using a for loop (remember that python’s range()
function is exclusive, so we set it to 181). Finally, we get the duty cycle value by translating the desired degrees using deg_to_duty(deg)
. After we have received the duty_cycle
value, we send the signal to the servo using servo.ChangeDutyCycle(duty_cycle)
.
After we finish going from 0 to 180 degrees, we exit the loop and clean up the GPIO pin using GPIO.cleanup()
And That’s it!
You’re done! Congratulations on finishing this project! Now you have a basic understanding of how servos are controlled using PWM signals and controlling a servo using Raspberry Pi and Python! Have fun continuing your project!