Continuous Rotation Servos with Python and the micro:bit

Continuous Rotation Servos with Python and the micro:bit

November 1, 2019 by Todd Beales

Welcome to our first edition of the micro:bit peripherals in Python series. This is a new series designed to educate micro:bit users on different peripherals. We will explain the peripheral and its uses, describe how to use it with the micro:bit, and provide example Python code to operate it. We hope to make your adventures with Python and the micro:bit just a little bit more enjoyable.

Check out the video demo here:

This first installment is all about continuous rotation servo motors. There are two primary types of servo motors that can be used with the micro:bit: continuous rotation servos and positional rotation servos. We are going to cover positional rotation servos in our next article. For now, continuous rotation servos, you guessed it, can rotate continuously in either direction. These tiny motors can run on small DC power sources. They can provide all kinds of functionality from turning wheels to running pulleys and rotating objects.

All the examples provided below will be done using the FEETECH FS90R Continuous Rotation Servo but will work with nearly any standard RC continuous rotation servo.

Servo Input lines

Continuous rotation servos are nearly all identical. They operate under some basic servo principles. Servos generally accept 3 input lines: power, ground, and signal.

The power line can accept a DC power source. You will want to check what voltage your peripheral is rated for before applying power. The FEETECH FS90R was designed to take either 5V or 3V power sources. The power line should be connected directly to the DC power source.

The ground line must be connected to the micro:bit ground. It is important that the two ground lines are connected so that the servo has a reference to use for the Signal line.

The signal line is connected to one of the Input / Output attachment points on the micro:bit. This signal line is where the magic happens when controlling a servo. This signal controls whether the servo is rotating, how fast it is rotating, and which direction it is turning.

The Signal

To operate a servo we must send it a 50 Hertz (Hz) pulsed signal. This is a standard for nearly all DC servo motors. A pulsed signal looks like this:

50 Hz means one pulse happens 50 times every second. If you break it down another way, one pulse is sent every 1/50th of a second which equals 20 milliseconds (ms for short).

Continuous servo motors also operate with the following rules.

  1. If the pulse is high or “ON” for 1.0 ms during those 20 ms then the Servo will run at 100% speed in the clockwise direction
  2. If the pulse is high or “ON” for 1.5 ms during those 20 ms then the Servo is considered stopped
  3. If the pulse is high or “ON” for 2.0 ms during those 20 ms then the Servo will run at 100% speed in the counterclockwise direction

You can also set anything in between to get less than 100% speed. For example: setting the pulse high for 1.2 ms will run the servo at 60% speed in the clockwise direction. See the table below for more values.

Pulse High TimePercent PowerDirection
1.0 ms100%Clockwise
1.1 ms80%Clockwise
1.2 ms60%Clockwise
1.3 ms40 %Clockwise
1.4 ms20%Clockwise
1.5 ms0%Stopped
1.6 ms20%Counterclockwise
1.7 ms40%Counterclockwise
1.8 ms60%Counterclockwise
1.9 ms80%Counterclockwise
2.0 ms100%Counterclockwise

Now let’s see how that 1.2 ms setting would look in pulse form.

How the Micro:bit Output works

In order to send this pulsed signal, we need to configure the micro:bit properly. Luckily for us, the micro:bit can send a Pulse-Width Modulation (PWM) signal. This signal is exactly what we want. So how do we configure it to work?

First, we need to set the length of one pulse to be 20 milliseconds. We do this by using the set_analog_period function on a pin. This function takes an input of milliseconds - PERFECT!! We would write the following in Python code:

pin0.set_analog_period(20)

Halfway there. The next step is to set the pulse to be on for 1.2 milliseconds. We do this by writing data out on the pin with the write_analog function on a pin. This function takes an input in the range of 0 to 1023. This sets the “duty cycle” of the periodic pulse. For example:

0 = the line is high or “ON” 0% of the time

511 = the line is high 50% of the time

1023 = the line is high 100% of the time

To use this function for our 1.2ms pulse example, we are going to have to convert 1.2 ms to a percentage of time the line is high. Since our period is 20ms, we can just use a ratio:

1.2 ms / 20 ms = 6%

Now we multiply the maximum input value (1023) by the percent of time the line needs to be high (6%) and we have our input value for the write_analog function:

Input value = 1023 * 6% = 61.38

So now we could write:

pin0.write_analog(61)

We could also write the following and let the micro:bit take care of this calculation for us:

