For those people who areย familiar with microcontroller programming, you may have asked yourself this question.
Can multiple processes be executed at the same time on Arduino?
And the answer is, indeed, possible. Although we cannot run multiple processes in parallel as a PC would. Yes, we can execute multiple processes asynchronously.
Asynchronous, you said? What is that?
In programming, asynchronous processing is a programming model in which tasks can run in the background without blocking the execution of the main program. In this model, tasks are added to a process queue and then are executed, one by one, when there are available resources.
In this article, I will show you how to create a coroutine step by step on Arduino UNO.
Well, a coroutine is a function that has the ability to pause and resume itself. This means that with coroutines, we can execute parts of a function instead of executing it completely as usual. To achieve this, we will use a design pattern calledย State Machines. This design pattern will allow us to split the function into states and then execute them individually. Take a look at the next example:
#include <cstdlib>
#include <cstdio>
int coroutine() {
static int state = 0;
switch( state ){
case 0: printf(":>A\n"); state++; return 1; break;
case 1: printf(":>B\n"); state++; return 1; break;
case 2: printf(":>C\n"); state++; return 1; break;
case 3: printf(":>D\n"); state++; return 1; break;
case 4: printf(":>E\n"); state++; return 1; break;
} return -1;
}
int main() {
while( coroutine()>0 ){}
}
In this example, we have made a basic coroutine using a static variable and a switch conditional, which together compose a simple state machine that, depending on the state variable, executes one part or another of the function.
But now, imagine coding complex coroutines using this method will cause you a headache. It's not easy to use nor efficient to write, right? To improve this, we will use macros to be able to reduce the complexity of coroutines. Take a look at the next example:
#define _Yield(VALUE) do { _state_ = VALUE; return 1; case VALUE:; } while (0)
#define _Start static int _state_ = 0; { switch(_state_) { case 0:;
#define _Goto(VALUE) do { _state_ = VALUE; return 1; } while (0)
#define _Stop } _state_ = 0; return -1; }
#include <cstdlib>
#include <cstdio>
int coroutine() {
static int x = 10;
_Start
while( x-->0 ){
printf(":>%d\n",x); _Yield(1);
}
_Stop
}
int main() {
while( coroutine()>0 ){}
}
So now, coroutines looks better. With this method, we can create more complex coroutines that are easy to use and more efficient to write.
Once we have a good understanding of coroutines and how to implement them in C++, we can apply this knowledge to create a program on the Arduino UNO that enables us to execute multiple processes simultaneously using coroutines. To demonstrate this, let's consider a simple example where we create two coroutines, each responsible for turning on and off an LED, with different frequencies and completely independent of each other. Take a look at the next example:
#define _Yield(VALUE) do { _state_ = VALUE; return 1; case VALUE:; } while (0)
#define _Start static int _state_ = 0; { switch(_state_) { case 0:;
#define _Goto(VALUE) do { _state_ = VALUE; return 1; } while (0)
#define _Stop } _state_ = 0; return -1; }
/*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/
using ulong = unsigned long int;
int OUT[] = { 12, 11 };
/*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/
template < class T >
int wrk1( const T& function, const ulong& time ){ //firts we create a worker
static ulong stamp = millis() + time; //then we define a time stamp as static
_Start //Begin of the coroutine
while( millis() < stamp ){ _Yield(1); } //Async waiting a period of time
stamp = millis() + time; //we redefine the time stamp
function(); //now we are able to execute the function
_Goto(0); //and then set the state machine as 0
_Stop //End of the coroutine
}
template < class T >
int wrk2( const T& function, const ulong& time ){
static ulong stamp = millis() + time;
_Start
while( millis() < stamp ){ _Yield(1); }
stamp = millis() + time;
function(); _Goto(0);
_Stop
}
/*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/
void setup(){
for( auto x : OUT ) pinMode( x, OUTPUT ); //Setting up Output pins
}
void loop(){
wrk1([](){ //Worker 1
static bool b=0; b=!b; //Simple Boolean Oscilator
digitalWrite( 12, b ); //setting up Output based on oscilations
},1000); //NOTE: 1000 represent time in ms
wrk2([](){
static bool b=0; b=!b;
digitalWrite( 11, b );
},500);
}
Creating asynchronous functions in C++ and Arduino is just the beginning. Stay tuned for the next part of this article, where we'll dive deeper into this topic. If you found this article helpful, show your support by sharing it with your friends and leaving a comment with this emoji ๐ค to let us know you're excited about the second part!