# Arduino millis\(\) vs delay\(\): robot state machine

> Replace delay\(\) with millis\(\) timing on an Arduino robot, handle rollover safely, and organise behaviour as a simple state machine. Untested outlines.

Canonical: https://roboticsengineering.in/arduino-millis-non-blocking-robot-state-machine/ | Published: 2026-11-04 | Updated: 2026-11-04 | Author: jaadudigital

**If your robot freezes while it waits, the usual cause is `delay()`.** During a delay the Arduino does nothing else: it cannot read a sensor, stop a motor or notice a button. This guide shows how to replace delays with `millis()` timing and how to organise a robot as a small state machine.

*Status: written from the Arduino language reference and community explanations. The sketches are untested outlines, and no timing measurements of our own are included.*

## Why delay() hurts a robot

`delay(1000)` pauses the sketch for a second. A line follower that calls it between steps cannot correct its steering in that time, and an obstacle robot cannot react to a wall. The fix is to check the clock instead of waiting, and keep the loop running.

## The millis() pattern

`millis()` returns the milliseconds since the board started. Store the time of the last action, and each pass of `loop()` compare the difference with the interval you want.

```
// Untested outline.
unsigned long lastSensor = 0;
const unsigned long SENSOR_INTERVAL = 50;   // ms

void loop() {
  unsigned long now = millis();
  if (now - lastSensor >= SENSOR_INTERVAL) {
    lastSensor = now;
    // read the sensor here
  }
  // other work runs every pass, nothing blocks
}
```

Use `unsigned long` for both variables and write the test as `now - last >= interval`. The Arduino reference says the counter wraps back to zero after roughly 49 to 50 days. Community explanations note that unsigned subtraction still gives the right difference across the wrap, but the form `millis() >= last + interval` can fail, and a signed `long` breaks the method.

## Several tasks at different rates

Give each job its own timestamp and interval. A common split for a small robot is a fast loop for motor control, a slower one for a distance sensor, and a very slow one for a display or battery check. The distance sensor in particular should not be read faster than it can respond; see the [HC-SR04 reliable distance guide](https://roboticsengineering.in/hc-sr04-reliable-distance/).

## Organising behaviour as a state machine

A robot is easier to reason about when it is always in one named state. For an obstacle-avoiding robot the states might be FORWARD, BACK, TURN and STOP. Each pass reads the sensors, checks whether a condition should change the state, then runs that state’s action.

```
// Untested outline.
enum State { FORWARD, BACK, TURN };
State state = FORWARD;
unsigned long stateStart = 0;

void setState(State s) { state = s; stateStart = millis(); }

void loop() {
  unsigned long now = millis();
  long dist = readDistance();            // your sensor function

  switch (state) {
    case FORWARD:
      driveForward();
      if (dist < 20) setState(BACK);
      break;
    case BACK:
      driveBackward();
      if (now - stateStart >= 400) setState(TURN);
      break;
    case TURN:
      turnRight();
      if (now - stateStart >= 500) setState(FORWARD);
      break;
  }
}
```

The numbers 20 cm, 400 ms and 500 ms are placeholders, not tuned values. Tune them on your own chassis. The same shape fits the [obstacle-avoiding robot project](https://roboticsengineering.in/projects/obstacle-avoiding-robot-arduino/) and the [wall-following robot](https://roboticsengineering.in/wall-following-robot-arduino/).

## Common mistakes

- **Using a signed variable** for a timestamp.
- **Keeping a delay() somewhere** in a helper function, which still blocks everything.
- **Not resetting the timestamp** when entering a state, so the first pass times out at once.
- **Long blocking calls** such as `pulseIn()` with a long timeout. See [the Fix library](https://roboticsengineering.in/fix/) for timeout problems.
- **Too many states** before the basic ones work. Add one state at a time.

## A test plan

- Blink an LED with `millis()` while a second LED blinks at a different rate.
- Add a button and confirm it responds instantly while both LEDs blink.
- Print the state name on Serial each time it changes and watch the sequence while you wave a hand at the sensor.
- Run for several minutes on the bench with the wheels off the ground before putting it on the floor.
- Record what you changed and why. Your notes make this guide your own.

*Sources: Arduino language reference for millis(); Arduino forum explanations of rollover and unsigned subtraction. Last reviewed November 2026. Written, checked and maintained by Jignesh Panchal.*
