Skip to content

Arduino - Time

Managing time is one of the most fundamental aspects of embedded programming. Arduino provides simple functions for pausing execution, but to create responsive and complex projects, you must learn to handle time in a non-blocking way.

The ‘delay()’ Function: Simple but Blocking

Section titled “The ‘delay()’ Function: Simple but Blocking”

The delay() function is often the first timing function a beginner learns. It pauses the program for a specified number of milliseconds.

Signatures:

void delay(unsigned long ms); // Pauses for 'ms' milliseconds
void delayMicroseconds(unsigned int us); // Pauses for 'us' microseconds

The Problem with delay(): When your program is inside a delay() call, it is blocked. It cannot read sensors, check for button presses, or communicate over serial. This makes it unsuitable for any project that needs to do more than one thing at a time.

The ‘millis()’ Function: The Key to Non-Blocking Code

Section titled “The ‘millis()’ Function: The Key to Non-Blocking Code”

The millis() function is the cornerstone of multitasking on an Arduino. It returns the number of milliseconds that have passed since the Arduino board began running the current program.

Signature:

unsigned long millis(); // Returns time since boot in milliseconds

The Core Idea: Instead of pausing, you record the time an event happened. Then, inside your main loop(), you continuously check if enough time has passed to trigger the next event. This is like looking at a clock to see if it’s time to do something, rather than starting a stopwatch and staring at it.

Section titled “Example: Blink an LED Without Using ‘delay()’”

This is a canonical example that demonstrates the power of millis(). It blinks an LED while leaving the loop() free to perform other tasks.

// Constants for the LED pin and blink interval
const int ledPin = LED_BUILTIN; // Use the built-in LED
const long interval = 1000; // Interval at which to blink (1000 milliseconds = 1 second)
// Variables to store the state
int ledState = LOW; // ledState used to set the LED
// Will store the last time the LED was updated
unsigned long previousMillis = 0;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
// Get the current time
unsigned long currentMillis = millis();
// Check if it's time to blink the LED
if (currentMillis - previousMillis >= interval) {
// Save the last time you blinked the LED
previousMillis = currentMillis;
// If the LED is off turn it on and vice-versa
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// Set the LED with the new ledState
digitalWrite(ledPin, ledState);
}
// --- YOU CAN ADD OTHER CODE HERE ---
// For example, read a sensor or check a button.
// This code will run continuously without being blocked by the LED blinking.
}

For tasks requiring higher temporal resolution (like controlling servos or reading certain sensors), the micros() function is available. It works exactly like millis() but returns the time in microseconds since the program started.

Signature:

unsigned long micros(); // Returns time since boot in microseconds

The millis() function uses an unsigned long, which can store a value up to 4,294,967,295. This means millis() will overflow (reset to zero) after approximately 49.7 days. The micros() function overflows much faster, after about 70 minutes.

Fortunately, the subtraction technique used in the “Blink Without Delay” example (currentMillis - previousMillis) is designed to handle this rollover correctly due to the nature of unsigned integer arithmetic. As long as you use this pattern, you do not need to worry about the rollover for intervals shorter than 49 days.

  • Avoid delay(): For any project that does more than one simple, sequential task, avoid delay(). Adopt the millis() pattern early.
  • Use unsigned long: Always store the result of millis() or micros() in an unsigned long variable to avoid overflow issues and bugs.
  • Manage Multiple Timers: You can manage multiple independent timed events by using a separate previousMillis and interval variable for each one.
  • Choose the Right Tool: Use millis() for most human-scale timing (seconds, minutes). Use micros() only when you need very high precision for fast hardware interactions.