As an experienced firmware developer, I often need precise control over microcontroller timings for building advanced embedded applications. The Arduino delayMicroseconds() function serves this purpose well, offering microsecond timing precision for time-critical code.

In this comprehensive guide, I cover the full spectrum of using delayMicroseconds() – from basic concepts to advanced use cases – based on industry best practices for utilizing microsecond delays effectively.

Introduction to Delaying Code Execution

Pausing program execution is a common requirement in microcontroller programming for spacing out events, creating timing critical logic and delaying state changes.

The Arduino language provides two main functions for halting code execution for a specified time duration:

1. delay() – Delays in milliseconds

2. delayMicroseconds() – Delays in microseconds

The key difference is the level of precision – milliseconds vs microseconds.

Why does this precision matter? Microsecond delays unlock abilities like:

  • Accurately timing sensor signals down to 1 millionth of a second resolution
  • Controlling high frequency electronic devices and motors
  • Implementing serial communication protocols requiring precise baud rate timing

Let‘s analyze some example projects showcasing these capabilities.

Case Study 1 – Optimizing Timing-Critical Code Logic

Consider this snippet which toggles an I/O pin in a tight loop:

void blinkFast() {

  while(1) {

    digitalWrite(ledPin, HIGH);
    digitalWrite(ledPin, LOW);

  }

}

This blinks an LED on and off rapidly by setting the pin high and low continuously inside the loop.

The problem?

There is no delay added in the loop! This means the LED blink speed depends on how fast the processor can execute the instructions.

On most Arduino boards, the approximate blink rate works out to around 15 kHz. This renders it useless for most applications requiring slower timed blinking.

The solution?

Introduce microsecond delays to precisely control the blink speed:

void blinkFast() {

  while(1) {

    digitalWrite(ledPin, HIGH);
    delayMicroseconds(500);

    digitalWrite(ledPin, LOW);  
    delayMicroseconds(500);

  }

}

Now the blink rate works out to 1 kHz – Much better for visible blinking effects and most timing needs.

This is a simple improvement but highlights an important coding best practice – Always add delays to time-critical logic loops to eliminate dependency on instruction execution speeds. Delaymicroseconds() allows easily configuring this in a dynamic way not possible with normal delay().

Case Study 2 – Generating Complex Sensor Signals

Many sensors require carefully timed triggering signals before starting a measurement. Consider an ultrasonic distance sensor which first needs a short 10 microsecond pulse:

             10 μs pulse
                  |
                ___    
               /   \
TRIGGER -----|     |
                 \__/

This pulse can be generated using delayMicroseconds() with OUTPUT pin toggling:


const int trigPin = 12;

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

void loop() {

  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10); // <- Pulse timing
  digitalWrite(trigPin, LOW);

  // Read distance echo

}

While simple, this only activates the basic distance measurement.

Advanced ultrasonic sensors support multiple triggering modes selected by timing parameters:

  • Longer pulse -> Higher ultrasound intensity
  • Precise 10 μs -> Highest accuracy
  • 100 μs pulse -> Low power

By modifying just the delayMicroseconds() value, all these modes can be selected through firmware!

This enables adjusting ultrasonic intensity, accuracy and power dynamically without any hardware changes. delayMicroseconds() provides the precision timing control to achieve this.

Case Study 3 – Implementing Serial Protocols

Serial protocols like I2C, SPI and UART involve master devices communicating digital data synchronized by clock/data signals.

Precise timing of these signals is key for reliable data transmission between hardware.

For example, here is I2C code to transmit a byte from an Arduino master to I2C slave:

byte data = 0x65; // Data to send

//Send Start Condition
digitalWrite(sdaPin, LOW);
delayMicroseconds(5);  

//Send 8 data bits
for(int i=0; i<8; i++) {

  digitalWrite(sclPin, LOW);
  delayMicroseconds(5);  

  //Set SDA high/low based on bit value
  digitalWrite(sdaPin, (data & 0x80) ? HIGH : LOW);  

  delayMicroseconds(5);
  digitalWrite(sclPin, HIGH); 

  data <<= 1; // Shift next bit into position  
}

//Send Stop Condition
delayMicroseconds(5)
digitalWrite(sdaPin, HIGH);

Here, the timed toggling of SDA and SCL signals transfers each data bit to the slave. Using delayMicroseconds() gives the precise 5 μs timing for each data and clock signal change mandated by the I2C protocol specs.

Getting this timing accurate is critical – a few microseconds skew over hundreds of bits transmitted will cause data corruption!

Case Study 4 – Frequency and Pulse-width Modulation

Microsecond delays also facilitate advanced control techniques like Frequency modulation and Pulse-width modulation used in power electronics and motor control applications.

For example, this code produces a Pulse Width Modulated signal to control LED brightness:

int ledPin = 5;
int brightness = 0; 

void loop() {

  analogWrite(ledPin, 255);   // Turn FULL ON
  delayMicroseconds(brightness); 

  analogWrite(ledPin, 0);    // Turn FULL OFF
  delayMicroseconds(500-brightness);

  brightness++; // Increase duty cycle
  if(brightness > 500) {
    brightness = 0; 
  }

}  

Here the on/off times are modulated in the microsecond domain using delayMicroseconds() to smoothly adjust LED brightness by modifying duty cycle. Generating fast PWM waves not possible with delay().

In advanced projects this can control high power devices like motors, heaters, etc. Accurate microseconds delays ensures precision duty cycle tuning.

Guidelines for Effective Usage

Through years of experience using delayMicroseconds() for timing critical systems, I have compiled some key guidelines and best practices:

1. Understand Precision Limits

  • Time values below 4 μs will be rounded to 4 μs. This is an artifact of crystal oscillator speeds.

2. Account for Loop Overheads

  • Code inside timing loops also takes some time to execute. If trying to precisely time multiple pins, use calibration delays –
delayMicroseconds(10 + 2); // 2 μs padding

3. Avoid Lengthy Delays

  • As Arduino is halted during the pause, excessively long delays can disrupt background processes and data monitoring. I recommend chunking any delays longer than 10-20 ms.

4. Use Interval Timing for Very Long Durations

  • For multi-second delays, instead of huge delayMicroseconds values, use interval timing with micros() and millis():
start = micros();

do {
   current = micros();
}
while(current - start < 2000000); // 2 second interval

This avoids potential overflows and arithmetic errors.

By applying these guidelines, developers can harness the full potential of microsecond timing precision for advanced embedded projects with Arduino!

Conclusion

In closing, the delayMicroseconds() function serves a key role in unlocking the true power of Arduino timing control for both basic and professional-grade applications.

Mastering microsecond delays provides that fine-grained precision over pulse widths, signal changes and timed logic not possible with millisecond delays.

I hope this guide gives both novice hobbyists and expert firmware architects ideas on creatively utilizing delayMicroseconds() to build the next generation of smart electronics!

Similar Posts