Adding Bumpers

JP's bot detects collisions by using the Circuit Playground Express's on board accelerometer. It's a simple, elegant solution (not to mention not requiring any additional parts or construction). However it has some shortcomings: a) collisions have to be at fairly high speed to register, b) it requires a CircuitPlayground Express, so you can't use a Featherwing CRICKIT and a Feather, and c) without more processing you can't determine in what direction the collision occurred.

This project ops for using mechanical switch based bumpers. A drawback is requiring additional parts and construction, but an advantage is that it makes use of more of the CRICKIT's capabilities.

Parts Required

Micro Switch w/Lever - 2 Terminal

PRODUCT ID: 818
Micro-switches are often found in arcade buttons and joysticks but they're also really handy in any kind of mechatronics project or when you need a basic sensor. They are always...
$1.50
IN STOCK

Premium Female/Male 'Extension' Jumper Wires - 20 x 6"

PRODUCT ID: 1954
These Female/Male Extension jumper wires are handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip'...
$1.95
IN STOCK

Wiring Additions

The wiring is shown here. Note that push-button switches are shown, but lever switches are used.

The Whiskers

I know... snakes don't have whiskers.  All I can say is that this one does.

We'll add some whiskers to the bot so that it can know with which side it ran into something. The lever switches are perfect for this. 

Start by separating two pairs of jumper wires. Remove the female ends. Strip and tin the fresh cut ends and solder them to the two contacts of the switches, one pair per switch

Then hot glue the switches to the front of the bot so that the levers point to the outside. Their edge should follow the front edge of the bot and they should be fairly close together. You won't be able to get the overly close, though, due to the way the contacts stick out.

Next hot glue two pieces of cardboard to the levers. They should be around 8cm long by 4cm tall. Position them so that they are approximately parallel to the ground. This will likely result in them not being parallel with the levers. The switch levers usually have some give to them and the weight of the whisker will pull them down somewhat. But don't worry too much about it; as long as they don't drag, you're ok. And you can always trim them if they do.

Notice the empty space between the whiskers. Right now it's a blind spot for your bot. It needs a nose to bump into things directly in front of it.

Thankfully, snakes do have noses.

The way to put this in place is to mount a switch vertically in the center of the front of the bot base, and glue a piece of cardboard to the switch lever that is wide enough to fill the gap between the whiskers. 

The switch needs to be mounted so that the bottom of the nose sticks out at the bottom. This way it is sure to actuate the lever to press the switch when something is bumped.

