Arduino Learning Studio full lab
0/13 lessons marked complete
● Saved in this browser← Back to homepage

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.

MODEL OF THIS SKETCHSTOPPED
State diagram · drawn from this sketch
echo, ≤ 20 cm300 ms passed600 ms passedD2 LOWD2 LOWD2 LOWDRIVEBRAKETURNSTOPPED
STOP button on D2 simulated input

Press while it runs. Run again to start over (like RESET).

Rover on the mat · seen from above (model) · the sensor sees straight ahead only (a stand-in for the 15° beam)
box20 cm

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

  1. Driver VCC → 5V; driver GND → GND.
  2. Motor battery + (through its power-off switch) → VM; battery − → driver GND, so every GND is joined.
  3. STBY → D3.
  4. Left motor: PWMA → D5, AIN1 → D4, AIN2 → D7; its leads → AO1 and AO2.
  5. Right motor: PWMB → D6, BIN1 → D12, BIN2 → D11; its leads → BO1 and BO2.
  6. Sensor at the front of the rover: VCC → 5V, GND → GND, Trig → D9, Echo → D10.
  7. STOP button across the breadboard's centre gap: one leg → D2; the diagonally opposite leg → GND. No resistor: INPUT_PULLUP provides it.
  8. 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.

Sketch · read-only
// 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.