Pages: [1]   Go Down
Author Topic: To delay or not to delay - that is *not* the question!  (Read 1494 times)
0 Members and 1 Guest are viewing this topic.
Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1202
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Final proper version on http://arduino.cc/playground/Code/AvoidDelay  this was a draft
Introduction

Why not to use delay(), and how to get two "delays" run independent of each other.

We know:
1: The loop() runs endlessly and very fast.
2: digitalRead will look at the input at the time it is called only, likewise digitalWrite and output.
3. delay() will wait - it stops everything (except interrupts) while we count the milliseconds

Attempt 1: When button is pushed, turn a LED ON for 15 secs (15000 millis)

Code:
(Note: This is pseudo code, ignoring the syntax of "{" and so on, concentrating on
  the flow of the code. Also we are assuming a perfect button without contact bounce.
  As it happens, most examples here will work with a "noisy" button,
  as the LED timing hides the bounce.)

if digitalread==Down
  then digitalWrite ON , delay 15sec , digitalWrite OFF
Results: It works, yes, but looking closely:
The button can change up or down as much as you like in the 15 seconds - it simply is not looked at.
The delay starts counting when button is pushed. If the button is still down when 15 seconds expire, the LED will blink Off for a very short time (the time it takes loop to start again)

Attempt 2: The same with two buttons and two LEDs

Code:
if digitalreadA==Down
  then digitalWriteA ON , delay 15 sec , digitalWriteA OFF
if digitalreadB==Down
  then digitalWriteB ON , delay 15 sec , digitalWriteB OFF
Result: This does NOT WORK the way it usually is intended - whilst A or B is on, we are totally ignoring the other button. This is why delay() is fatal and very wrong if you need to do more than one and only one thing at a time.



Example 1: Implementing a simple timer

Code:
unsigned long Timer   ALWAYS use unsigned long for timers, not int
 (variable declaration outside setup and loop, of course)

if digitalRead==Down
  then "set timer" Timer = millis , digitalWrite ON
if "timer expired" millis-Timer >= 15000UL
  then digitalWrite Off

(the "UL" after the number is a syntax detail that is important when
dealing with large numbers in millis and micros, therefore it
is shown although this is pseudo code)
Result: It works just as well as the delay version, but slightly differently:
The timer is constantly reset as long as the button is pushed. Thus only when releasing does it start "count" the 15 seconds. If you push shortly inside the 15 seconds period, it starts counting from anew. Notice in particular digitalRead is looking at the input as fast as loop() runs.

We are redundantly doing digitalWrite OFF,even when the LED is not lit for every loop() pass, and likewise digitalWrite ON as long as the button is pushed. This has no ill effects.

Example 2: as 1, but With two buttons and LEDs: Just "repeat" the above.

Code:
unsigned long TimerA    ALWAYS use unsigned long for timers.
unsigned long TimerB  

if digitalReadA==Down
  then  TimerA= millis() , digitalWriteA ON
if millis()-TimerA >= 15000UL
  then digitalWriteA Off
if digitalReadB==Down
  then  TimerB= millis() , digitalWriteB ON
if millis()-TimerB >= 15000UL
  then digitalWriteB Off
Result: This works! Two seperate "timers start and stop independently.
As shown in the single button/LED case, we are done with handling the A case in just a few tens of microseconds, whether we are counting seconds or not, and thus free to handle the B case. We thus examine and react to either buttons hundreds of times each millisecond.

Interesting sideissue: The 4 if statements can be freely rearranged - the end effect is the same. (There are some subtle differences only active for the few microseconds until loop() goes round again.)



The effect that the timer is to start on button push or button release, or if a button should be ignored once the timer has started may be a requirement. Timer code can be made to handle all variations by storing the desired state and skipping digitalRead. This will work fine with two buttons, too.

Example 3: Timer with State change

Code:
boolean LEDon = false
unsigned long Timer     ALWAYS use unsigned long

if not LEDon and digitalRead==Down
   then "set timer" Timer= millis , digitalWrite ON , LEDon = true
if LEDon and millis-Timer >= 15000UL
   then digitalWrite Off , LEDon = false
