investigating delayMicroseconds()

how to chase your own tail : :slight_smile:

Last year I investigated delayMicroseconds() for small values. That lead to a patch for the function
See - http://forum.arduino.cc/index.php/topic,132983.0.html -

Today I wrote a small program how delayMicroseconds() behaves in practice for values from 1-2000. Above 2000 micros, it is advised to use

delay(whole millis)
delay(remaindermicros);

The reason why I investigated this is because delayMicoseconds() is often used in handshakes with sensors (e.g. DHT22).
Errors in delayMicoseconds() might add up during a handshake which can cause hard to debug failures.

I started writing a simple test sketch in which I use micros() to test the timing of delayMicroseconds().
True, this is not a hardware timer accuracy but it gives a first indication.

As micros() returns always a multiple of four I decided to call delayMicroseconds four times which makes the math a bit easier.

//
//    FILE: test_delay_microseconds.ino
//  AUTHOR: Rob Tillaart
// VERSION: 2013-08-31
// PURPOSE: test accuracy of delayMicroseconds()
//     URL:
//
// Released to the public domain
//
void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println();

  for (int i=1; i<2000; i+=10) 
  {
    uint32_t start = micros();
    delayMicroseconds(i);
    delayMicroseconds(i);
    delayMicroseconds(i);
    delayMicroseconds(i);
    uint32_t stop = micros();
    int diff = (stop - start)/4;
    int delta = diff - i;
    Serial.print(i);
    Serial.print("\t");
    Serial.print(diff);
    Serial.print("\t");
    Serial.print(delta);
    Serial.print("\t");
    Serial.println(100.0*delta/i);
  }
}

void loop(){}

The output (the comma is decimal separator)

