Coroutines library

Hi!

I just started to use an Arduino Leonardo (embedded on a littleBit) a few weeks ago, and from the get-go I wished I could use coroutines to "script" event sequences in time... I did find an old thread on these forums that proposed a simple implementation, but it had 0 replies and it was a bit too straightforward, I wanted something more akin to coroutines the Unity game engine for example.

So I set out to make my own library to do C coroutines with more usable constructs like waiting, suspending and looping, as well as supporting some manner of stack preservation by saving local variables.

They're definitely macro-heavy, but I think they're pretty easy to use. Here's a coroutine that flashes a light once for 100 milliseconds :

// flashes a LED attached to analog pin 5 for 100ms
void flashOnce(COROUTINE_CONTEXT(coroutine))
{
    BEGIN_COROUTINE;
    
    analogWrite(5, 255);
    
    coroutine.wait(100);
    COROUTINE_YIELD;
    
    analogWrite(5, 0);
    
    END_COROUTINE;
}

And then you can start it at various points in your program without worrying about timers or interrupting other running code :

coroutines.start(flashOnce);

There's a couple more things involved to make them work though, (an update() callback and the instantiation of a manager object).
Take a look at the documentation on GitHub and give it a shot!

Renaud (@renaudbedard)

1 Like

@renaudbedard

Interesting approach! I have studied and implemented three+ variants in Cosa; Event/FSM driven, proto-threads and coroutines. Below are some links to example sketches.

  1. Event driven (and FSM)
    https://github.com/mikaelpatel/Cosa/blob/master/examples/Blink/CosaBlinkEvent/CosaBlinkEvent.ino
    Cosa/CosaBlinkFSM.ino at master · mikaelpatel/Cosa · GitHub
    Cosa/CosaBenchmarkFSM.ino at master · mikaelpatel/Cosa · GitHub

  2. Proto-threads
    Cosa/CosaBlinkProtoThread.ino at master · mikaelpatel/Cosa · GitHub
    https://github.com/mikaelpatel/Cosa/blob/master/examples/Benchmarks/CosaBenchmarkProtoThread/CosaBenchmarkProtoThread.ino

  3. Coroutines (Cosa Nucleo)
    Cosa/CosaBlinkThread.ino at master · mikaelpatel/Cosa · GitHub
    https://github.com/mikaelpatel/Cosa/blob/master/examples/Benchmarks/CosaBenchmarkNucleo/CosaBenchmarkNucleo.ino

More info can be found on the Cosa blog and the on-line documentation and research papers.

Cheers!

1 Like

Woa, I hadn't seen Cosa. It seems very impressive! I'll take a closer look and try it out when I have a chance.

Edit : Found another similar implementation under the "protothread" name here : http://harteware.blogspot.ca/2010/11/protothread-and-arduino-first-easy.html

@renaudbedard

I am considering a similar use of coroutines with an Arduino Leonardo. Consider an autonomous robot that drives around via 2 motors given a series (could be very lengthy) of digitalWrites and delays to those motors. I would like to incorporate wall sensors but obviously writing conditionals within a loop of considerable length would be useless and inaccurate. So, to get to the real question, does your coroutine implementation support conditionals and access to global variables? I would want to continually read the state of two sensors (1 for each side of my robot) and depending on the state, do something or do nothing.

Here is an example (very, very rough) checking one sensor with your coroutine:

int sw = 2;
int S1 = 3;
int E1 = 4;
int M1 = 5;
int E2 = 6; 
int M2 = 7;

int S1_Read;
int S2_Read;

unsigned long startTime;
unsigned long currentTime;

void setup()
{ 
	pinMode(S1, INPUT);
	pinMode(M1, OUTPUT); 
	pinMode(M2, OUTPUT);
	pinMode(E1, OUTPUT); 
	pinMode(E2, OUTPUT);
}


// forever check for wall collision
void wallCheck(COROUTINE_CONTEXT(coroutine))
{
    	COROUTINE_LOCAL(float, i);

	BEGIN_COROUTINE;

	// continually check sensor
	S1_Read = digitalRead(S1);

	// if sensor1 hit
	if (S1_Read == HIGH)
	{
		// go backwards
		digitalWrite(M1, LOW);
		digitalWrite(E1, HIGH);
		digitalWrite(M2, LOW);
		digitalWrite(E2, HIGH);

		// however much time has elapsed since last command
		currentTime = millis();
		elapsedTime = currentTime - startTime; 
		delay(elapsedTime);
	}

    	coroutine.loop();

	END_COROUTINE;
}


void loop()
{
	sw_Read = digitalRead(sw);

	// switch is on
	if (sw_Read == HIGH)
	{
		coroutines.start(wallCheck);

		startTime = millis();
		
		digitalWrite(M1, HIGH);
		digitalWrite(E1, HIGH);
		digitalWrite(M2, LOW);
		digitalWrite(E2, HIGH);
		digitalWrite(LED, LOW);
		delay(350);

		startTime = millis();

		digitalWrite(M1, HIGH);
		digitalWrite(E1, HIGH);
		digitalWrite(M2, HIGH);
		digitalWrite(E2, HIGH);
		digitalWrite(LED, LOW);
		delay(31500);

		startTime = millis();

		digitalWrite(M2, HIGH);
		digitalWrite(E2, HIGH);
		delay(300);

		startTime = millis();

		digitalWrite(M1, HIGH);
		digitalWrite(E1, HIGH);
		digitalWrite(M2, HIGH);
		digitalWrite(E2, HIGH);
		delay(20500);

		startTime = millis();

		digitalWrite(M2, HIGH);
		digitalWrite(E2, HIGH);
		delay(730);

		startTime = millis();

		digitalWrite(M1, HIGH);
		digitalWrite(E1, HIGH);
		digitalWrite(M2, HIGH);
		digitalWrite(E2, HIGH);
		delay(35500);

		startTime = millis();

		digitalWrite(M1, HIGH);
		digitalWrite(E1, HIGH);
		delay(370);

		startTime = millis();

		digitalWrite(M1, HIGH);
		digitalWrite(E1, HIGH);
		digitalWrite(M2, HIGH);
		digitalWrite(E2, HIGH);
		delay(13000);

	}

}

Does this look feasible to you? And, if so, do you have any suggestions/thoughts?

Cheers! And nice work on the Arduino coroutines!

Hey! Glad it piqued your curiosity. :slight_smile:

To answer your questions :

  • Yes, coroutines have read/write access to global variables. Since they run "on the main thread" (there's no multithreading), the usual warnings of race conditions and atomic read/writes or what have you do not apply, it's all safe.
  • You can do conditionals and any kind of inner loop you want inside of the coroutine as well.

However, the sketch as you wrote it would not work, because you use delay().
Coroutines will only run in as a result of calling .update() on the Coroutines object, so if you use delay() in your loop method, they won't get called in the idle time, unfortunately. One could override delay() to make it do that, but it makes it hard to read and understand. What I'd suggest instead is to move your loop() body to another coroutine, which would be able to interrupt itself with COROUTINE_YIELD at various points in time.
Same goes for delay() calls inside a coroutine. You should use coroutine.wait() followed by a COROUTINE_YIELD which effectively does the same thing, but makes sure the rest of the program can execute while your coroutine waits.

I'm not sure I understand the function of the delays in your loop function, so I don't know how to suggest a functioning way of doing what you want to do. In pseudocode or comments, can you describe the flow of what you're trying to achieve?

Cheers,
Renaud