Proof of Concept. Asynchronous programming in Arduino UNO

hello everyone.

I have recently been fascinated with how NodeJS works. Well, it is interesting to see how it is possible to execute multiple tasks at the same time in a single thread. and I asked myself, is it possible to do this same thing on an Arduino UNO?

Well, I was doing a little research on how it works. and I realized that Nodejs uses 2 algorithms to achieve concurrency on a single thread.

  • Event Loop: is responsible for saving and executing a list of tasks, each of these functions could easily be a coroutine.

  • Coroutines: are a type of function that have the ability to suspend their execution, in order to resume them in the future.

So I decided to replicate this same concurrent system on an Arduino UNO.

I started with the easiest part. the coroutines. Since C/C++ doesn’t have built-in coroutine support, I have made my own coroutine system. To achieve this, I found a very useful algorithm called Duff’s Device, This algorithm is not directly related to Coroutines, but will help us to make them.

Ok, The main idea is to create a static state variable, which will allow us to remember the state of the coroutine. then using a switch statement, we have to split the function into chunks, and using the state variable, we can execute the part we need. If you don’t understand what I’m talking about? Well, it’s something like this:

int coroutine(){
static int state = 0;

     switch(state){
         case 0: do { printf("Hello World 0"); state=1; return 1; case 1:; } while(0);
                 do { printf("Hello World 1"); state=2; return 1; case 2:; } while(0);
                 do { printf("Hello World 2"); state=3; return 1; case 3:; } while(0);
                 do { printf("Hello World 3"); state=4; return 1; case 4:; } while(0);
                 do { printf("Hello World 4"); state=5; return 1; case 5:; } while(0);
                 do { printf("Hello World 5"); state=0; return 1; } while(0);
     }

     return -1;

}

void setup(){
     Serial.begin( 9600 );
}

void loop(){
     coroutine();
     delay(1000);
}

It’s great, right? But imagine writing more complex coroutines using this method. Then it occurred to me to simplify the creation of Duff’s device using macros. Here are some:

#define coNext do { _state_ = _LINE_; return 1; case _LINE_:; } while (0)
#define coGoto(VALUE) do { _state_ = VALUE ; return 1; } while (0)
#define coYield(VALUE) do { _state_ = VALUE ; return 1; case VALUE:; } while (0)

/*──────────────────────────────────────────────────────*/

#define coStart static int _state_ = 0; { switch(_state_) { case 0:;
#define coEnd do { _state_ = 0; return -1; } while (0)
#define coStop } _state_ = 0; return -1; }
#define coSet(VALUE) _state_ = VALUE
#define coGet _state_

NOTE: I also added other very useful macros.

#define coDelay(VALUE)  do { static auto tm = millis()+VALUE; while( millis() < tm ){ coNext; } tm = millis()+VALUE; break; } while (0)
#define coUDelay(VALUE) do { static auto tm = micros()+VALUE; while( micros() < tm ){ coNext; } tm = micros()+VALUE; break; } while (0)

If we replicate the previous example, we would have something like this:

int coroutine(){
coStart

     printf("Hello World 0"); coNext;
     printf("Hello World 2"); coNext;
     printf("Hello World 3"); coNext;
     printf("Hello World 4"); coNext;
     printf("Hello World 5"); coNext;
     printf("Hello World 6");

coGoto(0);
coStop
}

void setup(){
     Serial.begin( 9600 );
}

void loop(){
     coroutine();
     delay(1000);
}

Now what about if we apply this thing in a real arduino project?

Suppose we want to create a program on Arduino UNO, which executes 3 asynchronous processes. which will allow us to turn on and off a series of LEDs. Well, here is the code:

int coroutine1(){
     static bool b=0;
coStart

     digitalWrite(7,b);
     coDelay(300); b=!b;

coGoto(0);
coStop
}

int coroutine2(){
     static bool b=0;
coStart

     digitalWrite(6,b);
     coDelay(1000); b=!b;

coGoto(0);
coStop
}

