Skip Navigation LinkshsCoRoutines

First, here is the files: hsCoRoutines.zip

including demoes so far.

In Arduino IDE do an "Sketch | Import Library | Add Library..." and supply this zip file.


 

Everything in one place

CoRoutines does the job

Assume a standard Blinky demo

void setup() {               

  pinMode(led13, OUTPUT);       // initialize the digital pin as an output.

}

void loop() {

  digitalWrite(led13, HIGH);    // turn the LED on (HIGH is the voltage level)

  delay(1000);                  // wait for a second

  digitalWrite(led13, LOW);     // turn the LED off by making the voltage LOW

  delay(1000);                  // wait for a second

}

Now this one will light led13 for 1 second, and keep it shut for 1 second.

Now assume just another Blinky demo, on led12, blinking somewhat faster

void setup() {               

  pinMode(led12, OUTPUT);       // initialize the digital pin as an output.

}

void loop() {

  digitalWrite(led12, HIGH);    // turn the LED on (HIGH is the voltage level)

  delay(100);                   // wait for a second

  digitalWrite(led12, LOW);     // turn the LED off by making the voltage LOW

  delay(200);                   // wait for a second

}

But how do we get both demo's to run at the same time, having exactly the same light sequences.

We could rebuild the whole program, like using another programming approach like

  • Keeping milliseconds variables handy and check every 1 mSec to see if now is a good time to flip the power.
  • Use state variables
  • Use a lot of "flags"
  • Use CoRoutines

This CoRoutines presented here is my best efford to make Arduino CoRoutine as easy to use as an Arduino program without CoRoutines.

The problem above can be programmed as elegant as this

void setup()

{ Serial.begin(9600);

  hsTASK_SETUP (Blinky1, 100);              //One CoRoutine, with its own stack of 100 bytes to use

  hsTASK_SETUP (Blinky2, 100);              //One CoRoutine, with its own stack of 100 bytes to use

}

