diff --git a/CHANGELOG.md b/CHANGELOG.md index 329da8b..9b9fdad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # Changelog * Unreleased +* 1.3.1 (2021-06-02) + * Bring back `COROUTINE_DELAY_MICROS()` and `COROUTINE_DELAY_SECONDS(), + with an alternate implemenation that increases flash and static memory + *only* if they are used. + * The `Coroutine` itself knows whether it is delaying in units of + milliseconds, microseconds, or seconds, based on its continuation + point. + * Make `CoroutineScheduler::runCoroutine()` always call into + `Coroutine::runCoroutine()` when the `Coroutine::mStatus` is delaying, + instead of preemptively trying to figure out if the delay has expired. + * `Coroutine` does not need a runtime `mDelayType` descriminator. + * The result is that the code to support `COROUTINE_DELAY_MICROS()` and + `COROUTINE_DELAY_SECONDS()` is not pulled into the program if they are + not used. * 1.3.0 (2021-06-02) * Activate GitHub Discussions for the project. * **Potentially Breaking**: Change `Coroutine` destructor from virtual to @@ -12,7 +26,7 @@ * **Potentially Breaking**: Lift `Coroutine` into `CoroutineTemplate` class. Lift `CoroutineScheduler` into `CoroutineSchedulerTemplate` class. * Define `Coroutine` to be `CoroutineTemplate`, almost - * fully backwards compatible with previous implementation. + fully backwards compatible with previous implementation. * Define `CoroutineScheduler` to be `CoroutineSchedulerTemplate`, almost fully backwards compatible with previous implementation. diff --git a/README.md b/README.md index 13087a5..c1bb46f 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ their life cycle: * `COROUTINE_AWAIT(condition)`: yield until `condition` becomes `true` * `COROUTINE_DELAY(millis)`: yields back execution for `millis`. The `millis` parameter is defined as a `uint16_t`. +* `COROUTINE_DELAY_MICROS(micros)`: yields back execution for `micros`. The + `micros` parameter is defined as a `uint16_t`. +* `COROUTINE_DELAY_SECONDS(seconds)`: yields back execution for + `seconds`. The `seconds` parameter is defined as a `uint16_t`. * `COROUTINE_LOOP()`: convenience macro that loops forever * `COROUTINE_CHANNEL_WRITE(channel, value)`: writes a value to a `Channel` * `COROUTINE_CHANNEL_READ(channel, value)`: reads a value from a `Channel` @@ -64,16 +68,16 @@ others (in my opinion of course): * ~1.2 microseconds on a 16 MHz ATmega328P * ~0.4 microseconds on a 48 MHz SAMD21 * ~0.3 microseconds on a 72 MHz STM32 - * ~0.4 microseconds on a 80 MHz ESP8266 - * ~0.03 microseconds on a 240 MHz ESP32 + * ~0.3 microseconds on a 80 MHz ESP8266 + * ~0.1 microseconds on a 240 MHz ESP32 * ~0.17 microseconds on 96 MHz Teensy 3.2 (depending on compiler settings) * Coroutine Scheduling (use `CoroutineScheduler::loop()`): - * ~5.2 microseconds on a 16 MHz ATmega328P - * ~1.9 microseconds on a 48 MHz SAMD21 - * ~1.4 microseconds on a 72 MHz STM32 - * ~1.1 microseconds on a 80 MHz ESP8266 - * ~0.3 microseconds on a 240 MHz ESP32 + * ~5.5 microseconds on a 16 MHz ATmega328P + * ~1.3 microseconds on a 48 MHz SAMD21 + * ~0.9 microseconds on a 72 MHz STM32 + * ~0.6 microseconds on a 80 MHz ESP8266 + * ~0.2 microseconds on a 240 MHz ESP32 * ~0.5 microseconds on 96 MHz Teensy 3.2 (depending on compiler settings) * uses the [computed goto](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html) @@ -106,7 +110,7 @@ AceRoutine is a self-contained library that works on any platform supporting the Arduino API (AVR, Teensy, ESP8266, ESP32, etc), and it provides a handful of additional macros that can reduce boilerplate code. -**Version**: 1.3 (2021-06-02) +**Version**: 1.3.1 (2021-06-02) **Changelog**: [CHANGELOG.md](CHANGELOG.md) @@ -501,45 +505,69 @@ etc) for a handful of AceRoutine features. Here are some highlights: **Arduino Nano (8-bits)** ``` -+--------------------------------------------------------------+ -| functionality | flash/ ram | delta | -|---------------------------------+--------------+-------------| -| Baseline | 606/ 11 | 0/ 0 | -|---------------------------------+--------------+-------------| -| One Delay Function | 654/ 13 | 48/ 2 | -| Two Delay Functions | 714/ 15 | 108/ 4 | -|---------------------------------+--------------+-------------| -| One Coroutine | 840/ 30 | 234/ 19 | -| Two Coroutines | 1010/ 47 | 404/ 36 | -|---------------------------------+--------------+-------------| -| Scheduler, One Coroutine | 946/ 32 | 340/ 21 | -| Scheduler, Two Coroutines | 1058/ 43 | 452/ 32 | -|---------------------------------+--------------+-------------| -| Blink Function | 938/ 14 | 332/ 3 | -| Blink Coroutine | 1154/ 30 | 548/ 19 | -+--------------------------------------------------------------+ ++------------------------------------------------------------------+ +| functionality | flash/ ram | delta | +|-------------------------------------+--------------+-------------| +| Baseline | 400/ 11 | 0/ 0 | +|-------------------------------------+--------------+-------------| +| One Delay Function | 450/ 13 | 50/ 2 | +| Two Delay Functions | 508/ 15 | 108/ 4 | +|-------------------------------------+--------------+-------------| +| One Coroutine | 628/ 30 | 228/ 19 | +| Two Coroutines | 796/ 47 | 396/ 36 | +|-------------------------------------+--------------+-------------| +| One Coroutine (micros) | 596/ 30 | 196/ 19 | +| Two Coroutines (micros) | 732/ 47 | 332/ 36 | +|-------------------------------------+--------------+-------------| +| One Coroutine (seconds) | 724/ 30 | 324/ 19 | +| Two Coroutines (seconds) | 920/ 47 | 520/ 36 | +|-------------------------------------+--------------+-------------| +| Scheduler, One Coroutine | 742/ 32 | 342/ 21 | +| Scheduler, Two Coroutines | 904/ 49 | 504/ 38 | +|-------------------------------------+--------------+-------------| +| Scheduler, One Coroutine (micros) | 710/ 32 | 310/ 21 | +| Scheduler, Two Coroutines (micros) | 840/ 49 | 440/ 38 | +|-------------------------------------+--------------+-------------| +| Scheduler, One Coroutine (seconds) | 838/ 32 | 438/ 21 | +| Scheduler, Two Coroutines (seconds) | 1028/ 49 | 628/ 38 | +|-------------------------------------+--------------+-------------| +| Blink Function | 546/ 14 | 146/ 3 | +| Blink Coroutine | 752/ 30 | 352/ 19 | ++------------------------------------------------------------------+ ``` **ESP8266 (32-bits)** ``` -+--------------------------------------------------------------+ -| functionality | flash/ ram | delta | -|---------------------------------+--------------+-------------| -| Baseline | 256924/26800 | 0/ 0 | -|---------------------------------+--------------+-------------| -| One Delay Function | 256988/26808 | 64/ 8 | -| Two Delay Functions | 257052/26808 | 128/ 8 | -|---------------------------------+--------------+-------------| -| One Coroutine | 257104/26820 | 180/ 20 | -| Two Coroutines | 257264/26844 | 340/ 44 | -|---------------------------------+--------------+-------------| -| Scheduler, One Coroutine | 257152/26828 | 228/ 28 | -| Scheduler, Two Coroutines | 257232/26844 | 308/ 44 | -|---------------------------------+--------------+-------------| -| Blink Function | 257424/26816 | 500/ 16 | -| Blink Coroutine | 257556/26836 | 632/ 36 | -+--------------------------------------------------------------+ ++------------------------------------------------------------------+ +| functionality | flash/ ram | delta | +|-------------------------------------+--------------+-------------| +| Baseline | 256924/26800 | 0/ 0 | +|-------------------------------------+--------------+-------------| +| One Delay Function | 256988/26808 | 64/ 8 | +| Two Delay Functions | 257052/26808 | 128/ 8 | +|-------------------------------------+--------------+-------------| +| One Coroutine | 257104/26820 | 180/ 20 | +| Two Coroutines | 257264/26844 | 340/ 44 | +|-------------------------------------+--------------+-------------| +| One Coroutine (micros) | 257136/26820 | 212/ 20 | +| Two Coroutines (micros) | 257296/26844 | 372/ 44 | +|-------------------------------------+--------------+-------------| +| One Coroutine (seconds) | 257136/26820 | 212/ 20 | +| Two Coroutines (seconds) | 257312/26844 | 388/ 44 | +|-------------------------------------+--------------+-------------| +| Scheduler, One Coroutine | 257152/26828 | 228/ 28 | +| Scheduler, Two Coroutines | 257280/26844 | 356/ 44 | +|-------------------------------------+--------------+-------------| +| Scheduler, One Coroutine (micros) | 257168/26828 | 244/ 28 | +| Scheduler, Two Coroutines (micros) | 257312/26844 | 388/ 44 | +|-------------------------------------+--------------+-------------| +| Scheduler, One Coroutine (seconds) | 257168/26828 | 244/ 28 | +| Scheduler, Two Coroutines (seconds) | 257328/26844 | 404/ 44 | +|-------------------------------------+--------------+-------------| +| Blink Function | 257424/26816 | 500/ 16 | +| Blink Coroutine | 257556/26836 | 632/ 36 | ++------------------------------------------------------------------+ ``` Comparing `Blink Function` and `Blink Coroutine` is probably the most @@ -567,7 +595,7 @@ Arduino Nano: |---------------------+--------+-------------+--------| | EmptyLoop | 10000 | 1.700 | 0.000 | | DirectScheduling | 10000 | 2.900 | 1.200 | -| CoroutineScheduling | 10000 | 6.900 | 5.200 | +| CoroutineScheduling | 10000 | 7.200 | 5.500 | +---------------------+--------+-------------+--------+ ``` @@ -577,9 +605,9 @@ ESP8266: +---------------------+--------+-------------+--------+ | Functionality | iters | micros/iter | diff | |---------------------+--------+-------------+--------| -| EmptyLoop | 10000 | 0.100 | 0.000 | -| DirectScheduling | 10000 | 0.500 | 0.400 | -| CoroutineScheduling | 10000 | 1.200 | 1.100 | +| EmptyLoop | 10000 | 0.200 | 0.000 | +| DirectScheduling | 10000 | 0.500 | 0.300 | +| CoroutineScheduling | 10000 | 0.800 | 0.600 | +---------------------+--------+-------------+--------+ ``` diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 7abdf4d..8179df2 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -4,7 +4,7 @@ See the [README.md](README.md) for installation instructions and other background information. This document describes how to use the library once it is installed. -**Version**: 1.3 (2021-06-02) +**Version**: 1.3.1 (2021-06-02) ## Table of Contents @@ -87,6 +87,10 @@ The following macros are available to hide a lot of boilerplate code: * `COROUTINE_AWAIT(condition)`: yields until `condition` become `true` * `COROUTINE_DELAY(millis)`: yields back execution for `millis`. The maximum allowable delay is 32767 milliseconds. +* `COROUTINE_DELAY_MICROS(micros)`: yields back execution for `micros`. The + maximum allowable delay is 32767 microseconds. +* `COROUTINE_DELAY_SECONDS(seconds)`: yields back execution for `seconds`. The + maximum allowable delay is 32767 seconds. * `COROUTINE_LOOP()`: convenience macro that loops forever, replaces `COROUTINE_BEGIN()` and `COROUTINE_END()` * `COROUTINE_CHANNEL_WRITE()`: writes a message to a `Channel` @@ -277,9 +281,12 @@ while (!condition) COROUTINE_YIELD(); ### Delay +**Delay Milliseconds** + The `COROUTINE_DELAY(millis)` macro yields back control to other coroutines -until `millis` milliseconds have elapsed. The following waits for 100 -milliseconds: +until `millis` milliseconds have elapsed. This is analogous to the built-in +Arduino `delay()` function, except that this is non-blocking. The following +waits for 100 milliseconds: ```C++ COROUTINE(waitMillis) { @@ -291,26 +298,80 @@ COROUTINE(waitMillis) { } ``` -The `millis` argument is a `uint16_t`, a 16-bit unsigned integer, which reduces +The `millis` argument is a `uint16_t`, a 16-bit unsigned integer, which saves the size of each coroutine instance by 4 bytes (8-bit processors) or 8 bytes -(32-bit processors). However, the actual maximum delay is limited to 32767 -milliseconds to avoid overflow situations if the other coroutines in the system -take too much time for their work before returning control to the waiting -coroutine. With this limit, the other coroutines have as much as 32767 -milliseconds before it must yield, which should be more than enough time for any -conceivable situation. In practice, coroutines should complete their work within -several milliseconds and yield control to the other coroutines as soon as -possible. - -For delays longer than 32767 milliseconds, we can use an explicit for-loop. For -example, to delay for 100,000 seconds, we can do this: +(32-bit processors) compared to using an `uint32_t`. The largest number that +can be represented by a `uint16_t` is 65535, but the actual maximum delay is +limited to 32767 milliseconds to avoid overflow situations if the other +coroutines in the system take too much time for their work before returning +control to the waiting coroutine. With this limit, the other coroutines have as +much as 32767 milliseconds before it must yield, which should be more than +enough time for any conceivable situation. In practice, coroutines should +complete their work within several milliseconds and yield control to the other +coroutines as soon as possible. + +**Delay Microseconds** + +On faster microcontrollers, it might be useful to yield for microseconds using +the `COROUTINE_DELAY_MICROS(delayMicros)`. The following example waits for 300 +microseconds: + +```C++ +COROUTINE(waitMicros) { + COROUTINE_BEGIN(); + ... + COROUTINE_DELAY_MICROS(300); + ... + COROUTINE_END(); +} +``` +This macro has a number constraints: + +* The maximum delay is 32767 micros. +* All other coroutines in the program *must* yield within 32767 microsecond, + otherwise the internal timing variable will overflow and an incorrect delay + will occur. +* The accuracy of `COROUTINE_DELAY_MICROS()` is not guaranteed because the + overhead of context switching and checking the delay's expiration may + consume a significant portion of the requested delay in microseconds. + +**Delay Seconds** + +For delays greater than 32767 milliseconds, we can use the +`COROUTINE_DELAY_SECONDS(seconds)` convenience macro. The following example +waits for 200 seconds: + +```C++ +COROUTINE(waitSeconds) { + COROUTINE_BEGIN(); + ... + COROUTINE_DELAY_SECONDS(200); + ... + COROUTINE_END(); +} +``` + +This macro has some constraints and caveats: + +* The maximum number of seconds is 32767 seconds. +* The delay is implemented using the `millis()` clock divided by 1000. + * On 8-bit processors without hardware division instruction, the software + division consumes CPU time and flash memory, about 100 bytes on an AVR. + * The `unsigned long` returned by `millis()` rolls over every + 4294967.296 seconds (49.7 days). During that rollover, the + `COROUTINE_DELAY_SECONDS()` will return 0.704 seconds too early. If the + delay value is relatively large, e.g. 100 seconds, this inaccuracy + probably won't matter too much. + +We can also use an explicit for-loop. For example, to delay for 1000 seconds, we +can loop 100 times over a `COROUTINE_DELAY()` of 10 seconds, like this: ```C++ COROUTINE(waitThousandSeconds) { static uint16_t i; COROUTINE_BEGIN(); - for (i = 0; i < 10000; i++) { + for (i = 0; i < 100; i++) { COROUTINE_DELAY(10000); // 10 seconds } ... diff --git a/docs/doxygen.cfg b/docs/doxygen.cfg index aeb8aee..e624d52 100644 --- a/docs/doxygen.cfg +++ b/docs/doxygen.cfg @@ -38,7 +38,7 @@ PROJECT_NAME = "AceRoutine" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.3.0 +PROJECT_NUMBER = 1.3.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/docs/html/AceRoutine_8h_source.html b/docs/html/AceRoutine_8h_source.html index 6baa5ef..27a763d 100644 --- a/docs/html/AceRoutine_8h_source.html +++ b/docs/html/AceRoutine_8h_source.html @@ -22,7 +22,7 @@
AceRoutine -  1.3.0 +  1.3.1
A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.
diff --git a/docs/html/Channel_8h_source.html b/docs/html/Channel_8h_source.html index 29a9d38..c46a3b5 100644 --- a/docs/html/Channel_8h_source.html +++ b/docs/html/Channel_8h_source.html @@ -22,7 +22,7 @@
AceRoutine -  1.3.0 +  1.3.1
A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.
diff --git a/docs/html/ClockInterface_8h_source.html b/docs/html/ClockInterface_8h_source.html index 60e1b80..ddf5be8 100644 --- a/docs/html/ClockInterface_8h_source.html +++ b/docs/html/ClockInterface_8h_source.html @@ -22,7 +22,7 @@
AceRoutine -  1.3.0 +  1.3.1
A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.
@@ -106,14 +106,20 @@
41  public:
43  static unsigned long millis() { return ::millis(); }
-
44 };
-
45 
-
46 }
+
44 
+
46  static unsigned long micros() { return ::micros(); }
47 
-
48 #endif
+
63  static unsigned long seconds() { return ::millis() / 1000; }
+
64 };
+
65 
+
66 }
+
67 
+
68 #endif
static unsigned long millis()
Get the current millis.
+
static unsigned long seconds()
Get the current seconds.
A utility class (all methods are static) that provides a layer of indirection to Arduino clock functi...
+
static unsigned long micros()
Get the current micros.