Pages: [1]   Go Down
Author Topic: delayMicrosecond bugs  (Read 1853 times)
0 Members and 1 Guest are viewing this topic.
texas
Offline Offline
God Member
*****
Karma: 27
Posts: 862
old, but not dead
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I was recently tinkering with some things and noticed a couple of issues with delayMicroseconds().  The first is a problem of calling it with an argument of 0 leading to a ~17mS delay.  This is apparently well known and will be fixed at some point or documented on the site I suppose.

The real crux of my post is that delayMicroseconds() seems to be adding ~4uS of extra delay to every call.  I understand about short delays not being exceedingly accurate, but no matter what I call delayMicroseconds() with, the delay is 4uS longer than asked.  I tested with values up to 20uS and the delay is always off by 4uS.

I am using 1.0.3 on an Uno R3 board.  I have verified my results with an oscilloscope and am confident of the problem.  I can provide a sample sketch and some captures from the scope if necessary, but it should be easy for anyone to verify that has the proper test equipment.
Logged

Experience, it's what you get when you were expecting something else.

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 178
Posts: 8060
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

How are you measuring the length of the delayMicroseconds() call with an oscilloscope?  Are you using something like digitalWrite() before and after?  Those calls to digitalWrite() might explain the extra 4 microseconds. 
Logged

Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 435
Posts: 23598
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Try using direct port manipulation instead of digitalWrite:
Code:
byte x = 10; // delayMicroseconds amount
byte pin2 = 2; // test with D2, PORTD bit 2

void setup(){
pinMode (pin2, OUTPUT);
}

void loop(){
PORTD = PORTD & B11111011;  // clears D2
delayMicroseconds(x);  // your test value
PORTD = PORTD | B00000100; // sets D2
delayMicroseconds(x);  // your test value
}
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

texas
Offline Offline
God Member
*****
Karma: 27
Posts: 862
old, but not dead
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I did some tests, it seems to take that long to do the digitalWrite() calls, sure wasn't expecting that.  The following code takes approximately 20uS to execute, that's expensive for toggling a pin.
Code:
 digitalWrite(OUTPin, HIGH);
  digitalWrite(OUTPin, LOW);
  digitalWrite(OUTPin, HIGH);
  digitalWrite(OUTPin, LOW);

Thanks, that explains that I suppose.
Logged

Experience, it's what you get when you were expecting something else.

texas
Offline Offline
God Member
*****
Karma: 27
Posts: 862
old, but not dead
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This runs in 250nS (4 clock cycles):
Code:
 PORTD = PORTD | B00001000; // sets D3
  PORTD = PORTD & B11110111;  // clears D3

Much more like it.  And sure enough, I can generate a 4MHz pulse rate on the pin.  I'm a PIC person, can you tell me what that assembles into?  Thanks.
« Last Edit: March 29, 2013, 09:40:39 am by afremont » Logged

Experience, it's what you get when you were expecting something else.

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 435
Posts: 23598
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I use direct port manipulation a lot with SPI for fast external interfacing.
Code:
PORTD = PORTD & B11111011;  // clears D2 - used for Slave Select for example
SPI.transfer(dataArray[x]);
PORTD = PORTD | B00000100; // sets D2 - used for Slave Select
For instance, I was able to shift out 41 bytes in 46uS - and doing it in a loop that runs every 58uS.

If you look at the code for digitalWrite, you'll see there are safeguards to put the hardware in the correct state for an output change.
Direct port manipulation does not.
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Left Coast, CA (USA)
Online Online
Brattain Member
*****
Karma: 331
Posts: 16459
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The main reason that functions like digitalRead() and digitalWrite() are so much slower then direct port register control is that by 'abstracting' the arduino pin numbers the same sketch command will work on arduino boards that use different AVR chips types that have different port/pin mappings. So it allows for portability of code across the various arduino AVR board types, a desirable quality in most cases. Where one can't afford the slower pin manipulation then one is free to utilize the PORTx and PINx method as long as you are aware that the resulting sketch will not run properly on a mega board if written for a uno board or visa versa.

