Arduino programming guide series

19

Concurrency with the Scheduler library on the Arduino Due and Zero

The Arduino Due and Arduino Zero are far more powerful than the Arduino Uno. They use microcontrollers based on 32-bit ARM technology. With the help of the Scheduler library, you can use them as potent multitasking machines.

As I explained in a separate article on multitasking on the Arduino Uno, it is possible to use simple "if" structures and the millis() function to execute arbitrary functions at specific intervals.

Once the number of concurrent functions increases to more then two or three, this method becomes too complicated. At that point, you can use a more sophisticated approach.

One such approach is implemented with the Scheduler library.

With the Scheduler library, you can set up multiple tasks within your sketch. Each task will be executed concurrently, and independently of each other. Remember that just like with the Arduino Uno, the Arduino Due and Zero are both single-core machines. Therefore, each task will be executed individually even though the effect is that they are all running concurrently. 

The effect of concurrency amounts to the ability of the processor to move from one task to the other before it is completed and then to return later to continue where it left off. 

Although you only have very basic control over the actual timing of execution, the greatest advantage of this library is its simplicity.

To understand what I mean by this, I have written a simple sketch to implement this functionality:

This is an Arduino Due controlling 4 LEDs. Each LED is toggling on/off on schedule, and each schedule is independent.

#include <Scheduler.h>

int led_red = 11;
int led_green = 10;
int led_yellow = 9;
int led_orange = 8;

void setup() {
 pinMode(led_red, OUTPUT);
 pinMode(led_green, OUTPUT);
 pinMode(led_yellow, OUTPUT);
 pinMode(led_orange, OUTPUT);
 Scheduler.startLoop(control_red);
 Scheduler.startLoop(control_green);
 Scheduler.startLoop(control_yellow);
 Scheduler.startLoop(control_orange);
}

void loop() {
 yield(); // This is important. With yield, control will be passed to one of the other tasks.
}

void control_red(){
 digitalWrite(led_red, HIGH);
 delay(300); // Control is passed to one of the other tasks.
 digitalWrite(led_red, LOW);
 delay(300); // Control is passed to one of the other tasks.
}

void control_green(){
 digitalWrite(led_green, HIGH);
 delay(400); // Control is passed to one of the other tasks.
 digitalWrite(led_green, LOW);
 delay(400); // Control is passed to one of the other tasks.
}

void control_yellow(){
 digitalWrite(led_yellow, HIGH);
 delay(500); // Control is passed to one of the other tasks.
 digitalWrite(led_yellow, LOW);
 delay(500); // Control is passed to one of the other tasks.
}

void control_orange(){
 digitalWrite(led_orange, HIGH);
 delay(600); // Control is passed to one of the other tasks.
 digitalWrite(led_orange, LOW);
 delay(600); // Control is passed to one of the other tasks.
}

First, install the library to your IDE (it does not ship by default). 

To set up a task, use the Scheduler's start loop function, and pass the name of the function that implements the task as the parameter. You can have as many of them as you like.

In this example, we set up four tasks. Each one controls an LED and turns it on and off. 

In the loop() function, there is only one instruction: "yield()" this allows the control of the execution to be given to one of the other tasks. 

Which one? The scheduler will decide. If you are curious about the details, you can have a look at the library source code.

The gist is that the Scheduler implements cooperative multitasking. In simple terms, this means that the scheduler depends on the executing task to relinquish control; this is what the yield function does. It tells the scheduler that it is ok for another task to be executed at this point. The library will also use the delay function to "know" when it is ok to transfer control to another task. 

For the end user, me and you, this means that to use the Scheduler library we only need to use the yield() and delay() functions to signal to the scheduler that control can be transferred to another task. We can use these functions at any point in the sketch where it makes sense. 

For example, have a look at function control_orange. I am making an LED blink, and as usual, I use the delay function to keep the LED at a particular state for, in this case, 600ms. If we were not using the Scheduler library, at this point the complete program would block the execution. Nothing else would happen until this delay was complete. 

But in this example, we are using the Scheduler library, so instead of the program blocking, the control is simply transferred to another task. 

This way, CPU wasted cycles are minimized, and you get a much more efficient design for minimal additional effort.

You can use the yield function instead of delay, with the same result, as I did in the loop function. 

If you have a Due or a Zero around, I encourage you to play with the Scheduler library to become familiar with it. Think about how your next project can use it so you can take advantage of the efficiency it offers. 

"Arduino programming" series

New to the Arduino?

Arduino Step by Step Getting Started is our most popular course for beginners.

This course is packed with high-quality video, mini-projects, and everything you need to learn Arduino from the ground up. We'll help you get started and at every step with top-notch instruction and our super-helpful course discussion space.

Tech Explorations Arduino intermediate level

Done with the basics? Looking for more advanced topics?

Arduino Step by Step Getting Serious is our comprehensive Arduino course for people ready to go to the next level.

Learn about Wifi, BLE and radio, motors (servo, DC and stepper motors with various controllers), LCD, OLED and TFT screens with buttons and touch interfaces, control large loads like relays and lights, and much much MUCH more.


>