Multitasking the Arduino

Multitasking the Arduino

Andrew Todd Marcus

https://learn.adafruit.com/multi-tasking-the-arduino-part-1?view=all

In this guide we will demonstrate that it is indeed possible for the Arduino to juggle multiple independent tasks while remaining responsive to external events like user input.

  • We will learn how to time things using millis() instead of delay() so we can free up the processor to do other things.

Bigger and Better Projects

Once you have mastered the basic blinking leds, simple sensors and sweeping servos, it’s time to move on to bigger and better projects.  That usually involves combining bits and pieces of simpler sketches and trying to make them work together.  The first thing you will discover is that some of those sketches that ran perfectly by themselves, just don’t play well with others.

The Arduino is a very simple processor with no operating system and can only run one program at a time.  Unlike your personal computer or a Raspberry Pi, the Arduino has no way to load and run multiple programs.

That doesn’t mean that we can’t manage multiple tasks on an Arduino.  We just need to use a different approach.  Since there is no operating system to help us out, We have to take matters into our own hands.

Ditch the delay()

The first thing you need to do is stop using delay().

Using delay() to control timing is probably one of the very first things you learned when experimenting with the Arduino.  Timing with delay() is simple and straightforward, but it does cause problems down the road when you want to add additional functionality.  The problem is that delay() is a "busy wait" that monopolizes the processor. 

During a delay() call, you can’t respond to inputs, you can't process any data and you can’t change any outputs.  The delay() ties up 100% of the processor.  So, if any part of your code uses a delay(), everything else is dead in the water for the duration.

Remember Blink?

Copy Code
  1. /*
  2. Blink
  3. Turns on an LED on for one second, then off for one second, repeatedly.
  4. This example code is in the public domain.
  5. */
  6. // Pin 13 has an LED connected on most Arduino boards.
  7. // give it a name:
  8. int led = 13;
  9.  
  10. // the setup routine runs once when you press reset:
  11. void setup() {
  12. // initialize the digital pin as an output.
  13. pinMode(led, OUTPUT);
  14. }
  15.  
  16. // the loop routine runs over and over again forever:
  17. void loop() {
  18. digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
  19. delay(1000); // wait for a second
  20. digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
  21. delay(1000); // wait for a second
  22. }

The simple Blink sketch spends almost all of its time in the delay() function.  So, the processor can't do anything else while it is blinking. 

And sweep too?

Sweep uses the delay() to control the sweep speed.  If you try to combine the basic blink sketch with the servo sweep example, you will find that it alternates between blinking and sweeping.  But it won't do both simultaneously.

Copy Code
  1. #include <Servo.h>
  2. // Pin 13 has an LED connected on most Arduino boards.
  3. // give it a name:
  4. int led = 13;
  5.  
  6. Servo myservo; // create servo object to control a servo
  7. // twelve servo objects can be created on most boards
  8. int pos = 0; // variable to store the servo position
  9. void setup()
  10. {
  11. // initialize the digital pin as an output.
  12. pinMode(led, OUTPUT);
  13. myservo.attach(9); // attaches the servo on pin 9 to the servo object
  14. }
  15. void loop()
  16. {
  17. digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
  18. delay(1000); // wait for a second
  19. digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
  20. delay(1000); // wait for a second
  21. for(pos = 0; pos <= 180; pos += 1) // goes from 0 degrees to 180 degrees
  22. { // in steps of 1 degree
  23. myservo.write(pos); // tell servo to go to position in variable 'pos'
  24. delay(15); // waits 15ms for the servo to reach the position
  25. }
  26. for(pos = 180; pos>=0; pos-=1) // goes from 180 degrees to 0 degrees
  27. {
  28. myservo.write(pos); // tell servo to go to position in variable 'pos'
  29. delay(15); // waits 15ms for the servo to reach the position
  30. }
  31. }

So, how do we control the timing without using the delay function?

Using millis() for timing

Become a clock-watcher!

One simple technique for implementing timing is to make a schedule and keep an eye on the clock.  Instead of a world-stopping delay, you just check the clock regularly so you know when it is time to act.  Meanwhile the processor is still free for other tasks to do their thing.  A very simple example of this is the BlinkWithoutDelay example sketch that comes with the IDE.

