Arduino's multitasking - Part One

Bigger and better projects

If you've mastered basic flashing LEDs, simple sensors andservos , you're ready to start the adventure with bigger and better projects. This usually involves combining parts of simpler sketches and trying to work together. The first thing you'll discover is that some of these sketches, which separately work perfectly, don't fit together with others.

Arduino is a very simple module without an operating system that can only run one program. Unlike your personal computer or Raspberry Pi, Arduino is not able to load and run multiple programs.

This doesn't mean that we can't do many tasks in Arduino.We just have to use another method. We don't have an operating system to help, so we have to take matters into our own hands.

Wielozadaniowość Arduino

Get rid of the delay()

The first thing you learned while playing with Arduino is probably the use of the above function (delay(). The delay() function is not complicated, but it causes problems when you add additional functions. The trouble is that this function is "active waiting". (with busy wait), which dominates the processor.

When calling this function, you cannot react to inputs, you cannot process any data and you cannot change any output. It blocks the whole processor. So, if some part of your code uses this function, for that time, everything else is stopped.

Remember Blink?

/*
Blink
Turns on an LED on for one second, then off for one second, repeatedly.

This example code is in the public domain.
*/

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}

A simple Blink sketch is almost all the time in the delay() function. So at this time, the processor can do nothing else.

You haven't forgotten your sweep?

Sweep uses the delay() function to control its speed. If you try to combine the basic blink sketch with an example of the servo of sweep, you will notice that alternately the LED flashes and the servo moves. However, this will not happen simultaneously.

#include 

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

Servo myservo; // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0; // variable to store the servo position

void setup()
{
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
myservo.attach(9); // attachches the servo on pin 9 to the servo object
}

void loop()
{
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second

for (pos = 0; pos <= 180; pos += 1) // goes from 0 degrees to 180 degrees
{ // in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) // go from 180 degrees to 0 degrees
{
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo

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

Using the millis() function in delay

A simple technique to implement a delay is to make a plan and watch the watch. Instead of focusing on the delay function, you simply systematically check the time to know when to act. In the meantime, other tasks can use the processor. A very simple example of this is the BlinkyWithoutDelay sketchwiththe development environment.

The connection diagram for the above code is shown below:

Wielozadaniowość Arduino - schemat podłączenia

Flickering without delay

Here is a sample code for BlinkWithoutDelay:

/* Blink without Delay

Turns on and off a light emitting diode(LED) connected to a digital
pin, without using the delay() function. This means that other code
can run at the same time without being interrupted by the LED code.

The circuit:
* LED attached from pin 13 to ground.
* Note: on most Arduinos, there is already an LED on the board
that's attached to pin 13, so no hardware is needed for this example.


created 2005
by David A. Mellis
modified 8 Feb 2010
by Paul Stoffregen

This example code is in the public domain.


http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
*/

// constants won't change. Used here to
// set pin numbers:
const int ledPin = 13; // the number of the LED pin

// Variables will change:
int ledState = LOW; // ledState used to set the LED
long previousMillis = 0; // will store last time LED was updated

// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 1000; // interval at which to blink (milliseconds)

void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}

void loop()
{
// here is where you'd put code that needs to be running all the time.

// check to see if it's time to blink the LED; that is, if the
// difference between the current time and last time you blinked
// the LED is bigger than the interval at which you want to
// blink the LED.
unsigned long currentMillis = millis();

if(currentMillis - previousMillis > interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;

// if the LED is off turn it on and vice-versa:
if (ledState == LOW)
ledState = HIGH;
else
ledState = LOW;

// set the LED with the ledState of the variable:
digitalWrite(ledPin, ledState);
}
}

Does that make any sense?

At first sight, BlinkWithoutDelay doesn't seem very interesting. It looks like a more complicated way to turn on the LEDs. However, BlinkWithoutDelay presents a very important concept known as StateMachine.

You no longer have to rely on the delay() function to create a flashing diode program. BlinkWithoutDelay remembers the current LED status and the last time it changed. After each loop, it checks the millis clock() to know if it should change the status of the LEDs again.

Welcome to the machine

Let's note the slightly more interesting blink variant, which has different on and off times. We called it "FlashWithoutDelay"

// These variables store the flash pattern
and the current state of the LED

int ledPin = 13; // the number of the LED pin
int ledState = LOW; // ledState used to set the LED
unsigned long previousMillis = 0; // will store last time LED was updated
long OnTime = 250; // milliseconds of on-time
long OffTime = 750; // milliseconds of off-time

void setup()
{
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}

void loop()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();

if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
{
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
{
ledState = HIGH; // turn it on
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}

Status + Mashine = State Machine

Note that we have variables that track whether the LED is on or off. There are also some that are watching the recent change. This is the part of the automat calledStatus.

We also have a code that watches over the status and decides when and how to change it. This is the part called the Machine. After each loop, we "start the machine" and it updates the status.

Then, we'll check how you connect the machines by starting them simultaneously.

Target - two at a time

Time for multitasking! First connect another LED, as shown in the diagram below:

Wielozadaniowość Arduino - schemat podłączenia diod LED

Then we will create another automaton for the second LED, which flashes at completely different speeds. Using two separate automats will make these two LEDs flash without being dependent on each other.

// These variables store the flash pattern
// and the current state of the LED

int ledPin1 = 12; // the number of the LED pin
int ledState1 = LOW; // ledState used to set the LED
unsigned long previousMillis1 = 0; // will store last time LED was updated
long OnTime1 = 250; // milliseconds of on-time
long OffTime1 = 750; // milliseconds of off-time

int ledPin2 = 13; // the number of the LED pin
int ledState2 = LOW; // ledState used to set the LED
unsigned long previousMillis2 = 0; // will store last time LED was updated
long OnTime2 = 330; // milliseconds of on-time
long OffTime2 = 400; // milliseconds of off-time

void setup()
{
// set the digital pin as output:
pinMode(ledPin1, OUTPUT);
pinMode(ledPin2, OUTPUT);
}

void loop()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();

if((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OnTime1))
{
ledState1 = LOW; // Turn it off
previousMillis1 = currentMillis; // Remember the time
digitalWrite(ledPin1, ledState1); // Update the actual LED
}
else if ((ledState1 == LOW) && (currentMillis - previousMillis1 >= OffTime1))
{
ledState1 = HIGH; // turn it on
previousMillis1 = currentMillis; // Remember the time
digitalWrite(ledPin1, ledState1); // Update the actual LED
}

if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))
{
ledState2 = LOW; // Turn it off
previousMillis2 = currentMillis; // Remember the time
digitalWrite(ledPin2, ledState2); // Update the actual LED
}
else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2))
{
ledState2 = HIGH; // turn it on
previousMillis2 = currentMillis; // Remember the time
digitalWrite(ledPin2, ledState2); // Update the actual LED
}
}