The choice lies with the programmer, you to choose and utilize the best method that your application requires.

Lefty
Logged

texas
Offline Offline
God Member
*****
Karma: 27
Posts: 862
old, but not dead
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks Lefty, I guess it's a mindset kinda thing.  I've almost always been an assembly language programmer so I am fairly aware of "what goes on down there".  Even when I used C on a micro, you only dreamed of floating point math and you didn't think about calling any kind of printf, sprintf etc..  I'm still getting caught by the level of abstraction that is used by the Arduino libraries to get this stuff to run on so much different hardware in a generic fashion.  When I see things like digitalWrite(), my brain is thinking MACRO that dissolves into a couple of instructions, not a real function call that takes 80 cycles.  I guess I'm old and kinda set in my ways.  smiley-wink
Logged

Experience, it's what you get when you were expecting something else.

texas
Offline Offline
God Member
*****
Karma: 27
Posts: 862
old, but not dead
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I use direct port manipulation a lot with SPI for fast external interfacing.
Code:
PORTD = PORTD & B11111011;  // clears D2 - used for Slave Select for example
SPI.transfer(dataArray[x]);
PORTD = PORTD | B00000100; // sets D2 - used for Slave Select
For instance, I was able to shift out 41 bytes in 46uS - and doing it in a loop that runs every 58uS.

If you look at the code for digitalWrite, you'll see there are safeguards to put the hardware in the correct state for an output change.
Direct port manipulation does not.


Thanks Crossroads.  That's pretty fast on the SPI, about 7MHz.  12uS to spare, what did you do with all that leftover time?  smiley-wink

When I take time to think, it makes perfect sense that setting or clearing a pin would take that long using the library.  Considering all that takes place, it's really not all that bad.
Logged

Experience, it's what you get when you were expecting something else.

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 435
Posts: 23598
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Yes, I used the 8 MHz SPI rate to send the data out.
Well, I plan to update those 41 bytes at a 2 KHz rate, so I have a little bit of time for other stuff betwee bursts smiley-cool
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 168
Posts: 12417
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@afremont

Issue is known, reported it + possible fix here - https://code.google.com/p/arduino/issues/detail?id=576 -

patch for wiring.c 
Code:
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);
#if F_CPU >= 20000000L
// for the 20 MHz clock on rare Arduino boards

// for a one-microsecond delay, simply wait 2 cycle and return. The overhead
// of the function call yields a delay of exactly a one microsecond.
__asm__ __volatile__ (
"nop" "\n\t"
"nop"); //just waiting 2 cycle
if (us <= 1)
return;
us--;

// the following loop takes a 1/5 of a microsecond (4 cycles)
// per iteration, so execute it five times for each microsecond of
// delay requested.
us = (us<<2) + us; // x5 us

// account for the time taken in the preceeding commands.
us -= 2;

#elif F_CPU >= 16000000L
// 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.

//  FIX
// if (--us == 0)
// return;
if (us < 2) return;
us--;

// 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;
#else
// for the 8 MHz internal clock on the ATmega168

// for a one- or two-microsecond delay, simply return.  the overhead of
// the function calls takes more than two microseconds.  can't just
// subtract two, since us is unsigned; we'd overflow.


// if (--us == 0)
// return;
// if (--us == 0)
// return;
if (us < 3) return;
us -= 2;


// the following loop takes half of a microsecond (4 cycles)
// per iteration, so execute it twice for each microsecond of
// delay requested.
us <<= 1;
   
// partially compensate for the time taken by the preceeding commands.
// we can't subtract any more than this or we'd overflow w/ small delays.
us--;
#endif

// busy wait
__asm__ __volatile__ (
"1: sbiw %0,1" "\n\t" // 2 cycles
"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
);
}
« Last Edit: March 30, 2013, 10:29:42 am by robtillaart » Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 168
Posts: 12417
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

oops, repo has moved - bug + fix is reported here - https://github.com/arduino/Arduino/issues/576
Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Pages: [1]   Go Up
Jump to: