AceRoutine
A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.
Install / Use
/learn @bxparks/AceRoutineREADME
AceRoutine
NEW: Profiling in v1.5: Version 1.5 adds the ability to profile the
execution time of Coroutine::runCoroutine() and render the histogram as a
table or a JSON object. See Coroutine
Profiling for details.
A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.
This library is an implementation of the
ProtoThreads library for the
Arduino platform. It emulates a stackless coroutine that can suspend execution
using a yield() or delay() functionality to allow other coroutines to
execute. When the scheduler makes its way back to the original coroutine, the
execution continues right after the yield() or delay().
There are only 2 core classes in this library:
Coroutineclass provides the context variables for all coroutinesCoroutineSchedulerclass handles the scheduling (optional)
The following classes are used for profiling:
CoroutineProfilerinterfaceLogBinProfilerprovides an implementation that tracks the execution time in 32 logarithmic bins from 1us to 4295s.LogBinTableRendererprints the histogram as a tableLogBinJsonRendererprints the histogram as a JSON object
The following is an experimental feature whose API and functionality may change considerably in the future:
Channelclass allows coroutines to send messages to each other
The library provides a number of macros to help create coroutines and manage their life cycle:
COROUTINE(): defines an instance of theCoroutineclass or an instance of a user-defined subclass ofCoroutineCOROUTINE_BEGIN(): must occur at the start of a coroutine bodyCOROUTINE_END(): must occur at the end of the coroutine bodyCOROUTINE_YIELD(): yields execution back to the caller, oftenCoroutineSchedulerbut not necessarilyCOROUTINE_AWAIT(condition): yield untilconditionbecomestrueCOROUTINE_DELAY(millis): yields back execution formillis. Themillisparameter is defined as auint16_t.COROUTINE_DELAY_MICROS(micros): yields back execution formicros. Themicrosparameter is defined as auint16_t.COROUTINE_DELAY_SECONDS(seconds): yields back execution forseconds. Thesecondsparameter is defined as auint16_t.COROUTINE_LOOP(): convenience macro that loops foreverCOROUTINE_CHANNEL_WRITE(channel, value): writes a value to aChannelCOROUTINE_CHANNEL_READ(channel, value): reads a value from aChannel
Here are some of the compelling features of this library compared to others (in my opinion of course):
- low memory usage
- 8-bit (e.g. AVR) processors:
- the first
Coroutineconsumes about 230 bytes of flash - each additional
Coroutineconsumes 170 bytes of flash - each
Coroutineconsumes 16 bytes of static RAM CoroutineSchedulerconsumes only about 40 bytes of flash and 2 bytes of RAM independent of the number of coroutines
- the first
- 32-bit (e.g. STM32, ESP8266, ESP32) processors
- the first
Coroutineconsumes between 120-450 bytes of flash - each additional
Coroutineconsumes about 130-160 bytes of flash, - each
Coroutineconsumes 28 bytes of static RAM CoroutineSchedulerconsumes only about 40-60 bytes of flash and 4 bytes of static RAM independent of the number of coroutines
- the first
- 8-bit (e.g. AVR) processors:
- extremely fast context switching
- Direct Scheduling (call
Coroutine::runCoroutine()directly)- ~1.0 microseconds on a 16 MHz ATmega328P
- ~0.4 microseconds on a 48 MHz SAMD21
- ~0.3 microseconds on a 72 MHz STM32
- ~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.3 microseconds on a 48 MHz SAMD21
- ~0.9 microseconds on a 72 MHz STM32
- ~0.8 microseconds on a 80 MHz ESP8266
- ~0.3 microseconds on a 240 MHz ESP32
- ~0.4 microseconds on 96 MHz Teensy 3.2 (depending on compiler settings)
- Direct Scheduling (call
- uses the
computed goto
feature of the GCC compiler (also supported by Clang) to avoid the
Duff's Device hack
- allows
switchstatements in the coroutines
- allows
- C/C++ macros eliminate boilerplate code and make the code easy to read
- the base
Coroutineclass is easy to subclass to add additional variables and functions - fully unit tested using AUnit
Some limitations are:
- A
Coroutinecannot return any values. - A
Coroutineis stackless and therefore cannot preserve local stack variables across multiple calls. Often the class member variables or function static variables are reasonable substitutes. - Coroutines are designed to be statically allocated, not dynamically created
and destroyed on the heap. Dynamic memory allocation on an 8-bit
microcontroller with 2kB of RAM would cause too much heap fragmentation. And
the virtual destructor pulls in
malloc()andfree()which increases flash memory by 600 bytes on AVR processors. - A
Channelis an experimental feature and has limited features. It is currently an unbuffered, synchronized channel. It can be used by only one reader and one writer.
After I had completed most of this library, I discovered that I had essentially
reimplemented the <ProtoThread.h> library in the
Cosa framework. The difference is that
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.5.1 (2022-09-20)
Changelog: CHANGELOG.md
Table of Contents
- Hello Coroutines
- Installation
- Documentation
- Comparisons
- Resource Consumption
- System Requirements
- License
- Feedback and Support
- Authors
<a name="HelloCoroutines"></a>
Hello Coroutines
<a name="HelloCoroutine"></a>
HelloCoroutine
This is the HelloCoroutine.ino sample sketch which
uses the COROUTINE() macro to automatically handle a number of boilerplate
code, and some internal bookkeeping operations. Using the COROUTINE() macro
works well for relatively small and simple coroutines.
#include <AceRoutine.h>
using namespace ace_routine;
const int LED = LED_BUILTIN;
const int LED_ON = HIGH;
const int LED_OFF = LOW;
COROUTINE(blinkLed) {
COROUTINE_LOOP() {
digitalWrite(LED, LED_ON);
COROUTINE_DELAY(100);
digitalWrite(LED, LED_OFF);
COROUTINE_DELAY(500);
}
}
COROUTINE(printHelloWorld) {
COROUTINE_LOOP() {
Serial.print(F("Hello, "));
Serial.flush();
COROUTINE_DELAY(1000);
Serial.println(F("World"));
COROUTINE_DELAY(4000);
}
}
void setup() {
delay(1000);
Serial.begin(115200);
while (!Serial); // Leonardo/Micro
pinMode(LED, OUTPUT);
}
void loop() {
blinkLed.runCoroutine();
printHelloWorld.runCoroutine();
}
The printHelloWorld coroutine prints "Hello, ", waits 1 second, then prints
"World", then waits 4 more seconds, then repeats from the start. At the same
time, the blinkLed coroutine blinks the builtin LED on and off, on for 100 ms
and off for 500 ms.
<a name="HelloScheduler"></a>
HelloScheduler
The HelloScheduler.ino sketch implements the same
thing using the CoroutineScheduler:
#include <AceRoutine.h>
using namespace ace_routine;
... // same as above
void setup() {
delay(1000);
Serial.begin(115200);
while (!Serial); // Leonardo/Micro
pinMode(LED, OUTPUT);
CoroutineScheduler::setup();
}
void loop() {
CoroutineScheduler::loop();
}
The CoroutineScheduler can automatically manage all coroutines defined by the
COROUTINE() macro, which eliminates the need to itemize your coroutines in the
loop() method manually. Unfortunately, this convenience is not free (see
MemoryBenchmark):
- The
CoroutineSchedulersingleton instance increases the flash memory by about 110 bytes. - The
CoroutineScheduler::loop()method calls theCoroutine::runCoroutine()method through thevirtualdispatch instead of directly, which is slower and takes more flash memory. - Each
Coroutineinstance consumes an additional ~70 bytes of flash when using theCoroutineScheduler.
On 8-bit processors with limited memory, the additional resource consumption can
be important. On 32-bit processors with far more memory, these additional
resources are often inconsequential. Therefore the CoroutineScheduler is
recommended mostly on 32-bit processors.
<a name="HelloManualCoroutine"></a>
HelloManualCoroutine
The HelloManualCoroutine.ino prog
Related Skills
node-connect
343.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
92.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
