From 9edbe90155b19267bfb0cc4439ee5216f0569ba9 Mon Sep 17 00:00:00 2001 From: Anatoli Arkhipenko Date: Tue, 24 Nov 2015 10:28:40 -0500 Subject: v1.8.5 bug fixes * incorrect calculation of next task invocation in case callback changed the interval * Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner --- README | 6 ++- extras/TaskScheduler.doc | Bin 201216 -> 206336 bytes extras/TaskScheduler.html | 89 +++++++++++++++++++++++++++----- extras/TaskScheduler_html_m68472eb8.png | Bin 0 -> 41281 bytes library.properties | 2 +- src/TaskScheduler.h | 67 +++++++++++++----------- 6 files changed, 117 insertions(+), 47 deletions(-) create mode 100644 extras/TaskScheduler_html_m68472eb8.png diff --git a/README b/README index 0505da1..b29b076 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ Task Scheduler – cooperative multitasking for Arduino microcontrollers -Version 1.8.4: 2015-11-17 +Version 1.8.5: 2015-11-23 OVERVIEW: A lightweight implementation of cooperative multitasking (task scheduling) supporting: @@ -13,6 +13,10 @@ A lightweight implementation of cooperative multitasking (task scheduling) suppo 8. Support for Local Task Storage pointer (allowing use of same callback code for multiple tasks) Changelog: +v1.8.5: + 2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval + 2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner + v1.8.4: 2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before. Especially since OnEnable method can change the interval diff --git a/extras/TaskScheduler.doc b/extras/TaskScheduler.doc index f73af96..ca503e1 100644 Binary files a/extras/TaskScheduler.doc and b/extras/TaskScheduler.doc differ diff --git a/extras/TaskScheduler.html b/extras/TaskScheduler.html index b295af4..b2fb997 100644 --- a/extras/TaskScheduler.html +++ b/extras/TaskScheduler.html @@ -28,7 +28,7 @@ Scheduler

cooperative multitasking for Arduino microcontrollers

-Version 1.8.4: 2015-11-17

+Version 1.8.5: 2015-11-23


OVERVIEW:

@@ -1120,18 +1120,40 @@ must return a value of true for task to be enabled. If regardless if task is already enabled or not. Alignment to current millis() is performed after OnEnable exits, so any changes to the interval inside OnEnable is taken into consideration.

+

TaskScheduler +allows tasks to be added to the a Scheduler and enabled at the time +of creation. Be very careful +with such tasks – the OnEnable method +will be executed immediately, while certain objects (i.e., other +Tasks, libraries) are not yet ready (e.g., Wire.begin() +was not yet called), or hardware not yet activated (pins not set to +INPUT or OUTPUT). +

+

It +is very much recommended to to enable all tasks at the end of setup() +method after all initializations are done. +

+

If +you require immediate execution of already enabled task, use +forceNextIteratoin() +method instead of enable(): +it achieves the result, but does not call OnEnable +method. +


NOTE: -in the event enable() method is called inside the OnEnable callback -method (thus basically creating indefinte loop), TaskScheduler will -only call OnEnable once (thus protecting the Task against OnEnable -infinite loop). +in the event enable() method is called inside the OnEnable +callback method (thus basically creating indefinte loop), +TaskScheduler will only call OnEnable once (thus protecting +the Task against OnEnable infinite loop).


-

bool enableIfNot();

-


+

+bool enableIfNot();

+

+

Enables the task only if it was previously disabled. Returns previous enable @@ -1164,6 +1186,9 @@ forceNextIteration();

Schedules the task for execution during immediate next scheduling pass.

+

The +Task must be already enabled prior to this method. +


Note: @@ -1240,8 +1265,10 @@ dynamic control of task execution parameters in one method call.

Note: OnEnable and OnDisable parameters can be omitted. In that case -they will be assigned to NULL and respective methods will no longer -be called. +they will be assigned to NULL and respective methods will no +longer be called. Therefore it is advisable to use either all five +parameters explicitly, or employ individual “setter” methods +below instead.


@@ -1266,18 +1293,24 @@ setOnDisable (void (*aCallback)())


-

