How does the delayMicroseconds() function works. From what I understood the prescaler of timer0 is set to 64. For a 16MHz clock gives 4.0uS per count. I am a bit confused on the math to get to 1uS interval?
Answer
The source code for this function is fairly well documented and can be found in /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c on Linux systems. Windows systems will have a similar path to the wiring.c file. Take the effort of finding the file and browsing through it. For now just focus on this single function, it doesn't rely on any other fucntions.
By inspecting the code you'll notice that it is not about timers, it is all about instruction cycles. The code heavily relies on compiler optimization being exactly the same for you as for the developer of the library. That an assumption of the author! The number of CPU cycles 'burnt' by each instruction is well documented in the Atmel AVR instruction set document.
First the delay value is checked for being equal to 1, in that case just returning from the routine already spent over a microsecond of CPU time.
Then the delay value is multiplied by four (<<=2
). The __asm__
-loop compiles into a 4 CPU cycle loop. 4 cycles × 4 = 16 cycles. 16MHz/(4×4) = 1MHz, which takes 1 us cycle time, the resolution that we are after.
The last -2 microseconds (before the loop is kicked off) is again a correction on compiler introduced overhead. Calling __asm__
-code from C requires some extra instructions to save CPU registers.
For a normal Arduino @16MHz only the following code will be compiled:
/* Delay for the given number of microseconds. Assumes a 8 or 16 MHz clock. */
void delayMicroseconds(unsigned int us)
{
// calling avrlib's delay_us() function with low values (e.g. 1 or
// 2 microseconds) gives delays longer than desired.
//delay_us(us);
// for the 16 MHz clock on most Arduino boards
// for a one-microsecond delay, simply return. the overhead
// of the function call yields a delay of approximately 1 1/8 us.
if (--us == 0)
return;
// the following loop takes a quarter of a microsecond (4 cycles)
// per iteration, so execute it four times for each microsecond of
// delay requested.
us <<= 2;
// account for the time taken in the preceeding commands.
us -= 2;
// busy wait
__asm__ __volatile__ (
"1: sbiw %0,1" "\n\t" // 2 cycles
"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
);
}
BTW: The compiled code is pretty accurate, but be aware of the following: On Arduino there are timed interrupts configured that most are unaware of. When an interrupt is received during the execution of the delayMicroseconds()
, the timing of delayMicroseconds()
will be wrong. You can of course stop interrupts before calling delayMicroseconds()
and enable them afterwards, but that again does impact timing accuracy by the duration of compiled code for enabling/disabling.
No comments:
Post a Comment