1	2	1	100
11	12	1	9,09
21	23	2	9,52
31	33	2	6,45
41	44	3	7,32
51	55	4	7,84
61	67	6	9,84
71	77	6	8,45
81	86	5	6,17
91	97	6	6,59
101	109	8	7,92
111	119	8	7,21
121	129	8	6,61
131	140	9	6,87
141	152	11	7,8
151	160	9	5,96
161	173	12	7,45
171	182	11	6,43
181	195	14	7,73
191	204	13	6,81
201	216	15	7,46
211	226	15	7,11
221	238	17	7,69
231	247	16	6,93
241	258	17	7,05
251	269	18	7,17
261	280	19	7,28
271	290	19	7,01
281	302	21	7,47
291	312	21	7,22
301	324	23	7,64
311	334	23	7,4
321	343	22	6,85
331	357	26	7,85
341	368	27	7,92
351	378	27	7,69
361	387	26	7,2
371	397	26	7,01
381	408	27	7,09
391	413	22	5,63
401	418	17	4,24
411	428	17	4,14
421	438	17	4,04
431	449	18	4,18
441	460	19	4,31
451	469	18	3,99
461	480	19	4,12
471	490	19	4,03
481	500	19	3,95
491	510	19	3,87
501	519	18	3,59
511	529	18	3,52
521	540	19	3,65
531	550	19	3,58
541	559	18	3,33
551	569	18	3,27
561	579	18	3,21
571	591	20	3,5
581	600	19	3,27
591	610	19	3,21
601	620	19	3,16
611	632	21	3,44
621	641	20	3,22
631	651	20	3,17
641	661	20	3,12
651	671	20	3,07
661	682	21	3,18
671	691	20	2,98
681	702	21	3,08
691	711	20	2,89
701	721	20	2,85
711	732	21	2,95
721	741	20	2,77
731	751	20	2,74
741	761	20	2,7
751	771	20	2,66
761	781	20	2,63
771	791	20	2,59
781	802	21	2,69
791	811	20	2,53
801	822	21	2,62
811	831	20	2,47
821	841	20	2,44
831	853	22	2,65
841	861	20	2,38
851	871	20	2,35
861	882	21	2,44
871	892	21	2,41
881	903	22	2,5
891	913	22	2,47
901	923	22	2,44
911	933	22	2,41
921	942	21	2,28
931	953	22	2,36
941	962	21	2,23
951	973	22	2,31
961	981	20	2,08
971	993	22	2,27
981	1003	22	2,24
991	1014	23	2,32
1001	1024	23	2,3
1011	1035	24	2,37
1021	1045	24	2,35
1031	1055	24	2,33
1041	1065	24	2,31
1051	1076	25	2,38
1061	1086	25	2,36
1071	1097	26	2,43
1081	1105	24	2,22
1091	1115	24	2,2
1101	1126	25	2,27
1111	1137	26	2,34
1121	1147	26	2,32
1131	1156	25	2,21
1141	1167	26	2,28
1151	1177	26	2,26
1161	1187	26	2,24
1171	1197	26	2,22
1181	1207	26	2,2
1191	1217	26	2,18
1201	1227	26	2,16
1211	1237	26	2,15
1221	1247	26	2,13
1231	1257	26	2,11
1241	1265	24	1,93
1251	1277	26	2,08
1261	1287	26	2,06
1271	1297	26	2,05
1281	1307	26	2,03
1291	1317	26	2,01
1301	1327	26	2
1311	1337	26	1,98
1321	1349	28	2,12
1331	1356	25	1,88
1341	1367	26	1,94
1351	1376	25	1,85
1361	1388	27	1,98
1371	1399	28	2,04
1381	1408	27	1,96
1391	1419	28	2,01
1401	1428	27	1,93
1411	1439	28	1,98
1421	1448	27	1,9
1431	1459	28	1,96
1441	1469	28	1,94
1451	1479	28	1,93
1461	1488	27	1,85
1471	1498	27	1,84
1481	1509	28	1,89
1491	1519	28	1,88
1501	1528	27	1,8
1511	1539	28	1,85
1521	1548	27	1,78
1531	1558	27	1,76
1541	1569	28	1,82
1551	1579	28	1,81
1561	1590	29	1,86
1571	1598	27	1,72
1581	1609	28	1,77
1591	1620	29	1,82
1601	1630	29	1,81
1611	1638	27	1,68
1621	1649	28	1,73
1631	1659	28	1,72
1641	1669	28	1,71
1651	1680	29	1,76
1661	1690	29	1,75
1671	1700	29	1,74
1681	1710	29	1,73
1691	1718	27	1,6
1701	1728	27	1,59
1711	1740	29	1,69
1721	1750	29	1,69
1731	1760	29	1,68
1741	1768	27	1,55
1751	1780	29	1,66
1761	1790	29	1,65
1771	1800	29	1,64
1781	1810	29	1,63
1791	1820	29	1,62
1801	1830	29	1,61
1811	1840	29	1,6
1821	1850	29	1,59
1831	1860	29	1,58
1841	1870	29	1,58
1851	1881	30	1,62
1861	1890	29	1,56
1871	1900	29	1,55
1881	1910	29	1,54
1891	1920	29	1,53
1901	1932	31	1,63
1911	1942	31	1,62
1921	1952	31	1,61
1931	1962	31	1,61
1941	1971	30	1,55
1951	1980	29	1,49
1961	1992	31	1,58
1971	2001	30	1,52
1981	2011	30	1,51
1991	2022	31	1,56

What we see is there is a slowly increasing absolute error (3rd column) which is a decreasing relative error (4rd column).

When putting it in a graph one can estimate the error by 2 relative simple linear formulas.
0..250 => error = 0.0679x;
250..2000 => error = 13 + 0.0097x;

to be continued...


update: I've been chasing my own tail as the printing of the numbers in the test program is done in parallel with the "test", so this is affecting the measurement.
I could see this by removing the print statements and checked the cumulative error (abs & rel) for the range [0..2000].

Lesson learned:
Still the above does mean that if a handshake is implemented with delayMicroseconds() and there is a (busy) interrupt in the background, either due to printing or some other sensor increasing a counter every 10 usec, it might be worth to check the timing of the handshake.m
For larger micro delays it might be wiser to use a tight loop as its max abs error is 4 uS (UNO16MHz) and it is more interrupt proof than delayMicroseconds.
* *uint32_t udelay = 300; uint32_t start = micros(); while (micros() - start >= udelay);* *

