Robotics
How to write Arduino robot code without delay()
To write Arduino robot code without delay(), stop waiting and start checking the clock. Remember when the current step began in a variable, and on every pass of loop() ask whether millis() minus that time has reached the step's length. Nothing ever waits, so loop() runs thousands of times a second and the robot reads its sensors on every pass.
Deaf while it waits
A simple patrol drives straight for a second, then pivots for half a second:
analogWrite(9, 255); analogWrite(10, 255);
delay(1000); // straight
analogWrite(10, 0);
delay(500); // pivot
bool bumped = (digitalRead(2) == LOW); // read once a lap
The bumper is only read once a lap, after both delays. Bump the robot 0.2 s into the straight leg and it grinds on for another 0.8 s, pivots for 0.5 s, and only then notices: 1.3 s late. A quick tap that's over by then is never seen at all.
Glance at the clock
Replace the waits with a timestamp, since, and a flag, turning, that says which leg the robot is on:
if (!turning && millis() - since >= 1000) { // straight leg over
turning = true;
since = millis();
}
if (turning && millis() - since >= 500) { // pivot over
turning = false;
since = millis();
}
Each if is checked every pass, and only one can fire. Resetting since starts the clock for the new leg; forget it and the next leg ends the moment it starts.
The bumper comes first
Now drive from the flag, with the bumper checked first so it beats any leg:
if (digitalRead(2) == LOW) { // bumped: freeze
analogWrite(9, 0); analogWrite(10, 0);
} else if (turning) {
analogWrite(9, 255); analogWrite(10, 0);
} else {
analogWrite(9, 255); analogWrite(10, 255);
}
The first matching branch wins, so a press stops both wheels whatever the patrol is doing, within a millisecond.
Live experiment
Poke the bumper mid-patrol
The rover patrols: straight for 1000 ms, pivot for 500 ms. Each poke holds the bumper for 0.3 s. Timed with delay(), the program reads pin 2 only once a lap, so a poke in the middle of a leg goes unheard. Timed with millis(), it reads the bumper every pass and stops the moment it's pressed.
“Patrol without waiting” is lesson 5 of 6 in Rover Ridge, Robotics World 1. Free with an account. Lessons unlock in order, so you’ll do the 4 before it first.
A state machine in miniature
turning is a two-state state machine: each state has its own wheel speeds and its own way out. Add more states, backing up or searching, and an enum with a switch keeps them tidy. The pattern underneath stays the same: check inputs first, check the clock, act, and never wait.
Free lesson
Now build it yourself
Each lesson comes in three short parts: see the idea, try it on a live board, then build it yourself, with quick checks as you go.
“Patrol without waiting” is lesson 5 of 6 in Rover Ridge, Robotics World 1. Free with an account. Lessons unlock in order, so you’ll do the 4 before it first.
Or start from the very first lesson, no account neededPractice questions
Three quick questions from the lessons. Pick an answer to check it.
Show the answer
Answer: 1.3 s later, once both delays are over. delay() stops everything, including reading the bumper, until it's over: 0.8 s left on the straight, then 0.5 s of pivot. A quick tap that's over by then is never seen at all.
- Why not “Never”? The press is held, so it's still there when the program finally reads pin 2 after the pivot.
- Why not “At once”? delay() blocks the whole program, so the bumper can't be read until both delays end.
- Why not “0.2 s later”? Nothing is read during a delay. The rest of the straight leg, 0.8 s, and the 0.5 s pivot both run first.
Show the answer
Answer: Never notices: it only reads pin 2 after both delays, and the poke is over by then. The delay() version reads the bumper once a lap, after the pivot. A quick poke comes and goes in between and is never seen. The millis() version reads it every pass and stops the same moment.
- Why not “Stops when the poke ends”? When the poke ends the bumper reads HIGH again, so even the late read sees nothing.
- Why not “Turns the other way”? Nothing in the program reacts by turning. The poke simply goes unseen.
- Why not “Stops at once”? Stopping at once needs the bumper read during the leg, which delay() prevents.
Show the answer
Answer: Starts the clock for the new leg, so the turn is timed from now. since marks when the current leg began. Setting it to now when a leg changes means the next comparison measures the new leg's length, not the total time.
- Why not “Stops the clock”? millis() keeps counting whatever happens. since just marks when the new leg began.
- Why not “Nothing”? Without the reset, the next leg's test measures time since the old leg began and ends almost at once.
- Why not “Restarts loop()”? loop() runs again on its own every pass. since is only a number the comparisons use.
“Patrol without waiting” is lesson 5 of 6 in Rover Ridge, Robotics World 1. Free with an account. Lessons unlock in order, so you’ll do the 4 before it first.
Questions people ask
Why does my Arduino robot ignore its sensors?
Usually a delay() somewhere in loop(). While it runs, the program does nothing else: no digitalRead, no analogRead, no reaction. A one-second delay makes the robot blind for a second at a time. Time things with millis() instead.
How do I time two different things with millis()?
Give each one its own timestamp, or use one timestamp and a variable that says which step you're on. Each pass, compare millis() minus the timestamp with that step's length; when it's reached, change step and reset the timestamp.
What happens when millis() overflows?
It wraps back to 0 after about 49.7 days. If you store times as unsigned long and always test millis() − since against a length, the subtraction still gives the right answer across the wrap.
Related topics
- millis() vs delay() on ArduinoWhy delay() freezes your sketch, and how millis() runs several jobs at once.
- How to blink an LED without delay() on ArduinoBlink an LED with millis() so loop() stays free for every other job.
- How to program a robot as a state machineOne state at a time, each with its own job and its own ways out.
- How to make a robot bump and turn with a bumper switchA switch on the nose, LOW on a bump, and a four-step escape: stop, pause, pivot, go.