For anyone unfamiliar with Arduino, it is an open-source platform made up of very simple hardware (an RISC microprocessor and a small amount of memory) and an equally simple software kit for programming in C and C++. Find more information on the Arduino website (arduino.cc) or Wikipedia.
The IDE includes several examples. One of the simplest makes an LED on the board blink:
/*
Blink
Turns on an LED on for one second, then off for one second, repeatedly.
This example code is in the public domain.
*/
void setup() {
// initialize the digital pin as an output.
// Pin 13 has an LED connected on most Arduino boards:
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH); // set the LED on
delay(1000); // wait for a second
digitalWrite(13, LOW); // set the LED off
delay(1000); // wait for a second
}In practice, Arduino calls setup only once, then runs loop for as long as the hardware has power.
All your code must run from this loop. Things can become complicated or messy when you add sensors, servos, LEDs, and shields that need to run independently. For example, you might read the environment with one sensor, control a robot's movement based on terrain mapping with another process, and periodically record temperature readings to create a history.
Many developers think multitasking systems must run programs in parallel. That is not quite right. Before multi-core processors became popular around 2005, most operating systems shared processor time between tasks on a single processor. The operating system had to choose which task (process) ran and how long it could use the processor.
The basic idea is that the shorter the time slice used by a process, the more processes get a chance to run and, in theory, the better multitasking feels. Processes are commonly switched on the processor thousands of times per second.
Many operating systems also prioritize processes to use I/O resources efficiently (when interacting with users or external devices) and complete tasks as quickly as possible. But that is another story—and could take all night to discuss.
Arduino hardware is simple, so you cannot run a complex operating system to schedule activities, much less pause a task halfway through to start a higher-priority one. At most, you could use a FreeRTOS port, which is somewhat difficult to load and use. From what I read, it seemed to support preemption, but I did not get very far in practice.
To solve my problem, I developed a small task-scheduling library called AOScheduler and published it on GitHub (https://github.com/phcco/arduino-scheduler). Unpack it into the IDE's libraries folder.
This library is intended to make projects with multiple features easier to develop. Each feature or task is called a Task and implemented by the AOTask class, which is scheduled by AOScheduler.
The example below lets you set how long an LED stays on and off. The MultiBlink code is shown here; a commented version is also included in the library.
#include "AOScheduler.h"
class MultiBlink : public AOTask {
private:
int _pin;
unsigned long _uptime,_downtime;
int _state;
public:
MultiBlink(int pin,unsigned long uptime, unsigned long downtime){
_pin = pin;
_uptime = uptime;
_downtime = downtime;
_state = LOW;
}
int setup(){
this->task_expected_period = _uptime;
pinMode(_pin, OUTPUT);
digitalWrite(_pin,_state = HIGH);
}
int loop(){
if(_state==LOW){
this->task_expected_period = _uptime;
_state = HIGH;
}else{
this->task_expected_period = _downtime;
_state = LOW;
}
digitalWrite(_pin, _state);
return AO_OK;
}
};
AOScheduler aos;
// Tempos em microsegundos
// MultiBlink blinker1(pin, tempo ligado, tempo desligado);
MultiBlink blinker1(13,SECOND,2*SECOND);
MultiBlink blinker2(12,MILLISECOND,1*SECOND);
MultiBlink blinker3(11,MILLISECOND,1*SECOND);
MultiBlink blinker4(9,SECOND/2,3*SECOND);
MultiBlink blinker5(8,MILLISECOND,1*SECOND);
MultiBlink blinker6(5,SECOND,4*SECOND);
void setup(){
aos.add(&blinker1);
aos.add(&blinker2);
aos.add(&blinker3);
aos.add(&blinker4);
aos.add(&blinker5);
aos.add(&blinker6);
}
void loop(){
aos.run();
}Line 1 includes the AOScheduler library; lines 3–33 define the task's behaviour. The setup function runs once when the task is added to the scheduler, and loop runs each time the task is scheduled. This makes each task execution atomic: its loop is not interrupted midway.
Because execution is atomic, any delay or delayMicroseconds call inside the function can delay the next task. The scheduler runs loop according to the microsecond value in task_expected_period. If configured for every 1,000 microseconds, it tries to keep that interval, adjusting the time between tasks to approach the requested period.
Line 35 creates the scheduler. Lines 38–43 create several MultiBlink tasks. The first parameter is the digital pin used to send the signal, the second is how long the LED stays on, and the third is how long it stays off.
The setup function adds the tasks to the scheduler, and loop starts it. The run method returns only when no tasks remain.
A task's
loopmethod must returnAO_OKwhen the task should be rescheduled. If it returnsAO_EXIT, it leaves the execution queue and will not run again.
This scheduler uses timers to run its tasks, so do not expect deadlines to be met exactly. It shares processor time and can miss them.
I plan to improve the library by making scheduling faster and adjusting timers to prevent tasks from drifting. The time spent scheduling is not yet included in those timers.
Tasks that emulate interrupts are also on the list; a beta version is already attached to the library. I will cover it in another article.