Low Level Servo Control

Low Level Servo Setup

The low level control is rarely recommended in comparison to the adafruit_motor library shown previously. It is shown here as it may take less memory than the higher level code and may help you understand the underlying code.

First you need to import a few modules to access the PWM output capabilities of CircuitPython:

Download: file
import board
import pulseio

Now you can create a PWM signal output that will drive the position of the servo:

Download: file
servo = pulseio.PWMOut(board.D5, frequency=50)

There are a couple important things happening with the line above.  This is an initializer which is creating an instance of the PWMOut class and part of that initialization process is specifying these two values:

  • The pin that will be the PWM output.  In this case it's pin D5 on the development board.  If you're using a different output be sure to specify the right pin name here (and make sure the pin supports PWM output as mentioned on the previous page!).
  • The frequency of the PWM signal.  This is an optional value that we're specifying as a keyword argument to tell the PWM output that we want a signal with a frequency of 50 hertz.  The frequency is how many times per second the servo signal changes and for most servos they expect a ~50 hz signal.

Position Control with PWM

Now we can control the position of the servo by changing the duty cycle of the PWM signal.  If you aren't familiar with PWM signals and duty cycle be sure you've read the CircuitPython analog I/O guide PWM page for more background.

With a servo motor it will change its position depending on the pulse length of the signal being sent to it.  Specifically a 1 millisecond high pulse tells the servo to move all the way to one extreme (left or right) and a value of 2 milliseconds will move all the way to the opposite extreme.  Any value in-between 1 to 2 milliseconds will move the servo to an appropriate in-between position.  For example a value of 1.5 milliseconds will move the servo to its center or middle position.

Note for continuous rotation servos they don't have a concept of moving to a specific position.  Instead they will rotate freely in a circle and change their speed depending on the pulse length sent to them.  A 1 millisecond pulse moves as quickly as possible in one direction, 1.5 millisecond pulse stops movement, and 2 millisecond pulse moves as quickly as possible in the opposite direction.

How do we control the amount of time a PWM signal is high vs. low?  As mentioned in the CircuitPython analog I/O PWM page we do so by changing the duty cycle of the PWM signal.  However before you set the duty cycle you'll need to convert from a desired millisecond value to a PWM duty cycle value.  Remember with CircuitPython duty cycle values are specified with a 16-bit unsigned value, i.e. something from 0 to 65535.  We can actually create a little Python function to help with doing this mathematical conversion:

Download: file
def servo_duty_cycle(pulse_ms, frequency=50):
    period_ms = 1.0 / frequency * 1000.0
    duty_cycle = int(pulse_ms / (period_ms / 65535.0))
    return duty_cycle

You can pass this function a pulse width in milliseconds and it will convert it into the appropriate duty cycle value to control the servo.  The function assumes a frequency of 50 hz by default too, but you can change it by specifying the frequency keyword too (however you don't typically need or want to change this unless you also changed the frequency of the PWM output!).

For example see what output value you get with pulse widths of 1.0 and 2.0 milliseconds:

Download: file
servo_duty_cycle(1.0)
servo_duty_cycle(2.0)

Now let's move the servo!  Try changing the servo PWM output duty cycle to a value that corresponds to a 1.0 millisecond long pulse.  You should see the servo move to one extreme:

Download: file
servo.duty_cycle = servo_duty_cycle(1.0)

If you don't see your servo move be sure the power supply is turned on and connected to the servo.  Double check all of your wiring connections and that the output for the signal supports PWM signals too!

Now move to the opposite extreme with a 2.0 millisecond long pulse:

Download: file
servo.duty_cycle = servo_duty_cycle(2.0)

You should see the servo move about 180 degrees to the opposite position.

Finally move to the center position with a 1.5 millisecond pulse:

Download: file
servo.duty_cycle = servo_duty_cycle(1.5)

Experiment with setting any value in-between 1.0 and 2.0 to see how the servo reacts.

Sweep Example

Here's a complete example that will sweep the servo between both extremes (1.0 and 2.0 millisecond long pulses) repeatedly.  Save this as a code.py file on your board (and be ready when you click save as the servo might instantly start moving!):

Download: file
import time

import board
import pulseio


# Initialize PWM output for the servo (on pin D5):
servo = pulseio.PWMOut(board.D5, frequency=50)

# Create a function to simplify setting PWM duty cycle for the servo:
def servo_duty_cycle(pulse_ms, frequency=50):
    period_ms = 1.0 / frequency * 1000.0
    duty_cycle = int(pulse_ms / (period_ms / 65535.0))
    return duty_cycle

# Main loop will run forever moving between 1.0 and 2.0 mS long pulses:
while True:
    servo.duty_cycle = servo_duty_cycle(1.0)
    time.sleep(1.0)
    servo.duty_cycle = servo_duty_cycle(2.0)
    time.sleep(1.0)

That's all there is to controlling a servo directly with CircuitPython with a PWM output.

This guide was first published on Jan 15, 2018. It was last updated on Jan 15, 2018. This page (Low Level Servo Control) was last updated on Aug 07, 2019.