10
How to do several things at the same time
Nope, it’s not an article on time management ! Sometimes I see people around pushing hard on their brains about “how to do several things at the same time” in an Arduino or any other microcontroller board. Sometimes they even start thinking about threads and operating systems, but in such small machines and with newbie programmers, the best thing to do really is to stay away from the complexity of preemptive multitask / time sharing systems and synchronization theory. But then, is there any programming method adequate to resource-scarce machines and easy to use? There is indeed. Just keep on reading
A multitasking system is complex for machines with limited resources like an Arduino and complex for newbies, so I recommend (and in fact that’s why I use, although I’m far from being a newbie) the “event model”, also called “cooperative multitasking”. This model works by events and it works very well as long as there are no “atomic” operations that take a long time.
In the event model, each task is executed in a function but that function cannot block. The program’s main loop calls all tasks (functions) sequentially, usually in a round-robin fashion. Then it’s a matter of applying some “tricks” to prevent the tasks from blocking execution, using state machines.
Let’s pick a problem I saw at LusoRobotica.com (a Portuguese robotics forum) that gave birth to the original version of this article: to have 2 LEDs blinking at different speeds and send an increasing number to the serial port. This comprises 3 tasks.
The main loop invokes the 3 tasks in sequence, and at the end it pauses for 10ms. This pause provides a known “time unit”, making the loop run at a certain known speed, otherwise it would be executed at maximum speed and it would be impossible to control the frequency at which, for example, the LEDs blink:
void loop ()
{
blinkLed1();
blinkLed2();
sendNumber();
delayMs(10);
}
Now we know that each task is invoked at 10ms intervals. If we want our LED1 blinking once a second, we have to invert the LED’s state every 500ms. Since we know that each task is called every 10ms, we need to invert the state every 500ms / 10ms = 50 task calls, and for that we use a variable to count the calls:
void blinkLed1 ()
{
static int counter = 0;
counter = counter + 1;
if (counter == 50)
{
togglePin(…);
counter = 0;
}
}
A variable declared with static
is like a global variable but is only visible inside the code block where it’s declared (a C/C++ code block is a piece of code enclosed in {}’s). I could have declare it outside all functions, but since it concerns, and is only used inside, that function, I decided to declare it as static to get things more clean and organized. Another reason to declare variables as static whenever possible is that we can have “global” variables with the same name - which is useful in the kind of programming model we’re talking about here since we can have many “counter” variables and other useful names. blinkLED2()
’s task function can use the same name for the “counter” variable since it’s only visible inside the block where it was declared.
This kind of variable must be “global” because their value must be preserved between calls at the same task.
Task blinkLED2()
is similar to blinkLED1()
; the only change is the LED pin and the number 50, which must be re-calculated for the desired blink frequency.
Now let’s do the sendNumber()
task, that may seem complex but in fact it isn’t. What we need to do is to have a variable to save the last number sent to the serial port, and then increment it and send it on each task call:
void sendNumber ()
{
static int number = 0;
number = number + 1;
Serial.println(number);
}
This task tries to send a number every 10ms, which can be just to fast for what we want or even for the serial port’s data transmission speed (baudrate). In that case, we need to change the number sending such that only 1 count is sent, say, every second. This is easy to do, because that’s exactly what the blinkLED()
tasks already do! For a time interval of 1 second our counter variable needs to count 1000ms / 10ms = 100 times before sending the next number:
void sendNumber() { static int counter = 0; counter = counter + 1; if (counter == 100) { static int number = 0; number = number + 1; Serial.println(number); counter = 0; } }
Note that the original sendNumber()
’s code is unchanged in the new function, but now inside the “if (counter == …” section so that it is now executed only once every 100 task calls.
There are a few more details I don’t mention in this article but the general technique is explained, and it allows us to do many things “simultaneously” in a simple way. What’s more important is that the tasks don’t take too long nor block; for example, you shouldn’t do delays inside them. If you need a delay, use the counter method to pause for the desired time and control the task using a state machine (like having 2 states, “delay” and “work”), just like in the examples above. Tasks must do something “quick” and leave.
Using this code structure you can implement lots of things and it’s a secure way of doing some multitasking.
Enjoy
p.s. The code doesn’t compile, it’s just for illustration purposes.
Release Your Inner Self