Now, I tested the channel accuracy and i received 14% error for a delay of one micro second. For a delay of 2 microseconds, my error was 0%, and for three microseconds, my error rate was 30%.
Microsecond resolution delays on an Arduino are tenuous at best. They aren't that accurate. Simply because you're hitting the limit of the speed of the CPU.
At 16MHz you have a clock period of 1/16000000 seconds. That's 63ns. That's the granularity of the delays you can achieve - and that can only be achieved by inserting a precise number of NOP
assembly instructions.
So for the best 1µs delay possible you would need (0.000001 / 0.000000063) 15.87 NOP
instructions. Of course, you can't have that many, so you have to round it either up or down. Normal integer truncation would result in 15. So that would actually be a delay of 945ns, which is an error of 5.5%.
2µs would be (0.000002 / 0.000000063) 31.74 NOP
instructions, which would round to 31, so 1.953µs, or an error of 2.35%. As you can see increasing the length generally reduces the error.
HOWEVER
The delay function on the Arduino for microsecond accuracy (delayMicroseconds()
) is maybe not as accurate as it could be. Simply because it's impossible to just have an exact number of NOP instructions for a delay since you can't know how long the delay should be until the function is called with a parameter detailing how long the delay should be. So it has to perform a number of calculations and make a number of assumptions. It tries to come close, but it was written for one specific version of the compiler and compilers change. Things may have been changed in how the function gets called, and how long parts of it take (what registers are pushed and popped may change with different compiler versions, for instance). So it's not a perfectly reliable delay.
For more detail you can look at the source code of it (it's in wiring.c
) and you can see how for small delays it's very hard to get it right. For instance, this couple of lines highlights the problem nicely:
// 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;
Note the use of the word approximately there...
Also you need to note that the delay functions are just that - a delay at that specific moment in time from the start of the call to its finish. It doesn't take into account anything else that's going on in your code, so a delay of 1µs in a loop of code that does other things will end up in a loop that takes X + 1µs
where X
is the amount of time that the rest of the code takes to execute.
And that's where the tunedDelay()
function comes in. It has been specially written to be used in that specific circumstance to provide a delay that takes into account the time the rest of the loop takes to run. That is, the loop's time plus the tunedDelay
time equate to the final period you want for the whole loop. For asynchronous serial communications the timing accuracy is important. Since there is no separate (or even embedded) clock to get the timing from both ends have to agree what speed the communication happens at (the baud rate), and any deviation from that can cause communication errors. So the timing has to be as close as possible. A normal delayMicroseconds()
function call just wouldn't cut the mustard - they needed it more precise than that. Manually tuned to have specific delay values that are as precise as possible, and that was done for specific baud rates on specific speed chips. Each supported frequency has a table such as this:
static const DELAY_TABLE PROGMEM table[] =
{
// baud rxcenter rxintra rxstop tx
{ 115200, 1, 17, 17, 12, },
{ 57600, 10, 37, 37, 33, },
{ 38400, 25, 57, 57, 54, },
{ 31250, 31, 70, 70, 68, },
{ 28800, 34, 77, 77, 74, },
{ 19200, 54, 117, 117, 114, },
{ 14400, 74, 156, 156, 153, },
{ 9600, 114, 236, 236, 233, },
{ 4800, 233, 474, 474, 471, },
{ 2400, 471, 950, 950, 947, },
{ 1200, 947, 1902, 1902, 1899, },
{ 600, 1902, 3804, 3804, 3800, },
{ 300, 3804, 7617, 7617, 7614, },
};
That's the value to send to the tunedDelay()
function in the four different situations where its used for each of the baud rates at that specific clock frequency (16MHz in that example).
tunedDelay()
itself is written in assembly code and is a very tight loop for the number of iterations specified in the parameter. The shortest possible piece of code with the minimum of overhead to get the maximum accuracy and finest granularity (least number of clock cycles for one iteration) out of it.
The regular delay used in the blink programs was what set the 2 microsecond delays.
- what do you mean by that?How do I link other posts?
- if you mention another StackExchange post it is automatically added to the linked posts.