Lesson 18 - Using micros() for Microsecond Timing
Learn how micros() enables non-blocking timing with microsecond precision for short interval tasks.
Progress indicator
Lesson 18 of 18
Learning Objectives
- Understand what micros() returns in Arduino/ESP32 programs.
- Use micros() syntax with unsigned long timing variables.
- Build a basic non-blocking micros() interval check pattern.
- Compare millis() and micros() use cases clearly.
- Handle micros() overflow safely with subtraction logic.
- Design cleaner timing code by naming units clearly (Us vs Ms).
Concept Explanation
What is micros()
micros() returns the number of microseconds since the board started running.
It is useful when your timing intervals are too short for millis().
This makes micros() helpful for pulse-level timing and short-interval experiments.
micros() Syntax
unsigned long nowUs = micros();Use unsigned long to store micros values safely.
How micros() Works
The microsecond counter increases continuously in the background. Your code checks elapsed time by subtracting a previous timestamp from current micros.
If elapsed time reaches your interval, run the timed action and update the previous timestamp.
Microseconds Counter Concept
Think of micros() as a faster stopwatch than millis(). It measures much smaller time steps.
You do not reset this counter manually. You only compare current and previous values.
millis() vs micros() (Comparison)
millis(): better for millisecond-level tasks (LED blink, UI refresh).micros(): better for short intervals and pulse-level timing.- Use micros() only when you need that extra timing granularity.
Overusing micros() in simple projects can make code harder to read without practical benefit.
Basic micros() Timing Pattern
unsigned long currentUs = micros();
if (currentUs - previousUs >= intervalUs) {
previousUs = currentUs;
// timed action
}When to Use micros()
- Pulse generation and pulse measurement
- Short protocol timing experiments
- Tasks where millisecond timing is too coarse
Handling micros() Overflow
micros() eventually wraps around. The subtraction pattern currentUs - previousUs >= intervalUs remains safe with unsigned values.
Avoid direct comparisons like currentUs > previousUs + intervalUs near overflow.
Example Code
This example toggles a pin using non-blocking micros() timing.
const int pulsePin = 2;
unsigned long previousMicros = 0;
const unsigned long intervalUs = 100;
bool pulseState = false;
void setup() {
pinMode(pulsePin, OUTPUT);
}
void loop() {
unsigned long currentMicros = micros();
if (currentMicros - previousMicros >= intervalUs) {
previousMicros = currentMicros;
pulseState = !pulseState;
digitalWrite(pulsePin, pulseState ? HIGH : LOW);
}
}What this example teaches
- How to toggle outputs with microsecond interval checks
- Why subtraction-based elapsed checks are the standard timing pattern
- How to keep loop non-blocking while still running timed actions
Example Code Explanation
previousMicrosstores the last trigger time.intervalUsstores target interval in microseconds.- Read current time with
currentMicros = micros(). - If elapsed time reaches interval, update timestamp and toggle output state.
- This pattern avoids blocking waits and keeps loop responsive.
Beginner reading tip
Focus on these three variables first: currentMicros, previousMicros, and intervalUs. They define the full timing rule.
Common Mistakes with micros()
- Using
intinstead ofunsigned long. - Mixing millisecond and microsecond units in the same variable.
- Using direct absolute comparisons instead of elapsed subtraction.
- Updating
previousMicrosevery loop even when interval is not reached.
Best Practices for micros()
- Keep timing variables as
unsigned long. - Always document units in variable names, like
intervalUs. - Use micros() only when you actually need microsecond-level timing.
- Keep interval constants explicit, for example
const unsigned long intervalUs = 100;.
Practice Task
- Change
intervalUsfrom 100 to 200 and describe output behavior. - Add a second task that uses
millis()in the same loop for a slower action. - Explain why both timing systems can coexist without blocking each other.
- Write one short note on why subtraction logic is overflow-safe.
Try it now
Open simulator workspace and practice microsecond-level timing patterns.