33A low-memory, fast-switching, cooperative multitasking library using
44stackless coroutines on Arduino platforms.
55
6- Version: 0.2.2 (2019-07-31)
7-
8- This library is currently in "beta" status. I'm releasing it through the Arduino
9- Library Manager to solicit feedback from interested users. Send me an email or
10- create a GitHub ticket.
11-
12- [ ![ AUniter Jenkins Badge] ( https://us-central1-xparks2018.cloudfunctions.net/badge?project=AceRoutine )] ( https://github.com/bxparks/AUniter )
13-
14- ## Summary
15-
166This library is an implementation of the
177[ ProtoThreads] ( http://dunkels.com/adam/pt ) library for the
188Arduino platform. It emulates a stackless coroutine that can suspend execution
@@ -38,9 +28,10 @@ their life cycle:
3828* ` COROUTINE_AWAIT(condition) ` : yield until ` condition ` becomes ` true `
3929* ` COROUTINE_DELAY(millis) ` : yields back execution for ` millis ` . The ` millis `
4030 parameter is defined as a ` uint16_t ` .
41- * ` COROUTINE_DELAY_SECONDS(loopCounter, seconds) ` : yields back execution for
42- ` seconds ` . The maximum value of ` seconds ` is determined by ` loopCounter `
43- which can be of any integer type.
31+ * ` COROUTINE_DELAY_MICROS(micros) ` : yields back execution for ` micros ` . The
32+ ` micros ` parameter is defined as a ` uint16_t ` .
33+ * ` COROUTINE_DELAY_SECONDS(seconds) ` : yields back execution for
34+ ` seconds ` . The ` seconds ` parameter is defined as a ` uint16_t ` .
4435* ` COROUTINE_LOOP() ` : convenience macro that loops forever
4536* ` COROUTINE_CHANNEL_WRITE(channel, value) ` : writes a value to a ` Channel `
4637* ` COROUTINE_CHANNEL_READ(channel, value) ` : reads a value from a ` Channel `
@@ -54,9 +45,10 @@ others (in my opinion of course):
5445 no matter how many coroutines are active
5546* extremely fast context switching
5647 * ~ 6 microseconds on a 16 MHz ATmega328P
57- * 1.1-2.0 microseconds on Teensy 3.2 (depending on compiler settings)
58- * ~ 1.7 microseconds on a ESP8266
59- * ~ 0.5 microseconds on a ESP32
48+ * ~ 2.9 microseconds on a 48 MHz SAMD21
49+ * ~ 1.7 microseconds on a 80 MHz ESP8266
50+ * ~ 0.4 microseconds on a 240 MHz ESP32
51+ * 0.7-1.1 microseconds on 96 MHz Teensy 3.2 (depending on compiler settings)
6052* uses the [ computed goto] ( https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html )
6153 feature of the GCC compiler (also supported by Clang) to avoid the
6254 [ Duff's Device] ( https://en.wikipedia.org/wiki/Duff%27s_device ) hack
@@ -87,7 +79,14 @@ AceRoutine is a self-contained library that works on any platform supporting the
8779Arduino API (AVR, Teensy, ESP8266, ESP32, etc), and it provides a handful of
8880additional macros that can reduce boilerplate code.
8981
90- ### HelloCoroutine
82+ Version: 0.3 (2019-08-26)
83+
84+ Status: In "beta". API has been relatively stable since 0.2. Breaking change
85+ made to ` COROUTINE_DELAY_SECONDS() ` in 0.3.
86+
87+ [ ![ AUniter Jenkins Badge] ( https://us-central1-xparks2018.cloudfunctions.net/badge?project=AceRoutine )] ( https://github.com/bxparks/AUniter )
88+
89+ ## HelloCoroutine
9190
9291This is the [ HelloCoroutine.ino] ( examples/HelloCoroutine ) sample sketch.
9392
@@ -221,6 +220,9 @@ The following example sketches are provided:
221220 primitive "shell". The shell is non-blocking and uses coroutines so that other
222221 coroutines continue to run while the board waits for commands to be typed on
223222 the serial port.
223+ * [ Delay.ino] ( examples/Delay ) : validate the various delay macros
224+ (` COROUTINE_DELAY() ` , ` COROUTINE_DELAY_MICROS() ` and
225+ ` COROUTINE_DELAY_SECONDS() ` )
224226* [ Pipe.ino] ( examples/Pipe ) : uses a ` Channel ` to allow a Writer to send
225227 messages to a Reader
226228* [ ChannelBenchmark.ino] ( examples/ChannelBenchmark ) : determines the amount of
@@ -253,9 +255,10 @@ The following macros are available to hide a lot of boilerplate code:
253255* ` COROUTINE_AWAIT(condition) ` : yield until ` condition ` become ` true `
254256* ` COROUTINE_DELAY(millis) ` : yields back execution for ` millis ` . The maximum
255257 allowable delay is 32767 milliseconds.
256- * ` COROUTINE_DELAY_SECONDS(loopCounter, seconds) ` : yields back execution for
257- ` seconds ` . The maximum allowable delay is the maximum value of the integer
258- type of ` loopCounter ` which can be of any integer type.
258+ * ` COROUTINE_DELAY_MICROS(micros) ` : yields back execution for ` micros ` . The
259+ maximum allowable delay is 32767 microseconds.
260+ * ` COROUTINE_DELAY_SECONDS(seconds) ` : yields back execution for ` seconds ` . The
261+ maximum allowable delay is 32767 seconds.
259262* ` COROUTINE_LOOP() ` : convenience macro that loops forever, replaces
260263 ` COROUTINE_BEGIN() ` and ` COROUTINE_END() `
261264* ` COROUTINE_CHANNEL_WRITE() ` : writes a message to a ` Channel `
@@ -357,81 +360,130 @@ while (!condition) COROUTINE_YIELD();
357360
358361### Delay
359362
360- The ` COROUTINE_DELAY(millis) ` macro delays the return of control until ` millis `
361- milliseconds have elapsed. The ` millis ` argument is a ` uint16_t ` , a 16-bit
362- unsigned integer, which reduces the size of each coroutine instance by 4 bytes.
363- However, the actual maximum delay is limited to 32767 milliseconds to avoid
364- overflow situations if the other coroutines in the system take too much time for
365- their work before returning control to the waiting coroutine. With this limit,
366- the other coroutines have as much as 32767 milliseconds to complete their work,
367- which should be more than enough time for any conceivable situation. In
368- practice, coroutines should complete their work within several milliseconds and
369- yield control to the other coroutines as soon as possible.
370-
371- To delay for longer, an explicit loop can be used. For example, to delay
372- for 1000 seconds, we can do this:
363+ The ` COROUTINE_DELAY(millis) ` macro yields back control to other coroutines
364+ until ` millis ` milliseconds have elapsed. The following waits for 100
365+ milliseconds:
366+
373367``` C++
374- COROUTINE (waitThousandSeconds ) {
368+ COROUTINE (waitMillis ) {
375369 COROUTINE_BEGIN();
376- static uint16_t i;
377- for (i = 0; i < 1000; i++) {
378- COROUTINE_DELAY(1000);
379- }
370+ ...
371+ COROUTINE_DELAY(100);
380372 ...
381373 COROUTINE_END();
382374}
383375```
384- See **For Loop** section below for a description of the for-loop construct.
385376
386- This for-loop construct happens often enough that it seemed worthwhile to
387- provide the `COROUTINE_DELAY_SECONDS(loopCounter, seconds)` convenience macro.
388- It replaces the above for-loop, like this:
377+ The `millis` argument is a `uint16_t`, a 16-bit unsigned integer, which reduces
378+ the size of each coroutine instance by 4 bytes (8-bit processors) or 8 bytes
379+ (32-bits processors). However, the actual maximum delay is limited to 32767
380+ milliseconds to avoid overflow situations if the other coroutines in the system
381+ take too much time for their work before returning control to the waiting
382+ coroutine. With this limit, the other coroutines have as much as 32767
383+ milliseconds before it must yield, which should be more than enough time for any
384+ conceivable situation. In practice, coroutines should complete their work within
385+ several milliseconds and yield control to the other coroutines as soon as
386+ possible.
387+
388+ To delay for longer period of time, we can use the
389+ `COROUTINE_DELAY_SECONDS(seconds)` convenience macro. The following example
390+ waits for 200 seconds:
389391```C++
390- COROUTINE(waitThousandSeconds ) {
392+ COROUTINE(waitSeconds ) {
391393 COROUTINE_BEGIN();
392- static uint16_t loopCounter;
393- COROUTINE_DELAY_SECONDS(loopCounter, 1000 );
394+ ...
395+ COROUTINE_DELAY_SECONDS(200 );
394396 ...
395397 COROUTINE_END();
396398}
397399```
398- The ` static uint16_t loopCounter ` variable is still required for the
399- ` COROUTINE_DELAY_SECONDS() ` macro because it needs a loop counter that must
400- preserve its value across multiple invocation of the coroutine. The
401- ` loopCounter ` may be any integer type, and the maximum number of seconds is the
402- maximum value of that particular integer type. For example, if the ` loopCounter `
403- was a ` uint8_t ` , the maximum delay would be 255 seconds. If the ` loopCounter `
404- was a ` uint32_t ` , the maximum delay would be about 4 billion seconds.
405-
406- The ` loopCounter ` may also be a member variable of the ` Coroutine ` subclass,
407- when you use custom or manual coroutines (see the sections on * Custom
408- Coroutines* or * Manual Coroutines* below for details). In other words, you can
409- define a coroutine with a ` mDelayCounter ` member variable, and use the
410- ` COROUTINE_DELAY_SECONDS() ` macro like this:
400+ The maximum number of seconds is 32767 seconds.
401+
402+ On faster microcontrollers, it might be useful to yield for microseconds using
403+ the ` COROUTINE_DELAY_MICROS(delayMicros) ` . The following example waits for 300
404+ microseconds:
405+
411406``` C++
412- class MyCoroutine : public Coroutine {
413- public:
414- int runCoroutine() override {
415- ...
416- COROUTINE_DELAY_SECONDS(mDelayCounter, 1000);
417- ...
418- }
407+ COROUTINE (waitMicros) {
408+ COROUTINE_BEGIN();
409+ ...
410+ COROUTINE_DELAY(300);
411+ ...
412+ COROUTINE_END();
413+ }
414+ ```
415+ This macro has a number constraints:
419416
420- private:
421- uint16_t mDelayCounter;
422- };
417+ * The maximum delay is 32767 micros.
418+ * All other coroutines in the program *must* yield within 32767 microsecond,
419+ otherwise the internal timing variable will overflow and an incorrect delay
420+ will occur.
421+ * The accuracy of `COROUTINE_DELAY_MICROS()` is not guaranteed because the
422+ overhead of context switching and checking the delay's expiration may
423+ consume a significant portion of the requested delay in microseconds.
424+
425+ If the above convenience macros are not sufficient, you can choose to write an
426+ explicit for-loop. For example, to delay for 100,000 seconds, instead of using
427+ the `COROUTINE_DELAY_SECONDS()`, we can do this:
428+
429+ ```C++
430+ COROUTINE(waitThousandSeconds) {
431+ COROUTINE_BEGIN();
432+ static uint32_t i;
433+ for (i = 0; i < 100000; i++) {
434+ COROUTINE_DELAY(1000);
435+ }
436+ ...
437+ COROUTINE_END();
438+ }
423439```
424440
441+ See ** For Loop** section below for a description of the for-loop construct.
442+
425443### Stackless Coroutines
426444
427445Each coroutine is stackless. More accurately, the stack of the coroutine
428446is destroyed and recreated on every invocation of the coroutine. Therefore,
429447any local variable created on the stack in the coroutine will not preserve
430448its value after a ` COROUTINE_YIELD() ` or a ` COROUTINE_DELAY() ` .
431449
432- The easiest way to get around ths problem is to use `static` variables inside
433- a `COROUTINE()`. Static variables are initialized once and preserve their value
434- through multiple calls to the function, which is exactly what is needed.
450+ The problem is worse for local * objects* (with non-trivial destructors). If the
451+ lifetime of the object straddles a continuation point of the Coroutine
452+ (` COROUTINE_YIELD() ` , ` COROUTINE_DELAY() ` , ` COROUTINE_END() ` ), the destructor of
453+ the object will be called incorrectly when the coroutine is resumed, and will
454+ probably crash the program. In other words, do ** not** do this:
455+
456+ ``` C++
457+ COROUTINE (doSomething) {
458+ COROUTINE_BEGIN();
459+ String s = "hello world"; // *** crashes when doSomething() is resumed***
460+ Serial.println(s);
461+ COROUTINE_DELAY(1000);
462+ ...
463+ COROUTINE_END();
464+ }
465+ ```
466+
467+ Instead, place any local variable or object completely inside a `{ }` block
468+ before the `COROUTINE_YIELD()` or `COROUTINE_DELAY()`, like this:
469+
470+ ```C++
471+ COROUTINE(doSomething) {
472+ COROUTINE_BEGIN();
473+ {
474+ String s = "hello world"; // ok, because String is properly destroyed
475+ Serial.println(s);
476+ }
477+ COROUTINE_DELAY(1000);
478+ ...
479+ COROUTINE_END();
480+ }
481+ ```
482+
483+ The easiest way to get around these problems is to avoid local variables
484+ and just use ` static ` variables inside a ` COROUTINE() ` . Static variables are
485+ initialized once and preserve their value through multiple calls to the
486+ function, which is exactly what is needed.
435487
436488### Conditional If-Else
437489
@@ -1260,7 +1312,7 @@ advantages:
12601312All objects are statically allocated (i.e. not heap or stack).
12611313
12621314* 8-bit processors (AVR Nano, UNO, etc):
1263- * ` sizeof(Coroutine) ` : 14
1315+ * ` sizeof(Coroutine) ` : 15
12641316 * ` sizeof(CoroutineScheduler) ` : 2
12651317 * ` sizeof(Channel<int>) ` : 5
12661318* 32-bit processors (e.g. Teensy ARM, ESP8266, ESP32)
@@ -1311,19 +1363,19 @@ MacOS 10.14.5.
13111363
13121364### Hardware
13131365
1314- The library is extensively tested on the following boards:
1366+ The library has been extensively tested on the following boards:
13151367
13161368* Arduino Nano clone (16 MHz ATmega328P)
1369+ * Arduino Pro Mini clone (16 MHz ATmega328P)
13171370* Arduino Pro Micro clone (16 MHz ATmega32U4)
1371+ * SAMD21 M0 Mini (48 MHz ARM Cortex-M0+) (compatible with Arduino Zero)
13181372* NodeMCU 1.0 clone (ESP-12E module, 80 MHz ESP8266)
13191373* ESP32 dev board (ESP-WROOM-32 module, 240 MHz dual core Tensilica LX6)
1320- * SAMD21 M0 Mini (48 MHz ARM Cortex-M0+) (compatible with Arduino Zero)
13211374
13221375I will occasionally test on the following hardware as a sanity check:
13231376
13241377* Teensy 3.2 (72 MHz ARM Cortex-M4)
13251378* Teensy LC (48 MHz ARM Cortex-M0+)
1326- * Arduino Pro Mini clone (16 MHz ATmega328P)
13271379* Mini Mega 2560 (Arduino Mega 2560 compatible, 16 MHz ATmega2560)
13281380
13291381## Changelog
0 commit comments