Procházet zdrojové kódy

* v3.1.0 - feature: added 4 cpu load monitoring methods for _TASK_TIMECRITICAL compilation option
- added CPU Idle sleep and scheduling overhead information collection

Anatoli Arkhipenko před 6 roky
rodič
revize
6f5cac8c30

+ 8 - 0
README

@@ -18,6 +18,7 @@ OVERVIEW:
 10. Support for std::functions (tested on ESPx only)
 11. Overall task timeout
 12. Static and dynamic callback method binding
+13. CPU load / idle statistics for time critical applications
 
 Scheduling overhead: between 15 and 18 microseconds per scheduling pass (check the benchmark example).
 
@@ -83,5 +84,12 @@ Check out what TaskScheduler can do:
     (by arkhipenko: http://www.instructables.com/id/Interactive-Halloween-Pumpkin/)
 
 
+  Interactive Predator Costume with Real-Time Head Tracking Plasma Cannon
+    (by arkhipenko: https://www.instructables.com/id/Interactive-Predator-Costume-With-Head-Tracking-Pl/)
+
+
+  Party Lights LEDs music visualization
+    (by arkhipenko: https://www.instructables.com/id/Portable-Party-Lights/)
+
 Changelog is located here: https://github.com/arkhipenko/TaskScheduler/wiki/Changelog
 =====================================================================================

+ 4 - 0
README.md

@@ -16,6 +16,7 @@ A lightweight implementation of cooperative multitasking (task scheduling) suppo
 10. Support for `std::functions` (tested on `ESPx` only)
 11. Overall task timeout
 12. Static and dynamic callback method binding
+13. CPU load / idle statistics for time critical applications
 
 Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Arduino UNO rev 3 @ `16MHz` clock, single scheduler w/o prioritization)
 
@@ -74,4 +75,7 @@ Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Ard
 
 * Interactive Predator Costume with Real-Time Head Tracking Plasma Cannon
     (by arkhipenko: https://www.instructables.com/id/Interactive-Predator-Costume-With-Head-Tracking-Pl/)
+
+* Party Lights LEDs music visualization
+    (by arkhipenko: https://www.instructables.com/id/Portable-Party-Lights/)
     

+ 84 - 0
examples/Scheduler_example24_CPU_LOAD/Scheduler_example24_CPU_LOAD.ino

@@ -0,0 +1,84 @@
+
+/**
+   This sketch collects scheduling overhead and CPU Idle Sleep information.
+   A task is invoked every 10 milliseconds for 10 seconds.
+   CPU statistics are collected
+
+   Compile and run once with _TASK_SLEEP_ON_IDLE_RUN enabled, then with _TASK_SLEEP_ON_IDLE_RUN disabled.
+   Compare the results.
+*/
+
+//#define _TASK_SLEEP_ON_IDLE_RUN
+#define _TASK_TIMECRITICAL
+#include <TaskScheduler.h>
+
+Scheduler ts;
+
+// Callback methods prototypes
+void Count();
+bool tOn(); void tOff();
+
+// Tasks
+Task c(10, TASK_FOREVER, &Count, &ts);
+Task t(10000, TASK_ONCE, NULL, &ts, true, &tOn, &tOff);
+
+
+volatile unsigned long c1, c2;
+bool tOn() {
+  c1 = 0;
+  c2 = 0;
+  c.enable();
+
+  return true;
+}
+
+void tOff() {
+  c.disable();
+  unsigned long cpuTot = ts.getCpuLoadTotal();
+  unsigned long cpuCyc = ts.getCpuLoadCycle();
+  unsigned long cpuIdl = ts.getCpuLoadIdle();
+
+  Serial.print("Loop counts c1="); Serial.println(c1);
+  Serial.print("Task counts c2="); Serial.println(c2);
+  Serial.print("Total CPU time="); Serial.print(cpuTot); Serial.println(" micros");
+  Serial.print("Scheduling overhead CPU time="); Serial.print(cpuCyc); Serial.println(" micros");
+  Serial.print("Idle Sleep CPU time="); Serial.print(cpuIdl); Serial.println(" micros");
+  Serial.print("CPU Callback time="); Serial.print(cpuTot - cpuIdl - cpuCyc); Serial.println(" micros");
+  Serial.println();
+
+  float idle = (float)cpuIdl / (float)cpuTot * 100;
+  Serial.print("CPU Idle Sleep "); Serial.print(idle); Serial.println(" % of time.");
+
+  float prod = (float)(cpuIdl + cpuCyc) / (float)cpuTot * 100;
+  Serial.print("CPU Callback processing "); Serial.print(100.00 - prod); Serial.println(" % of time.");
+
+}
+
+void setup() {
+  // put your setup code here, to run once:
+  Serial.begin(115200);
+  delay(1000);
+  Serial.println("CPU Time Measurement");
+  Serial.println("Start");
+
+  ts.startNow();
+  t.delay();
+  ts.cpuLoadReset();
+}
+
+void Count() {
+  c2++;  // number of task callback invocations
+
+  // Try different delay intervals to see CPU statistics change
+  //  delay(1);
+  //  delay(5);
+  //  delay(10);
+  //  delay(20);
+}
+
+
+void loop() {
+  // put your main code here, to run repeatedly:
+  ts.execute();
+  c1++;  // number of loop() cycles
+}

+ 4 - 0
keywords.txt

@@ -18,6 +18,7 @@ addTask	KEYWORD2
 allowSleep	KEYWORD2
 Callback	KEYWORD2
 completed	KEYWORD2
+cpuLoadReset	KEYWORD2
 currentLts	KEYWORD2
 currentScheduler	KEYWORD2
 currentTask	KEYWORD2
@@ -35,6 +36,9 @@ forceNextIteration	KEYWORD2
 getControlPoint	KEYWORD2
 getCount	KEYWORD2
 getCount	KEYWORD2
+getCpuLoadCycle	KEYWORD2
+getCpuLoadIdle	KEYWORD2
+getCpuLoadTotal	KEYWORD2
 getId	KEYWORD2
 getInternalStatusRequest	KEYWORD2
 getInterval	KEYWORD2

+ 1 - 1
library.json

@@ -16,7 +16,7 @@
       "maintainer": true
     }
   ],
-  "version": "3.0.3",
+  "version": "3.1.0",
   "frameworks": "arduino",
   "platforms": "*"
 }

+ 1 - 1
library.properties

@@ -1,5 +1,5 @@
 name=TaskScheduler
-version=3.0.3
+version=3.1.0
 author=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 maintainer=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 sentence=Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers.

+ 53 - 0
src/TaskScheduler.h

@@ -150,6 +150,9 @@
 //    2019-06-13 - feature: custom sleep callback method: setSleepMethod() - ability to dynamically control idle sleep for various microcontrollers
 //               - feature: support for MSP430 and MSP432 boards (pull request #75: big thanks to Guillaume Pirou, https://github.com/elominp)
 //               - officially discontinued support for offile documentation in favor of updating the Wiki pages
+//
+// v3.1.0:
+//    2020-01-07 - feture: added 4 cpu load monitoring methods for _TASK_TIMECRITICAL compilation option
 
 
 #include <Arduino.h>
@@ -679,6 +682,10 @@ void Scheduler::init() {
 #ifdef _TASK_SLEEP_ON_IDLE_RUN
     allowSleep(true);
 #endif  // _TASK_SLEEP_ON_IDLE_RUN
+
+#ifdef _TASK_TIMECRITICAL
+	cpuLoadReset();
+#endif  // _TASK_TIMECRITICAL
 }
 
 /** Appends task aTask to the tail of the execution chain.
@@ -846,8 +853,19 @@ Task& Scheduler::currentTask() { return *iCurrent; }
 #ifdef _TASK_LTS_POINTER
 void* Scheduler::currentLts() { return iCurrent->iLTS; }
 #endif  // _TASK_LTS_POINTER
+
 #ifdef _TASK_TIMECRITICAL
 bool Scheduler::isOverrun() { return (iCurrent->iOverrun < 0); }
+
+void Scheduler::cpuLoadReset() {
+	iCPUStart = micros();
+	iCPUCycle = 0;
+	iCPUIdle = 0;
+}
+
+unsigned long Scheduler::getCpuLoadTotal() {
+	return (micros() - iCPUStart);
+}
 #endif  // _TASK_TIMECRITICAL
 
 	
@@ -873,6 +891,12 @@ bool Scheduler::execute() {
     register unsigned long m, i;  // millis, interval;
 	unsigned long tStart, tFinish;
 
+#ifdef _TASK_TIMECRITICAL
+	unsigned long tPassStart;
+	unsigned long tTaskStart, tTaskFinish;
+	unsigned long tIdleStart;
+#endif  // _TASK_TIMECRITICAL
+
 	Task *nextTask;  // support for deleting the task in the onDisable method
     iCurrent = iFirst;
 
@@ -887,6 +911,11 @@ bool Scheduler::execute() {
 
     while (iCurrent) {
 
+#ifdef _TASK_TIMECRITICAL
+		tTaskStart = tTaskFinish = tIdleStart = 0;
+		tPassStart = micros();
+#endif  // _TASK_TIMECRITICAL
+
 #ifdef _TASK_PRIORITY
     // If scheduler for higher priority tasks is set, it's entire chain is executed on every pass of the base scheduler
         if (iHighPriority) idleRun = iHighPriority->execute() && idleRun;
@@ -949,6 +978,10 @@ bool Scheduler::execute() {
 #endif  // _TASK_TIMECRITICAL
 
                 iCurrent->iDelay = i;
+				
+#ifdef _TASK_TIMECRITICAL
+				tTaskStart = micros();
+#endif  // _TASK_TIMECRITICAL
 
 #ifdef _TASK_OO_CALLBACKS
                 idleRun = !iCurrent->Callback();
@@ -959,10 +992,20 @@ bool Scheduler::execute() {
                 }
 #endif // _TASK_OO_CALLBACKS
 
+#ifdef _TASK_TIMECRITICAL
+				tTaskFinish = micros();
+#endif  // _TASK_TIMECRITICAL
+
             }
         } while (0);    //guaranteed single run - allows use of "break" to exit
+		
+#ifdef _TASK_TIMECRITICAL
+		iCPUCycle += ( (micros() - tPassStart) - (tTaskFinish - tTaskStart) );
+#endif  // _TASK_TIMECRITICAL
+
         iCurrent = nextTask;
 		
+		
 #if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32)
         yield();
 #endif  // ARDUINO_ARCH_ESPxx
@@ -972,6 +1015,11 @@ bool Scheduler::execute() {
     tFinish = micros(); // Scheduling pass end time in microseconds.
 	
 #ifdef _TASK_SLEEP_ON_IDLE_RUN
+
+#ifdef _TASK_TIMECRITICAL
+	tIdleStart = micros();
+#endif  // _TASK_TIMECRITICAL
+
     if (idleRun && iAllowSleep) {
 		if ( iSleepScheduler == this ) { // only one scheduler should make the MC go to sleep. 
 			if ( iSleepMethod != NULL ) {
@@ -979,6 +1027,11 @@ bool Scheduler::execute() {
 			}
 		}
     }
+	
+#ifdef _TASK_TIMECRITICAL
+	iCPUIdle += (micros() - tIdleStart);
+#endif  // _TASK_TIMECRITICAL
+
 #endif  // _TASK_SLEEP_ON_IDLE_RUN
 
     return (idleRun);

+ 10 - 0
src/TaskSchedulerDeclarations.h

@@ -294,6 +294,10 @@ class Scheduler {
 
 #ifdef _TASK_TIMECRITICAL
     INLINE bool isOverrun();
+	INLINE void cpuLoadReset();
+	INLINE unsigned long getCpuLoadCycle(){ return iCPUCycle; };
+	INLINE unsigned long getCpuLoadIdle() { return iCPUIdle; };
+	INLINE unsigned long getCpuLoadTotal();
 #endif  // _TASK_TIMECRITICAL
 
 #ifdef _TASK_PRIORITY
@@ -311,6 +315,12 @@ class Scheduler {
 #ifdef _TASK_PRIORITY
     Scheduler  *iHighPriority;                    // Pointer to a higher priority scheduler
 #endif  // _TASK_PRIORITY
+
+#ifdef _TASK_TIMECRITICAL
+    unsigned long iCPUStart;
+	unsigned long iCPUCycle;
+	unsigned long iCPUIdle;
+#endif  // _TASK_TIMECRITICAL
 };