8

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?

  • Why are you bit-banging instead of accessing the UART directly? – Ignacio Vazquez-Abrams Apr 22 '17 at 2:27
  • So I can have more than one output. – bwoogie Apr 22 '17 at 2:32
  • The Mega has four UARTs. Even if you hold one permanently for programming that still gives you three universes. – Ignacio Vazquez-Abrams Apr 22 '17 at 2:33
  • I'm just testing with the mega because that's what I currently have available, the final project will have an ATMEGA328 – bwoogie Apr 22 '17 at 2:35
  • 1
    The problem here is how you measure. – Gerben Apr 22 '17 at 10:02
7

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.

3

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().

  • With 10 delays of 4 I'm getting a mixture of 32's and 28's... but 4*10 = 40. – bwoogie Apr 22 '17 at 2:42
  • What do you get with, say, 10 delays of 5 ? :) Also note, for bitbanging you might need to use fairly direct port accesses, ie, not digitalWrite(), which takes several microseconds to execute. – James Waldby - jwpat7 Apr 22 '17 at 2:46
  • 40 & 44... But shouldn't it be rounding up? What am I missing here? – bwoogie Apr 22 '17 at 2:50
  • By “rounding up” do you mean delayMicroseconds() ? I don't see that as any better than rounding down. ¶ Regarding the source of inaccuracy, if the routine gets inlined then timing depends on surrounding code. You could read assembly or disassembly listings to see. (See “Making assembly listings” section in my answer to question Equivalent for PORTB in Arduino Mega 2560, which may anyway be relevant to your bitbanging project – James Waldby - jwpat7 Apr 22 '17 at 3:50
2

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.

1

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);
0

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.

  • Note that the expected result is 65 ticks, not 64, because reading TCNT0 takes 1 CPU cycle. – Edgar Bonet Jul 1 '17 at 12:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.