Result: This will not do digitalRead or reset the timer, once the LED has turned on, ie we are starting the timer on button push (like attempt 1 earlier). Likewise it only tests the timer if the LED is on, and only turns it off once (which make very little difference, so I would normally omit the LEDon test in the 2nd part)

Example 4: One button, one LED - each with their own timer; A more usefull(?) example of doing two timing things "at once"

Code:
boolean LEDon = false
boolean buttondown = false
unsigned long LEDtimer = 0        ALWAYS use unsigned long. Not int.
unsigned long buttontimer = 0  

if millis - buttontimer >= 5UL "debounce 5 millisec"
   and not buttondown and digitalReadButton==Down
         then buttondown = true , buttontimer = millis
if millis - buttontimer >= 5UL
   and buttondown and digitalReadButton==Up
         then buttondown = false , buttontimer = millis

if not LEDon and buttondown   "can use: not buttondown"
  then LEDtimer = millis , digitalWrite ON , ledon = true
if millis - LEDtimer >= 15000UL
  then digitalWrite Off , LEDon = false
Result: The button timer will purposfully skip digitalRead of the button for 5 millisec after it has changed. This filters away the "noise" in any mechanical button. The Led on/off code can be set to trigger on button down or up.

A challenge smiley

If you use a previousbuttonup indicator instead of LEDon, you can ensure that the button has to be released before a new timer cycle can start - but that is left as an exercise, and is more about state transition than about timers.

So, this was the last time you used delay, right?  (there will be exceptions, of course)
« Last Edit: July 03, 2011, 07:59:23 pm by Msquare » Logged

Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 361
Posts: 17262
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Nice job. It's always better to be able to show someone rather then just telling them.

Quote
So, this was the last time you used delay, right?  (there will be exceptions, of course)

I live for the exceptions in life amoung the twilight spaces between right and wrong, left and right.  smiley-grin

Lefty
Logged

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 520
Posts: 26387
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

Looks like a good discussion to replace the blink without delay example
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
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12744
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
So, this was the last time you used delay, right?

 smiley-twist

Quote
if millis - buttontimer > 5L "debounce 5 millisec"

I do believe you forgot something necessary to make the code match the comment.
Logged

New Hampshire
Offline Offline
God Member
*****
Karma: 17
Posts: 781
There are 10 kinds of people, those who know binary, and those who don't.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Looks like a good discussion to replace the blink without delay example

How about a good discussion to just eliminate the blink with delay example?
Logged


Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48543
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
How about a good discussion to just eliminate the blink with delay example?
Code:
// Hang on a minute
delay(60000UL);
Logged

New Hampshire
Offline Offline
God Member
*****
Karma: 17
Posts: 781
There are 10 kinds of people, those who know binary, and those who don't.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

As long as I can do something else while I hang on...
Logged


Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 361
Posts: 17262
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The wisdom of using a delay or not really depends on how you want your sketch flow to work. A blocking delay may be properly what you want. Here is an example ( not using delay here, but certainly a blocking action) of where I want the program to do nothing until I send a character from the serial monitor, so that maybe I can prepare test equipment for a measurement I want to observe, etc. Even tongue in cheek saying that delay() should be banned seems pretty silly to me. Now getting the point across to newcomers to coding about the negative consequences of using the delay function is a whole other matter and worthy of further discussion.

Code:
void setup()
 {
   Wire.begin();
   Serial.begin(57600);
   Serial.println ("Hit a key to start");     // signal initalization done
   while(Serial.available() == 0){}           // wait for user to start
 }
  
Logged

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1202
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Made some minor tweaks, mostly formatting (and a typo/missing word)  Codingbadly suggested >= instead of > to be accurate and UL instead of L.

I do NOT want to start a delay is evil mantra. (See discussion on goto is evil elswhere on how constructive that is). I just want to show that delay will break any concurrency programming the coder may want to do.

I have not explained that here are two "wraparound" problems with timers. (for the uninitiated that may read this: The first being a non-problem; using unsigned means the code works correct even if the millis wrap while we are timing. The other is a problem; the timers will "reset" if they have not been used for the duration of a whole millis cycle (approx 49 days)) The lack is intentional.

