Bird
Raised Fist0
Arduinoprogramming~10 mins

millis() for non-blocking timing 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 - millis() for non-blocking timing
Start Program
Record startTime = millis()
Loop Start
Check: millis() - startTime >= interval?
NoDo other tasks
Yes
Perform timed action
Update startTime = millis()
Back to Loop Start
The program records the start time, then repeatedly checks if the set interval has passed without stopping other tasks. When the interval passes, it performs the action and resets the timer.
Execution Sample
Arduino
unsigned long startTime = 0;
const unsigned long interval = 1000;

void loop() {
  if (millis() - startTime >= interval) {
    // action
    startTime = millis();
  }
  // other code
}
This code runs an action every 1000 milliseconds without stopping other code from running.
Execution Table
Stepmillis() value (ms)startTime (ms)Condition millis() - startTime >= 1000?Action Taken
1000 - 0 >= 1000 → falseNo action, continue loop
25000500 - 0 >= 1000 → falseNo action, continue loop
3100001000 - 0 >= 1000 → truePerform action, update startTime to 1000
4150010001500 - 1000 >= 1000 → falseNo action, continue loop
5200010002000 - 1000 >= 1000 → truePerform action, update startTime to 2000
6250020002500 - 2000 >= 1000 → falseNo action, continue loop
7300020003000 - 2000 >= 1000 → truePerform action, update startTime to 3000
8350030003500 - 3000 >= 1000 → falseNo action, continue loop
9400030004000 - 3000 >= 1000 → truePerform action, update startTime to 4000
10450040004500 - 4000 >= 1000 → falseNo action, continue loop
ExitN/AN/ALoop runs indefinitelyNo exit
💡 Loop runs forever; timing checks continue without blocking
Variable Tracker
VariableStartAfter Step 3After Step 5After Step 7After Step 9Final
startTime010002000300040004000
millis()010002000300040004500
Key Moments - 3 Insights
Why do we use 'millis() - startTime >= interval' instead of 'millis() >= startTime + interval'?
Using subtraction avoids problems when millis() overflows (resets to 0 after ~50 days). The subtraction still works correctly with unsigned numbers, as shown in the execution_table steps.
Why doesn't this code stop other parts of the program from running?
Because it only checks the time difference and performs the action when needed, it never uses delay() or blocking calls, so other code can run continuously.
What happens if the action takes longer than the interval?
The next check will happen after the action finishes, so the timing might drift. The startTime updates after the action, as seen in the execution_table rows where startTime changes.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table at Step 3, what is the value of startTime after the action?
A1000
B0
C500
D1500
💡 Hint
Check the 'startTime' column in execution_table row for Step 3.
At which step does the condition 'millis() - startTime >= 1000' first become true?
AStep 2
BStep 3
CStep 4
DStep 5
💡 Hint
Look at the 'Condition' column in execution_table to find when it first becomes true.
If the interval was changed to 500 ms, how would the action frequency change in the execution_table?
AAction frequency would not change
BAction would happen half as often
CAction would happen twice as often
DAction would never happen
💡 Hint
Consider how interval affects the condition 'millis() - startTime >= interval' in the execution_table.
Concept Snapshot
millis() returns time since program start in ms
Use 'if (millis() - startTime >= interval)' to check elapsed time
Perform action and reset startTime = millis() when interval passes
Allows non-blocking timing without delay()
Keeps other code running smoothly
Handles millis() overflow safely
Full Transcript
This example shows how to use millis() for non-blocking timing in Arduino. The program records the start time, then repeatedly checks if the set interval has passed by subtracting startTime from the current millis() value. When the difference is equal or greater than the interval, it performs the action and updates startTime to the current millis(). This method avoids blocking the program, so other code can run continuously. The subtraction method also safely handles the overflow of millis() after about 50 days. The execution table traces millis() and startTime values step-by-step, showing when the action happens and how startTime updates. Key moments clarify why subtraction is used, how this avoids blocking, and what happens if the action takes longer than the interval. The visual quiz tests understanding of timing checks and variable changes. The snapshot summarizes the key points for quick reference.

Practice

(1/5)
1. What does the millis() function in Arduino return?
easy
A. The current date and time
B. The number of milliseconds since the Arduino board started running the current program
C. The number of microseconds since the last reset
D. The current time in seconds

Solution

  1. Step 1: Understand what millis() measures

    millis() returns the time in milliseconds since the Arduino started running the program.
  2. Step 2: Compare options with the definition

    Only The number of milliseconds since the Arduino board started running the current program correctly describes this behavior; others mention seconds, microseconds, or date/time which are incorrect.
  3. Final Answer:

    The number of milliseconds since the Arduino board started running the current program -> Option B
  4. Quick Check:

    millis() = milliseconds since start [OK]
