@@ -228,6 +228,10 @@ v3.8.0:
228
228
v3.8.1:
229
229
2023-05-11 - bug: conditional compile options missing from *.hpp files (Adafruit support)
230
230
231
+ v3.8.2:
232
+ 2023-09-27 - feature: _TASK_TICKLESS - support for tickless execution under FreeRTOS
233
+ - feature: _TASK_DO_NOT_YIELD - ability to disable yield() in execute() method
234
+
231
235
*/
232
236
233
237
@@ -267,6 +271,9 @@ extern "C" {
267
271
// #define _TASK_EXTERNAL_TIME // Custom millis() and micros() methods
268
272
// #define _TASK_THREAD_SAFE // Enable additional checking for thread safety
269
273
// #define _TASK_SELF_DESTRUCT // Enable tasks to "self-destruct" after disable
274
+ // #define _TASK_TICKLESS // Enable support for tickless sleep under FreeRTOS
275
+ // #define _TASK_DO_NOT_YIELD // Disable yield() method in execute()
276
+
270
277
271
278
#ifdef _TASK_MICRO_RES
272
279
@@ -308,7 +315,9 @@ extern "C" {
308
315
309
316
#ifndef _TASK_EXTERNAL_TIME
310
317
static uint32_t _task_millis () {return millis ();}
318
+ #ifdef _TASK_MICRO_RES
311
319
static uint32_t _task_micros () {return micros ();}
320
+ #endif // _TASK_MICRO_RES
312
321
#endif // _TASK_EXTERNAL_TIME
313
322
314
323
/* * Constructor, uses default values for the parameters
@@ -1323,17 +1332,19 @@ void Scheduler::setSleepMethod( SleepCallback aCallback ) {
1323
1332
* by running task more frequently
1324
1333
*/
1325
1334
1335
+ #ifdef _TASK_TICKLESS
1336
+ bool Scheduler::execute (unsigned long * aNextRun) {
1337
+ #else
1326
1338
bool Scheduler::execute () {
1339
+ #endif
1327
1340
1328
1341
bool idleRun = true ;
1329
1342
unsigned long m, i; // millis, interval;
1330
1343
1331
- #ifdef _TASK_SLEEP_ON_IDLE_RUN
1332
1344
unsigned long tFinish;
1333
- unsigned long tStart = micros ();
1334
- #endif // _TASK_SLEEP_ON_IDLE_RUN
1345
+ unsigned long tStart;
1335
1346
1336
- #ifdef _TASK_TIMECRITICAL
1347
+ #if defined( _TASK_TIMECRITICAL)
1337
1348
unsigned long tPassStart;
1338
1349
unsigned long tTaskStart, tTaskFinish;
1339
1350
@@ -1357,9 +1368,18 @@ bool Scheduler::execute() {
1357
1368
// after the higher priority scheduler has been invoked.
1358
1369
if ( !iEnabled ) return true ; // consider this to be an idle run
1359
1370
1371
+ // scheduling pass starts
1372
+ tStart = micros ();
1373
+
1374
+ #ifdef _TASK_TICKLESS
1375
+ iNextRun = UINT32_MAX; // we do not know yet if we can tell when next run will be
1376
+ iNextRunDetermined = _TASK_NEXTRUN_UNDEFINED;
1377
+ #endif
1378
+
1379
+
1360
1380
while (!iPaused && iCurrent) {
1361
1381
1362
- #ifdef _TASK_TIMECRITICAL
1382
+ #if defined( _TASK_TIMECRITICAL)
1363
1383
tPassStart = micros ();
1364
1384
tTaskStart = tTaskFinish = 0 ;
1365
1385
#endif // _TASK_TIMECRITICAL
@@ -1412,6 +1432,13 @@ bool Scheduler::execute() {
1412
1432
// Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to
1413
1433
// how they were placed into waiting state (waitFor or waitForDelayed)
1414
1434
if ( iCurrent->iStatus .waiting ) {
1435
+
1436
+ #ifdef _TASK_TICKLESS
1437
+ // if there is a task waiting on a status request we are obligated to run continously
1438
+ // because event can trigger at any point at time.
1439
+ iNextRunDetermined |= _TASK_NEXTRUN_IMMEDIATE; // immediate
1440
+ #endif
1441
+
1415
1442
#ifdef _TASK_TIMEOUT
1416
1443
StatusRequest *sr = iCurrent->iStatusRequest ;
1417
1444
if ( sr->iTimeout && (m - sr->iStarttime > sr->iTimeout ) ) {
@@ -1436,7 +1463,26 @@ bool Scheduler::execute() {
1436
1463
// this is the main scheduling decision point
1437
1464
// if the interval between current time and previous invokation time is less than the current delay - task should NOT be activated yet.
1438
1465
// this is millis-rollover-safe way of scheduling
1439
- if ( m - iCurrent->iPreviousMillis < iCurrent->iDelay ) break ;
1466
+ if ( m - iCurrent->iPreviousMillis < iCurrent->iDelay ) {
1467
+ #ifdef _TASK_TICKLESS
1468
+ // catch the reamining time until invocation as next time this should run
1469
+ // this does not handle millis rollover well - so for the rollover situation (once every 47 days)
1470
+ // we will require immediate execution
1471
+ unsigned long nextrun = iCurrent->iDelay + iCurrent->iPreviousMillis ;
1472
+ // nextrun should be after current millis() (except rollover)
1473
+ // nextrun should be sooner than previously determined
1474
+ if ( nextrun > m && nextrun < iNextRun ) {
1475
+ iNextRun = nextrun;
1476
+ iNextRunDetermined |= _TASK_NEXTRUN_TIMED; // next run timed
1477
+ }
1478
+ #endif // _TASK_TICKLESS
1479
+ break ;
1480
+ }
1481
+
1482
+
1483
+ #ifdef _TASK_TICKLESS
1484
+ iNextRunDetermined |= _TASK_NEXTRUN_IMMEDIATE; // next run timed
1485
+ #endif
1440
1486
1441
1487
if ( iCurrent->iIterations > 0 ) iCurrent->iIterations --; // do not decrement (-1) being a signal of never-ending task
1442
1488
iCurrent->iRunCounter ++;
@@ -1473,8 +1519,8 @@ bool Scheduler::execute() {
1473
1519
#endif // _TASK_TIMECRITICAL
1474
1520
1475
1521
iCurrent->iDelay = i;
1476
-
1477
- #ifdef _TASK_TIMECRITICAL
1522
+
1523
+ #if defined( _TASK_TIMECRITICAL)
1478
1524
tTaskStart = micros ();
1479
1525
#endif // _TASK_TIMECRITICAL
1480
1526
@@ -1487,7 +1533,7 @@ bool Scheduler::execute() {
1487
1533
}
1488
1534
#endif // _TASK_OO_CALLBACKS
1489
1535
1490
- #ifdef _TASK_TIMECRITICAL
1536
+ #if defined( _TASK_TIMECRITICAL)
1491
1537
tTaskFinish = micros ();
1492
1538
#endif // _TASK_TIMECRITICAL
1493
1539
@@ -1501,14 +1547,27 @@ bool Scheduler::execute() {
1501
1547
iCPUCycle += ( (micros () - tPassStart) - (tTaskFinish - tTaskStart) );
1502
1548
#endif // _TASK_TIMECRITICAL
1503
1549
1504
- #if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32)
1550
+ #if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32)
1551
+ #if !defined(_TASK_DO_NOT_YIELD)
1505
1552
yield ();
1506
- #endif // ARDUINO_ARCH_ESPxx
1553
+ #endif // _TASK_DO_NOT_YIELD
1554
+ #endif // ARDUINO_ARCH_ESPxx
1507
1555
}
1508
1556
1557
+ tFinish = micros (); // Scheduling pass end time in microseconds.
1558
+
1559
+ #ifdef _TASK_TICKLESS
1560
+ if ( aNextRun ) {
1561
+ *aNextRun = 0 ; // next iteration should be immediate by default
1562
+ // if the pass was "idle" and there are tasks scheduled
1563
+ if ( idleRun && iNextRunDetermined & _TASK_NEXTRUN_TIMED ) {
1564
+ m = millis ();
1565
+ if ( iNextRun > m ) *aNextRun = ( iNextRun - m );
1566
+ }
1567
+ }
1568
+ #endif
1509
1569
1510
1570
#ifdef _TASK_SLEEP_ON_IDLE_RUN
1511
- tFinish = micros (); // Scheduling pass end time in microseconds.
1512
1571
1513
1572
if (idleRun && iAllowSleep) {
1514
1573
if ( iSleepScheduler == this ) { // only one scheduler should make the MC go to sleep.
0 commit comments