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):

GPIO - Raspberry Pi Documentation

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.

Duty Cycle Examples.png

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:

how to use servo 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!