Bird
Raised Fist0
Arduinoprogramming~10 mins

Multiple timed events with millis() in Arduino - Step-by-Step Execution

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Concept Flow - Multiple timed events with millis()
Start program
Record current millis() as lastEvent1
Record current millis() as lastEvent2
Loop start
Check if millis() - lastEvent1 >= interval1?
NoCheck if millis() - lastEvent2 >= interval2?
Run event1 code
Update lastEvent1 = millis()
Check if millis() - lastEvent2 >= interval2?
Yes
Run event2 code
Update lastEvent2 = millis()
Loop back to start
The program tracks two separate timers using millis() and runs each event when its interval passes, updating the last run time.
Execution Sample
Arduino
unsigned long lastEvent1 = 0;
unsigned long lastEvent2 = 0;
const unsigned long interval1 = 1000;
const unsigned long interval2 = 1500;

void loop() {
  unsigned long current = millis();
  if (current - lastEvent1 >= interval1) {
    // Event 1 code
    lastEvent1 = current;
  }
  if (current - lastEvent2 >= interval2) {
    // Event 2 code
    lastEvent2 = current;
  }
}
This code runs two timed events independently using millis() without delay(), triggering event1 every 1000ms and event2 every 1500ms.
Execution Table
Stepmillis() (current)Check Event1 (current - lastEvent1 >= 1000)Event1 ActionUpdate lastEvent1Check Event2 (current - lastEvent2 >= 1500)Event2 ActionUpdate lastEvent2
100 - 0 >= 1000 → falseNolastEvent1=00 - 0 >= 1500 → falseNolastEvent2=0
210001000 - 0 >= 1000 → trueRun Event1lastEvent1=10001000 - 0 >= 1500 → falseNolastEvent2=0
315001500 - 1000 >= 1000 → falseNolastEvent1=10001500 - 0 >= 1500 → trueRun Event2lastEvent2=1500
420002000 - 1000 >= 1000 → trueRun Event1lastEvent1=20002000 - 1500 >= 1500 → falseNolastEvent2=1500
530003000 - 2000 >= 1000 → trueRun Event1lastEvent1=30003000 - 1500 >= 1500 → trueRun Event2lastEvent2=3000
635003500 - 3000 >= 1000 → falseNolastEvent1=30003500 - 3000 >= 1500 → falseNolastEvent2=3000
740004000 - 3000 >= 1000 → trueRun Event1lastEvent1=40004000 - 3000 >= 1500 → falseNolastEvent2=3000
845004500 - 4000 >= 1000 → falseNolastEvent1=40004500 - 3000 >= 1500 → trueRun Event2lastEvent2=4500
950005000 - 4000 >= 1000 → trueRun Event1lastEvent1=50005000 - 4500 >= 1500 → falseNolastEvent2=4500
Exit55005500 - 5000 >= 1000 → falseNolastEvent1=50005500 - 4500 >= 1500 → falseNolastEvent2=4500
💡 Simulation stops after step 9; no events triggered at 5500ms.
Variable Tracker
VariableStartAfter Step 2After Step 3After Step 4After Step 5After Step 6After Step 7After Step 8After Step 9
lastEvent1010001000200030003000400040005000
lastEvent2001500150030003000300045004500
Key Moments - 3 Insights
Why do we use 'current - lastEvent >= interval' instead of 'current >= lastEvent + interval'?
Using subtraction avoids problems when millis() overflows (resets to 0). The subtraction still works correctly due to unsigned arithmetic, as shown in the checks in the execution_table.
Why do both events run independently and not block each other?
Each event checks its own timer separately without delay(), so one event running does not stop the other from being checked, as seen in steps 5 and 8 where both events trigger at different times.
What happens if the event code takes too long to run?
If event code runs too long, it can delay checking other events, causing timing inaccuracies. The millis() checks rely on quick loop cycles, so event code should be short or non-blocking.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table at Step 5. What events run at 3000ms?
AOnly Event1 runs
BOnly Event2 runs
CBoth Event1 and Event2 run
DNo events run
💡 Hint
Check columns 'Event1 Action' and 'Event2 Action' at Step 5 in execution_table.
According to variable_tracker, what is lastEvent2 after Step 3?
A1500
B0
C3000
D1000
💡 Hint
Look at the 'lastEvent2' row and 'After Step 3' column in variable_tracker.
If interval1 was changed to 2000ms, how would the execution_table change?
AEvent2 timing would change to 2000ms
BEvent1 would run less often, roughly every 2000ms
CEvent1 would run more often, roughly every 500ms
DNo change in event timings
💡 Hint
Consider how interval1 controls Event1 timing in the 'Check Event1' column of execution_table.
Concept Snapshot
Use millis() to track elapsed time without delay().
Store last event time in a variable.
In loop, check if current millis() minus last event time >= interval.
If yes, run event and update last event time.
Allows multiple independent timed events.
Avoids blocking code and handles millis() overflow safely.
Full Transcript
This example shows how to run multiple timed events independently using the Arduino millis() function. We keep track of the last time each event ran using variables lastEvent1 and lastEvent2. In the main loop, we get the current time with millis() and check if enough time has passed for each event by subtracting the last event time from current time and comparing to the event's interval. If the interval has passed, we run the event code and update the last event time to the current time. This way, events run independently without blocking each other or using delay(), which would stop the program. The execution table traces the millis() values and decisions step by step, showing when each event runs and updates its timer. The variable tracker shows how lastEvent1 and lastEvent2 change over time. Key moments clarify why subtraction is used for timing, how events run independently, and the importance of keeping event code short. The visual quiz tests understanding of event timing and variable changes. This approach is essential for responsive Arduino programs with multiple timed tasks.

