delayMicroseconds is off by an order of magnitude

I need to create accurate pulses and was using delayMicroseconds() to do it.

The board is an Arduino Mega 2560 Rev3.

However, when looking at the actual pulse generated on the logic analyzer it
is off by an order of magnitude.

This produces ~104us delay:

digitalWrite(resetPin, LOW);
_delay_us(100);
digitalWrite(resetPin, HIGH);

This produces ~4412us delay:

digitalWrite(resetPin, LOW);
delayMicroseconds(100);
digitalWrite(resetPin, HIGH);

This is not just a minor error.

I cannot figure out what is going on here, can someone enlighten me?

Perhaps the code in context woukd afford some clues. The snippet you listed looks like it should be 200 us, so it isn't clear and I'm too lazy to think about what you are doing there.

Write a minimum it compiles we can try it ourselves sketch and post it next on this thread.

What Arduino board are you using?

a7

Ok, now I have also observed the deviation with the _delay_us() call.

My guess would be that something else must influence the digitalWrite().

Depending on what "accurate" means, it's quite possible those two ideas are mutually exclusive.

No doubt. Post a sketch so we can see what you see, and learn from your experiments.

What Arduino board are you using?

a7

Neither of those code snippets generate a pulse.
How are you actually generating the pulse?

The resetPin is on HIGH, then I am setting it to LOW, and after a delay back to HIGH.

If you want a pulse of exactly 100 µs, then you make a pulse of exactly 100 µs :wink:

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

void loop() 
{ 
  noInterrupts();
  bitSet(PORTB, 7);
  __builtin_avr_delay_cycles(1600-2);
  bitClear(PORTB, 7);
  interrupts();

  delay(1);
}

Did you know that we say on this from: The problem is in the part that you are not showing.
There is even a website for that: https://snippets-r-us.com/

A sketch to try the other delays as well:

// Test delays with delays being delays or something like that.
// 30 August 2023, by Koepel, Public Domain.
// Forum: https://forum.arduino.cc/t/delaymicroseconds-is-off-by-an-order-of-magnitude/1163575
// This Wokwi sketch: https://wokwi.com/projects/374490739256465409
//

const int pulsePin = 13;

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

void loop() 
{ 
  // -------------------------------------------------
  // Exactly 100 µs by using the clock cycles
  // Result: always 100.000000 µs
  // -------------------------------------------------
  noInterrupts();
  bitSet(PORTB, 7);
  __builtin_avr_delay_cycles(1600-2);  // minus 2 for setting the pin
  bitClear(PORTB, 7);
  interrupts();
  delay(1);

  // -------------------------------------------------
  // The common AVR delay
  // Result: 105.313 µs
  // -------------------------------------------------
  noInterrupts();
  digitalWrite(pulsePin, HIGH);
  _delay_us(100);
  digitalWrite(pulsePin, LOW);
  interrupts();
  delay(1);


  // -------------------------------------------------
  // The Arduino delayMicroseconds
  // Result: 104.062 µs
  // -------------------------------------------------
  noInterrupts();
  digitalWrite(pulsePin, HIGH);
  delayMicroseconds(100);
  digitalWrite(pulsePin, LOW);
  interrupts();
  delay(1);

  delay(10);       // extra delay to easily identify the three pulses
}

It results in pulses of 100, 105.313 and 104.062 µs. The pulse is never longer, never shorter, there is never any deviation.
Timer0 runs the interrupt for millis(). You can keep the interrupts enabled if you want to add that now and then to the pulse, but only if you have a very good reason to include that in the pulse.

Run it in the Wokwi simulation:

Using the Wokwi simulation with the simulated Logic Analyzer: Install PulseView on your computer (no need to install sigrok or Zadig, only PulseView). Read how to use it with Wokwi. Start the simulation, stop it as you see it running. Open the file in PulseView via "Import Value Change Dump data". Add the "Timing" decoder. Have fun :partying_face:

The Magnitude is a Myth.

Arduino Mega 2560 Rev3, like mentioned in the original question.

As for posting a sketch of the whole project, that unfortunately won't work, as it's a proprietary codebase of a client.

I'd use the one of the Mega 2560's hardware Timer / Counters.

The accuracy of delayMicroseconds is also influenced by any other interrupt activity.

Do you see the same with a basic sketch with just those few lines in a loop ? (and resetting the pin)

const byte resetPin  = 2;

void setup() {
  pinMode(2, OUTPUT);
  digitalWrite(resetPin, HIGH);
}

void loop() {
  digitalWrite(resetPin, LOW);
  _delay_us(100);
  digitalWrite(resetPin, HIGH);

  delay(1000);

  digitalWrite(resetPin, LOW);
  delayMicroseconds(100);
  digitalWrite(resetPin, HIGH);

  delay(1000);

}

Every program instruction-step takes some time and one C++ statement may result in more than one machine code instruction. ...Even with no "delay" there will be a delay. :wink:

Interesting. There does seem to be an issue with delayMicroseconds() assuming you haven't done anything else in you sketch that mucks with the timer h/w. (i.e. it would helpful to see the full sketch)
Also, do you have the proper F_CPU clock value? i.e. is your clock frequency on your board running at the same speed as the F_CPU define?
I'm traveling but I'll have to try that when I get back home.
As far as variation goes, that can happen from the Arduino core code using the timer interrupt to maintain its millis clock.

As far as the extra 4us that is due to the crappy code in the digital code in the Arduino AVR core library. The way it is implemented has quite a bit of overhead and the typical amount of time on an AVR is about 4us to set/clear a pin using digitalWrite().

Ok I figured it out.

Since my code is written like this

// Somewhere before in the code 
pinMode(resetPin, OUTPUT);

// other code...

digitalWrite(resetPin, LOW);
delayMicroseconds(100);
digitalWrite(resetPin, HIGH);

and the resetPin line is on HIGH by default because of a pull up, the code before "extends" my pulse window, since the pinMode call pulls my line to LOW, thus extending the interval.

Explicitly setting it to HIGH beforehand like this solves it.

digitalWrite(resetPin, HIGH);
pinMode(resetPin, OUTPUT);

I didn't even know digitalWrite() before pinMode() was legal, but apparently it is

See: pinMode and Default Output State of Pin - #5 by system

That is not a small working sketch :kissing_cat:

Are you changing the pinMode from input to output all the time ? And the pinMode() is not just in setup() ?

Not originally. You shoukd really not edit your original post so that makes trash of the thread, and us look like idiots.

And I did not ask for your secret code, I asked for something that would run and show us what you are seeing.

a7