In this tutorial, you will build tactile switches, called whiskers, on your cyber:bot. Whisker switches give the cyber:bot the ability to sense its surroundings through touch as it roams around, somewhat like a cat’s whiskers.
Also called bumper switches or touch switches, tactile switches have many uses in robotics. A robot programmed to pick up an object and move it to another conveyer belt might rely on a tactile switch to detect or “feel” the object. Automated factory lines might use tactile switches to detect and count objects, and to align parts for a certain step in a manufacturing process. In each case, switches provide inputs that trigger some form of programmed response.
These activities are written assuming you are completing the cyber:bot Prerequisite tutorials first and then doing the Main tutorials in sequence. You should complete through Navigation [1] and Circuits [2] at a minimum first.
For this tutorial you will need:
Once you are finished, you can learn about other types of sensors that can be used with the cyber:bot and then use them in conjunction with the whiskers.
The directions below will guide you through building whisker switch circuits on your cyber:bot. The whisker switch wires connect to posts on the front corners of the robot.
The whiskers are connected to ground (GND) because the plated holes at the outer edge of the board are all connected to GND. The metal standoffs and screws are touching the GND so they too are part of GND. The standoffs and screws provide the electrical GND connection to each whisker.
Since each whisker is connected to Propeller I/O pins P7 and P9, the cyber:bot can be programmed to detect which voltage is applied to each circuit, 3.3 V or 0 V. Your script can detect the pin’s state, HIGH or LOW, with the read_digital function.
On the left, the circuit applies 3.3 V to P7 when the whisker is not pressed, so bot(7).read_digital returns a value of 1 (HIGH). On the right, the circuit applies 0 V to the pin when the whisker is pressed, so bot(7).read_digital returns a value of 0 (LOW).
Most importantly, your script can store the return values in variables, such as whisker_left and whisker_right, and then use them to trigger actions or make decisions. The next example script will demonstrate how to accomplish this.
Switch Lingo — Each whisker is both the mechanical extension and the ground electrical connection of a normally open (off until pressed) momentary (on only while pressed) single-pole (one set of electrical contact points), single-throw (only one position conducts) switch, SPST for short.
There are many other types of switches in contrast to this momentary single-pole, single-throw, or SPST. For example there are also double-pole, single-throw (DPST); double-pole, double-throw (DPDT); and single-pole, double-throw (SPDT). The SPST style used for our whiskers in the most simple style to construct.
The next script tests the whiskers to make sure they are functioning properly, by using the micro:bit module’s LED matrix display. This gives a visual representation of the binary values (LED off = 1 and LED on = 0) returned by bot(7).read_digital() and bot(9).read_digital(). This way, you can press each whisker against its 3-pin header on the breadboard, and see if the Propeller I/O pin is sensing the electrical contact.
When neither whisker is pressed against its 3-pin header, you can expect your micro:bit module’s display to have no LEDs light up. If you press only the right whisker, the display should have one LED on the right side light up. If you press only the left whisker, one LED on the left side will light up. Finally, if you press both whiskers simultaneously, an LED on both sides will light up.
No Whiskers Touching: no lights on
Left Whisker Touching: LED lights up on left side of the cyber:bot
Right Whisker Touching: LED lights up on right side of the cyber:bot
Both Whiskers Touching: LED lights up on both sides
# whiskers_detect_test from cyberbot import * while True: whisker_left = bot(7).read_digital() whisker_right = bot(9).read_digital() if whisker_left == 0: display.set_pixel(4, 2, 9) #left side LED on if left whisker is pressed else: display.set_pixel(4, 2, 0) if whisker_right == 0: display.set_pixel(0, 2, 9) #right side LED on if right whisker is pressed else: display.set_pixel(0, 2, 0)
A lot of times, problems that occur when dealing with whiskers do not come from the script, but rather the physical connections of the whiskers. Try the following things to improve the physical connections of the whiskers.
These steps are important!
Seriously, you’ve got to make sure your circuit and code pass these tests before continuing. The rest of the examples in this chapter rely on the whiskers working correctly. If you haven’t tested and corrected any errors, the rest of the examples won’t work right.
The main part of the program is contained within the while True: loop, this is so the program continuously checks whether or not the whiskers are being touched and therefore it knows whether the LEDs should be on or off.
while True:
The first thing that the script does once it enters the loop is checks the whisker states using the read_digital function and stores them as a boolean 0 (whisker pressed) or 1 (whisker not pressed) to the variables whisker_left for the P7 state, and whisker_right for the P9 state.
whisker_left = bot(7).read_digital() whisker_right = bot(9).read_digital()
The script then enters into the first if statement. It checks to see if the value of whisker_left is 0, if it is 0, then the micro:bit module uses the display.set_pixel command to light up the LED located at (4,2) using the maximum brightness of 9. Any other value, with the only other boolean option being 1, falls under the else statement. The else statement turns off the pixel located at (4,2) with a brightness of 0.
if whisker_left == 0: display.set_pixel(4, 2, 9) else: display.set_pixel(4, 2, 0)
The second if statement does the same process for the other whisker. It checks to see if the value of whisker_right is 0, and if so, then the micro:bit module lights up the LED located at (0,2) with the maximum brightness of 9 using the display.set_pixel command. Any other value, with the only other boolean option being 1, falls under the else statement which turns off the pixel at (0,2) with a brightness of 0.
if whisker_right == 0: display.set_pixel(0, 2, 9) else: display.set_pixel(0, 2, 0)
After it runs through the two if statements, the while True: loop resets and starts over by checking the whisker states again.
In previous navigation programs, our scripts only made the cyber:bot execute a list of movements predefined by you, the programmer. Now that you can write a script to make the cyber:bot monitor whisker switches and trigger action in response, you can also write a script that lets the cyber:bot drive and select its own maneuver if it bumps into something. This is an example of autonomous robot navigation.
The roaming_with_whiskers.py script makes the cyber:bot go forward while monitoring its whisker inputs, until it encounters an obstacle with one or both of them. As soon as the cyber:bot senses whisker electrical contact, it uses an if…elif…else statement to decide what to do. The decision code checks for various whisker pressed/not pressed combinations, and calls navigation functions to execute back-up-and-turn maneuvers. Then, the cyber:bot resumes forward motion until it bumps into another obstacle.
Let’s try the script first, and then take a closer look at how it works.
# roaming_with_whiskers from cyberbot import * def forward(): bot(18).servo_speed(75) bot(19).servo_speed(-75) def backwards(): bot(18).servo_speed(-75) bot(19).servo_speed(75) sleep(250) def right(): bot(18).servo_speed(75) bot(19).servo_speed(75) sleep(250) def left(): bot(18).servo_speed(-75) bot(19).servo_speed(-75) sleep(250) while True: whisker_left = bot(7).read_digital() whisker_right = bot(9).read_digital() if whisker_left == 0 and whisker_right == 0: backwards() right() elif whisker_left == 1 and whisker_right == 0: backwards() left() elif whisker_left == 0 and whisker_right == 1: backwards() right() else: forward()
The first part of roaming_with_whiskers defines the four navigation functions: forward, left, right, and backwards. Notice how the left, right, and backwards functions all have a sleep function. Every time each of these functions are called, the cyber:bot moves in that direction for a predetermined amount of time. 750 milliseconds is enough time to back up away from an obstacle, and 500 milliseconds makes about a 90 degree turn to the right or the left. The forward function does not have a sleep function call inside of it.
def forward(): bot(18).servo_speed(75) bot(19).servo_speed(-75) def backwards(): bot(18).servo_speed(-75) bot(19).servo_speed(75) sleep(750) def right(): bot(18).servo_speed(75) bot(19).servo_speed(75) sleep(500) def left(): bot(18).servo_speed(-75) bot(19).servo_speed(-75) sleep(500)
After the four navigation functions have been defined, the script enters into the while True: loop. Inside of this loop, we have a four-part if statement. The first thing it checks is if both whiskers are touching, that is, if both whisker_left and whisker_right are equal to 0. If this is true, then the program runs through the backwards and right functions making the cyber:bot backup and then turn right, presumably away from the obstacle.
if whisker_left == 0 and whisker_right == 0: backwards() right()
If whisker_right and whisker_left are not touching, then the program goes to the next part of the if statement which says else if (elif) the left whisker is not touching (whisker_left == 1) and the right whisker is touching (whisker_right == 0), then the program executes the backwards and left functions.
elif whisker_left == 1 and whisker_right == 0: backwards() left()
If the if statement has still not found a true condition, then it checks to see if the left whisker is touching (whisker_left == 0) and the right whisker is not touching (whisker_right == 1). If this it true, the program executes the functions backwards and right.
elif whisker_left == 0 and whisker_right == 1: backwards() right()
Finally, if all three of the above conditions are not met, the program executes the forward function and drives a little bit before once again checking to see if an obstacle is detected.
Compact Code — The script could have lastly checked:
elif whisker_left == 1 and whisker_right == 1:
...instead of using just else:, however, the only condition remaining at the end was that neither whisker was touching (whisker_right and whisker_left are both equal to 1). Although both of these would have worked, else: is used because it shortens the code.
Compact it More — To make the script even more compact, shorten the variable names used to define each maneuver and to store the whisker I/O pin states. This could help if want to combine whisker navigation with other sensor input or with piezospeaker sounds.
When running roaming_with_whiskers.py, you may have seen your cyber:bot get stuck in a corner. Its left whisker touches a wall so it turns right, then its right whisker touches a wall so it turns left, over and over again, until you rescue your robot.
The next example script counts alternate whisker presses to determine if the cyber:bot is stuck in a corner. To do this, the script has to remember what state each whisker was in during the previous contact. If they are opposite, then one is added to a counter. If that counter goes over a threshold that you set, then it is time to turn around out of the corner (and reset that counter). In this way, your cyber:bot "remembers its experience" to be "aware of its predicament" so it can respond appropriately; a primitive example of artificial intelligence.
This script relies on nested if...else statements. The script checks for one condition, then if that condition is true, it checks for another one within the first condition. The script escaping_corners.py actually uses three if statements nested together.
# escaping_corners.py from cyberbot import * # define functions for maneuvers def forward(): bot(18).servo_speed(75) bot(19).servo_speed(-75) def backwards(): bot(18).servo_speed(-75) bot(19).servo_speed(75) sleep(750) def right(): bot(18).servo_speed(75) bot(19).servo_speed(75) sleep(500) def left(): bot(18).servo_speed(-75) bot(19).servo_speed(-75) sleep(500) # initialization counter = 0 w_l = 0 w_r = 0 o_w_l = 0 o_w_r = 1 bot(22).tone(4000,50) # main routine while True: w_l = bot(7).read_digital() # check left whisker state w_r = bot(9).read_digital() # check right whisker state if (w_l != w_r): # if a whisker was pressed if (o_w_l != w_l) and (o_w_r != w_r): # different from last time? counter = counter + 1 # in corner - add to counter o_w_l = w_l # record left whisker press o_w_r = w_r # record right whisker press if (counter > 4): # if in corner too long... bot(22).tone(3000, 50) # sound alarm counter = 1 # reset corner counter w_l = 1 # reset left whisker value w_r = 1 # reset right whisker value backwards() # back out of corner left() # turn away from corner left() else: counter = 1 # else, reset counter if (w_l == 0 and w_r == 0): # roam with whiskers backwards() right() elif w_r == 0: backwards() left() elif w_l == 0: backwards() right() else: forward()
The example script escaping_corners.py is an evolution of the original roaming_with_whiskers. If you didn't read through How Roaming with Whiskers Works [3], you might want to go back and do so now.
This script begins with the four familiar navigation function definitions for forward, left, right, and backwards.
def forward(): bot(18).servo_speed(75) bot(19).servo_speed(-75) def backwards(): bot(18).servo_speed(-75) bot(19).servo_speed(75) sleep(750) def right(): bot(18).servo_speed(75) bot(19).servo_speed(75) sleep(500) def left(): bot(18).servo_speed(-75) bot(19).servo_speed(-75) sleep(500)
Next comes an initialization section. There are five variable declarations. counter will be used to keep track of alternate whisker presses. w_l and w_r are shortened versions of the roaming code's whisker_left and whisker_right; remember that long variable names take more memory! With "o" for "old," the variables o_w_l and o_w_r will store the previous whisker states in order to compare them to the current whisker states each time through the main loop.
counter = 0 w_l = 0 w_r = 0 o_w_l = 0 o_w_r = 1
Notice that o_w_r is given an initial value of 1, not zero. This is necessary because the code always compares an alternating pattern; if o_w_l and o_w_r were both initialized to zero, the code would never find the second nested if statement to be true and would not count alternate corner presses.
The main routine starts with a short tone to signal the start of the program. Then, it enters and infinite while_True: loop. Just as with the roaming script, this loop begins by testing the whisker circuits' input pin states and saving the resulting binary values to variables.
bot(22).tone(4000,50) # main routine while True: w_l = bot(7).read_digital() # check left whisker state w_r = bot(9).read_digital() # check right whisker state
Now the fun begins! The outermost if statement uses the not-equals comparison operator != to see if w_l and w_r are different, which indicates one of the whiskers is pressed—it does not matter which one! If this is true, the nested if statement right below it uses the not-equals operator again o_w_l != w_l and to see if each whisker's current input state is different from its old input state.
if (w_l != w_r): # if a whisker was pressed if (o_w_l != w_l) and (o_w_r != w_r): # different from last time?
If it's true that both current whisker input states are different from their previous input states, the cyber:bot must have gone from having one whisker pressed to having the opposite whisker pressed—in other words, it encountered a corner. This event is tracked by incrementing the variable counter by one. Then, the value of w_l is stored in o_w_l and w_r is stored in o_w_r to make a another comparison next time through the loop.
counter = counter + 1 # in corner - add to counter o_w_l = w_l # record left whisker press o_w_r = w_r # record right whisker press
Next comes the innermost of the three nested if statements. It checks to see if counter has exceeded four, pretty strong evidence that the cyber:bot is indeed stuck in a corner. If true, then a tone plays signalling an escape is imminent. Before that happens, counter, w_l, and w_r are all set to 1, so the cyber:bot will resume roaming after executing the backwards and two left function calls to escape.
if (counter > 4): # if in corner too long... bot(22).tone(3000, 50) # sound alarm counter = 1 # reset corner counter w_l = 1 # reset left whisker value w_r = 1 # reset right whisker value backwards() # back out of corner left() # turn away from corner left()
These nested if statements end with an else: that sets counter back to 1 before moving on to the regular roaming with whiskers behavior.
else: counter = 1 # else, reset counter
Finally, after exiting the outermost nested if block, the code moves on to a more familiar if...elif...elif...else similar to the one in roaming_with_whiskers.py. Note that only the first if...checks both whisker states, and the others use the minimal amount of logic needed to accomplish the navigation tasks.
if (w_l == 0 and w_r == 0): # roam with whiskers backwards() right() elif w_l == 0: backwards() left() elif w_r == 0: backwards() right() else: forward()
Links
[1] https://learn.parallax.com/tutorials/robot/cyberbot/navigation-cyberbot
[2] https://learn.parallax.com/tutorials/robot/cyberbot/circuits-cyberbot
[3] https://learn.parallax.com/node/2002