Chapter 11

Up to now, we’ve used delay() everywhere.

And that’s okay — for learning.

But at some point, delay() stops being your friend.

This chapter explains why, and introduces a better way to handle time on Arduino.


⏳ What delay() Actually Does

When you write:
delay(1000);

Arduino does exactly this:

Stop everything. Wait for 1 second. Then continue.

During that delay:

  • no buttons are read

  • no sensors are checked

  • no logic runs

  • nothing else happens

The microcontroller is literally doing nothing.


🧠 Why This Is a Problem

Imagine you want to:

  • blink an LED

  • read a button

  • update a display

at the same time.

With delay():

  • LED blocks button reading

  • button blocks sensor updates

  • everything waits on everything else

This creates unresponsive systems.


🧪 Simple Example of the Problem

digitalWrite(LED_PIN, HIGH);
delay(1000);
digitalWrite(LED_PIN, LOW);
delay(1000);

During those 2 seconds:

  • a button press is ignored

  • a sensor change is missed

This is fine for demos, not for real systems.

Try using the button in this simulation to notice the blocking nature of Delay!

🔢 What Is millis()?

Instead of waiting, we can ask Arduino:

“Has enough time passed?”

That’s what millis() does.

millis() returns:

  • the number of milliseconds

  • since the Arduino started running

It keeps increasing:

0, 1, 2, 3, ...

And it never blocks your program.


🔁 The Core Idea (Very Important)

Instead of this:
delay(1000);

We do this:

  • remember the last time something happened

  • compare it to the current time

  • act only if enough time has passed


🧩 Non-Blocking Blink Example

const int ledPin = 13;
unsigned long previousTime = 0;
const long interval = 1000;

void setup() { pinMode(ledPin, OUTPUT); }

void loop() { unsigned long currentTime = millis();

if (currentTime - previousTime >= interval) { previousTime = currentTime; digitalWrite(ledPin, !digitalRead(ledPin)); } }

This LED still blinks every second.

But now:

  • the loop keeps running

  • buttons can be read

  • sensors can be checked

  • multiple tasks can coexist

Try using the button in this simulation to notice the non blocking nature of Millis!

🧠 Why This Works

You’re no longer pausing time.

You’re:

  • letting the program run freely

  • making decisions based on time differences

This is how real embedded systems work.


What happens when your Arduino executes delay(1000)? It can perform other tasks while waiting It pauses the program, preventing other code from running during the delay It automatically checks button inputs in the background Why is millis() preferred over delay() for many Arduino projects? It makes the Arduino run faster It allows multiple tasks to run without blocking the program It uses less memory than variables Using delay() blocks the Arduino from doing anything else until the delay finishes. With millis(), the Arduino keeps checking the elapsed time while continuing to read sensors, respond to buttons, and control outputs.

⚠️ Important Notes

  • millis() uses unsigned long

  • it overflows after ~49 days (we’ll handle that later)

  • this pattern scales to many tasks

Don’t worry if this feels harder than delay() — that’s normal.


🧰 Common Beginner Mistakes

  • Mixing delay() and millis() blindly

  • Forgetting to update previous time

  • Using int instead of unsigned long

  • Overcomplicating the logic early

Master the idea, not the syntax.


📌 What Comes Next

Now that we can:

  • control outputs

  • read inputs

  • handle time properly

We need a way to see what’s happening inside our code.

👉 Next Chapter — Serial Monitor: Your First Debug Tool