the time delta from the start / stop checking of micros(); look okay,

here's the result of my modified delaymicros()

1	2	1	100.00
11	12	1	9.09
21	21	0	0.000000e+0
31	32	1	3.23
41	42	1	2.44
51	51	0	0.000000e+0
61	62	1	1.64
71	72	1	1.41
81	84	3	3.70
91	92	1	1.10
101	103	2	1.98
111	112	1	0.90
121	124	3	2.48
131	133	2	1.53
141	142	1	0.71
151	152	1	0.66
161	163	2	1.24
171	173	2	1.17
181	183	2	1.10
191	193	2	1.05
201	203	2	1.00
211	213	2	0.95
221	223	2	0.90
231	233	2	0.87
241	243	2	0.83
251	253	2	0.80
261	264	3	1.15
271	273	2	0.74
281	284	3	1.07
291	293	2	0.69
301	304	3	1.00
311	313	2	0.64
321	325	4	1.25
331	334	3	0.91
341	345	4	1.17
351	353	2	0.57
361	365	4	1.11
371	373	2	0.54
381	383	2	0.52
391	395	4	1.02
401	405	4	1.00
411	413	2	0.49
421	423	2	0.48
431	435	4	0.93
441	445	4	0.91
451	455	4	0.89
461	465	4	0.87
471	475	4	0.85
481	484	3	0.62
491	494	3	0.61
501	505	4	0.80
511	514	3	0.59
521	524	3	0.58
531	535	4	0.75
541	547	6	1.11
551	555	4	0.73
561	565	4	0.71
571	575	4	0.70
581	585	4	0.69
591	596	5	0.85
601	604	3	0.50
611	615	4	0.65
621	626	5	0.81
631	634	3	0.48
641	646	5	0.78
651	656	5	0.77
661	664	3	0.45
671	675	4	0.60
681	685	4	0.59
691	696	5	0.72
701	707	6	0.86
711	716	5	0.70
721	726	5	0.69
731	736	5	0.68
741	747	6	0.81
751	756	5	0.67
761	767	6	0.79
771	776	5	0.65
781	786	5	0.64
791	796	5	0.63
801	806	5	0.62
811	816	5	0.62
821	827	6	0.73
831	837	6	0.72
841	846	5	0.59
851	856	5	0.59
861	867	6	0.70
871	876	5	0.57
881	888	7	0.79
891	898	7	0.79
901	906	5	0.55
911	917	6	0.66
921	927	6	0.65
931	937	6	0.64
941	947	6	0.64
951	957	6	0.63
961	967	6	0.62
971	977	6	0.62
981	988	7	0.71
991	998	7	0.71
1001	1008	7	0.70
1011	1017	6	0.59
1021	1028	7	0.69
1031	1037	6	0.58
1041	1048	7	0.67
1051	1057	6	0.57
1061	1068	7	0.66
1071	1077	6	0.56
1081	1089	8	0.74
1091	1098	7	0.64
1101	1108	7	0.64
1111	1119	8	0.72
1121	1128	7	0.62
1131	1138	7	0.62
1141	1148	7	0.61
1151	1159	8	0.70
1161	1169	8	0.69
1171	1179	8	0.68
1181	1189	8	0.68
1191	1199	8	0.67
1201	1209	8	0.67
1211	1220	9	0.74
1221	1228	7	0.57
1231	1239	8	0.65
1241	1249	8	0.64
1251	1259	8	0.64
1261	1269	8	0.63
1271	1279	8	0.63
1281	1289	8	0.62
1291	1299	8	0.62
1301	1309	8	0.61
1311	1321	10	0.76
1321	1329	8	0.61
1331	1339	8	0.60
1341	1349	8	0.60
1351	1359	8	0.59
1361	1370	9	0.66
1371	1381	10	0.73
1381	1389	8	0.58
1391	1399	8	0.58
1401	1409	8	0.57
1411	1419	8	0.57
1421	1430	9	0.63
1431	1441	10	0.70
1441	1451	10	0.69
1451	1459	8	0.55
1461	1469	8	0.55
1471	1480	9	0.61
1481	1491	10	0.68
1491	1500	9	0.60
1501	1511	10	0.67
1511	1521	10	0.66
1521	1530	9	0.59
1531	1541	10	0.65
1541	1551	10	0.65
1551	1562	11	0.71
1561	1571	10	0.64
1571	1580	9	0.57
1581	1591	10	0.63
1591	1601	10	0.63
1601	1612	11	0.69
1611	1622	11	0.68
1621	1631	10	0.62
1631	1641	10	0.61
1641	1650	9	0.55
1651	1662	11	0.67
1661	1672	11	0.66
1671	1682	11	0.66
1681	1692	11	0.65
1691	1702	11	0.65
1701	1710	9	0.53
1711	1721	10	0.58
1721	1730	9	0.52
1731	1742	11	0.64
1741	1752	11	0.63
1751	1762	11	0.63
1761	1773	12	0.68
1771	1782	11	0.62
1781	1792	11	0.62
1791	1802	11	0.61
1801	1813	12	0.67
1811	1822	11	0.61
1821	1833	12	0.66
1831	1842	11	0.60
1841	1852	11	0.60
1851	1864	13	0.70
1861	1872	11	0.59
1871	1882	11	0.59
1881	1892	11	0.58
1891	1902	11	0.58
1901	1914	13	0.68
1911	1923	12	0.63
1921	1934	13	0.68
1931	1944	13	0.67
1941	1953	12	0.62
1951	1963	12	0.62
1961	1972	11	0.56
1971	1982	11	0.56
1981	1993	12	0.61
1991	2004	13	0.65

