HomeArduinoCore TutorialsdelayMicroseconds() Tutorial

Arduino delayMicroseconds(): Precise Microsecond Timing with Examples

delayMicroseconds() pauses your Arduino sketch for a set number of microseconds. It is the function to use when delay() is too coarse — for generating sensor trigger pulses, bit-banging communication protocols, or controlling the timing of fast signals.

In this guide, you will learn:

  • How delayMicroseconds() works internally
  • Syntax, parameters, and allowed ranges
  • A working pulse generation example with oscilloscope results
  • Why the actual pulse is slightly longer than you specify — and how to fix it
  • When to use delayMicroseconds() and when to switch to something else

This is part of the Arduino Core Tutorials series. Related tutorials:

How to Use Arduino delayMicroseconds() Function with Precision

How delayMicroseconds() Works

delayMicroseconds() is a blocking busy-wait loop. When you call it, the CPU stops executing any other code and counts processor cycles until the requested time has passed. No other code runs. No interrupts are serviced (unless they were already pending). The processor is occupied for the full duration.

On a 16 MHz Arduino (UNO, Nano, Mega), one clock cycle is 62.5 nanoseconds. A 10 µs delay requires 160 clock cycles. The function calculates this count based on the board’s clock frequency and loops exactly that many times.

This makes it fast and predictable for short durations — but it also means your program is completely frozen during the delay.

Syntax

delayMicroseconds(time);

time is an unsigned integer — the number of microseconds to wait.

delayMicroseconds(10);   // Pause for 10 microseconds
delayMicroseconds(500);  // Pause for 500 microseconds

Limits and Accuracy

LimitValue
Minimum reliable delay~3 µs (values below this are inaccurate)
Maximum reliable delay~16,383 µs on 16 MHz boards
Recommended range3 µs to 1,000 µs

Below 3 µs: The function’s own instruction overhead eats into the delay. A request for 1 or 2 µs may produce inconsistent results.

Above ~16 ms: The internal counter overflows on 16 MHz boards. Use delay() or millis() for longer waits instead.

Interrupts: If an interrupt fires during delayMicroseconds(), it extends the actual delay by however long the ISR takes. For timing-critical code, disable interrupts first with noInterrupts() and re-enable them after with interrupts().

noInterrupts();
delayMicroseconds(10);
interrupts();

Pulse Generation Example

This example generates a 10 µs HIGH pulse on pin 8 every second — a common pattern for triggering sensors like the HC-SR04 ultrasonic module.

Code

const int pulsePin = 8;

void setup() {
  pinMode(pulsePin, OUTPUT);
}

void loop() {
  digitalWrite(pulsePin, HIGH);   // Pulse HIGH
  delayMicroseconds(10);          // Hold for 10 µs
  digitalWrite(pulsePin, LOW);    // End pulse
  delay(1000);                    // Wait 1 second before next pulse
}

pinMode(pulsePin, OUTPUT) configures pin 8 as an output. Inside loop(), digitalWrite(HIGH) raises the pin to 5V, delayMicroseconds(10) holds it there for 10 µs, and digitalWrite(LOW) ends the pulse. The delay(1000) gives a 1-second gap between pulses so each one is easy to observe.


Results

The logic analyzer shows one short pulse per second, with the pin staying LOW between pulses.

Logic analyzer wide view showing a short HIGH pulse on pin 8 repeating every 1 second

Zooming into a single pulse shows the exact duration:

Logic analyzer zoomed view showing Arduino pin 8 HIGH for approximately 10 to 13 microseconds

Why the Pulse is Slightly Longer Than 10 µs

The actual pulse width on the oscilloscope will be around 12–14 µs, not exactly 10 µs. This is expected and has two causes:

  1. digitalWrite() overheaddigitalWrite() is a high-level Arduino function. It looks up the pin number, validates it, checks pin modes, and then sets the hardware register. This process takes approximately 3–5 µs on a 16 MHz Arduino. Both the HIGH and LOW calls add overhead around the delayMicroseconds() call.
  2. Instruction execution time — Setting up the delay loop itself takes a few CPU cycles before the counting even begins.

If you need an exact 10 µs pulse, use direct port manipulation instead of digitalWrite():

// Direct port manipulation — much faster than digitalWrite()
PORTB |= (1 << PB0);    // Set pin 8 HIGH (PB0 = Arduino pin 8)
delayMicroseconds(10);
PORTB &= ~(1 << PB0);   // Set pin 8 LOW

Direct port manipulation takes a single clock cycle (62.5 ns on 16 MHz), so the overhead is negligible. The pulse width will be very close to the requested value.

delayMicroseconds() vs Other Timing Methods

