Lesson 12 of 13
Sense, decide, act
Objective: Give the rover a brain: a state machine that senses every 60 ms with millis(), decides with switch (state), acts through the motor driver, and always obeys its STOP button.
While the sketch runs, the diagram shows the state and each transition taken, the rover moves on the mat, and a timeline draws the state, the STOP button, when loop() reads D2 and every distance reading.
Stage 1 of 7 · What am I building?
Explore
A robot repeats one cycle: sense, decide, act. This rover senses a distance and its STOP button, decides with a state machine, and acts through the motor driver from lesson 10. The diagram is drawn from the sketch. Select each part below.
Pick a part. 0/8 inspected.
⚠ The STOP button is a software stop: it works only while the sketch runs. The power-off switch on the motor battery is the stop that always works. Test both before any trial, with the wheels lifted.
Before you start (optional refreshers)
Conditional logic
if (condition) { … } else { … } chooses what to do from a reading. The same test runs on every pass of loop().
Timing with millis()
millis() is the number of milliseconds since the board started, as an unsigned long. Saving it and comparing later measures how long has passed without stopping the sketch, unlike delay(). It rolls over to 0 after about 49.7 days; now - start >= interval still gives the right answer then, because unsigned subtraction rolls over too.
Thresholds and comparisons
Compare a reading to a chosen number to turn a continuous value into a decision. Whether the action happens below or above it depends on how the sensor is wired.
New ideas in this lesson
States and transitions
A state machine keeps one variable for what the robot is doing now (DRIVE, BRAKE, TURN…). Each pass of loop() runs that state's actions and checks its transitions: conditions that move it to another state. Every state needs a way out, unless it is meant to be final, and a stop that must always win is checked first, in every state.
Input → decision → output
Most projects read something, decide with a condition, and drive outputs. Recognising the pattern helps you design new ones.
Build it for real · Arduino Uno R3
You need
- Arduino Uno R3 (or compatible) and its USB cable
- Breadboard and jumper wires
- TB6612FNG dual motor driver (breakout board)
- Two-wheel rover chassis with 2 small DC gear motors
- HC-SR04 ultrasonic sensor
- Push button (4-leg tactile): the software STOP
- Motor battery: 4 × AA (6 V) with a power-off switch
Wiring
- Driver VCC → 5V; driver GND → GND.
- Motor battery + (through its power-off switch) → VM; battery − → driver GND, so every GND is joined.
- STBY → D3.
- Left motor: PWMA → D5, AIN1 → D4, AIN2 → D7; its leads → AO1 and AO2.
- Right motor: PWMB → D6, BIN1 → D12, BIN2 → D11; its leads → BO1 and BO2.
- Sensor at the front of the rover: VCC → 5V, GND → GND, Trig → D9, Echo → D10.
- STOP button across the breadboard's centre gap: one leg → D2; the diagonally opposite leg → GND. No resistor: INPUT_PULLUP provides it.
- Lift the wheels, upload, and test the STOP button and the power-off switch before the rover goes on the floor.
Before you power up: The STOP button is a software stop: it only works while the sketch runs. The power-off switch on the motor battery is the stop that always works: keep it in reach and test it first. Never power the motors from a pin or the board's 5V pin; join every GND.
Tip: The sketch stops with the driver's short brake: with IN1 and IN2 both HIGH the TB6612FNG brakes the motor whatever its PWM pin does.
The lab models ideal parts. Real circuits add tolerances, noise and loose wires, so check your wiring against this list before you plug in.
// Sense, decide, act: a rover that stops for obstacles and for its STOP button
// STOP button D2 to GND (INPUT_PULLUP); HC-SR04 Trig D9, Echo D10
// TB6612FNG: STBY D3~; left PWMA D5~, AIN1 D4, AIN2 D7; right PWMB D6~, BIN1 D12, BIN2 D11
// A reading every 60 ms, timed with millis()
const int stopPin = 2; // STOP button to GND: LOW = pressed
const int trigPin = 9;
const int echoPin = 10;
const int stbyPin = 3;
const int pwmAPin = 5; // left motor (A)
const int ain1Pin = 4;
const int ain2Pin = 7;
const int pwmBPin = 6; // right motor (B)
const int bin1Pin = 12;
const int bin2Pin = 11;
const int DRIVE = 0; // the rover's states
const int BRAKE = 1;
const int TURN = 2;
const int STOPPED = 3;
int state = DRIVE; // what the rover is doing now
const unsigned long readEvery = 60; // ms between distance readings
const int stopCm = 20;
const unsigned long brakeMs = 300;
const unsigned long turnMs = 600;
unsigned long lastRead = 0; // millis() at the last reading
unsigned long stateStart = 0; // millis() when this state began
void leftMotor(int in1, int in2, int speed) {
digitalWrite(ain1Pin, in1);
digitalWrite(ain2Pin, in2);
analogWrite(pwmAPin, speed);
}
void rightMotor(int in1, int in2, int speed) {
digitalWrite(bin1Pin, in1);
digitalWrite(bin2Pin, in2);
analogWrite(pwmBPin, speed);
}
unsigned long readEcho() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH); // a 10 µs pulse starts a reading
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
return pulseIn(echoPin, HIGH, 30000); // µs; 0 if no echo within 30 ms
}
void setup() {
pinMode(stopPin, INPUT_PULLUP); // HIGH until pressed
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(stbyPin, OUTPUT);
pinMode(ain1Pin, OUTPUT);
pinMode(ain2Pin, OUTPUT);
pinMode(bin1Pin, OUTPUT);
pinMode(bin2Pin, OUTPUT);
digitalWrite(stbyPin, HIGH); // wake the driver up
}
void loop() {
unsigned long now = millis(); // ms since the program started
bool close = false;
if (now - lastRead >= readEvery) { // safe when millis() rolls over
lastRead = now;
unsigned long duration = readEcho(); // waits in pulseIn() for the echo
float distance = duration * 0.0343 / 2;
close = duration > 0 && distance <= stopCm; // 0 is no reading, never close
}
if (digitalRead(stopPin) == LOW) state = STOPPED; // STOP wins: checked on every pass, first
switch (state) { // decide: run the case for the state
case DRIVE:
leftMotor(HIGH, LOW, 128); // act: both forward
rightMotor(HIGH, LOW, 128);
if (close) {
state = BRAKE;
stateStart = now;
}
break;
case BRAKE:
leftMotor(HIGH, HIGH, 0); // short brake
rightMotor(HIGH, HIGH, 0);
if (now - stateStart >= brakeMs) {
state = TURN;
stateStart = now;
}
break;
case TURN:
leftMotor(LOW, HIGH, 110); // spin left
rightMotor(HIGH, LOW, 110);
if (now - stateStart >= turnMs) state = DRIVE;
break;
case STOPPED:
leftMotor(HIGH, HIGH, 0); // stays here until RESET
rightMotor(HIGH, HIGH, 0);
break;
}
}Press ▶ Run.