Note: Next +

NOTE: Next execution time calculation takes place after the callback method is called, so new interval will be used immediately by the scheduler. For the situations when one task is changing the interval parameter for the other, setInterval method calls delay explicitly to guarantee schedule change, however it does not enable the task if task is disabled.

-

Note: Tasks that +

NOTE: Tasks that ran through all their allocated iterations are disabled. SetIterations() method DOES NOT enable the task. Either enable explicitly, or use restart methods.

+

Please note that as a +result execution of the taks is delayed +by the provided interval. If immediate invocation is required, call +forceNextIteration() +method after setting a new interval. +


STATUS REQUEST @@ -1569,8 +1602,36 @@ task being enabled or disabled. one scheduling pass, including end-of-pass sleep. This method is typically placed inside the loop() method of the sketch. Since execute exits after every pass, you can put additional -statements after execute inside the loop() +statements after execute inside the loop().

+

Generally, +execute will perform the following steps:

+
    +
  1. Ignore + task completely if it is disabled.

    +
  2. Disable + task if it ran out of iterations (calling OnDesable, if necessary).

    +
  3. Check + if task is waiting on a StatusRequest object, and make appropriate + scheduling arrangements

    +
  4. Perform + necessary timing calculations (including millis() rollover fix, if + requested)

    +
  5. Invoke + task's callback method, if it is time to do so, and one is provided. + +

    +
  6. Put + microcontroller to sleep (if requested and supported) if none of the + tasks were invoked. +

    +
+


+

Please +NOTE: schedule-related +calculations are performed prior to task's callback method +invocation. This allows tasks to manipulate their runtime parameters +(like execution interval) directly.


bool isOverrun()

@@ -2749,7 +2810,7 @@ time examples of TaskScheduler are available here:


-

5

+

31

