ISR best practices in Arduino - Time & Space Complexity
When using Interrupt Service Routines (ISRs) in Arduino, it's important to understand how their execution time affects your program.
We want to know how the time spent inside an ISR grows as the code inside it changes.
Analyze the time complexity of this simple ISR example.
volatile int count = 0;
void ISR_example() {
count++; // increment counter
for (int i = 0; i < 10; i++) {
// small fixed loop
}
}
void setup() {
attachInterrupt(digitalPinToInterrupt(2), ISR_example, RISING);
}
void loop() {
// main code
}
This ISR increments a counter and runs a small fixed loop each time it is triggered.
Look at what repeats inside the ISR.
- Primary operation: Incrementing the counter and running a loop 10 times.
- How many times: The loop runs 10 times every time the ISR is called.
Here, the loop inside the ISR runs a fixed 10 times, no matter what.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | ~1 increment + 10 loop steps |
| 100 | ~1 increment + 10 loop steps |
| 1000 | ~1 increment + 10 loop steps |
Pattern observation: The work inside the ISR stays the same each time it runs, so execution time per call does not grow with input size.
Time Complexity: O(1)
This means the ISR runs in constant time, no matter how many times it is triggered.
[X] Wrong: "Adding a loop inside the ISR will make the ISR time grow with the number of interrupts."
[OK] Correct: The loop inside the ISR runs a fixed number of times each call, so the time per interrupt stays constant, not growing with the number of interrupts.
Understanding ISR time complexity helps you write responsive Arduino programs that handle interrupts efficiently without slowing down the main code.
"What if the loop inside the ISR ran 'n' times instead of a fixed 10? How would the time complexity change?"
Practice
Solution
Step 1: Understand ISR impact on program flow
ISRs interrupt the main program to handle urgent events, so they must finish quickly to avoid blocking other tasks.Step 2: Consider consequences of long ISRs
If ISRs are long, they delay other interrupts and slow down the main program, causing performance issues.Final Answer:
To avoid delaying other important tasks and interrupts -> Option BQuick Check:
Keep ISRs short = avoid delays [OK]
- Thinking ISR length only affects code readability
- Assuming ISRs can run as long as needed
- Ignoring impact on other interrupts
Solution
Step 1: Identify variable usage in ISR and main code
Variables shared between ISR and main code must be declared volatile to prevent compiler optimizations that cause wrong values.Step 2: Check each declaration
Only 'volatile int counter = 0;' tells the compiler the variable can change anytime, ensuring correct behavior.Final Answer:
volatile int counter = 0; -> Option AQuick Check:
Shared variable = volatile [OK]
- Forgetting volatile keyword
- Using const for variables that change
- Using static without volatile
volatile int count = 0;
void setup() {
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(2), increment, RISING);
}
void loop() {
Serial.println(count);
count = 0;
delay(1000);
}
void increment() {
count++;
}
Assuming pin 2 receives 3 rising signals per second, what will Serial print each second?Solution
Step 1: Understand ISR increments
Each rising signal on pin 2 triggers the ISR 'increment' which increases 'count' by 1.Step 2: Calculate increments per second
3 rising signals per second means 'count' increases by 3 every second.Final Answer:
3 -> Option CQuick Check:
3 signals/sec = count increments 3 times [OK]
- Ignoring volatile keyword effect
- Assuming count resets automatically
- Thinking ISR runs slower than events
volatile int flag = 0;
void setup() {
attachInterrupt(digitalPinToInterrupt(3), toggleFlag, FALLING);
}
void loop() {
if(flag) {
// do something
flag = 0;
}
}
void toggleFlag() {
flag = !flag;
}Solution
Step 1: Analyze ISR operation on 'flag'
The ISR toggles 'flag' using '!flag', which is a read-modify-write operation and not atomic.Step 2: Understand atomicity importance
Non-atomic operations on shared variables can cause race conditions and unpredictable behavior.Final Answer:
Using non-atomic operation on 'flag' inside ISR -> Option DQuick Check:
ISR must avoid non-atomic variable changes [OK]
- Ignoring atomicity of operations
- Thinking volatile alone fixes all issues
- Confusing interrupt mode with logic error
Solution
Step 1: Review ISR best practices for counting events
ISRs should be short and only update volatile variables; printing or delays inside ISR are bad.Step 2: Evaluate each option
Increment a volatile counter in ISR; read and reset it safely in loop uses a volatile counter incremented in ISR and safely handled in loop, which is correct.Final Answer:
Increment a volatile counter in ISR; read and reset it safely in loop -> Option AQuick Check:
Keep ISR short; update volatile vars only [OK]
- Using Serial.print inside ISR causing delays
- Using delay() inside ISR causing blocking
- Not declaring shared variables volatile
Ready to prove it?
Pass the test and this pattern moves unknown