Hi Darryl,

do you have a link to your modified delaymicros()?

I still notice that although the relative error decreases the absolute error is slightly increasing - I'm very aware that it is very hard to minimize this .

here is what I use, based on some mods as suggested by a certain you a while ago ! of course changed your code a bit, and lengthened the delay loop, as it seemed a good idear at the time, but I cant remember what it is now !

/* Delay for the given number of microseconds ( 1% accurate of clock rate >20 )
   max we can pass on 8MHz -> 65535, on 16MHz its 32768 and for 20MHz its
   only 13107 due to having to times 5, then divide by 2 :-(
*/
void delayMicroseconds(uint16_t us)
{
	// playing around with altering _us_ means we top out early on the max value we can pass.
#if F_CPU >= 20000000L
    // for a zero  or one-microsecond delay, simply wait 2 cycle and return. The overhead
    // of the function call yields a delay of approx 0.8 microsecond.
    __asm__ volatile (
        "nop" "\n\t"
        "nop");
    if (us < 2) return;

    // the busy loop takes a 2/5 of a microsecond (8 cycles)
    // per iteration, so execute it 2.5 times for each microsecond
    us = (( us - 1 ) * 5 ) >>1;
#elif F_CPU >= 16000000L
    // for a zero or one-microsecond delay, simply return.  the overhead
    // of the function call yields a delay of approximately 1 us.
    if (us < 2) return;

    // the busy loop takes a half of a microsecond (8 cycles)
    // per iteration, so execute it twice for each microsecond of
    // delay requested. offset by time for above check
    us = ( us - 1 ) <<1;
#else
    // for a zero to two microsecond delay, simply return.  the overhead of
    // the function calls takes that. then each loop per microsecond :-)
    if (us < 3) return;
	us = us - 2;
#endif

    // busy wait ( 8 cycles = 1/2 microsecond on 16MHz )
    __asm__ volatile (
        "1: sbiw %0,1" "\n\t"	// 2 cycles
        "nop" "\n\t"			// 1 cycle
        "nop" "\n\t"			// 1 cycle
        "nop" "\n\t"			// 1 cycle
        "nop" "\n\t"			// 1 cycle
        "brne 1b" "\n\t"		// 2 cycles
		: "=w" (us)
		: "0" (us)
    );
}

also noticed a error is printing 0.00 in my version of print.cpp. which I've now corrected !

Recognition, I remember I lost those patches when switching to 1.0.4 have to check them tonight !