int coroutine3(){
     static int x=0; static bool b=0;
     unsigned char pin[] = { 13, 12, 11, 10, 9, 8 };
coStart

     while( x-->0 ){
         digitalWrite( pin[x], b );
         coDelay(100);
     } b=!b;

coGoto(0);
coStop
}

void setup(){
     unsigned char pin[] = { 13, 12, 11, 10, 9, 8, 7, 6 };
     for( auto &x: pin ) pinMode( pin, OUTPUT );
}

void loop(){
     coroutine1();
     coroutine2();
     coroutine3();
}

As you can see, asynchronous programming in Arduino UNO is possible and not complicated at all. I was short of time, and I couldn’t implement an event loop for this demo. But the loop function is a good replacement. If you are interested in this article, I may make a second part, explaining the Event Loop. I hope you liked this project. and see you later.

Before closing this article, I want to introduce you to a framework created by me, called Nodepp. It is a C++ framework that will allow us to write asynchronous code in Arduino with a syntax very similar to NodeJS. I hope you enjoy it.

https://www.arduino.cc/reference/en/libraries/nodepp/

1 Like

Ah, you mean like "loop()"

Now what about if we apply this thing in a real arduino project?

Sounds a lot like the various subroutine ("co-routine") based "non-blocking delay()" schemes that have been discussed. Although those usually count on the co-routine having little context or state and clear exit points.

switch(state){
         case 0: do { printf("Hello World 0"); state=1; return 1; case 1:; } while(0);
                 do { printf("Hello World 1"); state=2; return 1; case 2:; } while(0);
                 do { printf("Hello World 2"); state=3; return 1; case 3:; } while(0);
                 do { printf("Hello World 3"); state=4; return 1; case 4:; } while(0);
                 do { printf("Hello World 4"); state=5; return 1; case 5:; } while(0);
                 do { printf("Hello World 5"); state=0; return 1; } while(0);
     }

That's missing a bunch of "case" labels, isn't it?

The function after Auto and alto777 Formatting:

int coroutine()
{
  static int state = 0;

  switch (state) {
  case 0: do {
    Serial.println("Hello World 0");
    state = 1;
    return 1;
    
  case 1:;
    } while (0);
    do {
    Serial.println("Hello World 1");
    state = 2;
    return 1;
    
  case 2:;
    } while (0);
    do {
    Serial.println("Hello World 2");
    state = 3;
    return 1;

  case 3:;
    } while (0);
    do {
    Serial.println("Hello World 3");
    state = 4;
    return 1;

  case 4:;
    } while (0);
    do {
    Serial.println("Hello World 4");
    state = 5;
    return 1;
    
  case 5:;
    } while (0);
    do {
    Serial.println("Hello World 5");
    state = 0;
    return 1;
    } while (0);
  }

  return -1;
}

I don't see anything new in, and nothing to like about this code. Until it can be shown to do anything more than what is often recommended in these fora - recasting a function to play nice by breaking its functionality into small steps, one of which may be taken (or not) each time it is called, I won't remember it.

Finite State Machine and its good friends enum and switch/case.

Given the odd syntax of the method described - I would not have said an abomination like that switch/case could even compile - I stick with what still looks and reads like C.

The main problem, that of breaking down a function or process so it becomes cooperative, is not eliminated or eased at all. Anyone using this now "proven concept" would prolly be able to do it the older fashioned way in her sleep. There is no motive to use this.

The macros hide some stuff you might call ugly or whatever. I like to be able to read code, not wonder about the smoke and mirrors behind the curtain.

a7

No. A microprocessor has a relative simple processing unit inside . It's a single integrated circuit on a piece of silicone that performs various arithmetic and logic functions on digital signals. Many have additional circuitry to convert analog signals to digital so the microprocessor can work with them.

the UNO has only one ALU. You can do a lot of things very fast and it will appear they are running concurrently. There are some Arduinos with multiple cores, they would do what you want.