Hint: Remember millis() counts milliseconds since start [OK]
Common Mistakes:
  • Confusing millis() with delay()
  • Thinking millis() returns seconds
  • Assuming millis() gives current date/time
2. Which of the following is the correct way to store the current time using millis() in Arduino?
easy
A. char currentTime = millis();
B. int currentTime = millis();
C. float currentTime = millis();
D. unsigned long currentTime = millis();

Solution

  1. Step 1: Identify the correct data type for millis()

    millis() returns an unsigned long value representing milliseconds.
  2. Step 2: Match the data type with variable declaration

    Only unsigned long currentTime = millis(); uses unsigned long, which can hold large millisecond values without overflow.
  3. Final Answer:

    unsigned long currentTime = millis(); -> Option D
  4. Quick Check:

    Use unsigned long for millis() values [OK]
Hint: Use unsigned long to store millis() time [OK]
Common Mistakes:
  • Using int which can overflow quickly
  • Using float or char which are incorrect types
  • Not declaring variable before assignment
3. What will the following Arduino code print to the Serial Monitor?
unsigned long previousMillis = 0;
const long interval = 1000;

void setup() {
  Serial.begin(9600);
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    Serial.println("Tick");
  }
}
medium
A. Prints "Tick" once and stops
B. Prints "Tick" continuously without delay
C. Prints "Tick" every 1000 milliseconds without stopping the program
D. Causes a compile error due to variable scope

Solution

  1. Step 1: Understand the timing logic

    The code checks if 1000 milliseconds have passed since last print using millis() and updates previousMillis accordingly.
  2. Step 2: Analyze the output behavior

    When 1000 ms pass, it prints "Tick" and continues looping without blocking, so it prints every second repeatedly.
  3. Final Answer:

    Prints "Tick" every 1000 milliseconds without stopping the program -> Option C
  4. Quick Check:

    Non-blocking timing prints "Tick" every second [OK]
Hint: Check millis() difference to print repeatedly [OK]
Common Mistakes:
  • Thinking it prints only once
  • Confusing with delay() causing blocking
  • Assuming compile error due to variable scope
4. Identify the error in this Arduino code using millis() for timing:
unsigned long previousMillis;
const long interval = 2000;

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (millis() - previousMillis >= interval) {
    Serial.println("Hello");
  }
}
medium
A. previousMillis is never updated, so "Hello" prints continuously
B. interval should be unsigned long, not long
C. Serial.begin() is missing in setup()
D. millis() cannot be used in loop()

Solution

  1. Step 1: Check how previousMillis is used

    The code checks the time difference but never updates previousMillis after printing.
  2. Step 2: Understand the effect of missing update

    Without updating, the condition stays true, so "Hello" prints repeatedly without delay.
  3. Final Answer:

    previousMillis is never updated, so "Hello" prints continuously -> Option A
  4. Quick Check:

    Update previousMillis to avoid continuous printing [OK]
Hint: Always update previousMillis after action [OK]
Common Mistakes:
  • Forgetting to update previousMillis
  • Thinking interval type causes error
  • Assuming Serial.begin() is missing
5. You want to blink an LED every 500 milliseconds without stopping other code from running. Which code snippet correctly uses millis() for this non-blocking timing? A)
unsigned long previousMillis = 0;
const long interval = 500;
void loop() {
  if (millis() - previousMillis >= interval) {
    previousMillis = millis();
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
  // other code runs here
}
B)
void loop() {
  delay(500);
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
C)
unsigned long previousMillis = 0;
const long interval = 500;
void loop() {
  if (millis() >= previousMillis + interval) {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    previousMillis = millis();
  }
}
D)
unsigned long previousMillis = 0;
const long interval = 500;
void loop() {
  if (millis() - previousMillis > interval) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
  }
}
hard
A. Correct non-blocking blink using millis() and toggling LED
B. Uses delay(), which blocks other code from running
C. Correct logic but may cause overflow issues with addition
D. Uses delay() inside if, causing blocking and incorrect blink

Solution

  1. Step 1: Identify non-blocking timing usage

    Correct non-blocking blink using millis() and toggling LED uses millis() difference and updates previousMillis correctly, toggling LED without delay.
  2. Step 2: Compare other options for blocking or logic issues

    The other snippets either use delay(), which blocks other code from running, or use addition in the condition, which can cause overflow issues with large millisecond values.
  3. Final Answer:

    Correct non-blocking blink using millis() and toggling LED -> Option A
  4. Quick Check:

    Use millis() difference and update previousMillis [OK]
Hint: Toggle LED using millis() difference, avoid delay() [OK]
Common Mistakes:
  • Using delay() causing blocking
  • Not updating previousMillis properly
  • Using addition risking overflow bugs