Hot gluing the switch to the bot base and the front of the Circuit Playground Express (or it's mount) works well enough, but note that it does need to be angled slightly.

Code

The main addition in this version of the code is the react_to_bumpers() function.

Download: file
def react_to_bumpers():
    attempt_count = 0
    # keep trying to back away and turn until we're free
    while True:

        # give up after 3 tries
       if attempt_count == 3:
          return True

       bumped_left = not ss.digital_read(LEFT_BUMPER)
       bumped_right = not ss.digital_read(RIGHT_BUMPER)
       bumped_center = not ss.digital_read(CENTER_BUMPER)

       # Didn't bump into anything, we're done here
       if not bumped_left and not bumped_right and not bumped_center:
           return False

       # If the middle bumper was triggered, randomly pick a way to turn
       if bumped_center:
          bumped_left = random.choice([False, True])
           bumped_right = not bumped_left

       # Back away a bit
       set_left(-0.5)
       set_right(-0.5)
       time.sleep(0.5)

       # If we bumped on the left, turn to the right
       if bumped_left:
           set_left(1.0)
           set_right(0.0)

       # If we bumped on the right, turn left
       elif bumped_right:
          set_left(0.0)
          set_right(1.0)

       # time to turn for
       time.sleep(random.choice([0.2, 0.3, 0.4]))
       attempt_count += 1

It checks the state of the three bumper input signals and tries to deal with the situation. Note that when a switch is closed due to bumping into something, that input signal is False. 

In all cases where at least one bumper switch is activated it will back up a bit, then turn away from the bumper that detected a collision. How far does it turn? The code randomly turns for 200, 300, or 400 milliseconds.

There are a few other things going on in this code.

Counting Attempts

It counts how many times the loop executes and eventually gives up, recognizing that it's stuck. In that case the function returns True. After it backs away and turns if it finds that none of the bumpers detect a collision, it assumes the way ahead is clear and returns False

Handling 'nose' Bumps

The other thing is the handling of the center "nose" bumper.  This code randomly picks right or left and carries on as if that bumper has detected a collision.  Another approach to dealing with the nose bumper would be to turn 180 degrees and go back the way it came. To do that we'd need someway to measure how far it's turning. Rotation encoders would let us do that, but we're not using them. Turning 180 degrees would also interact badly if you add the tail.

Tack Movement

Another change is moving tacking to a separate function: tack(direction, duration):

Download: file
LEFT = False
RIGHT = True

def tack(direction, duration):
    target_time = time.monotonic() + duration
    if direction == LEFT:
       set_left(0.25)
       set_right(1.0)
    else:
       set_left(1.0)
       set_right(0.25)
    while time.monotonic() < target_time:
       if not(ss.digital_read(LEFT_BUMPER) and
               ss.digital_read(RIGHT_BUMPER) and
               ss.digital_read(CENTER_BUMPER)):
          return react_to_bumpers()
    return False

Tack starts the motors running to cause the bot to drift to the left or right, depending on the direction parameter. It then loops for the requested amount of time. Each time through the loop, all three bumpers are checked. If any have collided with something (indicated by at least one input being False), react_to_bumpers() is called to get out of trouble. If there are no collisions during the requested time, False is returned top indicate that everything is good.

The main loop calls tack() for each zig and zag. If it returns False, the bot is moving freely and continues on it's way. However, if it returns True that means it got stuck. In that case the loop exits and stops the motors.

Download: file
while True:
    if tack(LEFT, 0.75):
        break
    if tack(RIGHT, 0.75):
        break

Save the code below as code.py on the Circuit Playground Express.

import time
import random
from adafruit_crickit import crickit

LEFT = False
RIGHT = True

random.seed(int(time.monotonic()))
ss = crickit.seesaw

left_wheel = crickit.dc_motor_1
right_wheel = crickit.dc_motor_2

RIGHT_BUMPER = crickit.SIGNAL1
LEFT_BUMPER = crickit.SIGNAL2
CENTER_BUMPER = crickit.SIGNAL3

ss.pin_mode(RIGHT_BUMPER, ss.INPUT_PULLUP)
ss.pin_mode(LEFT_BUMPER, ss.INPUT_PULLUP)
ss.pin_mode(CENTER_BUMPER, ss.INPUT_PULLUP)

# These allow easy correction for motor speed variation.
# Factors are determined by observation and fiddling.
# Start with both having a factor of 1.0 (i.e. none) and
# adjust until the bot goes more or less straight
def set_right(speed):
    right_wheel.throttle = speed * 0.9

def set_left(speed):
    left_wheel.throttle = speed


# Uncomment this to find the above factors
# set_right(1.0)
# set_left(1.0)
# while True:
#     pass

# Check for bumper activation and move away accordingly
# Returns False if we got clear, True if we gave up
def react_to_bumpers():
    attempt_count = 0
    # keep trying to back away and turn until we're free
    while True:

        # give up after 3 tries
        if attempt_count == 3:
            return True

        bumped_left = not ss.digital_read(LEFT_BUMPER)
        bumped_right = not ss.digital_read(RIGHT_BUMPER)
        bumped_center = not ss.digital_read(CENTER_BUMPER)

        # Didn't bump into anything, we're done here
        if not bumped_left and not bumped_right and not bumped_center:
            return False

        # If the middle bumper was triggered, randomly pick a way to turn
        if bumped_center:
            bumped_left = random.choice([False, True])
            bumped_right = not bumped_left

        # Back away a bit
        set_left(-0.5)
        set_right(-0.5)
        time.sleep(0.5)

        # If we bumped on the left, turn to the right
        if bumped_left:
            set_left(1.0)
            set_right(0.0)

            # If we bumped on the right, turn left
        elif bumped_right:
            set_left(0.0)
            set_right(1.0)

            # time to turn for
        time.sleep(random.choice([0.2, 0.3, 0.4]))
        attempt_count += 1


def tack(direction, duration):
    target_time = time.monotonic() + duration
    if direction == LEFT:
        set_left(0.25)
        set_right(1.0)
    else:
        set_left(1.0)
        set_right(0.25)
    while time.monotonic() < target_time:
        if not(ss.digital_read(LEFT_BUMPER) and
               ss.digital_read(RIGHT_BUMPER) and
               ss.digital_read(CENTER_BUMPER)):
            return react_to_bumpers()
    return False


while True:
    if tack(LEFT, 0.75):
        break
    if tack(RIGHT, 0.75):
        break

set_left(0)
set_right(0)

while True:
    pass
This guide was first published on Jul 30, 2018. It was last updated on Jul 30, 2018. This page (Adding Bumpers) was last updated on Jun 11, 2019.