Embedded Systems
How to blink an LED without delay() on Arduino
To blink an LED without delay(), don't wait for the time to pass: glance at the clock. Each pass of loop(), check whether millis(), the milliseconds since the board started, is at least one interval past the last flip. If it is, flip the LED and note the time. If not, carry on. loop() never stops, so it can do other jobs between flips.
The whole sketch
const unsigned long interval = 500; // ms between flips
unsigned long since = 0; // when the LED last flipped
bool on = false;
void setup() {
pinMode(9, OUTPUT);
}
void loop() {
if (millis() - since >= interval) {
since = since + interval;
on = !on;
digitalWrite(9, on ? HIGH : LOW);
}
// other jobs go here, and run on every pass
}
Line by line
sinceremembers when the LED last flipped. It's anunsigned longbecausemillis()grows large.millis() - sinceis how long it's been. Elapsed time is always now minus then.>= intervalasks whether it's time yet. Most passes, the answer is no and the block is skipped.since = since + intervalmoves the note on by exactly one interval, ready for the next flip.on = !onflips a bool, anddigitalWriteputs it on the pin.
Without a delay() in it, this loop() comes round thousands of times a second. A blink every half second takes a tiny share of that.
Live experiment
Glance at the clock, don't wait
The bar is millis() − since filling up toward the interval. When it gets there, the LED flips and since moves on by one interval. Change the interval, or freeze the clock to read the numbers mid-glance.
“Tick, don't wait” is lesson 1 of 6 in Clockwork Cove, Embedded Systems World 2. Free with an account. Lessons unlock in order, so you’ll do the 7 before it first.
The bugs people hit
Setting since = millis() instead of adding the interval works, but the check always runs a little late, and that lateness adds up: the blink slowly drifts. Adding the interval keeps the rhythm exact.
Comparing millis() with a future deadline, like millis() >= next, breaks when millis() rolls over to 0 after about 49.7 days. The subtraction millis() − since survives the rollover, as long as both are unsigned.
Why bother
A blink with delay(500) twice can only read a button once a second. Here the button, a sensor and the blink all get checked on every pass. Two LEDs at different rates are just two since variables and two intervals: each job decides for itself when it's due.
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.
“Tick, don't wait” is lesson 1 of 6 in Clockwork Cove, Embedded Systems World 2. Free with an account. Lessons unlock in order, so you’ll do the 7 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: Does nothing else. delay() stops the program on that line until the time is up. Nothing else runs, so a press in that half second goes unnoticed.
- Why not “Runs loop() twice”? loop() can't start another pass while it's stuck inside delay(); the wait has to end first.
- Why not “Keeps reading the button”? delay() holds the program on that one line, so nothing reads the button until the wait is over.
- Why not “Blinks faster”? The blink speed is set by the waits themselves. delay() makes nothing faster; it only stops the board for a while.
Show the answer
Answer: No: only 450 ms. 4650 − 4200 = 450. Not yet: check again next time round the loop.
- Why not “Yes”? 4650 − 4200 is 450 ms, still 50 ms short of the 500 the blink is waiting for.
- Why not “Exactly”? Exactly 500 ms would need millis() to read 4700. At 4650 the gap is only 450.
- Why not “Can't tell”? Both times are known, so the gap is easy to find: now minus then, 4650 − 4200 = 450 ms.
Show the answer
Answer: once a second. Each time 1000 ms have passed, the block runs and moves since on by 1000, so the next run waits another second.
- Why not “never”? millis() keeps climbing, so the gap reaches 1000 after a second and the test comes true.
- Why not “on every pass of loop()”? The block only runs once millis() − since reaches 1000. On the passes in between the test is false and it's skipped.
“Tick, don't wait” is lesson 1 of 6 in Clockwork Cove, Embedded Systems World 2. Free with an account. Lessons unlock in order, so you’ll do the 7 before it first.
Questions people ask
What is blink without delay on Arduino?
A way to blink an LED that never stops the program. Instead of delay(), each pass of loop() checks whether enough time has passed since the last flip, using millis(). If it has, it flips the LED; if not, loop() carries on with its other jobs.
Why use unsigned long for millis()?
millis() returns an unsigned long, a whole number from 0 to about 4.3 billion. An int on an Uno stops at 32,767, which millis() passes after about 33 seconds, and the timing breaks.
Can I blink two LEDs at different rates without delay?
Yes. Give each LED its own since variable and its own interval, and check each one on every pass of loop(). Neither waits for the other, so each keeps its own rhythm.
Related topics
- millis() vs delay() on ArduinoWhy delay() freezes your sketch, and how millis() runs several jobs at once.
- How to debounce a button on ArduinoWhy one press counts as four, and the 50 ms fixes that make it count once.
- How does PWM work?How a pin that's only on or off dims an LED: duty cycle and analogWrite's 0–255.