Circuit Playground Express and CircuitPython

We'll be using CircuitPython for this project. Are you new to using CircuitPython? No worries, there is a full getting started guide here.

Adafruit suggests using the Mu editor to edit your code and have an interactive REPL in CircuitPython. You can learn about Mu and its installation in this tutorial.

For the CircuitPython version the Circuit Playground Express was choosen for variety, although any of the M0 or M4 boards could be used.

It's advised that you use the Circuit Playground Express version of CircuitPython that includes the CRICKIT library.  See this part of the CRICKIT guide for details and instructions.

To start, servos are allocated and lists of them are made for later use.

Download: file
# Each servo corresponds to one of the legs
front_right = crickit.continuous_servo_1
front_left = crickit.continuous_servo_2
rear_right = crickit.continuous_servo_3
rear_left = crickit.continuous_servo_4

# Useful groups of legs
all_legs = [front_right, front_left, rear_right, rear_left]
front_legs = [front_right, front_left]
rear_legs = [rear_right, rear_left]
right_legs = [front_right, rear_right]
left_legs = [front_left, rear_left]

If you noticed when we mounted the motors, they weren't all oriented the same way. Look at the labels... on two you can see them, on the others you can't. The result of this is that to have all legs work to move forward, two motors have to run in the opposite direction to the others. We can make that adjustment in the code closest to the servos so that most of the code can ignore this detail. This mapping helps implement that by providing a multiplier (+/-1) for the speed of each motor. It allows higher level code to set a motor throttle with positive being forward without regard to the orientation of the servo.

Download: file
# The sign (+1/-1) for forward motion for each servo
direction_values = {front_right: +1, 
                    front_left: -1, 
                    rear_right: +1, 
                    rear_left: -1}

The final bit of support data is value of the PWM limits for each servo. This can be adjusted for your servos so that setting the throttle to 0 causes them to stop.

Download: file
pwm_ranges = {front_right: (500, 2400), 
              front_left: (500, 2400), 
              rear_right: (500, 2400), 
              rear_left: (500, 2400)}

We have a function to set the servo limits, and stop each of them.

Download: file
def init():
    for leg in all_legs:
        limits = pwm_ranges[leg]
        leg.set_pulse_width_range(min_pulse=limits[0], max_pulse=limits[1])
        leg.throttle = 0

The core of the movement code is in the forward, reverse, and stop functions.  All three take a single leg or a list of legs. The forward and reverse functions also take a speed.

Notice how the direction_values list is used, and also how reverse multiplies the adjusted speed by -1 so that you can ask it to reverse with a positive speed.

Download: file
def forward(servo_or_servos, speed):
    if type(servo_or_servos) == list:
        for servo in servo_or_servos:
            servo.throttle = speed * direction_values[servo]
    else:
        servo_or_servos.throttle = speed * direction_values[servo_or_servos]


def reverse(servo_or_servos, speed):
    if type(servo_or_servos) == list:
        for servo in servo_or_servos:
            servo.throttle = speed * -1 * direction_values[servo]
    else:
        servo_or_servos.throttle = speed * -1 * direction_values[servo_or_servos]


def stop(servo_or_servos):
    if type(servo_or_servos) == list:
        for servo in servo_or_servos:
            servo.throttle = 0
    else:
        servo_or_servos.throttle = 0

Four other functions provide higher level behaviors that are useful in scripting motion. 

Download: file
def rotate_clockwise(speed):
    forward(left_legs, speed)
    reverse(right_legs, speed)


def rotate_counterclockwise(speed):
    forward(right_legs, speed)
    reverse(left_legs, speed)


def crawl_forward(speed):
    forward(all_legs, speed)


def crawl_backward(speed):
    reverse(all_legs, speed)

Now for the tail. wag_for is the main function that is used, the others just provide the required abstractions. It wags the tail for the specified time. Wag is the function that makes the tail do its thing: power the tail motor for 100 ms, then wait for 250 ms while the spring recenters the tail.

Download: file
def wag(speed):
    tail.throttle = speed
    time.sleep(0.1)
    tail.throttle = 0.0
    time.sleep(0.25)

    
def wag_for(seconds):
    target_time = time.monotonic() + seconds
    wag_throttle = 1.0
    while time.monotonic() < target_time:
        wag(wag_throttle)
        wag_throttle *= -1.0

