delayMicroseconds limitation

While refference page for delayMicroseconds states “the largest value that will produce an accurate delay is 16383”, I found that any value over 1950 gives VERY inaccurate results.

Values under 1950 are accurate with +/- 1% drift.

Here is a snippet that I used:

extern volatile unsigned long timer0_overflow_count;
unsigned int ticks = 950; //change the value to test different durations

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

unsigned long hpticks (void)
{
return ((timer0_overflow_count << 8) + TCNT0)*4;
}

void loop (void)
{
unsigned long hptime1, hptime2, delta;

hptime1 = hpticks(); //start time
delayMicroseconds (ticks);
hptime2 = hpticks(); //stop time

Serial.println((unsigned int) (hptime2 - hptime1)); //show duration
delay(300); //just to make sure serial’s got enough time
}

As I’m new to Arduino world, it’s possible that I’m doing something wrong.

Hope someone on the bord can help me with this.

Miroslav

Thanks for the detailed bug report.

Your code looks correct, so my guess is that this is indeed a bug in delayMicroseconds(). Can you post the numbers for the actual delays you're seeing vs. the parameter to delayMicroseconds()? How inaccurate is it?

I'm not sure why there'd be a change at 1950 microseconds. The code is simply a busy loop with a 16 bit counter. Anyone have any ideas?

Here are more details (for the sake of people that will be reading, I’m pasting only first 20 lines, the rest looks very similar). See bellow for results arround 1950 us. Green is OK (even though with some error). Red is totally off

0 - Now running 900: 900 900 900 900 904
1 - Now running 1000: 1004 1004 1004 1004 1008
2 - Now running 1100: 1104 1104 1104 1108 1104
3 - Now running 1200: 1204 1204 1204 1204 1204
4 - Now running 1300: 1304 1304 1308 1308 1308
5 - Now running 1400: 1404 1404 1404 1404 1404
6 - Now running 1500: 1504 1504 1504 1504 1508
7 - Now running 1600: 1604 1604 1604 1604 1604
8 - Now running 1700: 1704 1704 1704 1704 1704
9 - Now running 1800: 1804 1804 1804 1804 1804
10 - Now running 1900: 1904 1904 1904 1904 1904
11 - Now running 2000: 984 980 980 980 980
12 - Now running 2100: 1080 1080 1080 1080 1080
13 - Now running 2200: 1180 1180 1184 1180 1180
14 - Now running 2300: 1284 1280 1280 1280 1280
15 - Now running 2400: 1380 1380 1384 1380 1380
16 - Now running 2500: 1480 1484 1480 1480 1480
17 - Now running 2600: 1580 1580 1580 1580 1580
18 - Now running 2700: 1680 1680 1684 1680 1680
19 - Now running 2800: 1784 1780 1780 1780 1780
20 - Now running 2900: 1880 1880 1880 1880 1880
21 - Now running 3000: 960 1980 1980 956 956
22 - Now running 3100: 1056 1056 1056 1056 1056
23 - Now running 3200: 1156 1156 1156 1156 1156

And now arround the “breaking” point

0 - Now running 1950: 1956 1956 1956 1952 1956
1 - Now running 1951: 1956 1956 1956 1956 1956
2 - Now running 1952: 1956 1956 1956 1956 1956
3 - Now running 1953: 1960 1956 1960 1956 1956
4 - Now running 1954: 1960 1960 1956 1960 1960
5 - Now running 1955: 1960 1960 1960 1960 1960
6 - Now running 1956: 1960 1960 1960 1960 1960
7 - Now running 1957: 1960 1960 1960 1960 1960
8 - Now running 1958: 1964 1960 1960 1960 1960
9 - Now running 1959: 1964 1964 944 1964 1960
10 - Now running 1960: 1964 1964 1964 1964 1964
11 - Now running 1961: 1964 944 1964 944 1968
12 - Now running 1962: 1964 1968 1964 944 1964
13 - Now running 1963: 1964 944 944 1968 1968
14 - Now running 1964: 948 1968 1968 948 1968
15 - Now running 1965: 1972 1972 1968 944 1968
16 - Now running 1966: 1972 1968 948 1972 1972
17 - Now running 1967: 948 948 1968 948 1972
18 - Now running 1968: 948 1972 1972 1972 952
19 - Now running 1969: 1976 1972 948 1972 1972
20 - Now running 1970: 1972 956 1976 1972 956
21 - Now running 1971: 952 1976 952 1976 952

And here is the code

//Arduino 001 Alpha
extern volatile unsigned long timer0_overflow_count;
unsigned int ticks = 1950; //change the value to test different durations

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

unsigned long hpticks (void)
{
return ((timer0_overflow_count << 8) + TCNT0)*4;
}

void loop (void)
{
unsigned long hptime1, hptime2, delta;
int i, j, t;

while(serialRead() < 0) {} /wait till I switch to the serial monitor

for (i=0; i<30; i++) // Iterations - increment value of us sent to the function
{
t = ticks + i1; // modify “granularity” change "i1" to “i*100” for larger steps
Serial.print(i);
Serial.print(" - Now running “);
Serial.print(t);
Serial.print(”: ");
delay(30);

for (j=0; j<5; j++) // test 5 times with the same value
{
hptime1 = hpticks(); //start time
delayMicroseconds (t);
hptime2 = hpticks(); //stop time

Serial.print((unsigned int) (hptime2 - hptime1)); //show duration
Serial.print(" ");
delay(10); //just to make sure serial’s got enough time
}
Serial.println();
}
}

There is no bug here. delayMicroseconds() disables the timer0 overflow interrupt, so you can't use hpticks() to time the duration of delayMicroseconds(). I'm pretty sure that if you use an oscilloscope to monitor pulses generated by delayMicroseconds(), you will see that the widths of those pulses are accurate.

The problem would occur when two timer0 overflows occur during the delayMicroseconds() routine. If only one occurs, the timer0 overflow interrupt would be executed as soon as your delayMicroseconds() re-enables interrupts, and the net effect would be that your timing would be accurate except for the extra time it takes to carry out the ISR. If two occur, one overflow interrupt is missed forever and your timing will be fast by around a millisecond. The greater your argument to delayMicroseconds(), the greater the chance that two timer0 overflows will occur in that period, which is why you start seeing the problem around 1960 us and why it's not completely deterministic (i.e. it's not like the problem happens 100% of the time for arguments greater than some value and 0% of the time for arguments less than or equal to that value).

Green is OK (even though with some error).

The error you are seeing isn't necessarily a problem with delayMicroseconds(). Note that your timing routine hpticks() only has a resolution of 4 us, so your measured values are within the uncertainty of your measuring process. Your timing routine also unsafe and will produce buggy results periodically since the timer0 overflow interrupt could interrupt it in the middle of your timing calculation, which could corrupt your measurement and would introduce extra delays to the timing.

  • Ben

Even though this explanation ABSOLUTELY makes sense, I can't wait to give it a run on osciloscope :)

Never thought that I'll be using it to troubleshoot a software problem :)

Thanks much!

I've often found an oscilloscope to be quite useful when it comes to debugging some of my more complicated embedded programs.

  • Ben

Nice thread, guys. Miroslav: thanks for the detailed data. bens: thank you for the detailed explanation. You guys are great.

Let me know if you see any problems under the oscilloscope.