Thank you very much! Can I get another one?

You can add more slot machines as long as you have enough memory or GPIO pins. Each slot machine has its own flicker rate. As a task, edit the code above to be able to add a third slot machine.

  • First, copy all the status variables and the code of one of the slots.
  • Then, rename each variable again to avoid conflicting with the first machine.

This is not difficult to do. But rewriting the code all the time seems to be rather time consuming. There must be some other way to do it!

There are better ways to deal with it. These are programming techniques that are simpler and more effective.

Classes

Let's take another look at our last sketch. As you can see, it is very monotonous. The same code is copied almost word for word for every flickering LED. The only thing that changes (to a small extent) is the name of the variables.

This code is the best candidate forObject Oriented Programming(OOP)

OOP in a loop

Arduino language is a variation of C++ language, which supports object-oriented programming. Using OOP language we can collect all status variables and functions of flashing LED in C++ class.

This is not difficult to do, because we already have all the code written. We just have to repack it as a class.

Define the class:

We start by declaring the class "Flasher":

Then we add all variables from FlashWithoutDelay. They are part of the class, so we know them as member variables.

class Flasher
{
// Class Member Variables
// These are initialized at startup
int ledPin; // the number of the LED pin
long OnTime; // milliseconds of on-time
long OffTime; // milliseconds of off-time

// These maintain the current state
int ledState; // ledState used to set the LED
unsigned long previousMillis; // will store last time LED was updated
};

Then we add aconstructor. It hasthe same name as the class and its task is to initiate all variables.

class Flasher
{
// Class Member Variables
// These are initialized at startup
int ledPin; // the number of the LED pin
long OnTime; // milliseconds of on-time
long OffTime; // milliseconds of off-time

// These maintain the current state
int ledState; // ledState used to set the LED
unsigned long previousMillis; // will store last time LED was updated

// Constructor - creates a Flasher
// and initializes the member variables and state
public:
Flasher(int pin, long on, long off)
{
ledPin = pin;
pinMode(ledPin, OUTPUT);

OnTime = on;
OffTime = off;

ledState = LOW;
previousMillis = 0;
}
};

Finally, we change the loop into a component function called"Update()".

