Stop and back off
The bumper's job on a robot: on a hit, reverse for 400 ms, turn for 300 ms, and drive on. Three states and a clock, no delay(), so the bumper is read on every pass of loop() and a second hit in the middle of the escape is caught the moment it happens.
Three states and a clock
A robot that hits something has three jobs, one after the other: reverse
away, turn, drive on. Each takes time, and the obvious way to write it is
reverse(); delay(400); turnLeft(); delay(300);. For those 700 ms the sketch
reads nothing, and a robot that reverses into a chair leg behind it keeps
reversing.
So the sketch keeps a mode, DRIVE, BACK or TURN, and the time that mode
began. Every pass of loop() asks three questions, in order:
- Was there a hit? Then go to BACK, whatever the mode was.
- Is it BACK, and has it been 400 ms? Then go to TURN.
- Is it TURN, and has it been 300 ms? Then go to DRIVE.
Most passes, the answer to all three is no, and loop() goes round again
straight away.
Press Drive. The lever closes at the wall and the sketch changes mode on that same pass: BACK, the motors reverse, and the time is noted. Nothing waits. 400 ms later a pass finds the time is up and changes to TURN, and 300 ms after that, to DRIVE.
The hit, once
hit() is the debounce from counting
hits, turned into a
function that returns true once per hit: on the pass where the debounced
reading becomes HIGH, and never again until the lever has been released and
hit again. So a lever still held in while the robot starts reversing does
not restart BACK on every pass.
Where the motors go
forward(), reverse() and turnLeft() only print. That lets you test the
logic with the bumper wired and nothing else: push the lever, and the serial
monitor shows reverse, turn left and forward at the right intervals.
Then put your motor driver's calls in the three functions; for the DRV8833,
its four states are the calls you need.
The timings are the part to tune. How far 400 ms of reversing and 300 ms of turning take a robot depends on its motors, its wheels and its battery, and only your robot can tell you.
The code
A robot's bumper logic as three states: DRIVE, BACK and TURN. A debounced hit in any state starts BACK; BACK becomes TURN after BACK_MS, and TURN becomes DRIVE after TURN_MS. The motor functions only print, so it runs with just the bumper wired.
/*
Collision Sensor - stop and back off TK17 / /p/tk17
Wiring. Count from the square pad on the TinkerBlock board, switch
at the top, header at the bottom:
GND -> GND
VCC -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
(on a hit, SIGNAL gives your pin whatever VCC is)
NC -> nothing (unconnected on the board)
SIGNAL -> D2 on an Uno, GPIO 25 on an ESP32, GPIO 4 on an
ESP32-S3, GP15 on a Raspberry Pi Pico
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
*/
// The GPIO number SIGNAL is wired to.
// Uno: 2. ESP32: 25. ESP32-S3: 4. Pico: 15.
const int BUMPER_PIN = 4;
const unsigned long DEBOUNCE_MS = 20;
const unsigned long BACK_MS = 400; // reverse for this long
const unsigned long TURN_MS = 300; // then turn for this long
enum Mode { DRIVE, BACK, TURN };
Mode mode = DRIVE;
unsigned long modeSince = 0; // millis() when this mode began
int lastReading = LOW;
int bumper = LOW; // the debounced reading
unsigned long lastChange = 0;
// Your motor driver's calls go in these three. Here they only say
// what they would do, so the sketch runs with just the bumper wired.
void forward() { Serial.println("forward"); }
void reverse() { Serial.println("reverse"); }
void turnLeft() { Serial.println("turn left"); }
void enter(Mode next) {
mode = next;
modeSince = millis();
if (next == DRIVE) forward();
if (next == BACK) reverse();
if (next == TURN) turnLeft();
}
// True once per hit: the debounced reading has just become HIGH.
bool hit() {
int reading = digitalRead(BUMPER_PIN);
unsigned long now = millis();
if (reading != lastReading) {
lastReading = reading;
lastChange = now;
}
if (now - lastChange >= DEBOUNCE_MS && reading != bumper) {
bumper = reading;
return bumper == HIGH;
}
return false;
}
void setup() {
Serial.begin(115200);
pinMode(BUMPER_PIN, INPUT); // the block has its own pull-down
enter(DRIVE);
}
void loop() {
unsigned long inMode = millis() - modeSince;
if (hit()) { // in any mode: back off, now
enter(BACK);
} else if (mode == BACK && inMode >= BACK_MS) {
enter(TURN);
} else if (mode == TURN && inMode >= TURN_MS) {
enter(DRIVE);
}
}modeSince is when the current state began, and millis() - modeSince is how long it has lasted: the same subtraction as the debounce, so it keeps working when millis() wraps. Put your driver's calls in forward(), reverse() and turnLeft().
The same three states in MicroPython. hit() is the debounce from the last article, returning True once per hit; the mode and the time it began are two variables.
"""
Collision Sensor - stop and back off, MicroPython TK17 / /p/tk17
Wiring. Count from the square pad on the TinkerBlock board, switch
at the top, header at the bottom:
GND -> GND
VCC -> 3V3 (never 5V: on a hit, SIGNAL gives your pin VCC)
NC -> nothing (unconnected on the board)
SIGNAL -> GPIO 25 on an ESP32, GPIO 4 on an ESP32-S3,
GP15 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
Nothing to install: machine and time are built in.
"""
from machine import Pin
import time
# The GPIO number SIGNAL is wired to. ESP32: 25. ESP32-S3: 4. Pico: 15.
BUMPER_PIN = 4
DEBOUNCE_MS = 20
BACK_MS = 400 # reverse for this long
TURN_MS = 300 # then turn for this long
bumper = Pin(BUMPER_PIN, Pin.IN) # no pull: the block has its own
last_reading = 0
state = 0 # the debounced reading
last_change = time.ticks_ms()
# Your motor driver's calls go in these three.
def forward():
print("forward")
def reverse():
print("reverse")
def turn_left():
print("turn left")
def hit():
"""True once per hit: the debounced reading has just become 1."""
global last_reading, state, last_change
reading = bumper.value()
now = time.ticks_ms()
if reading != last_reading:
last_reading = reading
last_change = now
if (time.ticks_diff(now, last_change) >= DEBOUNCE_MS
and reading != state):
state = reading
return state == 1
return False
mode = "DRIVE"
mode_since = time.ticks_ms()
forward()
while True:
in_mode = time.ticks_diff(time.ticks_ms(), mode_since)
if hit(): # in any mode: back off, now
mode, mode_since = "BACK", time.ticks_ms()
reverse()
elif mode == "BACK" and in_mode >= BACK_MS:
mode, mode_since = "TURN", time.ticks_ms()
turn_left()
elif mode == "TURN" and in_mode >= TURN_MS:
mode, mode_since = "DRIVE", time.ticks_ms()
forward()ticks_diff again for every subtraction of two ticks_ms readings, so it survives the wrap. Replace the three prints with your motor driver's calls. Stop it with Ctrl-C in Thonny's shell.
When it does not work
Into forward(), reverse() and turnLeft(). They print so the sketch runs with nothing but the bumper wired, and so you can watch the states change with a finger on the lever. Replace each print with your motor driver's calls; the DRV8833 handbook shows what those are for its four states.
Lengthen TURN_MS so it turns further, or make the turn direction change each time. The timings here are a starting point: how far 300 ms turns a robot depends on its motors, its wheels and its battery, and nobody can give you that number but your robot.
The sketch sees a hit at power-up, so the pin reads HIGH from the start. Look at the red LED: lit means the lever really is held in by something on the robot. Dark means the wiring: a SIGNAL wire on a supply pin, or a sketch testing for LOW.
It works until you add anything else. During a delay() nothing is read: not this bumper, not a second one on the other side, not a distance sensor. The state machine does the same job in the same number of lines and never goes deaf.
The red LED first, then five symptoms and where to look for each.
When a hit does nothing →Edit this page — content/books/collision-sensor/stop-and-back-off.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Collision Sensor
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.