Timing-based state machines in Arduino - Time & Space Complexity
Start learning this pattern below
Jump into concepts and practice - no test required
We want to understand how the time a timing-based state machine takes grows as we add more states or longer delays.
How does the program's running time change when the state machine gets bigger or more complex?
Analyze the time complexity of the following code snippet.
unsigned long previousMillis = 0;
const long interval = 1000;
int state = 0;
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
state = (state + 1) % 3;
}
}
This code cycles through three states every 1000 milliseconds using timing checks instead of delays.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The loop() function runs repeatedly, checking the time and updating the state.
- How many times: The loop runs continuously, but the state changes only after each interval (every 1000 ms).
The loop runs constantly, but the main work (state change) happens at fixed time intervals, not depending on input size.
| Input Size (n) | Approx. Operations |
|---|---|
| 3 states | State changes every 1000 ms, constant checks each loop |
| 10 states | Still one check per loop, state change every 1000 ms, cycling through more states |
| 100 states | Same pattern: constant time checks, state changes at intervals |
Pattern observation: The number of states does not increase the number of operations per loop; the timing check stays constant.
Time Complexity: O(1)
This means the time per loop cycle stays the same no matter how many states the machine has.
[X] Wrong: "More states mean the program runs slower because it has to check each state every loop."
[OK] Correct: The code only checks the current state and time difference, not all states, so the time per loop stays constant.
Understanding timing-based state machines helps you design efficient programs that respond well over time without slowing down as complexity grows.
"What if we added nested loops inside the state changes? How would the time complexity change?"
Practice
What is the main purpose of using millis() in a timing-based state machine on Arduino?
Solution
Step 1: Understand what
millis()doesmillis()returns the number of milliseconds since the Arduino started running. It keeps counting without stopping the program.Step 2: Connect
Usingmillis()to timing-based state machinesmillis()lets the program check how much time passed and change states without pausing or blocking other tasks.Final Answer:
To track elapsed time without stopping the program -> Option CQuick Check:
millis()tracks time without delay [OK]
millis() never stops your code [OK]- Thinking
millis()pauses the program - Confusing
millis()with delay() - Using
millis()to reset Arduino
Which of the following is the correct way to check if 1000 milliseconds have passed using millis()?
unsigned long previousMillis = 0;
unsigned long interval = 1000;
void loop() {
unsigned long currentMillis = millis();
// What condition checks if interval passed?
if (__________) {
// do something
previousMillis = currentMillis;
}
}Solution
Step 1: Understand elapsed time calculation
Elapsed time is current time minus previous time:currentMillis - previousMillis.Step 2: Check if elapsed time reached interval
We compare if elapsed time is greater or equal to the interval:currentMillis - previousMillis >= interval.Final Answer:
currentMillis - previousMillis >= interval -> Option DQuick Check:
Elapsed time = current - previous [OK]
- Reversing subtraction order
- Adding times instead of subtracting
- Using <= instead of >=
What will be the output of this Arduino code snippet that uses a timing-based state machine?
unsigned long previousMillis = 0;
const long interval = 2000;
int ledState = LOW;
void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(13, ledState);
Serial.println(ledState);
}
}Solution
Step 1: Understand the timing and state toggle
Every 2000 ms, the code togglesledStatebetween LOW (0) and HIGH (1).Step 2: Check output printed
Each toggle prints the currentledState(0 or 1) to Serial, alternating every 2 seconds.Final Answer:
Prints alternating 0 and 1 every 2 seconds -> Option AQuick Check:
State toggles and prints 0,1 alternately [OK]
- Assuming constant output without toggle
- Confusing HIGH/LOW with 1/0
- Missing update of previousMillis
Identify the bug in this timing-based state machine code and choose the fix.
unsigned long previousMillis = 0;
const long interval = 1000;
int ledState = LOW;
void setup() {
pinMode(13, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > interval) {
ledState = !ledState;
digitalWrite(13, ledState);
}
}Solution
Step 1: Check timing update logic
The code never updatespreviousMillis, so the condition stays true forever after first pass.Step 2: Fix by updating
AddingpreviousMillispreviousMillis = currentMillis;inside the if-block resets the timer for the next interval.Final Answer:
AddpreviousMillis = currentMillis;inside the if-block -> Option AQuick Check:
Update previousMillis to reset timer [OK]
- Forgetting to update previousMillis
- Using bitwise NOT (~) instead of logical NOT (!)
- Removing timing check causes fast toggling
You want to create a state machine that cycles through three LED states: OFF, RED, GREEN. Each state lasts 3 seconds. Which code snippet correctly implements this using millis()?
unsigned long previousMillis = 0;
const long interval = 3000;
int state = 0;
void setup() {
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
state = (state + 1) % 3;
switch(state) {
case 0:
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
break;
case 1:
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
break;
case 2:
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, HIGH);
break;
}
}
}Solution
Step 1: Check timing and state update
The code usesmillis()to check 3 seconds passed, then updatesstatecycling 0,1,2 with modulo 3.Step 2: Verify LED outputs per state
State 0 turns both LEDs off, 1 turns RED on, 2 turns GREEN on. This matches the required cycle.Final Answer:
Correctly cycles OFF, RED, GREEN every 3 seconds -> Option BQuick Check:
State cycles with modulo and timing [OK]
- Forgetting to update previousMillis
- Incorrect modulo causing wrong cycles
- Not turning off LEDs in OFF state