class Flasher
{
// Class Member Variables
// These are initialized at startup
int ledPin; // the number of the LED pin
long OnTime; // milliseconds of on-time
long OffTime; // milliseconds of off-time

// These maintain the current state
int ledState; // ledState used to set the LED
unsigned long previousMillis; // will store last time LED was updated

// Constructor - creates a Flasher
// and initializes the member variables and state
public:
Flasher(int pin, long on, long off)
{
ledPin = pin;
pinMode(ledPin, OUTPUT);

OnTime = on;
OffTime = off;

ledState = LOW;
previousMillis = 0;
}

void Update()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();

if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
{
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
{
ledState = HIGH; // turn it on
previousMillis = currentMillis;

// Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}
};

By changing our code into a Flasher class, we have narrowed down all variables(status) and functions(machine) of the flashing LED.

Now let's use this:

For each LED we want to light up, we create an example ofthe Flasher class, invoking the constructor. Every timewe go throughthe loop, we have to invoke Update() for each example of the Flasher.

You no longer need to repeat the whole code of the machine. All we have to do is ask for more Flasher examples!

class Flasher
{
// Class Member Variables
// These are initialized at startup
int ledPin; // the number of the LED pin
long OnTime; // milliseconds of on-time
long OffTime; // milliseconds of off-time

// These maintain the current state
int ledState; // ledState used to set the LED
unsigned long previousMillis; // will store last time LED was updated

// Constructor - creates a Flasher
// and initializes the member variables and state
public:
Flasher(int pin, long on, long off)
{
ledPin = pin;
pinMode(ledPin, OUTPUT);

OnTime = on;
OffTime = off;

ledState = LOW;
previousMillis = 0;
}

void Update()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();

if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
{
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
{
ledState = HIGH; // turn it on

previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}
};


Flasher led1(12, 100, 400);
Flasher led2(13, 350, 350);

void setup()
{
}

void loop()
{
led1.Update();
led2.Update();
}

Less is more!

Finally -Each additional LED requires only two lines of code!

This code is shorter and easier to read. And since there is no duplicate code, there is lessto compile! Thisall saves memory for other projects!

What else can we do?

Let's apply the same rules to the servo codesand get started.

First, connect two servos to each other on the contact plate, as shown below. Then, connect the third LED as well .

Wielozadaniowość Arduino - schemat podłączenia serw i diod LED

Here's the standard code for your sweep servo. Note that it invokes the unwanted delay() function. We'll take some of this code to build the Sweeper.

// Sweep
// by BARRAGAN
// This example code is in the public domain.


#include

Servo myservo; // create servo object to control a servo
// a maximum of eight servo objects can be created

int pos = 0; // variable to store the servo position

void setup()
{
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}


void loop()
{
for(pos = 0; pos < 180; pos += 1) // goes from 0 degrees to 180 degrees
{ // in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
for(pos = 180; pos>=1; pos-=1) // go from 180 degrees to 0 degrees
{
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
}

The following Sweeper class summarizes the sweep action, but uses the millis() function to set the delay.

We also need to add Attach() and Detach() functions to bind the servo to a specific pin:

class Sweeper
{
Servo servo; // the servo
int pos; // current servo position
int increment; // increment to move for each interval
int updateInterval; // interval between updates
unsigned long lastUpdate; // last update of position

public:
Sweeper(int interval)
{
updateInterval = interval;
increment = 1;
}

void Attach(int pin)
{
servo.attach(pin);
}

void Detach()
{
servo.detach();
}

void Update()
{
if((millis() - lastUpdate) > updateInterval) // time to update
{
lastUpdate = millis();
pos += increment;
servo.write(pos);
Serial.println(pos);
if ((pos >= 180) || (pos <= 0)) // end of sweep
{
// reverse direction
increment = -increment;
}
}
}
};

How much?

Now we can make as many copies of Flashersand Sweepersaswe want.

Each example of a Flashlight requires two lines of code:

  • one to declare an example
  • the second to call up an update in the loop

Each Sweeper example requires only 3 lines of code:

  • one to declare an example
  • the second to tie the servo to the pin
  • and a third to recall the update in the loop

#include 

class Flasher
{
// Class Member Variables
// These are initialized at startup
int ledPin; // the number of the LED pin
long OnTime; // milliseconds of on-time
long OffTime; // milliseconds of off-time

// These maintain the current state
int ledState; // ledState used to set the LED
unsigned long previousMillis; // will store last time LED was updated

// Constructor - creates a Flasher
// and initializes the member variables and state
public:
Flasher(int pin, long on, long off)
{
ledPin = pin;
pinMode(ledPin, OUTPUT);

OnTime = on;
OffTime = off;

ledState = LOW;
previousMillis = 0;
}

void Update()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();

if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
{
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
{
ledState = HIGH; // turn it on
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}
};

class Sweeper
{
Servo servo; // the servo
int pos; // current servo position
int increment; // increment to move for each interval
int updateInterval; // interval between updates
unsigned long lastUpdate; // last update of position

public:
Sweeper(int interval)
{
updateInterval = interval;
increment = 1;
}

void Attach(int pin)
{
servo.attach(pin);
}

void Detach()
{
servo.detach();
}

void Update()
{
if((millis() - lastUpdate) > updateInterval) // time to update
{
lastUpdate = millis();
pos += increment;
servo.write(pos);
Serial.println(pos);
if ((pos >= 180) || (pos <= 0)) // end of sweep
{
// reverse direction
increment = -increment;
}
}
}
};


Flasher led1(11, 123, 400);
Flasher led2(12, 350, 350);
Flasher led3(13, 200, 222);

Sweeper sweeper1(15);
Sweeper sweeper2(25);

void setup()
{
Serial.begin(9600);
sweeper1.Attach(9);
sweeper2.Attach(10);
}


void loop()
{
sweeper1.Update();
sweeper2.Update();

led1.Update();
led2.Update();
led3.Update();
}

At the moment, we have 5 independent tasks running non-stop, without interruption. And our loop() function has only 5 lines of code!

All together!

We also want your entrance

Another problem with timing based on the delay() function is that user input, such as pressing a button, is often ignored. This is because the processor cannot check the state of the button when it is in the delay() function. With a delay based on the millis() function, the processor can regularly check the status of buttons and other inputs. This allows us to build complex programs that can do many things at once and still react.

We will demonstrate this by adding a button to our circuit, as shown in the figure:

Wielozadaniowość Arduino - schemat podłączenia z przyciskiem

The code below will check the state of the button on each loop passage. Led1and sweeper2will not be updatedwhen the button is pressed.

#include 


class Flasher
{
// Class Member Variables
// These are initialized at startup
int ledPin; // the number of the LED pin
long OnTime; // milliseconds of on-time
long OffTime; // milliseconds of off-time

// These maintain the current state
int ledState; // ledState used to set the LED
unsigned long previousMillis; // will store last time LED was updated

// Constructor - creates a Flasher
// and initializes the member variables and state
public:
Flasher(int pin, long on, long off)
{
ledPin = pin;
pinMode(ledPin, OUTPUT);

OnTime = on;
OffTime = off;

ledState = LOW;
previousMillis = 0;
}

void Update()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();

if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
{
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
{
ledState = HIGH; // turn it on
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}
};

class Sweeper
{
Servo servo; // the servo
int pos; // current servo position
int increment; // increment to move for each interval
int updateInterval; // interval between updates
unsigned long lastUpdate; // last update of position

public:
Sweeper(int interval)
{
updateInterval = interval;
increment = 1;
}

void Attach(int pin)
{
servo.attach(pin);
}

void Detach()
{
servo.detach();
}

void Update()
{
if((millis() - lastUpdate) > updateInterval) // time to update
{
lastUpdate = millis();
pos += increment;
servo.write(pos);
Serial.println(pos);
if ((pos >= 180) || (pos <= 0)) // end of sweep
{
// reverse direction
increment = -increment;
}
}
}
};


Flasher led1(11, 123, 400);
Flasher led2(12, 350, 350);
Flasher led3(13, 200, 222);

Sweeper sweeper1(15);
Sweeper sweeper2(25);

void setup()
{
Serial.begin(9600);
sweeper1.Attach(9);
sweeper2.Attach(10);
}


void loop()
{
sweeper1.Update();

if(digitalRead(2) == HIGH)
{
sweeper2.Update();
led1.Update();
}

led2.Update();
led3.Update();
}

3 LEDs will blink at their own rate. 2 sweepers willalsowork at their own speed. But when the button is pressed, sweeper2and led1willstopuntil the button is released.

wielozadaniowość Arduino - działanie

We now have 5 user input tasks, working independently. There are no delays that will block the processor. And our powerful object-oriented programming code leaves plenty of room for expansion!

Conclusion:

In this guide we've shown that Arduino can perform many different tasks and still react to external events.

  • We learned how to measure time with millis() instead of delay(), which allowed the CPU to do other things.
  • We learned how to define tasks as automats that can work at the same time as other automats, but independently of them.
  • We discovered how to include these slots in C++ classes to keep the code simple and concise.

These techniques will not turn your Arduino into a supercomputer. However, they will help you get the best out of this small but amazingly powerful module.

In the second part, we'll build using these techniques. We'll also discover other ways to make Arduino react to external events while keeping many tasks running.

part two ->

Source: https://learn.adafruit.com/multi-tasking-the-arduino-part-1

Botland.store - shop for makers!