hsTASK_INIT(Blinky1) {

{ if (Setup)

    pinMode(led13, OUTPUT);     // initialize the digital pin as an output.

  digitalWrite(led13, HIGH);    // turn the LED on (HIGH is the voltage level)

  delay(1000);                  // wait for a second

  digitalWrite(led13, LOW);     // turn the LED off by making the voltage LOW

  delay(1000);                  // wait for a second

}

 

hsTASK_INIT(Blinky2) {

{ if (Setup)

    pinMode(led12, OUTPUT);     // initialize the digital pin as an output.

  digitalWrite(led12, HIGH);    // turn the LED on (HIGH is the voltage level)

  delay(100);                   // wait for a second

  digitalWrite(led12, LOW);     // turn the LED off by making the voltage LOW

  delay(200);                   // wait for a second

}

 

void loop() {

  Serial.Print("Something");    // Not used here, but Arduino tells you to have this loop()

  delay(10000);

}

That's it, that's that.

CoRoutines

The magic is happening in the delay() routines. Normally the Arduino as doing nothing but waiting for say 1000 milliseconds, 1 second just burning off some heat, pure waste of battery power. The magic is that delay() never uses "burn cpu" but instead run some other functions that might be ready to do real work, and if no one is ready then eventually turn off the cpu.

Chapter 2

Simple programming

A lot of coding can be done without coroutines (and multi-threading and such).

A lot of coding can be done with coroutines and simple use of variables and flags, and I have done many of that kind.

Over time (last 50 years) some coding principles has been developed to help making different parts of a program work together in a safe way.

  • Semaphores, used for region protection, and counting too
  • Signals, tell when things happens
  • Mails, send simple messages (a single number)

Semaphore

A semaphore here is a byte variable, initialized as required.

When initialized to one, the normal use is for region protection, as everybody wanting to use some resource has to decrement then semaphore variable, but if already zero you have to stand in line until the one having the "token" does return it (increment the semaphore to 1)

When initialized to zero (or whatever), it is typical used for counting, like number of items processed, ready for next step. A cookie baker could increment on every cookie produced, and the packer could decrement (when a new cookie shows up), counting to 8 for a full box of cookies.

Signals

A signal in this implementation takes no memory, it is just a shared constant.

Multiple watchers might be interested in when event NOW_OK has happened. When NEW_OK is signaled, everyone waiting is released for instant execution (all, but one at a time as we still only have one cpu, so must act cooperative)

Mails, sending messages

Mails  does require a small memory buffer, shared for all mails.

When sending one mail, exactly one receiver gets it, if waiting for it already, or if trying to wait after mail has been posted.

hsCoRoutine, this implementation

This implementation is

  • very clean
  • very small
  • very simple api
  • can be used from anywhere, full stack and locals are saved
  • (most others and protoThreads too does not save stack)
  • signaling can be done from interrupts (later, not testet)
  • uses very little assembler code, and lets gcc compiler do the register savings for best conformance
  • uses about 10 bytes per coroutine, and user defined amount for the stack

Having said this, the minus is that hsCoRoutines are not fair, like it does not guarantee that first in line also gets first served

A few examples

Semaphores

//hsDemoSemaphores

 

#include <hsCoRoutines.h>

 

hsGLOBALS(10)

 

#define RXCnt 6

#define TXCnt 2

byte RXStacks[RXCnt] [120];

byte TXStacks[TXCnt] [120];

 

char SharedPrintFBuf[100];

 

byte SemaphoreVar=0;

 

//-----------------------------------------------------

hsTASK(Receivers)

{

  hsIdle(2000);

  //Serial.println("rx.");

  if (hsSemaWait(15000,SemaphoreVar)) sprintf(SharedPrintFBuf,"RX:%d ++",TaskIndex);

  else sprintf(SharedPrintFBuf,"RX:%d --",TaskIndex);

  if (SharedPrintFBuf[0]) Serial.println(SharedPrintFBuf);

}

 

hsTASK(Senders)

{

  hsIdle(random(2000,5000));

  for (byte ll=random(1,9); ll>0; ll--) {

    Serial.println("TX..");

    hsSemaSignal(SemaphoreVar);

  }

}

 

void setup()

{ // put your setup code here, to run once:

  Serial.begin(9600);

  Serial.println("start");

  for (byte ll=0; ll<RXCnt; ll++)

    hsTASK_SETUP_STACK(Receivers,RXStacks[ll]);

  for (byte ll=0; ll<TXCnt; ll++)

    hsTASK_SETUP_STACK(Senders,TXStacks[ll]);

}

 

void loop()

{ // put your main code here, to run repeatedly:

  //Serial.println("MAIN Loop");

  char Buf[60];

  hsIdle(1000);

  //if(0)

  for (byte ll=1; ll<=TaskMax; ll++)

    //if (Tasks[ll].SPCur) Serial.println(hsStatusS60(Buf,ll));

    if (Tasks[ll].State) Serial.println(hsStatusS60(Buf,ll));

  hsIdle(4000);

}

Events

//hsDemoEvents

 

#include <hsCoRoutines.h>

 

hsGLOBALS(10)

 

#define RXCnt 3

#define TXCnt 2

byte RXStacks[RXCnt] [150];

byte TXStacks[TXCnt] [150];

 

char SharedPrintFBuf[100];

 

//-----------------------------------------------------

hsTASK(Receivers)

{

  hsIdle(500);

  if (hsEventWait(5000,123)) sprintf(SharedPrintFBuf,"RX:%d ++",TaskIndex);

  else SharedPrintFBuf[0]=0; //Serial.println(sprintf(SharedPrintFBuf,"RX:%d --",TaskIndex));

  if (SharedPrintFBuf[0]) Serial.println(SharedPrintFBuf);

}

 

hsTASK(Senders)

{

  hsIdle(random(2000,5000));

  hsEventSignal(123);

  Serial.println("TX..");

}

 

void setup()

{ // put your setup code here, to run once:

  Serial.begin(9600);

  Serial.println("start");

  for (byte ll=0; ll<RXCnt; ll++)

    hsTASK_SETUP_STACK(Receivers,RXStacks[ll]);

  for (byte ll=0; ll<TXCnt; ll++)

    hsTASK_SETUP_STACK(Senders,TXStacks[ll]);

}

 

void loop()

{ // put your main code here, to run repeatedly:

  //Serial.println("MAIN Loop");

  char Buf[60];

  for (byte ll=1; ll<=TaskMax; ll++)

    //if (Tasks[ll].SPCur) Serial.println(hsStatusS60(Buf,ll));

    if (Tasks[ll].State) Serial.println(hsStatusS60(Buf,ll));

  hsIdle(4000);

}