diff --git a/README.md b/README.md index 0bbb8ab..26cae51 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Task Scheduler ### Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers -#### Version 3.6.2: 2022-10-04 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates) +#### Version 3.7.0: 2022-10-10 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates) [![arduino-library-badge](https://www.ardu-badge.com/badge/TaskScheduler.svg?)](https://www.ardu-badge.com/TaskScheduler)[![xscode](https://img.shields.io/badge/Available%20on-xs%3Acode-blue?style=?style=plastic&logo=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAAlUlEQVR42uzXSwqAMAwE0Mn9L+3Ggtgkk35QwcnSJo9S+yGwM9DCooCbgn4YrJ4CIPUcQF7/XSBbx2TEz4sAZ2q1RAECBAiYBlCtvwN+KiYAlG7UDGj59MViT9hOwEqAhYCtAsUZvL6I6W8c2wcbd+LIWSCHSTeSAAECngN4xxIDSK9f4B9t377Wd7H5Nt7/Xz8eAgwAvesLRjYYPuUAAAAASUVORK5CYII=)](https://xscode.com/arkhipenko/TaskScheduler) @@ -34,7 +34,8 @@ _“Everybody who learns concurrency and thinks they understand it, ends up find 13. CPU load / idle statistics for time critical applications 14. Scheduling options with priority for original schedule (with and without catchup) and interval 15. Ability to pause/resume and enable/disable scheduling -15. Thread-safe scheduling while running under preemptive scheduler (i. e., FreeRTOS) +16. Thread-safe scheduling while running under preemptive scheduler (i. e., FreeRTOS) +17. Optional self-destruction of dynamically created tasks upon disable Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Arduino UNO rev 3 @ `16MHz` clock, single scheduler w/o prioritization) @@ -43,7 +44,7 @@ Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Ard * Arduino Nano * Arduino Micro * ATtiny85 -* ESP8266 (Node MCU v2.0) +* ESP8266 * ESP32 * Teensy (tested on Teensy 3.5) * nRF52 (tested on nRF52832) diff --git a/examples/Scheduler_example00_Blink/Scheduler_example00_Blink.ino b/examples/Scheduler_example00_Blink/Scheduler_example00_Blink.ino index 8cec356..372f7b8 100644 --- a/examples/Scheduler_example00_Blink/Scheduler_example00_Blink.ino +++ b/examples/Scheduler_example00_Blink/Scheduler_example00_Blink.ino @@ -10,19 +10,28 @@ - STM32 Maple Mini */ - -// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns -#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass -#define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only -// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids -// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer -// #define _TASK_PRIORITY // Support for layered scheduling priority -// #define _TASK_MICRO_RES // Support for microsecond resolution -// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 and ESP32 ONLY) -// #define _TASK_DEBUG // Make all methods and variables public for debug purposes -// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations -// #define _TASK_TIMEOUT // Support for overall task timeout -// #define _TASK_OO_CALLBACKS // Support for dynamic callback method binding +// ---------------------------------------- +// The following "defines" control library functionality at compile time, +// and should be used in the main sketch depending on the functionality required +// +// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns +#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between runs if no callback methods were invoked during the pass +#define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only +// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids +// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer +// #define _TASK_PRIORITY // Support for layered scheduling priority +// #define _TASK_MICRO_RES // Support for microsecond resolution +// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY) +// #define _TASK_DEBUG // Make all methods and variables public for debug purposes +// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations +// #define _TASK_TIMEOUT // Support for overall task timeout +// #define _TASK_OO_CALLBACKS // Support for callbacks via inheritance +// #define _TASK_EXPOSE_CHAIN // Methods to access tasks in the task chain +// #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options +// #define _TASK_DEFINE_MILLIS // Force forward declaration of millis() and micros() "C" style +// #define _TASK_EXTERNAL_TIME // Custom millis() and micros() methods +// #define _TASK_THREAD_SAFE // Enable additional checking for thread safety +// #define _TASK_SELF_DESTRUCT // Enable tasks to "self-destruct" after disable #include // Debug and Test options diff --git a/examples/Scheduler_example00_Blink_Adafruit/Scheduler_example00_Blink_Adafruit.ino b/examples/Scheduler_example00_Blink_Namespace/Scheduler_example00_Blink_Namespace.ino similarity index 63% rename from examples/Scheduler_example00_Blink_Adafruit/Scheduler_example00_Blink_Adafruit.ino rename to examples/Scheduler_example00_Blink_Namespace/Scheduler_example00_Blink_Namespace.ino index 374d526..de1d399 100644 --- a/examples/Scheduler_example00_Blink_Adafruit/Scheduler_example00_Blink_Adafruit.ino +++ b/examples/Scheduler_example00_Blink_Namespace/Scheduler_example00_Blink_Namespace.ino @@ -8,18 +8,29 @@ */ -// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns -#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass -#define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only -// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids -// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer -// #define _TASK_PRIORITY // Support for layered scheduling priority -// #define _TASK_MICRO_RES // Support for microsecond resolution -// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 and ESP32 ONLY) -// #define _TASK_DEBUG // Make all methods and variables public for debug purposes -// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations -// #define _TASK_TIMEOUT // Support for overall task timeout -// #define _TASK_OO_CALLBACKS // Support for dynamic callback method binding +// ---------------------------------------- +// The following "defines" control library functionality at compile time, +// and should be used in the main sketch depending on the functionality required +// +// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns +#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between runs if no callback methods were invoked during the pass +#define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only +// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids +// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer +// #define _TASK_PRIORITY // Support for layered scheduling priority +// #define _TASK_MICRO_RES // Support for microsecond resolution +// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY) +// #define _TASK_DEBUG // Make all methods and variables public for debug purposes +// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations +// #define _TASK_TIMEOUT // Support for overall task timeout +// #define _TASK_OO_CALLBACKS // Support for callbacks via inheritance +// #define _TASK_EXPOSE_CHAIN // Methods to access tasks in the task chain +// #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options +// #define _TASK_DEFINE_MILLIS // Force forward declaration of millis() and micros() "C" style +// #define _TASK_EXTERNAL_TIME // Custom millis() and micros() methods +// #define _TASK_THREAD_SAFE // Enable additional checking for thread safety +// #define _TASK_SELF_DESTRUCT // Enable tasks to "self-destruct" after disable + #include // Debug and Test options @@ -40,7 +51,7 @@ #endif // Scheduler -TaskScheduler ts; +TsScheduler ts; /* Approach 1: LED is driven by the boolean variable; false = OFF, true = ON @@ -48,7 +59,7 @@ TaskScheduler ts; #define PERIOD1 500 #define DURATION 10000 void blink1CB(); -Task tBlink1 ( PERIOD1 * TASK_MILLISECOND, DURATION / PERIOD1, &blink1CB, &ts, true ); +TsTask tBlink1 ( PERIOD1 * TASK_MILLISECOND, DURATION / PERIOD1, &blink1CB, &ts, true ); /* Approac 2: two callback methods: one turns ON, another turns OFF @@ -56,14 +67,14 @@ Task tBlink1 ( PERIOD1 * TASK_MILLISECOND, DURATION / PERIOD1, &blink1CB, &ts, t #define PERIOD2 400 void blink2CB_ON(); void blink2CB_OFF(); -Task tBlink2 ( PERIOD2 * TASK_MILLISECOND, DURATION / PERIOD2, &blink2CB_ON, &ts, false ); +TsTask tBlink2 ( PERIOD2 * TASK_MILLISECOND, DURATION / PERIOD2, &blink2CB_ON, &ts, false ); /* Approach 3: Use RunCounter */ #define PERIOD3 300 void blink3CB(); -Task tBlink3 (PERIOD3 * TASK_MILLISECOND, DURATION / PERIOD3, &blink3CB, &ts, false); +TsTask tBlink3 (PERIOD3 * TASK_MILLISECOND, DURATION / PERIOD3, &blink3CB, &ts, false); /* Approach 4: Use status request objects to pass control from one task to the other @@ -73,8 +84,8 @@ bool blink41OE(); void blink41(); void blink42(); void blink42OD(); -Task tBlink4On ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink41, &ts, false, &blink41OE ); -Task tBlink4Off ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink42, &ts, false, NULL, &blink42OD ); +TsTask tBlink4On ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink41, &ts, false, &blink41OE ); +TsTask tBlink4Off ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink42, &ts, false, NULL, &blink42OD ); /* @@ -85,8 +96,8 @@ bool blink51OE(); void blink51(); void blink52(); void blink52OD(); -Task tBlink5On ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink51, &ts, false, &blink51OE ); -Task tBlink5Off ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink52, &ts, false, NULL, &blink52OD ); +TsTask tBlink5On ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink51, &ts, false, &blink51OE ); +TsTask tBlink5Off ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink52, &ts, false, NULL, &blink52OD ); /* @@ -96,7 +107,7 @@ Task tBlink5Off ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink52, &ts, fal void blink6CB(); bool blink6OE(); void blink6OD(); -Task tBlink6 ( PERIOD6 * TASK_MILLISECOND, DURATION / PERIOD6, &blink6CB, &ts, false, &blink6OE, &blink6OD ); +TsTask tBlink6 ( PERIOD6 * TASK_MILLISECOND, DURATION / PERIOD6, &blink6CB, &ts, false, &blink6OE, &blink6OD ); void setup() { // put your setup code here, to run once: @@ -212,7 +223,7 @@ void blink41() { // _PP(millis()); // _PL(": blink41"); LEDOn(); - StatusRequest* r = tBlink4On.getInternalStatusRequest(); + TsStatusRequest* r = tBlink4On.getInternalStatusRequest(); tBlink4Off.waitForDelayed( r ); counter++; } @@ -221,7 +232,7 @@ void blink42() { // _PP(millis()); // _PL(": blink42"); LEDOff(); - StatusRequest* r = tBlink4Off.getInternalStatusRequest(); + TsStatusRequest* r = tBlink4Off.getInternalStatusRequest(); tBlink4On.waitForDelayed( r ); counter++; } diff --git a/examples/Scheduler_example19_Dynamic_Tasks/Scheduler_example19_Dynamic_Tasks.ino b/examples/Scheduler_example19_Dynamic_Tasks/Scheduler_example19_Dynamic_Tasks.ino index 9ca39de..9a852d5 100644 --- a/examples/Scheduler_example19_Dynamic_Tasks/Scheduler_example19_Dynamic_Tasks.ino +++ b/examples/Scheduler_example19_Dynamic_Tasks/Scheduler_example19_Dynamic_Tasks.ino @@ -17,6 +17,8 @@ #include #include +int freeMemory(); + #if defined (ARDUINO_ARCH_AVR) #include #elif defined(__arm__) @@ -25,8 +27,11 @@ static int freeMemory() { char top = 't'; return &top - reinterpret_cast(sbrk(0)); } +#elif defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32) +int freeMemory() { return ESP.getFreeHeap();} #else -int freeMemory(); // supply your own +// Supply your own freeMemory method +int freeMemory() { return 0;} #endif Scheduler ts; diff --git a/examples/Scheduler_example19_Dynamic_Tasks_SelfDestruct/Scheduler_example19_Dynamic_Tasks_SelfDestruct.ino b/examples/Scheduler_example19_Dynamic_Tasks_SelfDestruct/Scheduler_example19_Dynamic_Tasks_SelfDestruct.ino new file mode 100644 index 0000000..afea5c4 --- /dev/null +++ b/examples/Scheduler_example19_Dynamic_Tasks_SelfDestruct/Scheduler_example19_Dynamic_Tasks_SelfDestruct.ino @@ -0,0 +1,121 @@ +/** + TaskScheduler Test sketch - test of Task destructor + Test case: + Main task runs every 100 milliseconds 100 times and in 50% cases generates a task object + which runs 1 to 10 times with 100 ms to 5 s interval, and then destroyed. + Self-destruct feature takes care of the task deletion after tasks complete + + This sketch uses the following libraries: + - FreeMemory library: https://github.com/McNeight/MemoryFree + - QueueArray library: https://playground.arduino.cc/Code/QueueArray/ +*/ + +#define _TASK_WDT_IDS // To enable task unique IDs +#define _TASK_SLEEP_ON_IDLE_RUN // Compile with support for entering IDLE SLEEP state for 1 ms if not tasks are scheduled to run +#define _TASK_LTS_POINTER // Compile with support for Local Task Storage pointer +#define _TASK_SELF_DESTRUCT // Enable tasks to "self-destruct" after disable +#include + +int freeMemory(); + +#if defined (ARDUINO_ARCH_AVR) +#include +#elif defined(__arm__) +extern "C" char* sbrk(int incr); +static int freeMemory() { + char top = 't'; + return &top - reinterpret_cast(sbrk(0)); +} +#elif defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32) +int freeMemory() { return ESP.getFreeHeap();} +#else +// Supply your own freeMemory method +int freeMemory() { return 0;} +#endif + +Scheduler ts; + +// Callback methods prototypes +void MainLoop(); +void GC(); + +// Statis task +Task tMain(100 * TASK_MILLISECOND, 100, &MainLoop, &ts, true); + +void Iteration(); +bool OnEnable(); +void OnDisable(); + +int noOfTasks = 0; + +void MainLoop() { + Serial.print(millis()); Serial.print("\t"); + Serial.print("MainLoop run: "); + int i = tMain.getRunCounter(); + Serial.print(i); Serial.print(F(".\t")); + + if ( random(0, 101) > 50 ) { // generate a new task only in 50% of cases + // Generating another task + long p = random(100, 5001); // from 100 ms to 5 seconds + long j = random(1, 11); // from 1 to 10 iterations) + Task *t = new Task(p, j, Iteration, &ts, false, OnEnable, OnDisable, true); // enable self-destruct + + Serial.print(F("Generated a new task:\t")); Serial.print(t->getId()); Serial.print(F("\tInt, Iter = \t")); + Serial.print(p); Serial.print(", "); Serial.print(j); + Serial.print(F("\tFree mem=")); Serial.print(freeMemory()); + Serial.print(F("\tNo of tasks=")); Serial.println(++noOfTasks); + t->enable(); + } + else { + Serial.println(F("Skipped generating a task")); + } +} + + +void Iteration() { + Task &t = ts.currentTask(); + + Serial.print(millis()); Serial.print("\t"); + Serial.print("Task N"); Serial.print(t.getId()); Serial.print(F("\tcurrent iteration: ")); + int i = t.getRunCounter(); + Serial.println(i); +} + +bool OnEnable() { + // to-do: think of something to put in here. + return true; +} + +void OnDisable() { + Task *t = &ts.currentTask(); + unsigned int tid = t->getId(); + + Serial.print(millis()); Serial.print("\t"); + Serial.print("Task N"); Serial.print(tid); Serial.println(F("\tfinished")); + Serial.print(F("\tNo of tasks=")); Serial.println(--noOfTasks); +} + +/** + Standard Arduino setup and loop methods +*/ +void setup() { + Serial.begin(115200); + + randomSeed(analogRead(0) + analogRead(5)); + noOfTasks = 0; + + Serial.println(F("Dynamic Task Creation/Destruction Example")); + Serial.println(); + + Serial.print(F("Free mem=")); Serial.print(freeMemory()); + Serial.print(F("\tNo of tasks=")); Serial.println(noOfTasks); + Serial.println(); +} + +void loop() { + ts.execute(); + if ( millis() > 5000 && noOfTasks == 0 ) { + Serial.print(F("\tFree mem=")); Serial.println(freeMemory()); + while(1); + } +} diff --git a/examples/Scheduler_example19_Dynamic_Tasks_stdQueue/Scheduler_example19_Dynamic_Tasks_stdQueue.ino b/examples/Scheduler_example19_Dynamic_Tasks_stdQueue/Scheduler_example19_Dynamic_Tasks_stdQueue.ino new file mode 100644 index 0000000..abe99ad --- /dev/null +++ b/examples/Scheduler_example19_Dynamic_Tasks_stdQueue/Scheduler_example19_Dynamic_Tasks_stdQueue.ino @@ -0,0 +1,137 @@ +/** + TaskScheduler Test sketch - test of Task destructor + Test case: + Main task runs every 100 milliseconds 100 times and in 50% cases generates a task object + which runs 1 to 10 times with 100 ms to 5 s interval, and then destroyed. + Garbage collection deletes all the tasks which have finished (enabled in their respective + OnDisable methods) + + This sketch uses the following libraries: + - FreeMemory library: https://github.com/McNeight/MemoryFree + - QueueArray library: https://playground.arduino.cc/Code/QueueArray/ +*/ + +#define _TASK_WDT_IDS // To enable task unique IDs +#define _TASK_SLEEP_ON_IDLE_RUN // Compile with support for entering IDLE SLEEP state for 1 ms if not tasks are scheduled to run +#define _TASK_LTS_POINTER // Compile with support for Local Task Storage pointer + +#include + +#include + +int freeMemory(); + +#if defined (ARDUINO_ARCH_AVR) +#include +#elif defined(__arm__) +extern "C" char* sbrk(int incr); +static int freeMemory() { + char top = 't'; + return &top - reinterpret_cast(sbrk(0)); +} +#elif defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32) +int freeMemory() { return ESP.getFreeHeap();} +#else +// Supply your own freeMemory method +int freeMemory() { return 0;} +#endif + +Scheduler ts; + +// Callback methods prototypes +void MainLoop(); +void GC(); + +// Statis task +Task tMain(100 * TASK_MILLISECOND, 100, &MainLoop, &ts, true); +Task tGarbageCollection(200 * TASK_MILLISECOND, TASK_FOREVER, &GC, &ts, false); + + +void Iteration(); +bool OnEnable(); +void OnDisable(); + +int noOfTasks = 0; +std::queue toDelete; + +void MainLoop() { + Serial.print(millis()); Serial.print("\t"); + Serial.print("MainLoop run: "); + int i = tMain.getRunCounter(); + Serial.print(i); Serial.print(F(".\t")); + + if ( random(0, 101) > 50 ) { // generate a new task only in 50% of cases + // Generating another task + long p = random(100, 5001); // from 100 ms to 5 seconds + long j = random(1, 11); // from 1 to 10 iterations) + Task *t = new Task(p, j, Iteration, &ts, false, OnEnable, OnDisable); + + Serial.print(F("Generated a new task:\t")); Serial.print(t->getId()); Serial.print(F("\tInt, Iter = \t")); + Serial.print(p); Serial.print(", "); Serial.print(j); + Serial.print(F("\tFree mem=")); Serial.print(freeMemory()); + Serial.print(F("\tNo of tasks=")); Serial.println(++noOfTasks); + t->enable(); + } + else { + Serial.println(F("Skipped generating a task")); + } +} + + +void Iteration() { + Task &t = ts.currentTask(); + + Serial.print(millis()); Serial.print("\t"); + Serial.print("Task N"); Serial.print(t.getId()); Serial.print(F("\tcurrent iteration: ")); + int i = t.getRunCounter(); + Serial.println(i); +} + +bool OnEnable() { + // to-do: think of something to put in here. + return true; +} + +void OnDisable() { + Task *t = &ts.currentTask(); + unsigned int tid = t->getId(); + toDelete.push(t); + tGarbageCollection.enableIfNot(); + + Serial.print(millis()); Serial.print("\t"); + Serial.print("Task N"); Serial.print(tid); Serial.println(F("\tfinished")); +} + +/** + Standard Arduino setup and loop methods +*/ +void setup() { + Serial.begin(115200); + + randomSeed(analogRead(0) + analogRead(5)); + noOfTasks = 0; + + Serial.println(F("Dynamic Task Creation/Destruction Example")); + Serial.println(); + + Serial.print(F("Free mem=")); Serial.print(freeMemory()); + Serial.print(F("\tNo of tasks=")); Serial.println(noOfTasks); + Serial.println(); +} + +void GC() { + if ( toDelete.empty() ) { + tGarbageCollection.disable(); + return; + } + Task *t = toDelete.front(); toDelete.pop(); + Serial.print(millis()); Serial.print("\t"); + Serial.print("Task N"); Serial.print(t->getId()); Serial.println(F("\tdestroyed")); + Serial.print("Free mem="); Serial.print(freeMemory()); + Serial.print(F("\tNo of tasks=")); Serial.println(--noOfTasks); + delete t; +} + +void loop() { + ts.execute(); +} diff --git a/examples/Scheduler_template/Scheduler_template.ino b/examples/Scheduler_template/Scheduler_template.ino index 4f05ca7..01ce3e3 100644 --- a/examples/Scheduler_template/Scheduler_template.ino +++ b/examples/Scheduler_template/Scheduler_template.ino @@ -37,21 +37,29 @@ // ==== INCLUDES ================================================================================== // ==== Uncomment desired compile options ================================= -// #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass +// ---------------------------------------- +// The following "defines" control library functionality at compile time, +// and should be used in the main sketch depending on the functionality required +// Should be defined BEFORE #include !!! +// // #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns +// #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between runs if no callback methods were invoked during the pass // #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only // #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids // #define _TASK_LTS_POINTER // Compile with support for local task storage pointer // #define _TASK_PRIORITY // Support for layered scheduling priority // #define _TASK_MICRO_RES // Support for microsecond resolution -// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 and ESP32 ONLY) +// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY) // #define _TASK_DEBUG // Make all methods and variables public for debug purposes // #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations // #define _TASK_TIMEOUT // Support for overall task timeout -// #define _TASK_OO_CALLBACKS // Support for dynamic callback method binding -// #define _TASK_DEFINE_MILLIS // Force forward declaration of millis() and micros() "C" style +// #define _TASK_OO_CALLBACKS // Support for callbacks via inheritance // #define _TASK_EXPOSE_CHAIN // Methods to access tasks in the task chain // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options +// #define _TASK_DEFINE_MILLIS // Force forward declaration of millis() and micros() "C" style +// #define _TASK_EXTERNAL_TIME // Custom millis() and micros() methods +// #define _TASK_THREAD_SAFE // Enable additional checking for thread safety +// #define _TASK_SELF_DESTRUCT // Enable tasks to "self-destruct" after disable #include @@ -66,23 +74,29 @@ void task2Callback(); // ==== Scheduling defines (cheat sheet) ===================== /* - TASK_MILLISECOND - TASK_SECOND - TASK_MINUTE - TASK_HOUR - TASK_IMMEDIATE - TASK_FOREVER - TASK_ONCE - TASK_NOTIMEOUT + TASK_MILLISECOND - one millisecond in millisecond/microseconds + TASK_SECOND - one second in millisecond/microseconds + TASK_MINUTE - one minute in millisecond/microseconds + TASK_HOUR - one hour in millisecond/microseconds + TASK_IMMEDIATE - schedule task to runn as soon as possible + TASK_FOREVER - run task indefinitely + TASK_ONCE - run task once + TASK_NOTIMEOUT - set timeout interval to No Timeout TASK_SCHEDULE - schedule is a priority, with "catch up" (default) TASK_SCHEDULE_NC - schedule is a priority, without "catch up" TASK_INTERVAL - interval is a priority, without "catch up" + + TASK_SR_OK - status request triggered with an OK code (all good) + TASK_SR_ERROR - status request triggered with an ERROR code + TASK_SR_CANCEL - status request was cancelled + TASK_SR_ABORT - status request was aborted + TASK_SR_TIMEOUT - status request timed out */ // ==== Task definitions ======================== Task t1 (100 * TASK_MILLISECOND, TASK_FOREVER, &task1Callback, &ts, true); -Task t2 (TASK_IMMEDIATE, 100, &task2Callback, &ts, true); +Task t2 (TASK_IMMEDIATE, 100 /* times */, &task2Callback, &ts, true); diff --git a/keywords.txt b/keywords.txt index e482c64..4adbb63 100644 --- a/keywords.txt +++ b/keywords.txt @@ -11,6 +11,10 @@ StatusRequest KEYWORD1 Task KEYWORD1 TS KEYWORD1 TaskScheduler KEYWORD1 +TsScheduler KEYWORD1 +TsStatusRequest KEYWORD1 +TsTask KEYWORD1 +TsTaskScheduler KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -56,6 +60,7 @@ getOverrun KEYWORD2 getPreviousTask KEYWORD2 getRunCounter KEYWORD2 getSchedulingOption KEYWORD2 +getSelfDestruct KEYWORD2 getStartDelay KEYWORD2 getStatus KEYWORD2 getStatusRequest KEYWORD2 @@ -84,6 +89,7 @@ setLtsPointer KEYWORD2 setOnDisable KEYWORD2 setOnEnable KEYWORD2 setSchedulingOption KEYWORD2 +setSelfDestruct KEYWORD2 setSleepMethod KEYWORD2 setTimeout KEYWORD2 setWaiting KEYWORD2 @@ -119,6 +125,7 @@ _TASK_SCHEDULING_OPTIONS LITERAL1 _TASK_DEFINE_MILLIS LITERAL1 _TASK_EXTERNAL_TIME LITERAL1 _TASK_THREAD_SAFE LITERAL1 +_TASK_SELF_DESTRUCT LITERAL1 SleepCallback LITERAL1 TASK_FOREVER LITERAL1 TASK_HOUR LITERAL1 diff --git a/library.json b/library.json index 61e3715..2925fb6 100644 --- a/library.json +++ b/library.json @@ -16,7 +16,7 @@ "maintainer": true } ], - "version": "3.6.2", + "version": "3.7.0", "frameworks": "arduino", "platforms": "*" } diff --git a/library.properties b/library.properties index 5d0473a..14b6f9e 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=TaskScheduler -version=3.6.2 +version=3.7.0 author=Anatoli Arkhipenko maintainer=Anatoli Arkhipenko sentence=Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers. -paragraph=Supports: periodic task execution (with dynamic execution period in milliseconds or microseconds – frequency of execution), number of iterations (limited or infinite number of iterations), execution of tasks in predefined sequence, dynamic change of task execution parameters (frequency, number of iterations, callback methods), power saving via entering IDLE sleep mode when tasks are not scheduled to run, event-driven task invocation via Status Request object, task IDs and Control Points for error handling and watchdog timer, Local Task Storage pointer (allowing use of same callback code for multiple tasks), layered task prioritization, std::functions (esp8266, esp32 only), overall task timeout, static and dynamic callback method binding. +paragraph=Supports: periodic task execution (with dynamic execution period in milliseconds or microseconds – frequency of execution), number of iterations (limited or infinite number of iterations), execution of tasks in predefined sequence, dynamic change of task execution parameters (frequency, number of iterations, callback methods), power saving via entering IDLE sleep mode when tasks are not scheduled to run, event-driven task invocation via Status Request object, task IDs and Control Points for error handling and watchdog timer, Local Task Storage pointer (allowing use of same callback code for multiple tasks), layered task prioritization, std::functions (where supported), overall task timeout, static and dynamic callback method binding. category=Timing url=https://github.com/arkhipenko/TaskScheduler.git architectures=* diff --git a/src/TScheduler.hpp b/src/TScheduler.hpp index a9837ee..11cf51c 100644 --- a/src/TScheduler.hpp +++ b/src/TScheduler.hpp @@ -16,7 +16,10 @@ namespace TS{ #include "TaskScheduler.h" } -using TaskScheduler = TS::Scheduler; -using SleepCallback = TS::SleepCallback; -using Task = TS::Task; -using StatusRequest = TS::StatusRequest; +using TsScheduler = TS::Scheduler; +using TsTask = TS::Task; +using TsStatusRequest = TS::StatusRequest; +using TsSleepCallback = TS::SleepCallback; +using TsTaskCallback = TS::TaskCallback; +using TsTaskOnDisable = TS::TaskOnDisable; +using TsTaskOnEnable = TS::TaskOnEnable; diff --git a/src/TSchedulerDeclarations.hpp b/src/TSchedulerDeclarations.hpp index 8f241be..26030ad 100644 --- a/src/TSchedulerDeclarations.hpp +++ b/src/TSchedulerDeclarations.hpp @@ -16,7 +16,10 @@ namespace TS{ #include "TaskSchedulerDeclarations.h" } -using TaskScheduler = TS::Scheduler; -using SleepCallback = TS::SleepCallback; -using Task = TS::Task; -using StatusRequest = TS::StatusRequest; \ No newline at end of file +using TsScheduler = TS::Scheduler; +using TsTask = TS::Task; +using TsStatusRequest = TS::StatusRequest; +using TsSleepCallback = TS::SleepCallback; +using TsTaskCallback = TS::TaskCallback; +using TsTaskOnDisable = TS::TaskOnDisable; +using TsTaskOnEnable = TS::TaskOnEnable; diff --git a/src/TaskScheduler.h b/src/TaskScheduler.h index 25df614..2f3377d 100644 --- a/src/TaskScheduler.h +++ b/src/TaskScheduler.h @@ -217,6 +217,10 @@ v3.6.2: 2022-10-04 - feature: added TScheduler.hpp and TSchedulerDeclarations.hpp - a workaround for conflicting declarations (e.g., nRF52840 using Adafruit Core). using namespace TS (credit: https://github.com/vortigont) +v3.7.0: + 2022-10-10 - feature: added ability for Task to "self-destruct" on disable. Useful for dynamic task management. + Added updated example 19 for this functionality. Updated the Sketch Template + (Thanks, https://github.com/vortigont for the idea). */ @@ -256,6 +260,7 @@ extern "C" { // #define _TASK_DEFINE_MILLIS // Force forward declaration of millis() and micros() "C" style // #define _TASK_EXTERNAL_TIME // Custom millis() and micros() methods // #define _TASK_THREAD_SAFE // Enable additional checking for thread safety +// #define _TASK_SELF_DESTRUCT // Enable tasks to "self-destruct" after disable #ifdef _TASK_MICRO_RES @@ -304,15 +309,29 @@ static uint32_t _task_micros() {return micros();} * so could be called with no parameters. */ #ifdef _TASK_OO_CALLBACKS -Task::Task( unsigned long aInterval, long aIterations, Scheduler* aScheduler, bool aEnable ) { +Task::Task( unsigned long aInterval, long aIterations, Scheduler* aScheduler, bool aEnable +#ifdef _TASK_SELF_DESTRUCT +, bool aSelfDestruct ) { +#else + ) { +#endif // #ifdef _TASK_SELF_DESTRUCT reset(); set(aInterval, aIterations); #else -Task::Task( unsigned long aInterval, long aIterations, TaskCallback aCallback, Scheduler* aScheduler, bool aEnable, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable ) { +Task::Task( unsigned long aInterval, long aIterations, TaskCallback aCallback, Scheduler* aScheduler, bool aEnable, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable +#ifdef _TASK_SELF_DESTRUCT +, bool aSelfDestruct ) { +#else + ) { +#endif // #ifdef _TASK_SELF_DESTRUCT reset(); set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable); #endif +#ifdef _TASK_SELF_DESTRUCT + setSelfDestruct(aSelfDestruct); +#endif // #ifdef _TASK_SELF_DESTRUCT + if (aScheduler) aScheduler->addTask(*this); #ifdef _TASK_WDT_IDS @@ -327,9 +346,8 @@ Task::Task( unsigned long aInterval, long aIterations, TaskCallback aCallback, S * prior to being deleted. */ Task::~Task() { - disable(); - if (iScheduler) - iScheduler->deleteTask(*this); + if ( this->isEnabled() ) disable(); + if (iScheduler) iScheduler->deleteTask(*this); } @@ -538,6 +556,11 @@ void Task::reset() { #ifdef _TASK_THREAD_SAFE iMutex = 0; #endif // _TASK_THREAD_SAFE + +#ifdef _TASK_SELF_DESTRUCT + iStatus.sd_request = false; +#endif // #ifdef _TASK_SELF_DESTRUCT + } /** Explicitly set Task execution parameters @@ -882,6 +905,10 @@ bool Task::disable() { #ifdef _TASK_STATUS_REQUEST iMyStatusRequest.signalComplete(); #endif + +#ifdef _TASK_SELF_DESTRUCT + if ( getSelfDestruct() ) iStatus.sd_request = true; +#endif // #ifdef _TASK_SELF_DESTRUCT return (previousEnabled); } @@ -895,6 +922,10 @@ void Task::abort() { #ifdef _TASK_STATUS_REQUEST iMyStatusRequest.signalComplete(TASK_SR_ABORT); #endif + +#ifdef _TASK_SELF_DESTRUCT + if ( getSelfDestruct() ) iStatus.sd_request = true; +#endif // #ifdef _TASK_SELF_DESTRUCT } @@ -1089,10 +1120,15 @@ void Scheduler::disableAll() { iEnabled = false; - Task *current = iFirst; + Task* current = iFirst; + Task* next; while (current) { + next = current->iNext; current->disable(); - current = current->iNext; +#ifdef _TASK_SELF_DESTRUCT + if ( current->iStatus.sd_request ) delete iCurrent; +#endif // #ifdef _TASK_SELF_DESTRUCT + current = next; } #ifdef _TASK_PRIORITY @@ -1300,6 +1336,9 @@ bool Scheduler::execute() { // Disable task on last iteration: if (iCurrent->iIterations == 0) { iCurrent->disable(); +#ifdef _TASK_SELF_DESTRUCT + if ( iCurrent->iStatus.sd_request ) delete iCurrent; +#endif // #ifdef _TASK_SELF_DESTRUCT break; } m = _TASK_TIME_FUNCTION(); @@ -1310,6 +1349,9 @@ bool Scheduler::execute() { if ( iCurrent->iTimeout && (m - iCurrent->iStarttime > iCurrent->iTimeout) ) { iCurrent->iStatus.timeout = true; iCurrent->disable(); +#ifdef _TASK_SELF_DESTRUCT + if ( iCurrent->iStatus.sd_request ) delete iCurrent; +#endif // #ifdef _TASK_SELF_DESTRUCT break; } #endif // _TASK_TIMEOUT diff --git a/src/TaskSchedulerDeclarations.h b/src/TaskSchedulerDeclarations.h index 4260491..7d646aa 100644 --- a/src/TaskSchedulerDeclarations.h +++ b/src/TaskSchedulerDeclarations.h @@ -28,6 +28,7 @@ // #define _TASK_DEFINE_MILLIS // Force forward declaration of millis() and micros() "C" style // #define _TASK_EXTERNAL_TIME // Custom millis() and micros() methods // #define _TASK_THREAD_SAFE // Enable additional checking for thread safety +// #define _TASK_SELF_DESTRUCT // Enable tasks to "self-destruct" after disable class Scheduler; @@ -143,14 +144,18 @@ typedef bool (*TaskOnEnable)(); typedef struct { bool enabled : 1; // indicates that task is enabled or not. bool inonenable : 1; // indicates that task execution is inside OnEnable method (preventing infinite loops) - bool canceled : 1; // indication that tast has been canceled prior to normal end of all iterations or regular call to disable() + bool canceled : 1; // indication that task has been canceled prior to normal end of all iterations or regular call to disable() +#ifdef _TASK_SELF_DESTRUCT + bool selfdestruct : 1; // indication that task has been requested to self-destruct on disable + bool sd_request : 1; // request for scheduler to delete task object and take task out of the queue +#endif // _TASK_SELF_DESTRUCT #ifdef _TASK_STATUS_REQUEST uint8_t waiting : 2; // indication if task is waiting on the status request -#endif +#endif // _TASK_STATUS_REQUEST #ifdef _TASK_TIMEOUT bool timeout : 1; // indication if task timed out -#endif +#endif // _TASK_TIMEOUT } __task_status; @@ -159,9 +164,19 @@ class Task { public: #ifdef _TASK_OO_CALLBACKS - INLINE Task(unsigned long aInterval=0, long aIterations=0, Scheduler* aScheduler=NULL, bool aEnable=false); + INLINE Task(unsigned long aInterval=0, long aIterations=0, Scheduler* aScheduler=NULL, bool aEnable=false +#ifdef _TASK_SELF_DESTRUCT + , bool aSelfDestruct=false); #else - INLINE Task(unsigned long aInterval=0, long aIterations=0, TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL); + ); +#endif // #ifdef _TASK_SELF_DESTRUCT +#else + INLINE Task(unsigned long aInterval=0, long aIterations=0, TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL +#ifdef _TASK_SELF_DESTRUCT + , bool aSelfDestruct=false); +#else + ); +#endif // #ifdef _TASK_SELF_DESTRUCT #endif // _TASK_OO_CALLBACKS @@ -214,7 +229,12 @@ class Task { INLINE unsigned long getInterval(); INLINE void setIterations(long aIterations); INLINE long getIterations(); - INLINE unsigned long getRunCounter() ; + INLINE unsigned long getRunCounter(); + +#ifdef _TASK_SELF_DESTRUCT + INLINE void setSelfDestruct(bool aSelfDestruct=true) { iStatus.selfdestruct = aSelfDestruct; } + INLINE bool getSelfDestruct() { return iStatus.selfdestruct; } +#endif // #ifdef _TASK_SELF_DESTRUCT #ifdef _TASK_OO_CALLBACKS virtual INLINE bool Callback() =0; // return true if run was "productive - this will disable sleep on the idle run for next pass