Sunday, January 22, 2017

Better way for beginners to sleep or delay on Arduino

Almost every single Arduino example includes a simple line similar to the following:
  • delay(1000)
It is primarily because folks seem to go with a simple one-line solution when focusing on other tasks.  But this continuously leads to the bad practice of beginners on using the Delay() function across the board for anytime they want to "sleep" for X seconds.  While there definitely are situations when you want to use this for the most part you just shouldn't.  Here are some times you would still use delay():

  • Immediately after a button press/release to not get phantom events or state-changes (100ms works reliably with most buttons/micro-switches)
  • Very short duration in-process events (LED blink)
The tutorial titled Blink without Delay shows the right way for beginner developers to approach timers/delays in most scenarios.  There are more extensive methods for more advanced use (including some nice libraries for power savings and better sleep functions), but this is the right basic approach for most development. 

To jump straight to it as an example, there is a built-in function called millis() on the Arduino platform.  This gives the number of milliseconds since the board booted.  By doing some minor arithmetic on this value (save the millis() result at the start of a process and compare to the value of millis() later in the process), you will know when X milliseconds have passed.  

Using delay() means that your code freezes at that point and ignores all events during that period.  Doing a millis() comparison means that you can continue processing items vs. just pausing.  

The Blink without Delay example again is probably the heart of what beginners should do for more time-sensitive results vs. delay().  For example, a flashing LED should be reasonably consistent.  delay() can be completely thrown off by code that runs between LED flashes.  Imagine if you were waiting for a response from a webserver or waiting for user input.  Do you want to stop processing your LED flashes while waiting for this?

With the right approach, it is fairly easy to send a command off to a subroutine every loop through to see if you should be blinking or not by using some base code similar to this:

const byte ledPin =  LED_BUILTIN;// the number of the LED pin
const int delayLED = 5000;  //number of ms between LED flash cycles
const byte numOfBlinks = 3;  //number of times to blink each delayLED
const byte delayBetweenBlinks = 40; //ms between numOfBlinks

unsigned long previousMillis;  //the last time we blinked

void setup() {
  //insert your setup code here
  pinMode(ledPin, OUTPUT);
}
void loop() {
  //insert your application here and call the CheckForBlink() sub below every loop
  CheckForBlink()
}

//Is it time to blink or not?
void CheckForBlink() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= delayLED) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // Blink the LED
    Blink(ledPin, delayBetweenBlinks, numOfBlinks);
  }
}

//Blink on/off "numofBlinks" times waiting "delayBetweenBlinks" between each
void Blink(byte PIN, byte DELAY_MS, byte loops){
  for (byte i=0; i<loops; i++)
  {
    digitalWrite(PIN,HIGH);
    delay(DELAY_MS);
    digitalWrite(PIN,LOW);
    delay(DELAY_MS);
  }
}

As a real-world example, if you were doing something like waiting for a button press to start/stop an LED from blinking, then you couldn't just constantly run delay(4000) between flashing the LED to get a 4 second pause between blinks.  You need to be checking for button presses (or some other event) during this time.  The code above would allow you to put your check for a button press to be iterating through the loop() subroutine while calling out the CheckForBlink() each time you pass through as well.  

Are there better or different ways to solve this problem.  Absolutely.  Interrupts can sometimes more applicable for such scenarios.  There are in fact a few nice libraries specifically around some interesting ways to approach events and timers.  But when starting out, this is the better way to learn how to perform tasks on a schedule vs. hard-coding simple delay() commands throughout.

No comments:

Post a Comment