MethodResolutionBlockingUse Case
delayMicroseconds()1 µsYesShort precise pulses, sensor triggers
delay()1 msYesSimple waits where ms precision is enough
micros()4 µsNoNon-blocking timing, measuring elapsed time
millis()1 msNoNon-blocking timing over longer periods
Hardware timers< 1 µsNoPWM, frequency generation, precise ISR timing

Use delayMicroseconds() when:

  • You need a blocking pause shorter than 1 ms
  • You are generating a one-shot pulse (sensor trigger, IR signal bit, DHT22 start pulse)
  • The rest of your code does not need to run during the delay

Switch to micros() or hardware timers when:

  • Your program needs to keep running while tracking time
  • You need delays longer than a few milliseconds
  • You are generating repeated waveforms — use PWM or a timer interrupt instead

Non-Blocking Alternative with micros()

If you need µs-level timing without blocking the CPU, use micros():

unsigned long startTime = micros();

// Do other work here...

if (micros() - startTime >= 10) {
  // 10 µs have passed — take action
}

micros() returns the number of microseconds since the Arduino started. It has a resolution of 4 µs on 16 MHz boards. Use it when your program cannot afford to freeze — for example, when reading multiple sensors or managing communication alongside timing.

Comparison: delay() vs delayMicroseconds()

Featuredelay()delayMicroseconds()
UnitMillisecondsMicroseconds
Minimum value1 ms~3 µs
Maximum reliable valueLimited by unsigned long~16,383 µs
BlockingYesYes
Interrupt handlingInterrupts still fireInterrupts can add timing error
Typical useLED blink, debounceSensor pulses, protocol timing

Tips for Accurate Microsecond Timing

Use noInterrupts() for critical timing. Background interrupt service routines — timer overflows, UART reception, millis() updates — fire every millisecond and add unpredictable jitter. Wrapping your pulse in noInterrupts() / interrupts() eliminates this.

Switch to direct port manipulation for sub-5-µs pulses. At this scale, digitalWrite() overhead is larger than the delay itself. Use PORTB, PORTD, or PORTC register writes instead.

Verify with an oscilloscope or logic analyzer. You cannot rely on calculated numbers alone for tight timing. Measure the actual pulse width and adjust the delayMicroseconds() argument to compensate for overhead.

Do not use it for long pauses. Values above 16,383 µs can overflow internally on 16 MHz boards. Use delay() for anything above 1 ms.

delayMicroseconds() Arduino: Frequently Asked Questions

Conclusion

delayMicroseconds() does one thing extremely well: it gives your Arduino a reliable, repeatable pause at the microsecond scale with zero setup — no timer configuration, no interrupt handler, just a single function call. For generating sensor trigger pulses, implementing 1-Wire or custom communication bit-banging, or inserting precise inter-step delays in stepper motor control, it is the right tool.

The two numbers to remember from this tutorial are 3 µs (minimum reliable delay on a 16 MHz board) and 16,383 µs (maximum before the internal calculation breaks). Below 3 µs, instruction overhead dominates and the actual pause is no longer proportional to the requested value. Above 16,383 µs, use delay() instead. And when your code needs to keep running while time passes — polling a sensor, reading Serial, or checking a button — micros() and millis() are always the right answer over any blocking delay function.

The logic analyzer output from the pulse generation example demonstrates the digitalWrite() overhead effect concretely: a delayMicroseconds(10) call produces a measured pulse of 12–13 µs because the digitalWrite(HIGH) and digitalWrite(LOW) calls each consume 2–3 µs of their own. If sub-microsecond pulse accuracy matters for your project, direct port manipulation or hardware PWM removes that overhead entirely.

The natural next step is Arduino PWM — the hardware timer approach to generating precise periodic waveforms without blocking loop() at all. For timing external events rather than generating them, Arduino External Interrupts fires a handler the instant a pin changes state — no polling, no delay, no missed edges. Download the full project above and explore the Arduino Core tutorials collection for more.

Download Arduino delayMicroseconds() Project Files

Complete Arduino sketches for microsecond delay timing: 10 µs pulse generation on pin 8 verified with a logic analyzer, direct port manipulation version for true 10 µs pulse width, and non-blocking pulse example using micros() for comparison. Compatible with Arduino Uno, Nano, and Mega (16 MHz boards). Free to download — support the work if it helped you.

Pulse Generation Code Direct Port Manipulation Uno / Nano / Mega · 16 MHz

Browse More Arduino Core Tutorials

1 2

About the Author
Arun Rawat
Arun Rawat
Embedded Systems Engineer · Founder, ControllersTech

Arun is an embedded systems engineer with 10+ years of experience in STM32, ESP32, and AVR microcontrollers. He created ControllersTech to share practical tutorials on embedded software, HAL drivers, RTOS, and hardware design — grounded in real industrial automation experience.

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
×

Don’t Miss Future STM32 Tutorials

Join thousands of developers getting free guides, code examples, and updates.