Blinking without stopping
delay() blinks the LED by stopping the whole sketch, so anything else the sketch should notice while it waits is missed. Checking the clock with millis() blinks it exactly the same and leaves the sketch free.
Deaf while it waits
Both lanes blink the LED identically: half a second on, half a second off. The difference is what else each sketch can do.
The first blink sketch spends its whole life inside delay(). It sets the pin,
waits 500 ms, sets it again and waits another 500 ms. If it also read a button,
it could only do that in the moment between the second delay and the first,
once a second. A press that begins and ends inside a delay is never seen at
all. In the figure, one press out of four happens to be held at that moment,
and it is caught by luck.
Look at the clock instead
millis() returns the number of milliseconds since the board started. Instead
of waiting half a second, the sketch asks, every time round loop(), whether
half a second has passed since the LED last changed. If it has, it changes the
LED and notes the time. If not, it carries on at once.
So loop() comes round thousands of times a second, and every pass is a chance
to read a button, a sensor or the serial port. The LED keeps perfect time, and
nothing else waits for it.
Three details that matter
unsigned long, for every variable that holds a time.millis()outgrows anintin about half a minute on an Uno.now - lastChange >= INTERVAL_MS, written as a subtraction. After about 49 daysmillis()wraps back to zero, and the subtraction still gives the right answer across the wrap. Comparing against a future time does not.- Nothing else in
loop()may block. Onedelay()elsewhere brings the problem back.
The LED as a signal from your code
This is how the LED becomes useful beyond blinking. A sketch can light it when a reading crosses a threshold, flash it while the Wi-Fi connects, or blink a count to report an error, all without stopping to do it. Most of the TinkerBlock lessons after this one use it that way, as a light that says what the code is doing.
The code
Blinks the LED once a second and counts loop() passes, to show the sketch never stops. No library. Change LED_PIN to the pin you wired SIGNAL to.
/*
XL LED - blinking without delay() TK01 / /p/tk01
Wiring. Count from the square pad on the TinkerBlock board, LED side
up, header at the bottom:
GND -> GND
NC -> nothing (both NC pins are unconnected on the board)
SIGNAL -> D9 on an Uno, GPIO 4 on an ESP32 or ESP32-S3,
GP15 on a Raspberry Pi Pico
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
*/
// The GPIO number SIGNAL is wired to. Uno: 9. ESP32, ESP32-S3: 4. Pico: 15.
const int LED_PIN = 4;
const unsigned long INTERVAL_MS = 500; // on for 500 ms, off for 500 ms
unsigned long lastChange = 0;
bool ledOn = false;
unsigned long passes = 0;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
unsigned long now = millis();
// Has half a second gone by since the LED last changed?
if (now - lastChange >= INTERVAL_MS) {
lastChange = now;
ledOn = !ledOn;
digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
if (ledOn) { // once a second, report and reset
Serial.print("loop() ran ");
Serial.print(passes);
Serial.println(" times in the last second");
passes = 0;
}
}
passes++;
// Anything else goes here: read a button, a sensor, the serial port.
// None of it waits for the LED.
}The count printed each second is how many times loop() ran in that second: tens of thousands on an Uno, more on an ESP32. With delay() it would be one. Every one of those passes is a chance to read a button or a sensor, which is the whole point.
The same pattern in MicroPython: ticks_ms is the clock and ticks_diff subtracts two readings of it, so the loop never sleeps and never stops.
"""
XL LED - blinking without sleep(), MicroPython TK01 / /p/tk01
Wiring. Count from the square pad on the TinkerBlock board, LED side
up, header at the bottom:
GND -> GND
NC -> nothing (both NC pins are unconnected on the board)
SIGNAL -> GPIO 4 on an ESP32 or ESP32-S3, GP15 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
Nothing to install: machine and time are built in.
"""
from machine import Pin
import time
# The GPIO number SIGNAL is wired to. ESP32, ESP32-S3: 4. Pico: 15.
LED_PIN = 4
INTERVAL_MS = 500 # on for 500 ms, off for 500 ms
led = Pin(LED_PIN, Pin.OUT)
last_change = time.ticks_ms()
passes = 0
while True:
now = time.ticks_ms()
# Has half a second gone by since the LED last changed?
if time.ticks_diff(now, last_change) >= INTERVAL_MS:
last_change = now
led.value(not led.value())
if led.value(): # once a second, report and reset
print("loop ran", passes, "times in the last second")
passes = 0
passes += 1
# Anything else goes here: read a button, a sensor, the serial port.
# None of it waits for the LED.Use ticks_diff, never a plain subtraction. ticks_ms wraps round much sooner than Arduino's millis, and ticks_diff gives the right answer across the wrap. The count printed is how many times the loop ran in a second.
When it does not work
millis() counts milliseconds since the board started, and it passes 32,767 after about half a minute. On an Uno an int would overflow and the blink would stop. An unsigned long lasts about 49 days, and the subtraction in the sketch still works when it wraps round.
millis() wraps back to zero, and the sketch keeps working. now - lastChange is computed in unsigned arithmetic, which gives the right answer across the wrap. Comparing millis() with a future time, such as millis() > nextChange, is the version that breaks.
Something else in loop() is blocking: another delay(), a long Serial print at low baud, or a library call that waits. The millis() pattern only keeps time if loop() comes round often. Find the blocking call and give it the same treatment.
Yes, that is what this pattern is for. Give each LED its own lastChange variable and interval, and its own if block in loop(). Two TK01 blocks on two pins will blink independently without either waiting for the other.
The short list of reasons, in the order they are usually the answer.
When it stays dark →Edit this page — content/books/xl-led/blinking-without-stopping.mdx
Questions about this product
See what other owners have asked, and read their solutions.
XL LED
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.