10

I'm trying to bit bang DMX data and that requires 4us pulses. Not having much luck with the results I'm checking to see how good the Arduino is at delaying... Seems to be pretty terrible at it.

Here's a quick little test I did:

unsigned long ptime;

void setup() {
  Serial.begin(9600);
}

void loop() {
  ptime = micros();
  delayMicroseconds(4);
  Serial.println(micros() - ptime);

  delay(1000); //just to keep the serial monitor from going nuts
}

And the results:

8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 4 8 4

I was kinda shocked at just how bad it's accuracy is. It's double the time I wanted to delay, but it's not even consistent to where I could just divide by 2!

Is there anything I can do to get correct, consistent results?

bwoogie
  • 203
  • 2
  • 9

5 Answers5

9

As explained in the previous answers, your actual problem is not the accuracy of delayMicroseconds(), but rather the resolution of micros().

However, to answer your actual question, there is a more accurate alternative to delayMicroseconds(): the function _delay_us() from the AVR-libc is cycle-accurate and, for example

_delay_us(1.125);

does exactly what it says. The main caveat is that the argument has to be a compile-time constant. You have to #include <util/delay.h> in order to have access to this function.

Note also that you have to block the interrupts if you want any kind of accurate delay.

Edit: As an example, if I were to generate a 4 µs pulse on PD2 (pin 19 on the Mega), I would proceed as follows. First, notice that the following code

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

makes a 0.125 µs long pulse (2 CPU cycles), because that's the time it takes to execute the instruction that sets the port LOW. Then, just add the missing time in a delay:

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
_delay_us(3.875);
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

and you have a cycle-accurate pulse width. It is worth noting that this cannot be achieved with digitalWrite(), as a call to this function takes about 5 µs.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
4

Your test results are misleading. delayMicroseconds() actually delays fairly precisely (for delays of more than 2 or 3 microseconds). You can examine its source code in file /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c (on a Linux system; or on some similar path on other systems).

However, the resolution of micros() is four microseconds. (See, eg, the garretlab page about micros().) Hence, you will never see a reading between 4 microseconds and 8 microseconds. The actual delay might be just a few cycles over 4 microseconds, but your code will report it as 8.

Try doing 10 or 20 delayMicroseconds(4); calls in a row (by duplicating the code, not by using a loop) and then report the result of micros().

James Waldby - jwpat7
  • 8,920
  • 3
  • 21
  • 33
3

I'm checking to see how good the Arduino is at delaying... Seems to be pretty terrible at it.

micros() has a well-documented resolution of 4 µs.

You could improve the resolution by changing the prescaler for Timer 0 (of course that throws out the figures, but you can compensate for that).

Alternatively use Timer 1 or Timer 2 with a prescaler of 1, which gives you a resolution of 62.5 ns.


 Serial.begin(9600);

That's going to be slow anyway.


8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 4 8 4

Your output is exactly consistent with the 4 µs resolution of micros() coupled with the fact that sometimes you would get two "ticks" and sometimes one, depending on exactly when you started the timing.


Your code is an interesting example of measurement error. delayMicroseconds(4); will delay for close to 4 µs. However your attempts to measure it are at fault.

Also, if an interrupt occurs then it will stretch the interval a bit. You need to turn interrupts off if you want an exact delay.

Nick Gammon
  • 38,901
  • 13
  • 69
  • 125
2

When measured with an oscilloscope, I found that:

delayMicroseconds(0) = delayMicroseconds(1) = 4 μs real delay.

So, if you want a 35 μs delay you need:

delayMicroseconds(31);
Greenonline
  • 3,152
  • 7
  • 36
  • 48
sigi
  • 21
  • 1
2

Is there anything I can do to get correct, consistent results?

The Arduino implementation is quite generic thus may not be as effective in some applications. There are a few ways for short delays, each with its own shortfalls.

  1. Use nop. Each is one instruction so 16th of a us.

  2. Use tcnt0 directly. Each is 4us as the prescaler is set to 64. You can change the prescaker to achieve 16th us resolution.

  3. Use ticks, you can implement a clone of systick and use it as basis of the delay. It offers finer resolution plus accuracy.

edit:

I used the following block to time the various approaches:

time0=TCNT0;
delay4us();             //65
//t0delayus(4*16);          //77
//_delay_us(4);             //65
//delayMicroseconds(4);     //45
time1=TCNT0 - time0;        //64 expected

before that, I had reset the timer0 prescaler to 1:1 so each TCNT0 tick is 1/16th of a microsecond.

  1. delay4us() is built up from NOP(); it produced a delay of 65 ticks, or just over 4us;
  2. t0delayus() is built up from timer0 delays. it produced a delay of 77 ticks; slightly worse than delay4us()
  3. _delay_us() is a gcc-avr function. performance on par with delay4us();
  4. delayMicroseconds() produced a delay of 45 ticks. the way arduino implemented its timing functions, it tends to under-count, unless in an environment with other interrupts.

hope it helps.

dannyf
  • 2,813
  • 11
  • 13