Finally there's a sample script that runs forward for 5 seconds, rotates clockwise, run backwards for 2 seconds, rotate the other way and run forward again, finally stopping.

Download: file
def demo1():
    crawl_forward(0.5)
    wag_for(5.0)
    rotate_clockwise(0.25)
    wag_for(3.0)
    crawl_backward(0.5)
    wag_for(2.0)
    rotate_counterclockwise(0.25)
    wag_for(3.0)
    crawl_forward(0.5)
    wag_for(5.0)
    stop(all_legs)
"""
Continuous servo based walking/waddling/etc robot.

Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!

Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.

All text above must be included in any redistribution.
"""

import time
from adafruit_crickit import crickit

tail = crickit.dc_motor_1

# Each servo corresponds to one of the legs
front_right = crickit.continuous_servo_1
front_left = crickit.continuous_servo_2
rear_right = crickit.continuous_servo_3
rear_left = crickit.continuous_servo_4

# Useful groups of legs
all_legs = [front_right, front_left, rear_right, rear_left]
front_legs = [front_right, front_left]
rear_legs = [rear_right, rear_left]
right_legs = [front_right, rear_right]
left_legs = [front_left, rear_left]

# The sign (+1/-1) for forward motion for each servo
direction_values = {front_right: +1,
                    front_left: -1,
                    rear_right: +1,
                    rear_left: -1}

# Tweak the pwn ranges for each servo so that throttle of 0 stops the motor
pwm_ranges = {front_right: (500, 2400),
              front_left: (500, 2400),
              rear_right: (500, 2400),
              rear_left: (500, 2400)}


def init():
    for leg in all_legs:
        limits = pwm_ranges[leg]
        leg.set_pulse_width_range(min_pulse=limits[0], max_pulse=limits[1])
        leg.throttle = 0


def wag(speed):
    tail.throttle = speed
    time.sleep(0.1)
    tail.throttle = 0.0
    time.sleep(0.25)


def wag_for(seconds):
    target_time = time.monotonic() + seconds
    wag_throttle = 1.0
    while time.monotonic() < target_time:
        wag(wag_throttle)
        wag_throttle *= -1


def forward(servo_or_servos, speed):
    if isinstance(servo_or_servos, list):
        for servo in servo_or_servos:
            servo.throttle = speed * direction_values[servo]
    else:
        servo_or_servos.throttle = speed * direction_values[servo_or_servos]


def reverse(servo_or_servos, speed):
    if isinstance(servo_or_servos, list):
        for servo in servo_or_servos:
            servo.throttle = speed * -1 * direction_values[servo]
    else:
        servo_or_servos.throttle = speed * -1 * direction_values[servo_or_servos]


def stop(servo_or_servos):
    if isinstance(servo_or_servos, list):
        for servo in servo_or_servos:
            servo.throttle = 0
    else:
        servo_or_servos.throttle = 0


def rotate_clockwise(speed):
    forward(left_legs, speed)
    reverse(right_legs, speed)


def rotate_counterclockwise(speed):
    forward(right_legs, speed)
    reverse(left_legs, speed)


def crawl_forward(speed):
    forward(all_legs, speed)


def crawl_backward(speed):
    reverse(all_legs, speed)


def turtle():
    stop([rear_right, rear_left])
    stop(rear_left)
    forward(front_right, 0.5)
    forward(front_left, 0.5)


def snake_step():
    stop(all_legs)
    forward(right_legs, 0.5)
    time.sleep(1.0)
    stop(right_legs)
    forward(left_legs, 0.5)
    time.sleep(1.0)
    stop(left_legs)


init()

def demo1():
    crawl_forward(0.5)
    wag_for(5.0)
    rotate_clockwise(0.25)
    wag_for(3.0)
    crawl_backward(0.5)
    wag_for(2.0)
    rotate_counterclockwise(0.25)
    wag_for(3.0)
    crawl_forward(0.5)
    wag_for(5.0)
    stop(all_legs)

demo1()
This guide was first published on Sep 25, 2018. It was last updated on Sep 25, 2018. This page (Circuit Playground Express and CircuitPython) was last updated on Nov 10, 2019.