Practice

(1/5)
1. What is the main advantage of using millis() for timing multiple events in Arduino instead of delay()?
easy
A. It resets the Arduino automatically.
B. It stops the program until the time passes.
C. It makes the program run slower.
D. It allows the program to run other tasks while waiting.

Solution

  1. Step 1: Understand delay() behavior

    delay() pauses the whole program, stopping all other actions.
  2. Step 2: Understand millis() advantage

    millis() returns elapsed time without stopping the program, so other tasks can run simultaneously.
  3. Final Answer:

    It allows the program to run other tasks while waiting. -> Option D
  4. Quick Check:

    millis() enables multitasking [OK]
Hint: Remember: delay() stops, millis() doesn't [OK]
Common Mistakes:
  • Thinking millis() pauses the program
  • Confusing delay() with non-blocking timing
  • Believing millis() resets Arduino
2. Which of the following is the correct way to declare a variable to store the last time an event occurred using millis()?
easy
A. unsigned long lastEventTime = 0;
B. int lastEventTime = 0;
C. float lastEventTime = 0.0;
D. char lastEventTime = '0';

Solution

  1. Step 1: Identify the correct data type for time

    Since millis() returns an unsigned long value, the variable must be unsigned long.
  2. Step 2: Check variable initialization

    Initializing to 0 is correct to mark the start time.
  3. Final Answer:

    unsigned long lastEventTime = 0; -> Option A
  4. Quick Check:

    Use unsigned long for millis() times [OK]
Hint: Use unsigned long for time variables with millis() [OK]
Common Mistakes:
  • Using int which can overflow quickly
  • Using float which is not precise for time
  • Using char which is for characters, not numbers
3. What will be the output of the following Arduino code snippet if millis() returns 5000 at the moment of checking?
unsigned long previousMillis = 3000;
unsigned long interval = 1500;

if (millis() - previousMillis >= interval) {
  Serial.println("Event triggered");
} else {
  Serial.println("Waiting");
}
medium
A. Waiting
B. No output
C. Event triggered
D. Compilation error

Solution

  1. Step 1: Calculate elapsed time

    Elapsed time = 5000 (current millis) - 3000 (previousMillis) = 2000 ms.
  2. Step 2: Compare elapsed time with interval

    Interval is 1500 ms. Since 2000 >= 1500, the condition is true, so "Event triggered" should print.
  3. Final Answer:

    Event triggered -> Option C
  4. Quick Check:

    Elapsed time 2000 >= 1500 triggers event [OK]
Hint: Subtract previousMillis from millis() to check elapsed time [OK]
Common Mistakes:
  • Mixing up >= and > operators
  • Forgetting to subtract previousMillis
  • Assuming output without calculation
4. Identify the error in this code snippet that tries to blink two LEDs at different intervals using millis():
unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;
const long interval1 = 1000;
const long interval2 = 2000;

void loop() {
  if (millis() - previousMillis1 >= interval1) {
    digitalWrite(LED1, !digitalRead(LED1));
    previousMillis1 = millis();
  }
  if (millis() - previousMillis1 >= interval2) {
    digitalWrite(LED2, !digitalRead(LED2));
    previousMillis2 = millis();
  }
}
medium
A. LED pins are not defined.
B. Second if condition uses previousMillis1 instead of previousMillis2.
C. Intervals should be unsigned long, not long.
D. digitalWrite cannot toggle LEDs.

Solution

  1. Step 1: Check timing variables in conditions

    The second if condition incorrectly uses previousMillis1 instead of previousMillis2, causing wrong timing for LED2.
  2. Step 2: Understand impact of error

    This mistake means LED2 timing depends on LED1 timing, so LED2 won't blink at its own interval.
  3. Final Answer:

    Second if condition uses previousMillis1 instead of previousMillis2. -> Option B
  4. Quick Check:

    Each event needs its own previousMillis variable [OK]
Hint: Use separate previousMillis for each timed event [OK]
Common Mistakes:
  • Reusing the same previousMillis variable for multiple events
  • Not updating previousMillis after event
  • Confusing interval variables
5. You want to control three LEDs blinking at 500ms, 1000ms, and 1500ms intervals respectively without using delay(). Which approach correctly manages all three timed events using millis()?
hard
A. Use three separate previousMillis variables and check each with its own interval inside loop().
B. Use one previousMillis variable and reset it after each LED toggles.
C. Use delay(500) and toggle all LEDs together.
D. Use millis() only once and toggle LEDs based on dividing millis() by intervals.

Solution

  1. Step 1: Understand independent timing needs

    Each LED needs its own timer to blink independently at different intervals.
  2. Step 2: Evaluate options

    Use three separate previousMillis variables and check each with its own interval inside loop(), allowing independent timing without blocking.
  3. Final Answer:

    Use three separate previousMillis variables and check each with its own interval inside loop(). -> Option A
  4. Quick Check:

    Separate timers for each event [OK]
Hint: Assign each event its own timer variable for independent control [OK]
Common Mistakes:
  • Using one timer for all events causing sync issues
  • Using delay which blocks other events
  • Trying to calculate toggles from one millis() value without state