Project Overview
Arduino Nano + millis() multitasking: In this build, you will use an Arduino (Nano or Uno) and the built-in millis() timer to blink two LEDs at different rates, adjust one blink speed live with a potentiometer, and read a button instantly without using delay().
delay() is often the first function beginners learn and the first one that holds them back: while the Arduino is delaying, it cannot read a button, update a display, or blink a second LED. The fix is to replace “wait” with “check the clock.” This guide rebuilds the blink sketch around millis(), adds a second LED with its own rhythm, lets a potentiometer change the speed live, and reads a button with zero lag, all in one loop that never blocks.
- Time: ~30 minutes
- Skill level: Beginner
- What you will build: A non-blocking sketch with independent timers, a live-adjustable blink rate, an instant button, and a reusable timer helper you will paste into every future project.
Parts List
From ShillehTek
- Arduino Nano V3.0 Pre-Soldered - the board used for the demo wiring and code.
- Arduino Uno R3 Super Starter Kit - includes LEDs, a potentiometer, and a button.
- Resistor Kit - 220Ω x2 for the LED current limiting resistors.
- Tactile Button Kit - momentary pushbutton for instant input testing.
- 400-Point Breadboard - for quick prototyping.
- Dupont Jumper Wires - for breadboard connections.
External
- None
Note: millis() returns the number of milliseconds since the board started, as an unsigned long. Always write timing as millis() - previous >= interval; that subtraction stays correct even when millis() rolls over after 49.7 days.
Step-by-Step Guide
Step 1 - Wire Two LEDs, a Pot, and a Button
Goal: Build the hardware setup with four things to juggle: two independent LED blink outputs, one analog input (pot), and one digital input (button).
What to do: Wire the following:
- LED A: D5 → 220Ω → LED → GND
- LED B: D6 → 220Ω → LED → GND
- Potentiometer: ends to 5V and GND, wiper → A0
- Button: D2 to GND (use the internal pull-up in code)
Expected result: Your circuit is ready for both the “bad” (delay-based) and the “good” (millis-based) sketch behavior.
Step 2 - Feel the Problem
Goal: Understand why delay() has to go for responsive input and multiple tasks.
What to do: Write the obvious version: toggle LED A, delay(1000), and in the same loop check the button and light LED B while it is held. Press the button.
It works only if you happen to press during the tiny gap between delays. Most presses are missed, and LED B can lag by up to a second.
Expected result: You see laggy or missed button input, which demonstrates the blocking problem.
Step 3 - The Sketch: Everything on the Clock
Goal: Replace blocking delays with independent timers so each task can run on its own schedule.
What to do: Upload this sketch, then turn the pot and tap/hold the button.
Code:
const int LED_A = 5, LED_B = 6, POT = A0, BTN = 2;
// a tiny reusable timer: "has `interval` ms passed since I last fired?"
struct Every {
unsigned long interval, last = 0;
Every(unsigned long ms) : interval(ms) {}
bool ready() {
unsigned long now = millis();
if (now - last >= interval) { last = now; return true; } // rollover-safe
return false;
}
};
Every blinkA(1000); // LED A: fixed 1 s
Every blinkB(250); // LED B: set by the pot
Every report(500); // Serial status twice a second
void setup() {
Serial.begin(9600);
pinMode(LED_A, OUTPUT); pinMode(LED_B, OUTPUT); pinMode(BTN, INPUT_PULLUP);
}
void loop() {
// task 1: LED A blinks at 1 Hz, no matter what else happens
if (blinkA.ready()) digitalWrite(LED_A, !digitalRead(LED_A));
// task 2: LED B blinks at a rate set live by the knob (50 ms ... 1000 ms)
blinkB.interval = map(analogRead(POT), 0, 1023, 50, 1000);
if (blinkB.ready()) digitalWrite(LED_B, !digitalRead(LED_B));
// task 3: the button is checked thousands of times a second: instant response
if (digitalRead(BTN) == LOW) { digitalWrite(LED_A, HIGH); digitalWrite(LED_B, HIGH); }
// task 4: periodic status without slowing anything down
if (report.ready()) {
Serial.print("LED B interval: "); Serial.print(blinkB.interval); Serial.println(" ms");
}
// notice: no delay() anywhere
}
Expected result: LED A ticks steadily at one second, LED B speeds up and slows down as you turn the knob, and both LEDs light instantly whenever the button is pressed, while the Serial Monitor reports on schedule.
Step 4 - Read the Pattern, Then Reuse It
Goal: Internalize the non-blocking “task” pattern so you can scale it to larger projects.
What to do: Notice that every task follows the same shape: remember when it last ran, and if enough time has passed, run it again and update the timestamp. The Every helper packages that so each task becomes one if (x.ready()).
Add a fifth task (for example, a heartbeat print every 10 seconds) and notice it takes two lines and changes nothing else.
Expected result: Adding features no longer breaks the timing of existing features.
Step 5 - Where delay() Still Hides
Goal: Learn where blocking behavior can still show up, even after you remove explicit delay() calls.
What to do: Look for blocking calls you did not write directly:
-
pulseIn()on an ultrasonic sensor (use a timeout) - a DHT read (only every 2 s)
- long Serial prints at 9600 baud (raise the baud rate)
- an LCD full redraw (redraw only what changed)
Handle button debouncing with a timestamp too, instead of a delay. For state that spans time (for example, “beep for 200 ms then stop”), store an end time and check it, rather than delaying.
Expected result: Sketches that stay responsive as they grow.
Conclusion
Replacing delay() with millis() is one of the biggest upgrades in an Arduino programmer’s toolkit. With one small timer helper, tasks become independent, the button becomes instant, and adding the next feature stops being scary.
Reference credit: photos and images are credited to mdraber on Hackster.io, whose original guide served as the reference for this ShillehTek version.
Want the exact parts used in this build? Grab them from ShillehTek.com. If you want help customizing this project or building something for your product, check out our IoT consulting services.