The code on this page uses the wiring shown in the diagram below:

microcontrollers_Single.png

Blink Without Delay

This is the BlinkWithoutDelay example sketch from the IDE:

Copy Code
  1. /* Blink without Delay
  2. Turns on and off a light emitting diode(LED) connected to a digital
  3. pin, without using the delay() function. This means that other code
  4. can run at the same time without being interrupted by the LED code.
  5. The circuit:
  6. * LED attached from pin 13 to ground.
  7. * Note: on most Arduinos, there is already an LED on the board
  8. that's attached to pin 13, so no hardware is needed for this example.
  9. created 2005
  10. by David A. Mellis
  11. modified 8 Feb 2010
  12. by Paul Stoffregen
  13. This example code is in the public domain.
  14.  
  15. http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
  16. */
  17.  
  18. // constants won't change. Used here to
  19. // set pin numbers:
  20. const int ledPin = 13; // the number of the LED pin
  21.  
  22. // Variables will change:
  23. int ledState = LOW; // ledState used to set the LED
  24. long previousMillis = 0; // will store last time LED was updated
  25.  
  26. // the follow variables is a long because the time, measured in miliseconds,
  27. // will quickly become a bigger number than can be stored in an int.
  28. long interval = 1000; // interval at which to blink (milliseconds)
  29.  
  30. void setup() {
  31. // set the digital pin as output:
  32. pinMode(ledPin, OUTPUT);
  33. }
  34.  
  35. void loop()
  36. {
  37. // here is where you'd put code that needs to be running all the time.
  38.  
  39. // check to see if it's time to blink the LED; that is, if the
  40. // difference between the current time and last time you blinked
  41. // the LED is bigger than the interval at which you want to
  42. // blink the LED.
  43. unsigned long currentMillis = millis();
  44. if(currentMillis - previousMillis > interval) {
  45. // save the last time you blinked the LED
  46. previousMillis = currentMillis;
  47.  
  48. // if the LED is off turn it on and vice-versa:
  49. if (ledState == LOW)
  50. ledState = HIGH;
  51. else
  52. ledState = LOW;
  53.  
  54. // set the LED with the ledState of the variable:
  55. digitalWrite(ledPin, ledState);
  56. }
  57. }

What's the point of that?

At first glance, BlinkWithoutDelay  does not seem to be a very interesting sketch.  It looks like just a more complicated way to blink a LED.  However, BinkWithoutDelay illustrates a very important concept known as a State Machine.  

Instead of relying on delay() to time the blinking.  BlinkWithoutDelay remembers the current state of the LED and the last time it changed.  On each pass through the loop, it looks at the millis() clock to see if it is time to change the state of the LED again.

Welcome to the Machine

Let’s look at a slightly more interesting blink variant that has a different on-time and off-time.  We’ll call this one “FlashWithoutDelay”.  

Copy Code
  1. // These variables store the flash pattern
  2. // and the current state of the LED
  3.  
  4. int ledPin = 13; // the number of the LED pin
  5. int ledState = LOW; // ledState used to set the LED
  6. unsigned long previousMillis = 0; // will store last time LED was updated
  7. long OnTime = 250; // milliseconds of on-time
  8. long OffTime = 750; // milliseconds of off-time
  9.  
  10. void setup()
  11. {
  12. // set the digital pin as output:
  13. pinMode(ledPin, OUTPUT);
  14. }
  15.  
  16. void loop()
  17. {
  18. // check to see if it's time to change the state of the LED
  19. unsigned long currentMillis = millis();
  20. if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
  21. {
  22. ledState = LOW; // Turn it off
  23. previousMillis = currentMillis; // Remember the time
  24. digitalWrite(ledPin, ledState); // Update the actual LED
  25. }
  26. else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
  27. {
  28. ledState = HIGH; // turn it on
  29. previousMillis = currentMillis; // Remember the time
  30. digitalWrite(ledPin, ledState); // Update the actual LED
  31. }
  32. }

State + Machine = State Machine

Note that we have variables to keep track of whether the LED is ON or OFF.  And variables to keep track of when the last change happened.   That is the State part of the State Machine.  

We also have code that looks at the state and decides when and how it needs to change.  That is the Machine part.  Every time through the loop we ‘run the machine’ and the machine takes care of updating the state.