- + \ No newline at end of file diff --git a/extras/TaskScheduler_html_m68472eb8.png b/extras/TaskScheduler_html_m68472eb8.png new file mode 100644 index 0000000..febb8c7 Binary files /dev/null and b/extras/TaskScheduler_html_m68472eb8.png differ diff --git a/library.properties b/library.properties index 67b7a7c..ac0d61f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=TaskScheduler -version=1.8.4 +version=1.8.5 author=Anatoli Arkhipenko maintainer=Anatoli Arkhipenko sentence=A light-weight cooperative multitasking library for arduino microcontrollers. diff --git a/src/TaskScheduler.h b/src/TaskScheduler.h index dbe8dba..7ec6f0b 100644 --- a/src/TaskScheduler.h +++ b/src/TaskScheduler.h @@ -61,6 +61,10 @@ // v1.8.4: // 2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before. Especially since OnEnable method can change the interval // 2015-11-16 - further optimizations of the task scheduler execute loop +// +// v1.8.5: +// 2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval +// 2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner /* ============================================ @@ -98,10 +102,10 @@ THE SOFTWARE. * 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 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_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_ROLLOVER_FIX // Compensate for millis() rollover once every 47 days */ @@ -134,8 +138,8 @@ class StatusRequest { inline int getStatus() { return iStatus; } private: - unsigned int iCount; // waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient - int iStatus; // negative = error; zero = OK; >positive = OK with a specific status + unsigned int iCount; // number of statuses to wait for. waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient + int iStatus; // status of the last completed request. negative = error; zero = OK; >positive = OK with a specific status }; #endif @@ -192,31 +196,31 @@ class Task { private: void reset(); - volatile bool iEnabled; - volatile bool iInOnEnable; - volatile unsigned long iInterval; - volatile unsigned long iPreviousMillis; + volatile bool iEnabled; // indicates that task is enabled or not. @todo: combine iEnabled, iInOnEnable and iWaiting into one byte since those are bits really. + volatile bool iInOnEnable; // indicates that task execution is inside OnEnable method (preventing infinite loops) + volatile unsigned long iInterval; // execution interval in milliseconds. 0 - immediate + volatile unsigned long iPreviousMillis; // previous invocation time (millis). Next invocation = iPreviousMillis + iInterval. Delayed tasks will "catch up" #ifdef _TASK_TIMECRITICAL - volatile long iOverrun; + volatile long iOverrun; // negative if task is "catching up" to it's schedule (next invocation time is already in the past) #endif - volatile long iIterations; - long iSetIterations; - unsigned long iRunCounter; - void (*iCallback)(); - bool (*iOnEnable)(); - void (*iOnDisable)(); - Task *iPrev, *iNext; - Scheduler *iScheduler; + volatile long iIterations; // number of iterations left. 0 - last iteration. -1 - infinite iterations + long iSetIterations; // number of iterations originally requested (for restarts) + unsigned long iRunCounter; // current number of iteration (starting with 1). Resets on enable. + void (*iCallback)(); // pointer to the void callback method + bool (*iOnEnable)(); // pointer to the bolol OnEnable callback method + void (*iOnDisable)(); // pointer to the void OnDisable method + Task *iPrev, *iNext; // pointers to the previous and next tasks in the chain + Scheduler *iScheduler; // pointer to the current scheduler #ifdef _TASK_STATUS_REQUEST - StatusRequest *iStatusRequest; - byte iWaiting; + StatusRequest *iStatusRequest; // pointer to the status request task is or was waiting on + byte iWaiting; // indication if task is waiting on the status request #endif #ifdef _TASK_WDT_IDS - unsigned int iTaskID; - unsigned int iControlPoint; + unsigned int iTaskID; // task ID (for debugging and watchdog identification) + unsigned int iControlPoint; // current control point within the callback method. Reset to 0 by scheduler at the beginning of each pass #endif #ifdef _TASK_LTS_POINTER - void *iLTS; + void *iLTS; // pointer to task's local storage. Needs to be recast to appropriate type (usually a struct). #endif }; @@ -242,16 +246,16 @@ class Scheduler { #endif private: - Task *iFirst, *iLast, *iCurrent; + Task *iFirst, *iLast, *iCurrent; // pointers to first, last and current tasks in the chain #ifdef _TASK_SLEEP_ON_IDLE_RUN - bool iAllowSleep; + bool iAllowSleep; // indication if putting avr to IDLE_SLEEP mode is allowed by the program at this time. #endif }; // ------------------ TaskScheduler implementation -------------------- #ifdef _TASK_WDT_IDS - static unsigned int __task_id_counter = 0; + static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically. #endif /** Constructor, uses default values for the parameters * so could be called with no parameters. @@ -361,7 +365,7 @@ void Task::reset() { * @param aOnDisable - pointer to the callback method which is called on disable() */ void Task::set(unsigned long aInterval, long aIterations, void (*aCallback)(),bool (*aOnEnable)(), void (*aOnDisable)()) { - iInterval = aInterval; + setInterval(aInterval); iSetIterations = iIterations = aIterations; iCallback = aCallback; iOnEnable = aOnEnable; @@ -591,9 +595,8 @@ void Scheduler::execute() { break; } m = millis(); - p = iCurrent->iPreviousMillis; i = iCurrent->iInterval; - #ifdef _TASK_STATUS_REQUEST + #ifdef _TASK_STATUS_REQUEST // If StatusRequest object was provided, and still pending, and task is waiting, this task should not run // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to // how they were placed into waiting state (waitFor or waitForDelayed) @@ -603,6 +606,8 @@ void Scheduler::execute() { iCurrent->iWaiting = 0; } #endif + p = iCurrent->iPreviousMillis; + // Determine when current task is supposed to run // Once every 47 days there is a rollover execution which will occur due to millis and targetMillis rollovers // That is why there is an option to compile with rollover fix @@ -631,13 +636,13 @@ void Scheduler::execute() { #endif if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task iCurrent->iRunCounter++; + iCurrent->iPreviousMillis = targetMillis; //p + i if ( iCurrent->iCallback ) { ( *(iCurrent->iCallback) )(); #ifdef _TASK_SLEEP_ON_IDLE_RUN idleRun = false; #endif } - iCurrent->iPreviousMillis = targetMillis; } } while (0); //guaranteed single run - allows use of "break" to exit iCurrent = iCurrent->iNext; -- cgit v1.2.3