Arduino - Time
Arduino - Time and Concurrency
Section titled “Arduino - Time and Concurrency”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' millisecondsvoid delayMicroseconds(unsigned int us); // Pauses for 'us' microsecondsThe 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 millisecondsThe 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.
Example: Blink an LED Without Using ‘delay()’
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 intervalconst int ledPin = LED_BUILTIN; // Use the built-in LEDconst long interval = 1000; // Interval at which to blink (1000 milliseconds = 1 second)
// Variables to store the stateint ledState = LOW; // ledState used to set the LED
// Will store the last time the LED was updatedunsigned 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.}Microsecond Precision: ‘micros()’
Section titled “Microsecond Precision: ‘micros()’”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 microsecondsHandling Rollover (Overflow)
Section titled “Handling Rollover (Overflow)”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.
Best Practices
Section titled “Best Practices”- Avoid
delay(): For any project that does more than one simple, sequential task, avoiddelay(). Adopt themillis()pattern early. - Use
unsigned long: Always store the result ofmillis()ormicros()in anunsigned longvariable to avoid overflow issues and bugs. - Manage Multiple Timers: You can manage multiple independent timed events by using a separate
previousMillisandintervalvariable for each one. - Choose the Right Tool: Use
millis()for most human-scale timing (seconds, minutes). Usemicros()only when you need very high precision for fast hardware interactions.