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.

pwmio is built into CircuitPython and therefore you do not need to install any libraries.

Python Library Install

You'll need to install the Adafruit_Blinka library that provides the CircuitPython support in Python. Since each platform is a little different, and Linux changes often, please visit the CircuitPython on Linux guide to get your computer ready!

Once that's done, from your command line run the following command to ensure you're running the latest version of Adafruit Blinka:

  • pip3 install --upgrade adafruit-blinka

If your default Python is version 3 you may need to run 'pip' instead. Just make sure you aren't trying to use CircuitPython on Python 2.x, it isn't supported!

Low Level Servo Setup

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

import board
import pwmio

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

servo = pwm.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:

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


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:

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:

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:

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 file on your board (and be ready when you click save as the servo might instantly start moving!):

# SPDX-FileCopyrightText: 2020 Kattni Rembor for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import pwmio

# Initialize PWM output for the servo (on pin D5):
servo = pwmio.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)
    servo.duty_cycle = servo_duty_cycle(2.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 Jun 13, 2024.

This page (Low Level Servo Control) was last updated on Jun 12, 2024.

Text editor powered by tinymce.