I am thinking of removing the "(except interrupts)" as I see no need for interrupts. They open so many new issues for the "beginner"-coder. I have made code that control DCmotors with simple encoders, thermostats, serial communication (both directions) all in ONE sketch using active polling (timed at <= 80uS for each loop) without timing hickups just by "a little care" in the code flow.

Any other constructive criticism (=unbridled praise  smiley-wink)?
Logged

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1202
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

--> playground

It's a little more wordy than the OP.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello;

After having gotten an Arduino just a few months ago, I am rather new to the system. After much investment of time (and money);
I decided to create a small test robot.  When it began to get more complicated, I ran into problems with delay() and reading my PING))) sensor.(I had a MaxSonar EZ1; but had a terrible experience with it)

Here is the second beta of my code...
Quote
#include <Servo.h>
Servo Direction;
Servo DriveR;
Servo DriveL;
const int forwardThreshold = 30;// 12  inches
int forwardDist;
int RightDist;
int LeftDist;
unsigned long sensorWait = 150, servoWait = 500;
void setup()
{
  Serial.begin(9600); //for debugging
  DriveR.attach(9);
  DriveL.attach(11);
  Direction.attach(10);
}

void loop()
//needs improvement.
{  
  int sensor = AquireDist();
  DriveR.write(360);
  DriveL.write(-360);
  if(sensor < forwardThreshold)
  {
    DriveR.detach();
    DriveL.detach();
    Direction.write(90);
    
    forwardDist = AquireDist();
    Serial.println(forwardDist); // for debugging
    if(millis()>= sensorWait);
    sensorWait = sensorWait + millis();
    Direction.write(0);
    if(millis()>= servoWait);
    servoWait = servoWait + millis();
    LeftDist = AquireDist();
    Serial.println(LeftDist);// for debugging
     if(millis()>= sensorWait);
     sensorWait = sensorWait + millis()+150;
    Direction.write(200);
    delay(300);
    RightDist = AquireDist();
    Serial.println(RightDist);// for debugging
     if(millis()>= sensorWait);
     sensorWait = sensorWait + millis()+300;
    Direction.write(90);
    if(millis()>= servoWait);
    servoWait = servoWait + millis()+ 150;
    DriveR.attach(9);
    DriveL.attach(11);
  }
  if(RightDist> LeftDist && RightDist > forwardThreshold)
  {
    DriveL.write(360);
    delay(1500);
  }

  else
  {
    DriveR.write(360);
    delay(1500);
  }

   if(sensor > forwardDist && sensor > forwardThreshold)
  {}// do nothing, go back to  beginning... goto is messy when used too often

}

int AquireDist() // this can be used with any digital in/out sonar type distance sensor (PWM).
{
  const  int sensor = 7;
  int duration;
  pinMode(sensor, INPUT);
  //ping…(wait for the pong)
  pinMode(sensor, OUTPUT);
  digitalWrite(sensor, LOW);
  delayMicroseconds(2);
  digitalWrite(sensor, HIGH);
  delayMicroseconds(5);
  digitalWrite(sensor, LOW);
  //listening for pong now
  pinMode(sensor, INPUT);
  duration = pulseIn(sensor, HIGH);//PpPpPOoOoOnnnnnnng(hopefully)
  Serial.println(duration / 29 / 2 ); // for debugging
  return duration / 29 / 2 ;//convert to a human number with a unit...
}












Logged

RoboDev @ InfiniteSystems

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1202
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

And the question is .... ?

Well, I can guess - although I prefer people ask.
Code:
if(millis()>= sensorWait);
is not doing anything usefull at all. You have no conditional code between the ")" and the ";". Syntax here.

May I respectfully suggest that you post any further questions in a new topic, as this one is for discussing the instructional value of the long explanation.
Logged

Global Moderator
UK
Offline Offline
Brattain Member
*****
Karma: 290
Posts: 25735
I don't think you connected the grounds, Dave.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
int AquireDist() // this can be used with any digital in/out sonar type distance sensor (PWM).
..just as long as it is attached to pin 7.
Logged

"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

Pages: [1]   Go Up
Jump to: