paint-brush
AsyncIO in Arduino: Introduction to Asynchronous Processing Part 1โ€‚by@becerracenmanueld
369 reads
369 reads

AsyncIO in Arduino: Introduction to Asynchronous Processing Part 1

by Enmanuel D Becerra COctober 18th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Asynchronous processing is a programming model in which tasks can run in the background without blocking the execution of the main program...
featured image - AsyncIO in Arduino: Introduction to Asynchronous Processing Part 1
Enmanuel D Becerra C HackerNoon profile picture



AsyncIO in Arduino

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.




What is a coroutine?

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);

}



Conclusion:

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!