pin0.write_analog(1023 * 1.2 / 20)

To recap. With just two short lines of code you can get your continuous servo motor running at 60% speed in the clockwise direction:

# Run servo clockwise at 60% speed
pin0.set_analog_period(20)  # Send pulses at 50 Hz
pin0.write_analog(1023 * 1.2 / 20)  # Each pulse is 1.2 ms

How to Run the Examples:

When running any of the code samples below you should:

  1. Connect the servo as outlined in one of the diagrams below.
  2. Type one of the code examples below into: https://makebit.firialabs.com (opens in a new tab).
  3. Run the example (do not press any buttons on the micro:bit yet).
    • If your servo is turning when it should be stopped, you may need to adjust it.
      • For the FS90R, rotate the screw on the bottom of the servo with a screwdriver until it comes to a complete stop (and it stops making any noise).

If you haven't tried CodeSpace with the micro:bit, you're missing out on a FANTASTIC coding experience! Give it a try!

Setting Up the Device

Shown below are two possible wiring options:

  1. Connect the micro:bit to USB / battery power and the servo to the micro:bit.

NOTE: Using the micro:bit to power the servo directly is simple, but beware that some servos require more power than the USB can deliver reliably in this way. Connecting an external battery pack to the micro:bit’s battery connector is recommended.

  1. Power the servo from its own 3 - 6V power supply, but connect all grounds together. For large, powerful servos this is required due to the power needs of the servo. Note: In this configuration the battery pack shown is powering the servo, but not the micro:bit. Be very careful with the battery (+/-) polarity, or you could damage your micro:bit!

Python Code Examples:

Code Example 1: Bare Minimum Code

from microbit import *
 
pin0.set_analog_period(20)
 
def main():
    pin0.write)analog(1023 * 1.0 / 20)  # full speed clockwise
    sleep(2000)
    pin0.write)analog(1023 * 2.0 / 20)  # full speed counterclockwise
    sleep(2000)
    pin0.write)analog(1023 * 1.5 / 20)  # stopped

Code Example 2: An Example with Functions

from microbit import *
 
# 2.0 ms on = 100% speed counterclockwise
# 1.5 ms on = Stopped
# 1.0 ms on = 100% clockwise
 
def set_servo_pulse(pin, ms_on)
    # The full-scale analog value for the Microbit is 1023 which equals 20ms
    # To set the amount of "ON" time we will:
    # divide the ms_on by 20 to get a percentage of "ON" time
    # multiply the percentage by the full-scale value of 1023
    pin.write_analog(1023 * ms_on / 20)
 
def init_servo_pin(pin):
    # Set a 20 ms period (50 Hz pulse stream)
    pin.set_analog_period(20)
    # Set at the stopped position (1.5ms)
    set_servo_pulse(pin, 1.5)
 
def main()
    init_servo_pin(pin0)  
    set_servo_pulse(pin0, 1.0)   # full speed clockwise
    sleep(2000)
    set_servo_pulse(pin0, 2.0)   # full speed counterclockwise
    sleep(2000)
    set_servo_pulse(pin0, 1.5)   # stopped
 
main() 

Code Example 3: An Expert "Class" Example

from microbit import *
 
class ContinuousServo:
    def __init__(self, io_p_in=pin0):
        self.pin = io_pin
        # 20 ms = 50 Hz the RC Servo Standard
        self.pin.set_analog_period(20)
        self.stop()
 
    def set_ms_pulse(self, ms_on)
        self.pin.write_analog(1023 * ms_on / 20)
 
    def stop(self):
        # 1.5 ms is the stop value for a servo
        self.set_ms_pulse(1.5)
 
    def run_clockwise(self):
        # 1.0 ms is 100% speed clockwise for a servo
        self.set_ms_pulse(1.0)
 
    def run_counterclockwise(self):
         # 2.0 ms is 100% speed counterclockwise for a servo
        self.set_ms_pulse(2.0)
 
# The servo will default to pin 0
# Ex: to use pin 1 instead of pin 0
#   servo = ContinuousServo(pin1)
servo1 = ContinuousServo()
 
while True:
    sleep(20)
 
    if button_a_is_preassed() and button_b_is_pressed():
        # hold button_b to rotate the servo clockwise
            servo1.stop()
 
    if button_a_is_preassed() 
            servo1.run_counterclockwise       
 
    if button_b_is_preassed() 
            servo1.run_clockwise