@gilshultz JavaScript runs in one thread. and its principle is that blocking is not possible. so nothing can wait for something to finish. follow up functions are provided as callbacks to a functions which would block. a way to start an independent function is to use setTimeout function which schedules the function for execution, but it will executed in the same thread.

the overhead of the scheduler is not wort it on an Uno

People were writing and teaching this on the forum when I got here in 2011. It has been the main lesson the whole time.

Back in the 80's it was called Main Loop Coding. Event-driven code was another name. I've seen it in an Intel booklet from 77, in the mid-80's and one of my EE friends (I was a programmer) did expound on synchronous vs asynchronous code after seeing that I was breaking into asynch coding to make better user-io in a metal fab shop... it wasn't new then.

I have a non-blocking dev tool that counts void loop() runs per second just to see what changes do to loop count compared to before, just to catch bad moves. It's pretty lean and should be removed from the finished sketch. Would you like a copy? It's a popular download.

You reinvented the wheel already invented by Knuth many years ago. See e.g. my TaskMakros.

Nonetheless you did a great job! Congratulations :slight_smile:

1 Like

[guess not.] I didn't scroll far enough to the right...

That doesn't look like the usual Duff's Device :frowning:
From wikipedia:

send(to, from, count)
register short *to, *from;
register count;
{
    register n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
            } while (--n > 0);
    }
}

They both use switch/case. :expressionless:

a7

I think that we need to be exposed to the useable meanings for the following concepts in the context of task execution by a single microcontroller/microprocessor (MCU/MPU).
1. At the same time,

2. Simultaneously,

3. Concurrently,

4. Together,

5. Mult-tasking,

6. Pseudo Parallel operation, and

7. True parallelism.
Assume that a thread has 5 tasks and the tasks are being executed independently by five MCUs. This kind of operation has been termed as True Parallelism in the literature.

The Dual Core ESP32 and FressRTOS is an excellent platform to understand the operation of True Parallelsm and Multi-tasking.

The Single Core Arduino UNO and Arduino_FreeRTOS is also an excellent platform to understand the operation of Multi-tasking.

1 Like

Knuth! That's a name I haven't seen or heard in years!

The Parallax Propeller with 8 RISC cores....

and I would on need run multiple small tasks on any or all of them!

On a single thread If I blink 2 leds independently at the same time, they both are ON or OFF at any instant at the same time which being the function of blinking --- that's quite good enough for me as long as I don't need to blink at a frequency too high to attend to both.
If THAT'S a breaker then WHATTABOUT TIME becomes "Well what about 100MHz blinking on a 16MHz Uno?" since pedanticallly that's the next side-step!

That would prove there are tasks that can't be run on a single Uno thread...

my therefore is that "as close" does matter in practical tasking and not to worry over strictness as it approaches what doesn't matter.

When I can read multiple serial streams (Mega 2560 has 4 UARTs) and operate on them without disrupting any or missing a char while on-the-fly... that's parallel enough as long as I keep the baud rates within limits that I set when engineering that solution.

Perhaps add "within limits" to the tasking description and realize that ALL WORKING CODE meets that criteria.

You don't have that poster of him posing with the MMIX/NNIX ASICs?

a7

1 Like

This, no matter what those macros do or don't is flawed. x starts at zero, is soon decremented and only comes back into a sensible range for use as a pin index after many thousands more decrements...

I don't even guess as to what this coroutine is supposed to do.

int coroutine3(){
     static int x=0; static bool b=0;
     unsigned char pin[] = { 13, 12, 11, 10, 9, 8 };
coStart

     while( x-->0 ){
         digitalWrite( pin[x], b );
         coDelay(100);
     } b=!b;

coGoto(0);
coStop
}

I was opening my mind to the idea that the proposed mechanism could leave in the middle of a *for* loop and allow control to return there and pick up again (neopixel color wipe, e.g.).

So I started to look at the coroutines written as examples. Stopping for now until things catch up.

a7

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.