├── .gitignore ├── DeepSleepScheduler.h ├── DeepSleepScheduler_avr_definition.h ├── DeepSleepScheduler_avr_implementation.h ├── DeepSleepScheduler_esp_definition.h ├── DeepSleepScheduler_esp_implementation.h ├── DeepSleepScheduler_esp_includes.h ├── LICENSE ├── README.md ├── examples ├── AdjustSleepTimeCorrections │ └── AdjustSleepTimeCorrections.ino ├── Blink │ └── Blink.ino ├── BlinkRunnable │ └── BlinkRunnable.ino ├── PwmSleep │ └── PwmSleep.ino ├── ScheduleFromInterrupt │ └── ScheduleFromInterrupt.ino ├── ScheduleRepeated │ └── ScheduleRepeated.ino ├── SchedulerWithOtherTaskPriority │ └── SchedulerWithOtherTaskPriority.ino ├── SerialWithDeepSleepDelay │ └── SerialWithDeepSleepDelay.ino ├── ShowSleep │ └── ShowSleep.ino ├── Supervision │ └── Supervision.ino ├── SupervisionWithCallback │ └── SupervisionWithCallback.ino ├── WdtTimesGenerator │ └── WdtTimesGenerator.ino └── WdtTimesMeasurer │ └── WdtTimesMeasurer.ino ├── keywords.txt └── library.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /DeepSleepScheduler.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-2016 Peter Rosenberg 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | /* 17 | This file is part of the DeepSleepScheduler library for Arduino. 18 | Definition and code are in the header file in order to allow the user to configure the library by using defines. 19 | 20 | The following options are available: 21 | - #define LIBCALL_DEEP_SLEEP_SCHEDULER: This h file can only be included once within a project as it also contains the implementation. 22 | To use it in multiple files, define LIBCALL_DEEP_SLEEP_SCHEDULER before all include statements except one. 23 | All following options are to be set before the include where no LIBCALL_DEEP_SLEEP_SCHEDULER is defined. 24 | - #define SLEEP_DELAY: Prevent the CPU from entering sleep for the specified amount of milli seconds after finishing the previous task. 25 | - #define SUPERVISION_CALLBACK: Allows to specify a callback Runnable to be called when a task runs too long. When 26 | the callback returns, the CPU is restarted after 15 ms by the watchdog. The callback method is called directly 27 | from the watchdog interrupt. This means that e.g. delay() does not work. 28 | - #define SUPERVISION_CALLBACK_TIMEOUT: Specify the timeout of the callback on AVR until the watchdog resets the CPU. Defaults to WDTO_1S. 29 | - #define AWAKE_INDICATION_PIN: Show on a LED if the CPU is active or in sleep mode. HIGH = active, LOW = sleeping. 30 | */ 31 | 32 | #ifndef DEEP_SLEEP_SCHEDULER_H 33 | #define DEEP_SLEEP_SCHEDULER_H 34 | 35 | // ------------------------------------------------------------------------------------------------- 36 | // Definition (usually in H file) 37 | // ------------------------------------------------------------------------------------------------- 38 | #include 39 | #if defined(ESP32) || defined(ESP8266) 40 | #include "DeepSleepScheduler_esp_includes.h" 41 | #endif 42 | 43 | #define BUFFER_TIME 2 44 | #define NOT_USED 255 45 | 46 | enum TaskTimeout { 47 | TIMEOUT_15Ms, 48 | TIMEOUT_30MS, 49 | TIMEOUT_60MS, 50 | TIMEOUT_120MS, 51 | TIMEOUT_250MS, 52 | TIMEOUT_500MS, 53 | TIMEOUT_1S, 54 | TIMEOUT_2S, 55 | TIMEOUT_4S, 56 | TIMEOUT_8S, 57 | NO_SUPERVISION 58 | }; 59 | 60 | /** 61 | Extend from Runnable in order to have the run() method run by the scheduler. 62 | */ 63 | class Runnable { 64 | public: 65 | virtual void run() = 0; 66 | }; 67 | 68 | class Scheduler { 69 | public: 70 | /** 71 | Schedule the callback method as soon as possible but after other tasks 72 | that are to be scheduled immediately and are in the queue already. 73 | @param callback: the method to be called on the main thread 74 | */ 75 | void schedule(void (*callback)()); 76 | /** 77 | Schedule the Runnable as soon as possible but after other tasks 78 | that are to be scheduled immediately and are in the queue already. 79 | @param runnable: the Runnable on which the run() method will be called on the main thread 80 | */ 81 | void schedule(Runnable *runnable); 82 | 83 | /** 84 | Schedule the callback method as soon as possible and remove all other 85 | tasks with the same callback. This is useful if you call it 86 | from an interrupt and want one execution only even if the interrupt triggers 87 | multiple times. 88 | @param callback: the method to be called on the main thread 89 | */ 90 | void scheduleOnce(void (*callback)()); 91 | /** 92 | Schedule the Runnable as soon as possible and remove all other 93 | tasks with the same Runnable. This is useful if you call it 94 | from an interrupt and want one execution only even if the interrupt triggers 95 | multiple times. 96 | @param runnable: the Runnable on which the run() method will be called on the main thread 97 | */ 98 | void scheduleOnce(Runnable *runnable); 99 | 100 | /** 101 | Schedule the callback after delayMillis milliseconds. 102 | @param callback: the method to be called on the main thread 103 | @param delayMillis: the time to wait in milliseconds until the callback shall be made 104 | */ 105 | void scheduleDelayed(void (*callback)(), unsigned long delayMillis); 106 | /** 107 | Schedule the callback after delayMillis milliseconds. 108 | @param runnable: the Runnable on which the run() method will be called on the main thread 109 | @param delayMillis: the time to wait in milliseconds until the callback shall be made 110 | */ 111 | void scheduleDelayed(Runnable *runnable, unsigned long delayMillis); 112 | 113 | /** 114 | Schedule the callback uptimeMillis milliseconds after the device was started. 115 | Please be aware that uptimeMillis is stopped when no task is pending. In this case, 116 | the CPU may only wake up on an external interrupt. 117 | @param callback: the method to be called on the main thread 118 | @param uptimeMillis: the time in milliseconds since the device was started 119 | to schedule the callback. 120 | */ 121 | void scheduleAt(void (*callback)(), unsigned long uptimeMillis); 122 | /** 123 | Schedule the callback uptimeMillis milliseconds after the device was started. 124 | Please be aware that uptimeMillis is stopped when no task is pending. In this case, 125 | the CPU may only wake up on an external interrupt. 126 | @param runnable: the Runnable on which the run() method will be called on the main thread 127 | @param uptimeMillis: the time in milliseconds since the device was started 128 | to schedule the callback. 129 | */ 130 | void scheduleAt(Runnable *runnable, unsigned long uptimeMillis); 131 | 132 | /** 133 | Schedule the callback method as next task even if other tasks are in the queue already. 134 | @param callback: the method to be called on the main thread 135 | */ 136 | void scheduleAtFrontOfQueue(void (*callback)()); 137 | /** 138 | Schedule the callback method as next task even if other tasks are in the queue already. 139 | @param runnable: the Runnable on which the run() method will be called on the main thread 140 | */ 141 | void scheduleAtFrontOfQueue(Runnable *runnable); 142 | 143 | /** 144 | Check if this callback is scheduled at least once already. 145 | This method can be called in an interrupt but bear in mind, that it loops through 146 | the run queue until it finds it or reaches the end. 147 | @param callback: callback to check 148 | */ 149 | bool isScheduled(void (*callback)()) const; 150 | 151 | /** 152 | Check if this runnable is scheduled at least once already. 153 | This method can be called in an interrupt but bear in mind, that it loops through 154 | the run queue until it finds it or reaches the end. 155 | @param runnable: Runnable to check 156 | */ 157 | bool isScheduled(Runnable *runnable) const; 158 | 159 | /** 160 | Returns the scheduled time of the task that is currently running. 161 | If no task is currently running, 0 is returned. 162 | */ 163 | unsigned long getScheduleTimeOfCurrentTask() const; 164 | 165 | /** 166 | Cancel all schedules that were scheduled for this callback. 167 | @param callback: method of which all schedules shall be removed 168 | */ 169 | void removeCallbacks(void (*callback)()); 170 | /** 171 | Cancel all schedules that were scheduled for this runnable. 172 | @param runnable: instance of Runnable of which all schedules shall be removed 173 | */ 174 | void removeCallbacks(Runnable *runnable); 175 | 176 | /** 177 | Acquire a lock to prevent the CPU from entering sleep. 178 | acquireNoSleepLock() supports up to 255 locks. 179 | You need to call releaseNoSleepLock() the same amount of times 180 | to allow the CPU to enter sleep again. 181 | */ 182 | void acquireNoSleepLock(); 183 | 184 | /** 185 | Release the lock acquired by acquireNoSleepLock(). Please make sure you 186 | call releaseNoSleepLock() the same amount of times as acquireNoSleepLock(), 187 | otherwise the CPU is not allowed to enter sleep. 188 | */ 189 | void releaseNoSleepLock(); 190 | 191 | /** 192 | return: true if the CPU is currently allowed to enter sleep, false otherwise. 193 | */ 194 | inline bool doesSleep() const; 195 | 196 | /** 197 | Configure the supervision of future tasks. Can be deactivated with NO_SUPERVISION. 198 | Default: TIMEOUT_8S 199 | @param taskTimeout: The task timeout to be used 200 | */ 201 | void setTaskTimeout(TaskTimeout taskTimeout); 202 | 203 | /** 204 | Resets the task watchdog. After this call returns, the currently running 205 | Task can run up to the configured TaskTimeout set by setTaskTimeout(). 206 | */ 207 | void taskWdtReset(); 208 | 209 | /** 210 | return: The milliseconds since startup of the device where the sleep time was added. 211 | This value does not consider the time when the CPU is in infinite deep sleep 212 | while nothing is in the queue. 213 | */ 214 | unsigned long getMillis() const; 215 | 216 | #ifdef SUPERVISION_CALLBACK 217 | #ifdef ESP8266 218 | #error "SUPERVISION_CALLBACK not supported for ESP8266" 219 | #endif 220 | /** 221 | Sets the runnable to be called when the task supervision detects a task that runs too long. 222 | The run() method will be called from the watchdog interrupt what means, that 223 | e.g. the method delay() does not work. 224 | On AVR, when run() returns, the CPU will be restarted after 15ms. 225 | On ESP32, the interrupt service routine as a whole has a time limit and calls 226 | abort() when returning from this method. 227 | See description of SUPERVISION_CALLBACK and SUPERVISION_CALLBACK_TIMEOUT. 228 | @param runnable: instance of Runnable where the run() method is called 229 | */ 230 | void setSupervisionCallback(Runnable *runnable) { 231 | supervisionCallbackRunnable = runnable; 232 | } 233 | #endif 234 | 235 | /** 236 | This method needs to be called from your loop() method and does not return. 237 | */ 238 | void execute(); 239 | 240 | /** 241 | Constructor of the scheduler. Do not all this method as there is only one instance of Scheduler supported. 242 | */ 243 | Scheduler(); 244 | 245 | private: 246 | class Task { 247 | public: 248 | Task(const unsigned long scheduledUptimeMillis, const bool isCallbackTask) 249 | : scheduledUptimeMillis(scheduledUptimeMillis), isCallbackTask(isCallbackTask), next(NULL) { 250 | } 251 | void execute() { 252 | // do in base class to prevent virtual method 253 | if (isCallbackTask) { 254 | ((CallbackTask*)this)->callback(); 255 | } else { 256 | ((RunnableTask*)this)->runnable->run(); 257 | } 258 | } 259 | bool equalCallback(Task *task) { 260 | // do in base class to prevent virtual method 261 | if (isCallbackTask) { 262 | return task->isCallbackTask && ((CallbackTask*)task)->callback == ((CallbackTask*)this)->callback; 263 | } else { 264 | return !task->isCallbackTask && ((RunnableTask*)task)->runnable == ((RunnableTask*)this)->runnable; 265 | } 266 | } 267 | const unsigned long scheduledUptimeMillis; 268 | // dynamic_cast is not supported by default as it compiles with -fno-rtti 269 | // Therefore, we use this variable to detect which Task type it is. 270 | const bool isCallbackTask; 271 | Task *next; 272 | }; 273 | class CallbackTask: public Task { 274 | public: 275 | CallbackTask(void (*callback)(), const unsigned long scheduledUptimeMillis) 276 | : Task(scheduledUptimeMillis, true), callback(callback) { 277 | } 278 | void (* const callback)(); 279 | }; 280 | class RunnableTask: public Task { 281 | public: 282 | RunnableTask(Runnable *runnable, const unsigned long scheduledUptimeMillis) 283 | : Task(scheduledUptimeMillis, false), runnable(runnable) { 284 | } 285 | Runnable * const runnable; 286 | }; 287 | 288 | /** 289 | controls if sleep is done, 0 does sleep 290 | */ 291 | byte noSleepLocksCount; 292 | 293 | void insertTask(Task *task); 294 | void insertTaskAndRemoveExisting(Task *newTask); 295 | void deleteTask(Task *taskToDelete, Task *previousTask); 296 | 297 | private: 298 | enum SleepMode { 299 | NO_SLEEP, 300 | IDLE, 301 | SLEEP 302 | }; 303 | 304 | #ifdef SUPERVISION_CALLBACK 305 | static Runnable *supervisionCallbackRunnable; 306 | #endif 307 | 308 | /** 309 | currently set task timeout 310 | */ 311 | TaskTimeout taskTimeout; 312 | /** 313 | first element in the run queue 314 | */ 315 | Task *first; 316 | /* 317 | the task currently running or null if none running 318 | */ 319 | Task *current; 320 | #ifdef SLEEP_DELAY 321 | /** 322 | The time in millis since start up when the last task finished. 323 | Used to delay deep sleep. 324 | */ 325 | unsigned long lastTaskFinishedMillis; 326 | #endif 327 | 328 | inline void setupTaskTimeoutIfConfigured(); 329 | inline bool executeNextIfTime(); 330 | inline void reactivateTaskTimeoutIfRequired(); 331 | 332 | // These methods MUST be defined by the definition include 333 | // void taskWdtEnable(const uint8_t value); 334 | // void taskWdtReset(); 335 | // void taskWdtDisable(); 336 | // void sleepIfRequired(); 337 | // 338 | // // only used by AVR as the watchdog timer is used 339 | // bool isWakeupByOtherInterrupt(); 340 | // 341 | // // only used for AVR 342 | // void wdtEnableInterrupt(); 343 | #if defined(ESP32) || defined(ESP8266) 344 | #include "DeepSleepScheduler_esp_definition.h" 345 | #else 346 | #include "DeepSleepScheduler_avr_definition.h" 347 | #endif 348 | }; 349 | 350 | extern Scheduler scheduler; 351 | 352 | #ifndef LIBCALL_DEEP_SLEEP_SCHEDULER 353 | // ------------------------------------------------------------------------------------------------- 354 | // Implementation (usuallly in CPP file) 355 | // ------------------------------------------------------------------------------------------------- 356 | /** 357 | the one and only instance of Scheduler 358 | */ 359 | Scheduler scheduler; 360 | 361 | #ifdef SUPERVISION_CALLBACK 362 | Runnable *Scheduler::supervisionCallbackRunnable; 363 | #endif 364 | 365 | Scheduler::Scheduler() { 366 | #ifdef AWAKE_INDICATION_PIN 367 | pinMode(AWAKE_INDICATION_PIN, OUTPUT); 368 | #endif 369 | taskTimeout = TIMEOUT_8S; 370 | 371 | first = NULL; 372 | current = NULL; 373 | noSleepLocksCount = 0; 374 | 375 | init(); 376 | } 377 | 378 | void Scheduler::schedule(void (*callback)()) { 379 | Task *newTask = new CallbackTask(callback, getMillis()); 380 | insertTask(newTask); 381 | } 382 | 383 | void Scheduler::schedule(Runnable *runnable) { 384 | Task *newTask = new RunnableTask(runnable, getMillis()); 385 | insertTask(newTask); 386 | } 387 | 388 | void Scheduler::scheduleOnce(void (*callback)()) { 389 | Task *newTask = new CallbackTask(callback, getMillis()); 390 | insertTaskAndRemoveExisting(newTask); 391 | } 392 | 393 | void Scheduler::scheduleOnce(Runnable *runnable) { 394 | Task *newTask = new RunnableTask(runnable, getMillis()); 395 | insertTaskAndRemoveExisting(newTask); 396 | } 397 | 398 | void Scheduler::scheduleDelayed(void (*callback)(), unsigned long delayMillis) { 399 | Task *newTask = new CallbackTask(callback, getMillis() + delayMillis); 400 | insertTask(newTask); 401 | } 402 | 403 | void Scheduler::scheduleDelayed(Runnable *runnable, unsigned long delayMillis) { 404 | Task *newTask = new RunnableTask(runnable, getMillis() + delayMillis); 405 | insertTask(newTask); 406 | } 407 | 408 | void Scheduler::scheduleAt(void (*callback)(), unsigned long uptimeMillis) { 409 | Task *newTask = new CallbackTask(callback, uptimeMillis); 410 | insertTask(newTask); 411 | } 412 | 413 | void Scheduler::scheduleAt(Runnable *runnable, unsigned long uptimeMillis) { 414 | Task *newTask = new RunnableTask(runnable, uptimeMillis); 415 | insertTask(newTask); 416 | } 417 | 418 | void Scheduler::scheduleAtFrontOfQueue(void (*callback)()) { 419 | Task *newTask = new CallbackTask(callback, getMillis()); 420 | noInterrupts(); 421 | newTask->next = first; 422 | first = newTask; 423 | interrupts(); 424 | } 425 | 426 | void Scheduler::scheduleAtFrontOfQueue(Runnable *runnable) { 427 | Task *newTask = new RunnableTask(runnable, getMillis()); 428 | noInterrupts(); 429 | newTask->next = first; 430 | first = newTask; 431 | interrupts(); 432 | } 433 | 434 | bool Scheduler::isScheduled(void (*callback)()) const { 435 | bool scheduled = false; 436 | noInterrupts(); 437 | Task *currentTask = first; 438 | while (currentTask != NULL) { 439 | if (currentTask->isCallbackTask && ((CallbackTask*)currentTask)->callback == callback) { 440 | scheduled = true; 441 | break; 442 | } 443 | currentTask = currentTask->next; 444 | } 445 | interrupts(); 446 | return scheduled; 447 | } 448 | 449 | bool Scheduler::isScheduled(Runnable *runnable) const { 450 | bool scheduled = false; 451 | noInterrupts(); 452 | Task *currentTask = first; 453 | while (currentTask != NULL) { 454 | if (!currentTask->isCallbackTask && ((RunnableTask*)currentTask)->runnable == runnable) { 455 | scheduled = true; 456 | break; 457 | } 458 | currentTask = currentTask->next; 459 | } 460 | interrupts(); 461 | return scheduled; 462 | } 463 | 464 | unsigned long Scheduler::getScheduleTimeOfCurrentTask() const { 465 | noInterrupts(); 466 | if (current != NULL) { 467 | return current->scheduledUptimeMillis; 468 | } 469 | interrupts(); 470 | return 0; 471 | } 472 | 473 | void Scheduler::removeCallbacks(void (*callback)()) { 474 | noInterrupts(); 475 | if (first != NULL) { 476 | Task *previousTask = NULL; 477 | Task *currentTask = first; 478 | while (currentTask != NULL) { 479 | if (currentTask->isCallbackTask && ((CallbackTask*)currentTask)->callback == callback) { 480 | Task *taskToDelete = currentTask; 481 | if (previousTask == NULL) { 482 | // remove the first task 483 | first = taskToDelete->next; 484 | } else { 485 | previousTask->next = taskToDelete->next; 486 | } 487 | currentTask = taskToDelete->next; 488 | delete taskToDelete; 489 | } else { 490 | previousTask = currentTask; 491 | currentTask = currentTask->next; 492 | } 493 | } 494 | } 495 | interrupts(); 496 | } 497 | 498 | void Scheduler::removeCallbacks(Runnable *runnable) { 499 | noInterrupts(); 500 | if (first != NULL) { 501 | Task *previousTask = NULL; 502 | Task *currentTask = first; 503 | while (currentTask != NULL) { 504 | if (!currentTask->isCallbackTask && ((RunnableTask*)currentTask)->runnable == runnable) { 505 | Task *taskToDelete = currentTask; 506 | if (previousTask == NULL) { 507 | // remove the first task 508 | first = taskToDelete->next; 509 | } else { 510 | previousTask->next = taskToDelete->next; 511 | } 512 | currentTask = taskToDelete->next; 513 | delete taskToDelete; 514 | } else { 515 | previousTask = currentTask; 516 | currentTask = currentTask->next; 517 | } 518 | } 519 | } 520 | interrupts(); 521 | } 522 | 523 | void Scheduler::acquireNoSleepLock() { 524 | noSleepLocksCount++; 525 | } 526 | 527 | void Scheduler::releaseNoSleepLock() { 528 | if (noSleepLocksCount != 0) { 529 | noSleepLocksCount--; 530 | } 531 | } 532 | 533 | bool Scheduler::doesSleep() const { 534 | return noSleepLocksCount == 0; 535 | } 536 | 537 | void Scheduler::setTaskTimeout(TaskTimeout taskTimeout) { 538 | noInterrupts(); 539 | this->taskTimeout = taskTimeout; 540 | interrupts(); 541 | } 542 | 543 | // Inserts a new task in the ordered lists of tasks. 544 | void Scheduler::insertTask(Task *newTask) { 545 | noInterrupts(); 546 | if (first == NULL) { 547 | first = newTask; 548 | } else { 549 | if (first->scheduledUptimeMillis > newTask->scheduledUptimeMillis) { 550 | // insert before first 551 | newTask->next = first; 552 | first = newTask; 553 | } else { 554 | Task *previousTask = first; 555 | while (previousTask->next != NULL 556 | && previousTask->next->scheduledUptimeMillis <= newTask->scheduledUptimeMillis) { 557 | previousTask = previousTask->next; 558 | } 559 | // insert after previousTask 560 | newTask->next = previousTask->next; 561 | previousTask->next = newTask; 562 | } 563 | } 564 | interrupts(); 565 | } 566 | 567 | // Inserts a new task in the ordered lists of tasks and remove all existing tasks with the same callback 568 | void Scheduler::insertTaskAndRemoveExisting(Task *newTask) { 569 | noInterrupts(); 570 | 571 | // remove first if it has the same callback as we insert 572 | while (first != NULL && first->equalCallback(newTask)) { 573 | deleteTask(first, NULL); 574 | } 575 | // here first is not equal to the one we insert 576 | 577 | if (first == NULL) { 578 | first = newTask; 579 | } else { 580 | if (first->scheduledUptimeMillis > newTask->scheduledUptimeMillis) { 581 | // insert before first 582 | newTask->next = first; 583 | first = newTask; 584 | } else { 585 | Task *previousTask = first; 586 | while (previousTask->next != NULL 587 | && previousTask->next->scheduledUptimeMillis <= newTask->scheduledUptimeMillis) { 588 | if (previousTask->next->equalCallback(newTask)) { 589 | deleteTask(previousTask->next, previousTask); 590 | // previousTask->next was deleted so we keep previousTask 591 | // and will check the next previousTask->next on next loop 592 | } else { 593 | previousTask = previousTask->next; 594 | } 595 | } 596 | // insert after previousTask 597 | newTask->next = previousTask->next; 598 | previousTask->next = newTask; 599 | } 600 | 601 | // delete all existing tasks after the insert one 602 | Task *previousTask = newTask; 603 | while (previousTask->next != NULL) { 604 | if (previousTask->next->equalCallback(newTask)) { 605 | deleteTask(previousTask->next, previousTask); 606 | } else { 607 | previousTask = previousTask->next; 608 | } 609 | } 610 | } 611 | 612 | interrupts(); 613 | } 614 | 615 | void Scheduler::deleteTask(Task *taskToDelete, Task *previousTask) { 616 | if (previousTask == NULL) { 617 | // remove the first task 618 | first = taskToDelete->next; 619 | } else { 620 | previousTask->next = taskToDelete->next; 621 | } 622 | delete taskToDelete; 623 | } 624 | 625 | void Scheduler::setupTaskTimeoutIfConfigured() { 626 | noInterrupts(); 627 | if (taskTimeout != NO_SUPERVISION) { 628 | taskWdtEnable(taskTimeout); 629 | #ifdef SUPERVISION_CALLBACK 630 | wdtEnableInterrupt(); 631 | #endif 632 | } 633 | interrupts(); 634 | } 635 | 636 | bool Scheduler::executeNextIfTime() { 637 | noInterrupts(); 638 | if (first != NULL && first->scheduledUptimeMillis <= getMillis()) { 639 | current = first; 640 | first = current->next; 641 | } 642 | interrupts(); 643 | 644 | if (current != NULL) { 645 | taskWdtReset(); 646 | current->execute(); 647 | taskWdtReset(); 648 | #ifdef SLEEP_DELAY 649 | // use millis() instead of getMillis() because getMillis() may be manipulated by our WTD interrupt. 650 | lastTaskFinishedMillis = millis(); 651 | #endif 652 | delete current; 653 | noInterrupts(); 654 | current = NULL; 655 | interrupts(); 656 | return true; 657 | } else { 658 | return false; 659 | } 660 | } 661 | 662 | void Scheduler::reactivateTaskTimeoutIfRequired() { 663 | if (!isWakeupByOtherInterrupt()) { 664 | // woken up due to WDT interrupt in case of AVR 665 | // always executed for esp 666 | noInterrupts(); 667 | const TaskTimeout taskTimeoutLocal = taskTimeout; 668 | interrupts(); 669 | if (taskTimeoutLocal != NO_SUPERVISION) { 670 | // change back to taskTimeout 671 | taskWdtReset(); 672 | taskWdtEnable(taskTimeoutLocal); 673 | #ifdef SUPERVISION_CALLBACK 674 | wdtEnableInterrupt(); 675 | #endif 676 | } else { 677 | // tasks are not supervised, deactivate WDT 678 | taskWdtDisable(); 679 | } 680 | } // else the wd is still running in case of AVR 681 | } 682 | 683 | void Scheduler::execute() { 684 | setupTaskTimeoutIfConfigured(); 685 | while (true) { 686 | bool hasExecuted = executeNextIfTime(); 687 | while (hasExecuted) { 688 | hasExecuted = executeNextIfTime(); 689 | } 690 | 691 | sleepIfRequired(); 692 | reactivateTaskTimeoutIfRequired(); 693 | } 694 | // never executed so no need to deactivate the WDT 695 | } 696 | 697 | #endif // #ifndef LIBCALL_DEEP_SLEEP_SCHEDULER 698 | 699 | #if defined(ESP32) || defined(ESP8266) 700 | #include "DeepSleepScheduler_esp_implementation.h" 701 | #else 702 | #include "DeepSleepScheduler_avr_implementation.h" 703 | #endif 704 | 705 | #endif // #ifndef DEEP_SLEEP_SCHEDULER_H 706 | -------------------------------------------------------------------------------- /DeepSleepScheduler_avr_definition.h: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------------------------------------------------------- 3 | // Definition of AVR, included inside of class Scheduler 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | // values changeable by the user 7 | #ifndef SLEEP_MODE 8 | #define SLEEP_MODE SLEEP_MODE_PWR_DOWN 9 | #endif 10 | 11 | #ifndef SUPERVISION_CALLBACK_TIMEOUT 12 | #define SUPERVISION_CALLBACK_TIMEOUT WDTO_1S 13 | #endif 14 | 15 | #ifndef MIN_WAIT_TIME_FOR_SLEEP 16 | #define MIN_WAIT_TIME_FOR_SLEEP SLEEP_TIME_1S 17 | #endif 18 | 19 | #ifndef SLEEP_TIME_15MS_CORRECTION 20 | #define SLEEP_TIME_15MS_CORRECTION 3 21 | #endif 22 | #ifndef SLEEP_TIME_30MS_CORRECTION 23 | #define SLEEP_TIME_30MS_CORRECTION 4 24 | #endif 25 | #ifndef SLEEP_TIME_60MS_CORRECTION 26 | #define SLEEP_TIME_60MS_CORRECTION 7 27 | #endif 28 | #ifndef SLEEP_TIME_120MS_CORRECTION 29 | #define SLEEP_TIME_120MS_CORRECTION 13 30 | #endif 31 | #ifndef SLEEP_TIME_250MS_CORRECTION 32 | #define SLEEP_TIME_250MS_CORRECTION 15 33 | #endif 34 | #ifndef SLEEP_TIME_500MS_CORRECTION 35 | #define SLEEP_TIME_500MS_CORRECTION 28 36 | #endif 37 | #ifndef SLEEP_TIME_1S_CORRECTION 38 | #define SLEEP_TIME_1S_CORRECTION 54 39 | #endif 40 | #ifndef SLEEP_TIME_2S_CORRECTION 41 | #define SLEEP_TIME_2S_CORRECTION 106 42 | #endif 43 | #ifndef SLEEP_TIME_4S_CORRECTION 44 | #define SLEEP_TIME_4S_CORRECTION 209 45 | #endif 46 | #ifndef SLEEP_TIME_8S_CORRECTION 47 | #define SLEEP_TIME_8S_CORRECTION 415 48 | #endif 49 | 50 | // Constants 51 | // ========= 52 | #define SLEEP_TIME_15MS 15 + SLEEP_TIME_15MS_CORRECTION 53 | #define SLEEP_TIME_30MS 30 + SLEEP_TIME_30MS_CORRECTION 54 | #define SLEEP_TIME_60MS 60 + SLEEP_TIME_60MS_CORRECTION 55 | #define SLEEP_TIME_120MS 120 + SLEEP_TIME_120MS_CORRECTION 56 | #define SLEEP_TIME_250MS 250 + SLEEP_TIME_250MS_CORRECTION 57 | #define SLEEP_TIME_500MS 500 + SLEEP_TIME_500MS_CORRECTION 58 | #define SLEEP_TIME_1S 1000 + SLEEP_TIME_1S_CORRECTION 59 | #define SLEEP_TIME_2S 2000 + SLEEP_TIME_2S_CORRECTION 60 | #define SLEEP_TIME_4S 4000 + SLEEP_TIME_4S_CORRECTION 61 | #define SLEEP_TIME_8S 8000 + SLEEP_TIME_8S_CORRECTION 62 | 63 | private: 64 | void init(); 65 | public: 66 | /** 67 | Do not call this method, it is used by the watchdog interrupt. 68 | */ 69 | static void isrWdt(); 70 | private: 71 | // variables used in the interrupt 72 | static volatile unsigned int wdtSleepTimeMillis; 73 | static volatile unsigned long millisInDeepSleep; 74 | static volatile unsigned long millisBeforeDeepSleep; 75 | /** 76 | Stores the time of the task from which the sleep time of the WDT is 77 | calculated when it is put to sleep. 78 | In case an interrupt schedules a new time, this time is compared against 79 | it to check if the new time is before the WDT would wake up anyway. 80 | */ 81 | unsigned long firstRegularlyScheduledUptimeAfterSleep; 82 | 83 | void taskWdtEnable(const uint8_t value); 84 | inline void taskWdtDisable(); 85 | inline void sleepIfRequired(); 86 | bool isWakeupByOtherInterrupt(); 87 | 88 | void wdtEnableInterrupt(); 89 | inline SleepMode evaluateSleepModeAndEnableWdtIfRequired(); 90 | inline unsigned long wdtEnableForSleep(unsigned long maxWaitTimeMillis); 91 | 92 | -------------------------------------------------------------------------------- /DeepSleepScheduler_avr_implementation.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #ifndef LIBCALL_DEEP_SLEEP_SCHEDULER 6 | // ------------------------------------------------------------------------------------------------- 7 | // Implementation (usuallly in CPP file) 8 | // ------------------------------------------------------------------------------------------------- 9 | 10 | volatile unsigned int Scheduler::wdtSleepTimeMillis; 11 | volatile unsigned long Scheduler::millisInDeepSleep; 12 | volatile unsigned long Scheduler::millisBeforeDeepSleep; 13 | 14 | void Scheduler::init() { 15 | wdtSleepTimeMillis = 0; 16 | millisInDeepSleep = 0; 17 | millisBeforeDeepSleep = 0; 18 | firstRegularlyScheduledUptimeAfterSleep = 0; 19 | } 20 | 21 | unsigned long Scheduler::getMillis() const { 22 | unsigned long value; 23 | noInterrupts(); 24 | value = millis() + millisInDeepSleep; 25 | interrupts(); 26 | return value; 27 | } 28 | 29 | void Scheduler::taskWdtEnable(const uint8_t value) { 30 | wdt_enable(value); 31 | } 32 | 33 | void Scheduler::taskWdtDisable() { 34 | wdt_disable(); 35 | } 36 | 37 | void Scheduler::taskWdtReset() { 38 | wdt_reset(); 39 | } 40 | 41 | bool Scheduler::isWakeupByOtherInterrupt() { 42 | noInterrupts(); 43 | unsigned long wdtSleepTimeMillisLocal = wdtSleepTimeMillis; 44 | interrupts(); 45 | return wdtSleepTimeMillisLocal != 0; 46 | } 47 | 48 | void Scheduler::sleepIfRequired() { 49 | // Enable sleep bit with sleep_enable() before the sleep time evaluation because it can happen 50 | // that the WDT interrupt occurs during sleep time evaluation but before the CPU 51 | // sleeps. In that case, the WDT interrupt clears the sleep bit and the CPU will not sleep 52 | // but continue execution immediatelly. 53 | sleep_enable(); // enables the sleep bit, a safety pin 54 | noInterrupts(); 55 | bool queueEmpty = first == NULL; 56 | interrupts(); 57 | SleepMode sleepMode = IDLE; 58 | if (!queueEmpty) { 59 | sleepMode = evaluateSleepModeAndEnableWdtIfRequired(); 60 | } else { 61 | // nothing in the queue 62 | if (doesSleep() 63 | #ifdef SLEEP_DELAY 64 | && millis() >= lastTaskFinishedMillis + SLEEP_DELAY 65 | #endif 66 | ) { 67 | taskWdtDisable(); 68 | sleepMode = SLEEP; 69 | } else { 70 | sleepMode = IDLE; 71 | } 72 | } 73 | if (sleepMode != NO_SLEEP) { 74 | #ifdef AWAKE_INDICATION_PIN 75 | digitalWrite(AWAKE_INDICATION_PIN, LOW); 76 | #endif 77 | byte adcsraSave = 0; 78 | if (sleepMode == SLEEP) { 79 | noInterrupts(); 80 | set_sleep_mode(SLEEP_MODE); 81 | adcsraSave = ADCSRA; 82 | ADCSRA = 0; // disable ADC 83 | // turn off brown-out in software 84 | #if defined(BODS) && defined(BODSE) 85 | sleep_bod_disable(); 86 | #endif 87 | interrupts (); // guarantees next instruction executed 88 | sleep_cpu(); // here the device is actually put to sleep 89 | } else { // IDLE 90 | set_sleep_mode(SLEEP_MODE_IDLE); 91 | sleep_cpu(); // here the device is actually put to sleep 92 | } 93 | // THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP 94 | #ifdef AWAKE_INDICATION_PIN 95 | digitalWrite(AWAKE_INDICATION_PIN, HIGH); 96 | #endif 97 | if (adcsraSave != 0) { 98 | // re-enable ADC 99 | ADCSRA = adcsraSave; 100 | } 101 | } 102 | sleep_disable(); 103 | } 104 | 105 | inline Scheduler::SleepMode Scheduler::evaluateSleepModeAndEnableWdtIfRequired() { 106 | noInterrupts(); 107 | unsigned long wdtSleepTimeMillisLocal = wdtSleepTimeMillis; 108 | unsigned long currentSchedulerMillis = getMillis(); 109 | 110 | unsigned long firstScheduledUptimeMillis = 0; 111 | if (first != NULL) { 112 | firstScheduledUptimeMillis = first->scheduledUptimeMillis; 113 | } 114 | interrupts(); 115 | 116 | SleepMode sleepMode = NO_SLEEP; 117 | if (!isWakeupByOtherInterrupt()) { 118 | // not woken up during WDT sleep 119 | 120 | unsigned long maxWaitTimeMillis = 0; 121 | if (firstScheduledUptimeMillis > currentSchedulerMillis) { 122 | maxWaitTimeMillis = firstScheduledUptimeMillis - currentSchedulerMillis; 123 | } 124 | 125 | if (maxWaitTimeMillis == 0) { 126 | sleepMode = NO_SLEEP; 127 | } else if (!doesSleep() || maxWaitTimeMillis < MIN_WAIT_TIME_FOR_SLEEP + BUFFER_TIME 128 | #ifdef SLEEP_DELAY 129 | || millis() < lastTaskFinishedMillis + SLEEP_DELAY 130 | #endif 131 | ) { 132 | // use SLEEP_MODE_IDLE for values less then MIN_WAIT_TIME_FOR_SLEEP 133 | sleepMode = IDLE; 134 | } else { 135 | sleepMode = SLEEP; 136 | firstRegularlyScheduledUptimeAfterSleep = firstScheduledUptimeMillis; 137 | 138 | wdtSleepTimeMillisLocal = wdtEnableForSleep(maxWaitTimeMillis); 139 | 140 | noInterrupts(); 141 | wdtSleepTimeMillis = wdtSleepTimeMillisLocal; 142 | wdtEnableInterrupt(); 143 | millisBeforeDeepSleep = millis(); 144 | interrupts(); 145 | } 146 | } else { 147 | // wdt already running, so we woke up due to an other interrupt then WDT. 148 | // continue sleepting without enabling wdt again 149 | sleepMode = SLEEP; 150 | wdtEnableInterrupt(); 151 | // A special case is when the other interrupt scheduled a task between now and before the WDT interrupt occurs. 152 | // In this case, we prevent SLEEP_MODE_PWR_DOWN until it is scheduled. 153 | // If the WDT interrupt occurs before that, it is executed earlier as expected because getMillis() will be 154 | // corrected when the WTD occurs. 155 | 156 | if (firstScheduledUptimeMillis < firstRegularlyScheduledUptimeAfterSleep) { 157 | sleepMode = IDLE; 158 | } else { 159 | #ifdef SLEEP_DELAY 160 | // The CPU was woken up by an interrupt other than WDT. 161 | // The interrupt may have scheduled a task to run immediatelly. In that case we delay deep sleep. 162 | if (millis() < lastTaskFinishedMillis + SLEEP_DELAY) { 163 | sleepMode = IDLE; 164 | } else { 165 | sleepMode = SLEEP; 166 | } 167 | #endif 168 | } 169 | } 170 | return sleepMode; 171 | } 172 | 173 | inline unsigned long Scheduler::wdtEnableForSleep(const unsigned long maxWaitTimeMillis) { 174 | unsigned long wdtSleepTimeMillis; 175 | if (maxWaitTimeMillis >= SLEEP_TIME_8S + BUFFER_TIME) { 176 | wdtSleepTimeMillis = SLEEP_TIME_8S; 177 | wdt_enable(WDTO_8S); 178 | } else if (maxWaitTimeMillis >= SLEEP_TIME_4S + BUFFER_TIME) { 179 | wdtSleepTimeMillis = SLEEP_TIME_4S; 180 | wdt_enable(WDTO_4S); 181 | } else if (maxWaitTimeMillis >= SLEEP_TIME_2S + BUFFER_TIME) { 182 | wdtSleepTimeMillis = SLEEP_TIME_2S; 183 | wdt_enable(WDTO_2S); 184 | } else if (maxWaitTimeMillis >= SLEEP_TIME_1S + BUFFER_TIME) { 185 | wdtSleepTimeMillis = SLEEP_TIME_1S; 186 | wdt_enable(WDTO_1S); 187 | } else if (maxWaitTimeMillis >= SLEEP_TIME_500MS + BUFFER_TIME) { 188 | wdtSleepTimeMillis = SLEEP_TIME_500MS; 189 | wdt_enable(WDTO_500MS); 190 | } else if (maxWaitTimeMillis >= SLEEP_TIME_250MS + BUFFER_TIME) { 191 | wdtSleepTimeMillis = SLEEP_TIME_250MS; 192 | wdt_enable(WDTO_250MS); 193 | } else if (maxWaitTimeMillis >= SLEEP_TIME_120MS + BUFFER_TIME) { 194 | wdtSleepTimeMillis = SLEEP_TIME_120MS; 195 | wdt_enable(WDTO_120MS); 196 | } else if (maxWaitTimeMillis >= SLEEP_TIME_60MS + BUFFER_TIME) { 197 | wdtSleepTimeMillis = SLEEP_TIME_60MS; 198 | wdt_enable(WDTO_60MS); 199 | } else if (maxWaitTimeMillis >= SLEEP_TIME_30MS + BUFFER_TIME) { 200 | wdtSleepTimeMillis = SLEEP_TIME_30MS; 201 | wdt_enable(WDTO_30MS); 202 | } else { // maxWaitTimeMs >= 17 203 | wdtSleepTimeMillis = SLEEP_TIME_15MS; 204 | wdt_enable(WDTO_15MS); 205 | } 206 | return wdtSleepTimeMillis; 207 | } 208 | 209 | void Scheduler::isrWdt() { 210 | sleep_disable(); 211 | millisInDeepSleep += wdtSleepTimeMillis; 212 | millisInDeepSleep -= millis() - millisBeforeDeepSleep; 213 | #ifdef SUPERVISION_CALLBACK 214 | const unsigned int wdtSleepTimeMillisBefore = wdtSleepTimeMillis; 215 | #endif 216 | wdtSleepTimeMillis = 0; 217 | #ifdef SUPERVISION_CALLBACK 218 | if (wdtSleepTimeMillisBefore == 0 && supervisionCallbackRunnable != NULL) { 219 | wdt_reset(); 220 | // give the callback some time but reset if it fails 221 | wdt_enable(SUPERVISION_CALLBACK_TIMEOUT); 222 | supervisionCallbackRunnable->run(); 223 | // trigger restart 224 | wdt_enable(WDTO_15MS); 225 | while (1); 226 | } 227 | #endif 228 | } 229 | 230 | /** 231 | first timeout will be the interrupt, second system reset 232 | */ 233 | void Scheduler::wdtEnableInterrupt() { 234 | // http://forum.arduino.cc/index.php?topic=108870.0 235 | #if defined( __AVR_ATtiny25__ ) || defined( __AVR_ATtiny45__ ) || defined( __AVR_ATtiny85__ ) || defined( __AVR_ATtiny87__ ) || defined( __AVR_ATtiny167__ ) 236 | WDTCR |= (1 << WDCE) | (1 << WDIE); 237 | #else 238 | WDTCSR |= (1 << WDCE) | (1 << WDIE); 239 | #endif 240 | } 241 | 242 | ISR (WDT_vect) { 243 | // WDIE & WDIF is cleared in hardware upon entering this ISR 244 | Scheduler::isrWdt(); 245 | } 246 | 247 | #endif // #ifndef LIBCALL_DEEP_SLEEP_SCHEDULER 248 | -------------------------------------------------------------------------------- /DeepSleepScheduler_esp_definition.h: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------------------------------------------------------- 3 | // Definition of ESP, included inside of class Scheduler 4 | // ------------------------------------------------------------------------------------------------- 5 | #ifndef ESP32_TASK_WDT_TIMER_NUMBER 6 | #define ESP32_TASK_WDT_TIMER_NUMBER 3 7 | #endif 8 | #ifndef ESP8266_MAX_DELAY_TIME_MS 9 | #define ESP8266_MAX_DELAY_TIME_MS 7000 10 | #endif 11 | 12 | private: 13 | void init(); 14 | 15 | #ifdef ESP32 16 | // --------------------------------------------------------------------------------------------- 17 | public: 18 | 19 | /** 20 | Do not call this method, it is used by the watchdog interrupt. 21 | */ 22 | static void IRAM_ATTR isrWatchdogExpiredStatic(); 23 | private: 24 | hw_timer_t *timer = NULL; 25 | #elif ESP8266 26 | public: 27 | #endif 28 | // --------------------------------------------------------------------------------------------- 29 | 30 | private: 31 | void taskWdtEnable(const uint8_t value); 32 | void taskWdtDisable(); 33 | inline unsigned long wdtTimeoutToDurationMs(const uint8_t value); 34 | void sleepIfRequired(); 35 | inline void sleep(unsigned long durationMs, bool queueEmpty); 36 | inline SleepMode evaluateSleepMode(); 37 | 38 | // unused here, only used for AVR 39 | bool isWakeupByOtherInterrupt() { 40 | return false; 41 | } 42 | void wdtEnableInterrupt() {} 43 | 44 | -------------------------------------------------------------------------------- /DeepSleepScheduler_esp_implementation.h: -------------------------------------------------------------------------------- 1 | 2 | #ifdef ESP32 3 | #include 4 | #include 5 | #include 6 | #elif ESP8266 7 | #include 8 | #endif 9 | 10 | #ifndef LIBCALL_DEEP_SLEEP_SCHEDULER 11 | // ------------------------------------------------------------------------------------------------- 12 | // Implementation (usuallly in CPP file) 13 | // ------------------------------------------------------------------------------------------------- 14 | #define ESP8266_MAX_DELAY_TIME_WDT_MS 7500 15 | void Scheduler::init() {} 16 | 17 | #ifdef ESP32 18 | // ------------------------------------------------------------------------------------------------- 19 | unsigned long Scheduler::getMillis() const { 20 | // read RTC clock which runs from initial boot/reset (also during sleep) 21 | // https://forum.makehackvoid.com/t/playing-with-the-esp-32/1144/11 22 | uint64_t rtcTime = rtc_time_get(); 23 | uint64_t rtcTimeUs = rtcTime * 20 / 3; // ticks -> us 1,000,000/150,000 24 | return rtcTimeUs / 1000; 25 | } 26 | 27 | void IRAM_ATTR Scheduler::isrWatchdogExpiredStatic() { 28 | #ifdef SUPERVISION_CALLBACK 29 | if (supervisionCallbackRunnable != NULL) { 30 | // No need to supervise this call as this interrupt has a time limit. 31 | // When it expires, the system is restarted. 32 | supervisionCallbackRunnable->run(); 33 | } 34 | #endif 35 | 36 | ets_printf("Watchdog abort by DeepSleepScheduler\n"); 37 | ESP_ERROR_CHECK(ESP_ERR_TIMEOUT); 38 | } 39 | 40 | /** 41 | Interrupt service routine called when the timer expires. 42 | */ 43 | void IRAM_ATTR isrWatchdogExpired() { 44 | Scheduler::isrWatchdogExpiredStatic(); 45 | } 46 | 47 | void Scheduler::taskWdtEnable(const uint8_t value) { 48 | if (value != NO_SUPERVISION) { 49 | const unsigned long durationMs = wdtTimeoutToDurationMs(value); 50 | if (timer == NULL) { 51 | // div 80 52 | timer = timerBegin(ESP32_TASK_WDT_TIMER_NUMBER, 80, true); 53 | timerAttachInterrupt(timer, &isrWatchdogExpired, true); 54 | } 55 | //set time in us 56 | timerAlarmWrite(timer, durationMs * 1000, false); 57 | //enable interrupt 58 | //only works after taskWdtDisable() if yield() is done before 59 | yield(); 60 | timerAlarmEnable(timer); 61 | } else { 62 | taskWdtDisable(); 63 | } 64 | } 65 | 66 | void Scheduler::taskWdtDisable() { 67 | if (timer != NULL) { 68 | //disable interrupt 69 | timerAlarmDisable(timer); 70 | timerDetachInterrupt(timer); 71 | timerEnd(timer); 72 | timer = NULL; 73 | } 74 | } 75 | 76 | void Scheduler::taskWdtReset() { 77 | //reset timer (feed watchdog) 78 | if (timer != NULL) { 79 | timerWrite(timer, 0); 80 | } 81 | } 82 | 83 | #elif ESP8266 84 | // ------------------------------------------------------------------------------------------------- 85 | unsigned long Scheduler::getMillis() const { 86 | // on ESP8266 we do not support sleep, so millis() stays correct. 87 | return millis(); 88 | } 89 | 90 | void Scheduler::taskWdtEnable(const uint8_t value) { 91 | const unsigned long durationMs = wdtTimeoutToDurationMs(value); 92 | ESP.wdtEnable(durationMs); 93 | } 94 | 95 | void Scheduler::taskWdtDisable() { 96 | ESP.wdtDisable(); 97 | } 98 | 99 | void Scheduler::taskWdtReset() { 100 | ESP.wdtFeed(); 101 | } 102 | #endif 103 | // ------------------------------------------------------------------------------------------------- 104 | 105 | inline unsigned long Scheduler::wdtTimeoutToDurationMs(const uint8_t value) { 106 | unsigned long durationMs; 107 | switch (value) { 108 | case TIMEOUT_15Ms: { 109 | durationMs = 15; 110 | break; 111 | } 112 | case TIMEOUT_30MS: { 113 | durationMs = 30; 114 | break; 115 | } 116 | case TIMEOUT_60MS: { 117 | durationMs = 60; 118 | break; 119 | } 120 | case TIMEOUT_120MS: { 121 | durationMs = 120; 122 | break; 123 | } 124 | case TIMEOUT_250MS: { 125 | durationMs = 250; 126 | break; 127 | } 128 | case TIMEOUT_500MS: { 129 | durationMs = 500; 130 | break; 131 | } 132 | case TIMEOUT_1S: { 133 | durationMs = 1000; 134 | break; 135 | } 136 | case TIMEOUT_2S: { 137 | durationMs = 2000; 138 | break; 139 | } 140 | case TIMEOUT_4S: { 141 | durationMs = 4000; 142 | break; 143 | } 144 | case TIMEOUT_8S: { 145 | durationMs = 8000; 146 | break; 147 | } 148 | default: { 149 | // should not happen 150 | durationMs = 15; 151 | } 152 | } 153 | return durationMs; 154 | } 155 | 156 | void Scheduler::sleepIfRequired() { 157 | noInterrupts(); 158 | bool queueEmpty = first == NULL; 159 | interrupts(); 160 | SleepMode sleepMode = IDLE; 161 | if (!queueEmpty) { 162 | sleepMode = evaluateSleepMode(); 163 | } else { 164 | // nothing in the queue 165 | if (doesSleep() 166 | #ifdef SLEEP_DELAY 167 | && millis() >= lastTaskFinishedMillis + SLEEP_DELAY 168 | #endif 169 | ) { 170 | sleepMode = SLEEP; 171 | } else { 172 | sleepMode = IDLE; 173 | } 174 | } 175 | if (sleepMode != NO_SLEEP) { 176 | #ifdef AWAKE_INDICATION_PIN 177 | digitalWrite(AWAKE_INDICATION_PIN, LOW); 178 | #endif 179 | if (sleepMode == SLEEP) { 180 | taskWdtDisable(); 181 | noInterrupts(); 182 | unsigned long currentSchedulerMillis = getMillis(); 183 | 184 | unsigned long firstScheduledUptimeMillis = 0; 185 | if (first != NULL) { 186 | firstScheduledUptimeMillis = first->scheduledUptimeMillis; 187 | } 188 | 189 | unsigned long maxWaitTimeMillis = 0; 190 | if (firstScheduledUptimeMillis > currentSchedulerMillis) { 191 | maxWaitTimeMillis = firstScheduledUptimeMillis - currentSchedulerMillis; 192 | } 193 | interrupts(); 194 | 195 | sleep(maxWaitTimeMillis, queueEmpty); 196 | } else { // IDLE 197 | yield(); 198 | } 199 | // THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP 200 | #ifdef AWAKE_INDICATION_PIN 201 | digitalWrite(AWAKE_INDICATION_PIN, HIGH); 202 | #endif 203 | } 204 | } 205 | 206 | inline Scheduler::SleepMode Scheduler::evaluateSleepMode() { 207 | noInterrupts(); 208 | unsigned long currentSchedulerMillis = getMillis(); 209 | 210 | unsigned long firstScheduledUptimeMillis = 0; 211 | if (first != NULL) { 212 | firstScheduledUptimeMillis = first->scheduledUptimeMillis; 213 | } 214 | interrupts(); 215 | 216 | SleepMode sleepMode = NO_SLEEP; 217 | unsigned long maxWaitTimeMillis = 0; 218 | if (firstScheduledUptimeMillis > currentSchedulerMillis) { 219 | maxWaitTimeMillis = firstScheduledUptimeMillis - currentSchedulerMillis; 220 | } 221 | 222 | if (maxWaitTimeMillis == 0) { 223 | sleepMode = NO_SLEEP; 224 | } else if (!doesSleep() || maxWaitTimeMillis < BUFFER_TIME 225 | #ifdef SLEEP_DELAY 226 | || millis() < lastTaskFinishedMillis + SLEEP_DELAY 227 | #endif 228 | ) { 229 | // use IDLE for values less then BUFFER_TIME 230 | sleepMode = IDLE; 231 | } else { 232 | sleepMode = SLEEP; 233 | } 234 | return sleepMode; 235 | } 236 | 237 | #ifdef ESP32 238 | // ------------------------------------------------------------------------------------------------- 239 | void Scheduler::sleep(unsigned long durationMs, bool queueEmpty) { 240 | bool timerWakeup; 241 | if (durationMs > 0) { 242 | esp_sleep_enable_timer_wakeup(durationMs * 1000L); 243 | timerWakeup = true; 244 | } else if (queueEmpty) { 245 | #ifdef ESP_DEEP_SLEEP_FOR_INFINITE_SLEEP 246 | esp_deep_sleep_start(); // does not return 247 | #endif 248 | timerWakeup = false; 249 | } else { 250 | // should not happen 251 | esp_sleep_enable_timer_wakeup(1); 252 | timerWakeup = true; 253 | } 254 | 255 | esp_light_sleep_start(); 256 | 257 | if (timerWakeup) { 258 | esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER); 259 | } 260 | } 261 | #elif ESP8266 262 | // ------------------------------------------------------------------------------------------------- 263 | void Scheduler::sleep(unsigned long durationMs, bool queueEmpty) { 264 | #ifdef ESP_DEEP_SLEEP_FOR_INFINITE_SLEEP 265 | if (queueEmpty) { 266 | ESP.deepSleep(0); // does not return 267 | } 268 | #endif 269 | 270 | if (durationMs > ESP8266_MAX_DELAY_TIME_MS) { 271 | durationMs = ESP8266_MAX_DELAY_TIME_MS; 272 | } 273 | 274 | delay(durationMs); 275 | ESP.wdtFeed(); 276 | } 277 | #endif 278 | // ------------------------------------------------------------------------------------------------- 279 | 280 | #endif // #ifndef LIBCALL_DEEP_SLEEP_SCHEDULER 281 | 282 | -------------------------------------------------------------------------------- /DeepSleepScheduler_esp_includes.h: -------------------------------------------------------------------------------- 1 | 2 | #ifdef ESP32 3 | #include 4 | #elif ESP8266 5 | #include 6 | #endif 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino DeepSleepScheduler Library # 2 | https://github.com/PRosenb/DeepSleepScheduler 3 | 4 | DeepSleepScheduler is a lightweight, cooperative task scheduler library with configurable sleep and task supervision. 5 | 6 | ## Features ## 7 | - Easy to use 8 | - Configurable task supervision (using hardware watchdog on AVR) 9 | - Schedule in interrupt 10 | - Small footprint 11 | - Supports multiple CPU architectures with the same API 12 | - AVR based Arduino boards like Arduino Uno, Mega, Nano etc. 13 | - ESP32 14 | - ESP8266 (no sleep support) 15 | - Configurable sleep with `SLEEP_MODE_PWR_DOWN` or `SLEEP_MODE_IDLE` while no task is running (on AVR) 16 | 17 | ## Installation ## 18 | - The library can be installed directly in the [Arduino Software (IDE)](https://www.arduino.cc/en/Main/Software) as follows: 19 | - Menu Sketch->Include Library->Manage Libraries... 20 | - On top right in "Filter your search..." type: DeepSleepScheduler 21 | - The DeepSleepScheduler library will show 22 | - Click on it and then click "Install" 23 | - For more details see manual [Installing Additional Arduino Libraries](https://www.arduino.cc/en/Guide/Libraries#toc3) 24 | - If you do not use the [Arduino Software (IDE)](https://www.arduino.cc/en/Main/Software): 25 | - [Download the latest version](https://github.com/PRosenb/DeepSleepScheduler/releases/latest) 26 | - Uncompress the downloaded file 27 | - This will result in a folder containing all the files for the library. The folder name includes the version: **DeepSleepScheduler-x.y.z** 28 | - Rename the folder to **DeepSleepScheduler** 29 | - Copy the renamed folder to your **libraries** folder 30 | - From time to time, check on https://github.com/PRosenb/DeepSleepScheduler if updates become available 31 | 32 | ## Getting Started ## 33 | Simple blink: 34 | ```c++ 35 | #include 36 | 37 | #ifdef ESP32 38 | #include 39 | #endif 40 | 41 | bool ledOn = true; 42 | 43 | void toggleLed() { 44 | if (ledOn) { 45 | ledOn = false; 46 | digitalWrite(LED_BUILTIN, HIGH); 47 | } else { 48 | ledOn = true; 49 | digitalWrite(LED_BUILTIN, LOW); 50 | } 51 | scheduler.scheduleDelayed(toggleLed, 1000); 52 | } 53 | 54 | void setup() { 55 | #ifdef ESP32 56 | // ESP_PD_DOMAIN_RTC_PERIPH needs to be kept on 57 | // in order for the LED to stay on during sleep 58 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); 59 | #endif 60 | 61 | pinMode(LED_BUILTIN, OUTPUT); 62 | scheduler.schedule(toggleLed); 63 | } 64 | 65 | void loop() { 66 | scheduler.execute(); 67 | } 68 | ``` 69 | Simple blink with Runnable: 70 | ```c++ 71 | #include 72 | 73 | #ifdef ESP32 74 | #include 75 | #endif 76 | 77 | class BlinkRunnable: public Runnable { 78 | private: 79 | bool ledOn = true; 80 | const byte ledPin; 81 | const int delay; 82 | public: 83 | BlinkRunnable(byte ledPin, int delay) : ledPin(ledPin), delay(delay) { 84 | pinMode(ledPin, OUTPUT); 85 | } 86 | virtual void run() { 87 | if (ledOn) { 88 | ledOn = false; 89 | digitalWrite(ledPin, HIGH); 90 | } else { 91 | ledOn = true; 92 | digitalWrite(ledPin, LOW); 93 | } 94 | scheduler.scheduleDelayed(this, delay); 95 | } 96 | }; 97 | 98 | void setup() { 99 | #ifdef ESP32 100 | // ESP_PD_DOMAIN_RTC_PERIPH needs to be kept on 101 | // in order for the LED to stay on during sleep 102 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); 103 | #endif 104 | 105 | BlinkRunnable *blinkRunnable = new BlinkRunnable(LED_BUILTIN, 1000); 106 | scheduler.schedule(blinkRunnable); 107 | } 108 | 109 | void loop() { 110 | scheduler.execute(); 111 | } 112 | ``` 113 | 114 | ## Examples ## 115 | The following example sketches are included in the **DeepSleepScheduler** library. 116 | You can also see them in the [Arduino Software (IDE)](https://www.arduino.cc/en/Main/Software) in menu File->Examples->DeepSleepScheduler. 117 | ### General ### 118 | - [**Blink**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/Blink/Blink.ino): On other simple LED blink example 119 | - [**BlinkRunnable**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/BlinkRunnable/BlinkRunnable.ino): A simple LED blink example using Runnable 120 | - [**ScheduleRepeated**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/ScheduleRepeated/ScheduleRepeated.ino): Shows how to execute a repeated task. The library does not support it intrinsic to save memory. 121 | - [**ScheduleFromInterrupt**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/ScheduleFromInterrupt/ScheduleFromInterrupt.ino): Shows how you can schedule a callback on the main thread from an interrupt 122 | - [**ShowSleep**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/ShowSleep/ShowSleep.ino): Shows with the LED, when the CPU is in sleep or awake 123 | - [**Supervision**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/Supervision/Supervision.ino): Shows how to activate the task supervision in order to restart the CPU when a task takes too much time 124 | - [**SupervisionWithCallback**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/SupervisionWithCallback/SupervisionWithCallback.ino): Shows how to activate the task supervision and get a callback when a task takes too much time 125 | - [**SerialWithDeepSleepDelay**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/SerialWithDeepSleepDelay/SerialWithDeepSleepDelay.ino): Shows how to use `SLEEP_DELAY` to allow serial write to finish before entering sleep 126 | - [**PwmSleep**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/PwmSleep/PwmSleep.ino): Shows how to use analogWrite() and still use low power mode. 127 | ### AVR Specific ### 128 | - [**AdjustSleepTimeCorrections**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/AdjustSleepTimeCorrections/AdjustSleepTimeCorrections.ino): Shows how to adjust the sleep time corrections to your specific CPU 129 | ### ESP32 Specific ### 130 | - [**SchedulerWithOtherTaskPriority**](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/SchedulerWithOtherTaskPriority/SchedulerWithOtherTaskPriority.ino): Shows how to set an other FreeRTOS task priority for tasks scheduled by DeepSleepScheduler 131 | 132 | ## Reference ## 133 | ### Methods ### 134 | ```c++ 135 | /** 136 | Schedule the callback method as soon as possible but after other tasks 137 | that are to be scheduled immediately and are in the queue already. 138 | @param callback: the method to be called on the main thread 139 | */ 140 | void schedule(void (*callback)()); 141 | /** 142 | Schedule the Runnable as soon as possible but after other tasks 143 | that are to be scheduled immediately and are in the queue already. 144 | @param runnable: the Runnable on which the run() method will be called on the main thread 145 | */ 146 | void schedule(Runnable *runnable); 147 | 148 | /** 149 | Schedule the callback method as soon as possible and remove all other 150 | tasks with the same callback. This is useful if you call it 151 | from an interrupt and want one execution only even if the interrupt triggers 152 | multiple times. 153 | @param callback: the method to be called on the main thread 154 | */ 155 | void scheduleOnce(void (*callback)()); 156 | /** 157 | Schedule the Runnable as soon as possible and remove all other 158 | tasks with the same Runnable. This is useful if you call it 159 | from an interrupt and want one execution only even if the interrupt triggers 160 | multiple times. 161 | @param runnable: the Runnable on which the run() method will be called on the main thread 162 | */ 163 | void scheduleOnce(Runnable *runnable); 164 | 165 | /** 166 | Schedule the callback after delayMillis milliseconds. 167 | @param callback: the method to be called on the main thread 168 | @param delayMillis: the time to wait in milliseconds until the callback shall be made 169 | */ 170 | void scheduleDelayed(void (*callback)(), unsigned long delayMillis); 171 | /** 172 | Schedule the callback after delayMillis milliseconds. 173 | @param runnable: the Runnable on which the run() method will be called on the main thread 174 | @param delayMillis: the time to wait in milliseconds until the callback shall be made 175 | */ 176 | void scheduleDelayed(Runnable *runnable, unsigned long delayMillis); 177 | 178 | /** 179 | Schedule the callback uptimeMillis milliseconds after the device was started. 180 | Please be aware that uptimeMillis is stopped when no task is pending. In this case, 181 | the CPU may only wake up on an external interrupt. 182 | @param callback: the method to be called on the main thread 183 | @param uptimeMillis: the time in milliseconds since the device was started 184 | to schedule the callback. 185 | */ 186 | void scheduleAt(void (*callback)(), unsigned long uptimeMillis); 187 | /** 188 | Schedule the callback uptimeMillis milliseconds after the device was started. 189 | Please be aware that uptimeMillis is stopped when no task is pending. In this case, 190 | the CPU may only wake up on an external interrupt. 191 | @param runnable: the Runnable on which the run() method will be called on the main thread 192 | @param uptimeMillis: the time in milliseconds since the device was started 193 | to schedule the callback. 194 | */ 195 | void scheduleAt(Runnable *runnable, unsigned long uptimeMillis); 196 | 197 | /** 198 | Schedule the callback method as next task even if other tasks are in the queue already. 199 | @param callback: the method to be called on the main thread 200 | */ 201 | void scheduleAtFrontOfQueue(void (*callback)()); 202 | /** 203 | Schedule the callback method as next task even if other tasks are in the queue already. 204 | @param runnable: the Runnable on which the run() method will be called on the main thread 205 | */ 206 | void scheduleAtFrontOfQueue(Runnable *runnable); 207 | 208 | /** 209 | Check if this callback is scheduled at least once already. 210 | This method can be called in an interrupt but bear in mind, that it loops through 211 | the run queue until it finds it or reaches the end. 212 | @param callback: callback to check 213 | */ 214 | bool isScheduled(void (*callback)()) const; 215 | 216 | /** 217 | Check if this runnable is scheduled at least once already. 218 | This method can be called in an interrupt but bear in mind, that it loops through 219 | the run queue until it finds it or reaches the end. 220 | @param runnable: Runnable to check 221 | */ 222 | bool isScheduled(Runnable *runnable) const; 223 | 224 | /** 225 | Returns the scheduled time of the task that is currently running. 226 | If no task is currently running, 0 is returned. 227 | */ 228 | unsigned long getScheduleTimeOfCurrentTask() const; 229 | 230 | /** 231 | Cancel all schedules that were scheduled for this callback. 232 | @param callback: method of which all schedules shall be removed 233 | */ 234 | void removeCallbacks(void (*callback)()); 235 | /** 236 | Cancel all schedules that were scheduled for this runnable. 237 | @param runnable: instance of Runnable of which all schedules shall be removed 238 | */ 239 | void removeCallbacks(Runnable *runnable); 240 | 241 | /** 242 | Acquire a lock to prevent the CPU from entering sleep. 243 | acquireNoSleepLock() supports up to 255 locks. 244 | You need to call releaseNoSleepLock() the same amount of times 245 | to allow the CPU to enter sleep again. 246 | */ 247 | void acquireNoSleepLock(); 248 | 249 | /** 250 | Release the lock acquired by acquireNoSleepLock(). Please make sure you 251 | call releaseNoSleepLock() the same amount of times as acquireNoSleepLock(), 252 | otherwise the CPU is not allowed to enter sleep. 253 | */ 254 | void releaseNoSleepLock(); 255 | 256 | /** 257 | return: true if the CPU is currently allowed to enter sleep, false otherwise. 258 | */ 259 | bool doesSleep() const; 260 | 261 | /** 262 | Configure the supervision of future tasks. Can be deactivated with NO_SUPERVISION. 263 | Default: TIMEOUT_8S 264 | @param taskTimeout: The task timeout to be used 265 | */ 266 | void setTaskTimeout(TaskTimeout taskTimeout); 267 | 268 | /** 269 | Resets the task watchdog. After this call returns, the currently running 270 | Task can run up to the configured TaskTimeout set by setTaskTimeout(). 271 | */ 272 | void taskWdtReset(); 273 | 274 | /** 275 | return: The milliseconds since startup of the device where the sleep time was added. 276 | This value does not consider the time when the CPU is in infinite deep sleep 277 | while nothing is in the queue. 278 | */ 279 | unsigned long getMillis() const; 280 | 281 | /** 282 | Sets the runnable to be called when the task supervision detects a task that runs too long. 283 | The run() method will be called from the watchdog interrupt what means, that 284 | e.g. the method delay() does not work. 285 | On AVR, when run() returns, the CPU will be restarted after 15ms. 286 | On ESP32, the interrupt service routine as a whole has a time limit and calls 287 | abort() when returning from this method. 288 | See description of SUPERVISION_CALLBACK and SUPERVISION_CALLBACK_TIMEOUT. 289 | @param runnable: instance of Runnable where the run() method is called 290 | */ 291 | void setSupervisionCallback(Runnable *runnable); 292 | 293 | /** 294 | This method needs to be called from your loop() method and does not return. 295 | */ 296 | void execute(); 297 | ``` 298 | 299 | ### Enumerations ### 300 | ```c++ 301 | enum TaskTimeout { 302 | TIMEOUT_15Ms, 303 | TIMEOUT_30MS, 304 | TIMEOUT_60MS, 305 | TIMEOUT_120MS, 306 | TIMEOUT_250MS, 307 | TIMEOUT_500MS, 308 | TIMEOUT_1S, 309 | TIMEOUT_2S, 310 | TIMEOUT_4S, 311 | TIMEOUT_8S, 312 | NO_SUPERVISION 313 | }; 314 | ``` 315 | 316 | ### Define Options ### 317 | - `#define LIBCALL_DEEP_SLEEP_SCHEDULER`: The header file contains definition and implementation. For that reason, it can be included once only in a project. To use it in multiple files, define `LIBCALL_DEEP_SLEEP_SCHEDULER` before all include statements except one. 318 | 319 | All following options are to be set **before** the include where **no** `LIBCALL_DEEP_SLEEP_SCHEDULER` is defined. 320 | 321 | #### General options #### 322 | - `#define SLEEP_DELAY`: Prevent the CPU from entering sleep for the specified amount of milliseconds after finishing the previous task. 323 | - `#define SUPERVISION_CALLBACK`: Allows to specify a callback `Runnable` to be called when a task runs too long. When 324 | the callback returns, the CPU is restarted after 15 ms by the watchdog. The callback method is called directly 325 | from the watchdog interrupt. This means that e.g. `delay()` does not work. 326 | - `#define SUPERVISION_CALLBACK_TIMEOUT`: Specify the timeout of the callback until the watchdog resets the CPU. Defaults to `WDTO_1S`. 327 | - `#define AWAKE_INDICATION_PIN`: Show on a LED if the CPU is active or in sleep mode. 328 | HIGH = active, LOW = sleeping 329 | 330 | #### AVR specific options #### 331 | - `#define SLEEP_MODE`: Specifies the sleep mode entered when doing deep sleep. Default is `SLEEP_MODE_PWR_DOWN`. 332 | - `#define MIN_WAIT_TIME_FOR_SLEEP`: Specify the minimum wait time (until the next task will be executed) to put the CPU in sleep mode. Default is 1 second. 333 | - `#define SLEEP_TIME_XXX_CORRECTION`: Adjust the sleep time correction for the time when the CPU is in `SLEEP_MODE_PWR_DOWN` and waking up. See [Implementation Notes](#implementation-notes) and example [AdjustSleepTimeCorrections](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/AdjustSleepTimeCorrections/AdjustSleepTimeCorrections.ino). 334 | 335 | #### ESP32 specific options ### 336 | - `#ESP32_TASK_WDT_TIMER_NUMBER`: Specifies the timer number to be used for task supervision. Default is 3. 337 | 338 | #### ESP8266 specific options #### 339 | - `ESP8266_MAX_DELAY_TIME_MS`: The maximum time in milliseconds the CPU will be delayed while no task is scheduled. Default is 7000 due to the watchdog timeout of 8 seconds. Set this value lower if you expect interrupts while no task is running. 340 | 341 | ## Implementation Notes ## 342 | ### General ### 343 | - Definition and code are in the header file. It is done like this to allow the user to configure the library by using `#define`. You can still include the header file in multiple files of a project by using `#define LIBCALL_DEEP_SLEEP_SCHEDULER`. See [Define Options](#define-options). 344 | - It is possible to schedule callbacks in interrupts. The run time of the `scheduleXX()` methods is relatively short but it blocks execution of other interrupts. If you have very time critical interrupts, they may still be blocked for too long. 345 | - No matter how callbacks were scheduled, they are always run on the thread that runs the scheduler.execute() function. The scheduler can therefore be used as a convenient way to pass control from an interrupt to a regular thread. 346 | 347 | ### AVR ### 348 | - On AVR the watchdog timer is used to wake the CPU up from `SLEEP_MODE_PWR_DOWN` and for task supervision. It can therefore not be used for other means. 349 | - When the CPU enters `SLEEP_MODE_PWR_DOWN`, the watchdog timer is used to wake it up again. The accuracy of the watchdog timer is not very well though. Further, the wake up time depends on the CPU type you are using. If you have certain time constraints, it may happen, that the schedule times are not precise enough. 350 | One possibility is to adapt the sleep time corrections by setting the defines `SLEEP_TIME_XXX_CORRECTION` (see [Define Options](#define-options) and example [AdjustSleepTimeCorrections](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/AdjustSleepTimeCorrections/AdjustSleepTimeCorrections.ino)). 351 | An other option is to disable sleep (`SLEEP_MODE_PWR_DOWN`) while scheduling with tight time constraints. To do so, use the methods `acquireNoSleepLock()` and `releaseNoSleepLock()` (see [Methods](#methods)). Please report values back to me if you do time measuring, thanks. 352 | - While the CPU is in `SLEEP_MODE_PWR_DOWN`, the millis timer is not running. For this reason the current uptime is not known when an external interrupt occurs during this time. Instead of the current uptime, the uptime when the CPU started to sleep is taken when calculating the schedule time of a delayed task. This means that these tasks are potentially scheduled too early because the uptime is corrected when the sleep time is finished. 353 | 354 | ### ESP32 ### 355 | - At time of writing, the ESP32 implementation available in the Arduino IDE does not allow access to the hardware watchdog of ESP32. To still allow supervision of the tasks, DeepSleepScheduler employs timer 3 to measure the time and restart the CPU if a task runs too long. See [Define Options](#define-options) on how to change the timer. 356 | - On ESP32 FreeRTOS is used. It allows to run multiple threads in parallel and manages their switching and prioritisation. DeepSleepScheduler (that also runs on memory constrained CPUs) is a cooperative task scheduler that runs all tasks on the thread that calls scheduler.execute(). The advantage of that is, that there is no need to synchronize the tasks against each other. On the other hand, they do not run in parallel. To change the FreeRTOS priority of all tasks run by DeepSleepScheduler, set it before scheduler.execute() is called. See [SchedulerWithOtherTaskPriority](https://github.com/PRosenb/DeepSleepScheduler/blob/master/examples/SchedulerWithOtherTaskPriority/SchedulerWithOtherTaskPriority.ino) for details. 357 | 358 | ## Contributions ## 359 | Enhancements and improvements are welcome. 360 | 361 | ## License ## 362 | ``` 363 | Arduino DeepSleepScheduler Library 364 | Copyright (c) 2018 Peter Rosenberg (https://github.com/PRosenb). 365 | 366 | Licensed under the Apache License, Version 2.0 (the "License"); 367 | you may not use this file except in compliance with the License. 368 | You may obtain a copy of the License at 369 | 370 | http://www.apache.org/licenses/LICENSE-2.0 371 | 372 | Unless required by applicable law or agreed to in writing, software 373 | distributed under the License is distributed on an "AS IS" BASIS, 374 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 375 | See the License for the specific language governing permissions and 376 | limitations under the License. 377 | ``` 378 | -------------------------------------------------------------------------------- /examples/AdjustSleepTimeCorrections/AdjustSleepTimeCorrections.ino: -------------------------------------------------------------------------------- 1 | 2 | // The watchdog timer can be configured to sleep from 15ms to 8s at a time. 3 | // As the sleep times differ slighly from the directed times, the scheduler 4 | // adds the correction values to improve the callback times. 5 | // Example: 6 | // When the CPU is supposed to sleep 15ms but it sleeps 3ms too long (18ms), 7 | // you configure 8 | // #define SLEEP_TIME_15MS_CORRECTION 3 9 | // The sleep time will then be calculated as 15ms + 3ms = 18ms. 10 | // See example WdtTiemsMeasurer. 11 | 12 | // Please be aware that the CPU is not set to SLEEP_MODE_PWR_DOWN 13 | // when the total sleep time is less than about a second. 14 | 15 | #define SLEEP_TIME_15MS_CORRECTION 3 16 | #define SLEEP_TIME_30MS_CORRECTION 4 17 | #define SLEEP_TIME_60MS_CORRECTION 7 18 | #define SLEEP_TIME_120MS_CORRECTION 13 19 | #define SLEEP_TIME_250MS_CORRECTION 15 20 | #define SLEEP_TIME_500MS_CORRECTION 28 21 | #define SLEEP_TIME_1S_CORRECTION 54 22 | #define SLEEP_TIME_2S_CORRECTION 106 23 | #define SLEEP_TIME_4S_CORRECTION 209 24 | #define SLEEP_TIME_8S_CORRECTION 415 25 | 26 | #include 27 | 28 | #define LED_PIN 13 29 | 30 | void ledOn() { 31 | digitalWrite(LED_PIN, HIGH); 32 | scheduler.scheduleDelayed(ledOff, 5000); 33 | } 34 | 35 | void ledOff() { 36 | digitalWrite(LED_PIN, LOW); 37 | scheduler.scheduleDelayed(ledOn, 5000); 38 | } 39 | 40 | void setup() { 41 | pinMode(LED_PIN, OUTPUT); 42 | scheduler.schedule(ledOn); 43 | } 44 | 45 | void loop() { 46 | scheduler.execute(); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/Blink/Blink.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef ESP32 4 | #include 5 | #endif 6 | 7 | void ledOn() { 8 | digitalWrite(LED_BUILTIN, HIGH); 9 | scheduler.scheduleDelayed(ledOff, 1000); 10 | } 11 | 12 | void ledOff() { 13 | digitalWrite(LED_BUILTIN, LOW); 14 | scheduler.scheduleDelayed(ledOn, 1000); 15 | } 16 | 17 | void setup() { 18 | #ifdef ESP32 19 | // ESP_PD_DOMAIN_RTC_PERIPH needs to be kept on 20 | // in order for the LED to stay on during sleep 21 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); 22 | #endif 23 | 24 | pinMode(LED_BUILTIN, OUTPUT); 25 | scheduler.schedule(ledOn); 26 | } 27 | 28 | void loop() { 29 | scheduler.execute(); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/BlinkRunnable/BlinkRunnable.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class BlinkRunnable: public Runnable { 4 | private: 5 | bool ledOn = true; 6 | const byte ledPin; 7 | const unsigned long delay; 8 | public: 9 | BlinkRunnable(byte ledPin, int delay) : ledPin(ledPin), delay(delay) { 10 | pinMode(ledPin, OUTPUT); 11 | } 12 | virtual void run() { 13 | if (ledOn) { 14 | ledOn = false; 15 | digitalWrite(ledPin, HIGH); 16 | } else { 17 | ledOn = true; 18 | digitalWrite(ledPin, LOW); 19 | } 20 | scheduler.scheduleDelayed(this, delay); 21 | } 22 | }; 23 | 24 | void setup() { 25 | #ifdef ESP32 26 | // ESP_PD_DOMAIN_RTC_PERIPH needs to be kept on 27 | // in order for the LED to stay on during sleep 28 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); 29 | #endif 30 | 31 | BlinkRunnable *blinkRunnable = new BlinkRunnable(LED_BUILTIN, 1000); 32 | scheduler.schedule(blinkRunnable); 33 | } 34 | 35 | void loop() { 36 | scheduler.execute(); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /examples/PwmSleep/PwmSleep.ino: -------------------------------------------------------------------------------- 1 | // do not use SLEEP_MODE_PWR_DOWN to prevent that 2 | // the asynchronous clock is stopped 3 | #define SLEEP_MODE SLEEP_MODE_PWR_SAVE 4 | // show then the CPU is active on LED_BUILTIN 5 | #define AWAKE_INDICATION_PIN LED_BUILTIN 6 | #include 7 | 8 | // the PWM signal can only be used in sleep mode on 9 | // Arduino UNO PINs 3 and 11 because only these two 10 | // use an asynchronous clock that is not stopped 11 | // with SLEEP_MODE_PWR_SAVE 12 | #define PWN_PIN 3 13 | 14 | void highValue() { 15 | analogWrite(PWN_PIN, 200); 16 | scheduler.scheduleDelayed(lowValue, 5000); 17 | } 18 | 19 | void lowValue() { 20 | analogWrite(PWN_PIN, 10); 21 | scheduler.scheduleDelayed(highValue, 5000); 22 | } 23 | 24 | void setup() { 25 | pinMode(PWN_PIN, OUTPUT); 26 | #ifdef ESP8266 27 | analogWriteRange(255); 28 | #endif 29 | 30 | scheduler.schedule(lowValue); 31 | } 32 | 33 | void loop() { 34 | scheduler.execute(); 35 | } 36 | 37 | -------------------------------------------------------------------------------- /examples/ScheduleFromInterrupt/ScheduleFromInterrupt.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef ESP32 4 | #define INTERRUPT_PIN 4 5 | #elif ESP8266 6 | #define INTERRUPT_PIN D2 7 | #else 8 | #define INTERRUPT_PIN 2 9 | #endif 10 | 11 | void ledOn() { 12 | digitalWrite(LED_BUILTIN, HIGH); 13 | scheduler.scheduleDelayed(ledOff, 2000); 14 | } 15 | 16 | void ledOff() { 17 | digitalWrite(LED_BUILTIN, LOW); 18 | attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), isrInterruptPin, FALLING); 19 | } 20 | 21 | void isrInterruptPin() { 22 | scheduler.schedule(ledOn); 23 | // detach interrupt to prevent executing it multiple 24 | // time when touching more more than once. 25 | detachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN)); 26 | } 27 | 28 | void setup() { 29 | #ifdef ESP32 30 | // wake up using ext0 (4 low) 31 | esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, LOW); 32 | #endif 33 | 34 | pinMode(INTERRUPT_PIN, INPUT_PULLUP); 35 | attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), isrInterruptPin, FALLING); 36 | pinMode(LED_BUILTIN, OUTPUT); 37 | } 38 | 39 | void loop() { 40 | scheduler.execute(); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /examples/ScheduleRepeated/ScheduleRepeated.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void toggleLed() { 4 | if (digitalRead(LED_BUILTIN) == HIGH) { 5 | digitalWrite(LED_BUILTIN, LOW); 6 | } else { 7 | digitalWrite(LED_BUILTIN, HIGH); 8 | } 9 | scheduler.scheduleAt(toggleLed, scheduler.getScheduleTimeOfCurrentTask() + 1000); 10 | } 11 | 12 | void setup() { 13 | #ifdef ESP32 14 | // ESP_PD_DOMAIN_RTC_PERIPH needs to be kept on 15 | // in order for the LED to stay on during sleep 16 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); 17 | #endif 18 | 19 | pinMode(LED_BUILTIN, OUTPUT); 20 | scheduler.schedule(toggleLed); 21 | } 22 | 23 | void loop() { 24 | scheduler.execute(); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /examples/SchedulerWithOtherTaskPriority/SchedulerWithOtherTaskPriority.ino: -------------------------------------------------------------------------------- 1 | #define SLEEP_DELAY 100 2 | #include 3 | 4 | void setup() { 5 | Serial.begin(115200); 6 | 7 | Serial.print("setup() FreeRTOS priority: "); 8 | Serial.print(uxTaskPriorityGet(NULL)); 9 | Serial.print(" of "); 10 | Serial.println(configMAX_PRIORITIES); 11 | 12 | scheduler.schedule(task1); 13 | } 14 | 15 | void task1() { 16 | Serial.print("task1 FreeRTOS priority: "); 17 | Serial.println(uxTaskPriorityGet(NULL)); 18 | } 19 | 20 | void loop() { 21 | // set the FreeRTOS task priority the scheduler will use for the tasks is schedules. 22 | vTaskPrioritySet(NULL, 6); 23 | scheduler.execute(); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /examples/SerialWithDeepSleepDelay/SerialWithDeepSleepDelay.ino: -------------------------------------------------------------------------------- 1 | 2 | // show the awake times of the CPU on output LED_BUILTIN 3 | #define AWAKE_INDICATION_PIN LED_BUILTIN 4 | // delay sleep by 100 milli seconds to allow finishing serial write 5 | #define SLEEP_DELAY 100 6 | #include 7 | 8 | void printMillis() { 9 | // usage of F() puts the strings to the flash and therefore saves main memory 10 | Serial.print(F("millis(): ")); 11 | Serial.print(millis()); 12 | Serial.print(F(", scheduler.getMillis(): ")); 13 | Serial.println(scheduler.getMillis()); 14 | 15 | // If you use around 1 second or less, millis() and scheduler.getMillis() 16 | // will be equal because the CPU is not put to SLEEP_MODE_PWR_DOWN in that case. 17 | scheduler.scheduleDelayed(printMillis, 2000); 18 | } 19 | 20 | void setup() { 21 | Serial.begin(115200); 22 | scheduler.schedule(printMillis); 23 | } 24 | 25 | void loop() { 26 | scheduler.execute(); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /examples/ShowSleep/ShowSleep.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Shows when the CPU is in sleep mode (LED off) and active (LED on). 3 | Please not that the CPU is not put to deep sleep for schedule times 4 | shorter than 1 second. For these, it uses SLEEP_MODE_IDLE. 5 | 6 | You will notice, that it's not on in full brightness because 7 | the CPU is sleeping shortly almost all the time when it is set 8 | to SLEEP_MODE_IDLE. In that case, it wakes up on every timer tick 9 | what is every around 1 ms. It also wakes up on all kinds of other 10 | interrupts (see CPU description for more details). 11 | 12 | It can further be seen, that it's not off for the whole time it's 13 | set to be in deep sleep. That is because only part of the schedule 14 | time is in deep sleep. About one second before a schedule callback, 15 | it wakes up to handle that callback. 16 | */ 17 | 18 | #define AWAKE_INDICATION_PIN LED_BUILTIN 19 | #include 20 | 21 | void keepCpuOn() { 22 | // As many times as aquireNoSleepLock() is called, we also need 23 | // to call releaseNoSleepLock() to let the CPU fall into deep sleep 24 | // again. 25 | scheduler.acquireNoSleepLock(); 26 | scheduler.scheduleDelayed(allowCpuToSleep, 3000); 27 | } 28 | 29 | void allowCpuToSleep() { 30 | scheduler.releaseNoSleepLock(); 31 | scheduler.scheduleDelayed(keepCpuOn, 3000); 32 | } 33 | 34 | void setup() { 35 | scheduler.schedule(keepCpuOn); 36 | } 37 | 38 | void loop() { 39 | scheduler.execute(); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /examples/Supervision/Supervision.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void block() { 4 | digitalWrite(LED_BUILTIN, HIGH); 5 | while (1); 6 | } 7 | 8 | void setup() { 9 | pinMode(LED_BUILTIN, OUTPUT); 10 | digitalWrite(LED_BUILTIN, LOW); 11 | scheduler.setTaskTimeout(TIMEOUT_2S); 12 | scheduler.scheduleDelayed(block, 1000); 13 | } 14 | 15 | void loop() { 16 | scheduler.execute(); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /examples/SupervisionWithCallback/SupervisionWithCallback.ino: -------------------------------------------------------------------------------- 1 | #define SUPERVISION_CALLBACK 2 | #define SUPERVISION_CALLBACK_TIMEOUT WDTO_1S 3 | #include 4 | 5 | void block() { 6 | while (1); 7 | } 8 | 9 | class SupervisionCallback: public Runnable { 10 | void run() { 11 | digitalWrite(LED_BUILTIN, HIGH); 12 | // this method is called from the interrupt so 13 | // delay() does not work 14 | 15 | // to see that the LED is on, 16 | // we block until the watchdog resets the CPU what 17 | // is configured with SUPERVISION_CALLBACK_TIMEOUT 18 | // and defaults to 1s 19 | while (1); 20 | } 21 | }; 22 | 23 | void setup() { 24 | pinMode(LED_BUILTIN, OUTPUT); 25 | digitalWrite(LED_BUILTIN, LOW); 26 | 27 | scheduler.setTaskTimeout(TIMEOUT_2S); 28 | scheduler.setSupervisionCallback(new SupervisionCallback()); 29 | scheduler.scheduleDelayed(block, 1000); 30 | } 31 | 32 | void loop() { 33 | scheduler.execute(); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /examples/WdtTimesGenerator/WdtTimesGenerator.ino: -------------------------------------------------------------------------------- 1 | /** 2 | This sketch is used together with WdtTimesMeasuerer, please read the description there. 3 | */ 4 | #include 5 | #include 6 | 7 | // ports to use 8 | #define OUTPUT_PIN 2 9 | #define LED_PIN 13 10 | 11 | // constants and variables 12 | #define BEFORE_FIRST -1 13 | #define FINISHED -2 14 | 15 | volatile unsigned int wdtTime = BEFORE_FIRST; 16 | 17 | void setup() { 18 | Serial.begin(115200); 19 | pinMode(OUTPUT_PIN, OUTPUT); 20 | digitalWrite(OUTPUT_PIN, LOW); 21 | pinMode(LED_PIN, OUTPUT); 22 | 23 | Serial.println(F("WdtTimesGenerator")); 24 | delay(3000); 25 | digitalWrite(LED_PIN, HIGH); 26 | } 27 | 28 | void loop() { 29 | noInterrupts(); 30 | const byte outputPinState = digitalRead(OUTPUT_PIN); 31 | interrupts(); 32 | if (outputPinState == LOW) { 33 | wdtTime = getAndPrintNextWdtTime(wdtTime); 34 | delay(100); // finish serial out 35 | 36 | if (wdtTime == FINISHED) { 37 | digitalWrite(LED_PIN, LOW); 38 | pwrDownSleep(); 39 | } 40 | 41 | digitalWrite(OUTPUT_PIN, HIGH); 42 | wdt_enable(wdtTime); 43 | // enable interrupt 44 | // first timeout will be the interrupt, second system reset 45 | WDTCSR |= (1 << WDCE) | (1 << WDIE); 46 | pwrDownSleep(); 47 | wdt_disable(); 48 | } 49 | 50 | // gap between different times 51 | delay(1000); 52 | } 53 | 54 | void pwrDownSleep() { 55 | sleep_enable(); // enables the sleep bit, a safety pin 56 | noInterrupts(); 57 | set_sleep_mode(SLEEP_MODE_PWR_DOWN); 58 | byte adcsraSave = ADCSRA; 59 | ADCSRA = 0; // disable ADC 60 | // turn off brown-out in software 61 | #if defined(BODS) && defined(BODSE) 62 | sleep_bod_disable(); 63 | #endif 64 | interrupts (); // guarantees next instruction executed 65 | sleep_cpu(); // here the device is actually put to sleep 66 | 67 | if (adcsraSave != 0) { 68 | // re-enable ADC 69 | ADCSRA = adcsraSave; 70 | } 71 | } 72 | 73 | ISR (WDT_vect) { 74 | // WDIE & WDIF is cleared in hardware upon entering this ISR 75 | sleep_disable(); 76 | digitalWrite(OUTPUT_PIN, LOW); 77 | } 78 | 79 | unsigned int getAndPrintNextWdtTime(unsigned int last) { 80 | switch (last) { 81 | case BEFORE_FIRST: 82 | Serial.println(F("WDTO_15MS")); 83 | return WDTO_15MS; 84 | case WDTO_15MS: 85 | Serial.println(F("WDTO_30MS")); 86 | return WDTO_30MS; 87 | case WDTO_30MS: 88 | Serial.println(F("WDTO_60MS")); 89 | return WDTO_60MS; 90 | case WDTO_60MS: 91 | Serial.println(F("WDTO_120MS")); 92 | return WDTO_120MS; 93 | case WDTO_120MS: 94 | Serial.println(F("WDTO_250MS")); 95 | return WDTO_250MS; 96 | case WDTO_250MS: 97 | Serial.println(F("WDTO_500MS")); 98 | return WDTO_500MS; 99 | case WDTO_500MS: 100 | Serial.println(F("WDTO_1S")); 101 | return WDTO_1S; 102 | case WDTO_1S: 103 | Serial.println(F("WDTO_2S")); 104 | return WDTO_2S; 105 | case WDTO_2S: 106 | Serial.println(F("WDTO_4S")); 107 | return WDTO_4S; 108 | case WDTO_4S: 109 | Serial.println(F("WDTO_8S")); 110 | return WDTO_8S; 111 | case WDTO_8S: 112 | case FINISHED: 113 | Serial.println(F("finished")); 114 | return FINISHED; 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /examples/WdtTimesMeasurer/WdtTimesMeasurer.ino: -------------------------------------------------------------------------------- 1 | /** 2 | This sketch is used together with WdtTimesGenerator to find out what 3 | correction times can be used for the used CPU. 4 | 5 | Required hardware fo the measurement: 6 | - 2 Arduino boards (e.g. Arduino Uno) 7 | - 4 wires to connect them 8 | 9 | Program the Arduino board under test with the sketch WdtTimesGenerator and 10 | the other Arduino board with WdtTimesMeasurer. 11 | 12 | Then hook them up with 4 cables as follows: 13 | WdtTimesGenerator PIN 5V -- WdtTimesMeasurer PIN 5V 14 | WdtTimesGenerator PIN GND -- WdtTimesMeasurer PIN GND 15 | WdtTimesGenerator PIN 2 -- WdtTimesMeasurer PIN 2 16 | WdtTimesGenerator PIN RESET -- WdtTimesMeasurer PIN 3 17 | 18 | Then connect the WdtTimesMeasurer board with USB and open Serial Monitor 19 | to see the measurements. 20 | The values are calculated as average values when the test is run multiple 21 | repeats. So let it run for some repeats and then take the last output. 22 | */ 23 | #include 24 | 25 | // ports to use 26 | #define INTERRUPT_PIN 2 27 | #define RESET_PIN 3 28 | #define LED_PIN 13 29 | 30 | // constants and variables 31 | #define FINISHED -1 32 | #define CORRECTIONS_COUNT 10 33 | 34 | volatile boolean ignoreInterrupts = false; 35 | volatile unsigned int wdtTime = WDTO_15MS; 36 | volatile unsigned long startTime = 0; 37 | volatile unsigned long endTime = 0; 38 | 39 | volatile unsigned int repeat = 0; 40 | volatile unsigned int avgCorrectionSumIndex = 0; 41 | volatile signed long avgCorrectionSum[CORRECTIONS_COUNT]; 42 | 43 | void setup() { 44 | Serial.begin(115200); 45 | delay(150); 46 | Serial.println(F("WdtTimesMeasurer")); 47 | 48 | pinMode(LED_PIN, OUTPUT); 49 | pinMode(RESET_PIN, OUTPUT); 50 | pinMode(INTERRUPT_PIN, INPUT_PULLUP); 51 | 52 | for (int i = 0; i < CORRECTIONS_COUNT; i++) { 53 | avgCorrectionSum[i] = 0; 54 | } 55 | 56 | initMeasurement(); 57 | } 58 | 59 | void loop() { 60 | noInterrupts(); 61 | const unsigned long startTimeLocal = startTime; 62 | const unsigned long endTimeLocal = endTime; 63 | interrupts(); 64 | if (endTimeLocal != 0) { 65 | printNextCorrection(endTime - startTime); 66 | noInterrupts(); 67 | startTime = 0; 68 | endTime = 0; 69 | interrupts(); 70 | 71 | if (wdtTime == FINISHED) { 72 | Serial.println(); 73 | initMeasurement(); 74 | } 75 | } 76 | yield(); 77 | } 78 | 79 | void initMeasurement() { 80 | wdtTime = WDTO_15MS; 81 | repeat++; 82 | avgCorrectionSumIndex = 0; 83 | 84 | noInterrupts(); 85 | ignoreInterrupts = true; 86 | interrupts(); 87 | detachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN)); 88 | 89 | Serial.println(F("resetting..")); 90 | digitalWrite(RESET_PIN, LOW); 91 | delay(150); 92 | digitalWrite(RESET_PIN, HIGH); 93 | 94 | // await startup 95 | delay(2000); 96 | 97 | Serial.print(F("start repeat ")); 98 | Serial.println(repeat); 99 | Serial.println(); 100 | attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), isrInterruptPin, CHANGE); 101 | 102 | // ignore pending interrupts 103 | delay(100); 104 | noInterrupts(); 105 | ignoreInterrupts = false; 106 | interrupts(); 107 | } 108 | 109 | void isrInterruptPin() { 110 | if (ignoreInterrupts) { 111 | return; 112 | } 113 | if (digitalRead(INTERRUPT_PIN) == HIGH) { 114 | if (startTime == 0) { 115 | startTime = millis(); 116 | digitalWrite(LED_PIN, HIGH); 117 | } 118 | } else { 119 | if (endTime == 0) { 120 | endTime = millis(); 121 | digitalWrite(LED_PIN, LOW); 122 | } 123 | } 124 | } 125 | 126 | void printNextCorrection(unsigned long duration) { 127 | Serial.print(F("#define SLEEP_TIME_")); 128 | signed long correction = 0; 129 | switch (wdtTime) { 130 | case WDTO_15MS: 131 | Serial.print(F("15MS")); 132 | correction = duration - 15; 133 | wdtTime = WDTO_30MS; 134 | break; 135 | case WDTO_30MS: 136 | Serial.print(F("30MS")); 137 | correction = duration - 30; 138 | wdtTime = WDTO_60MS; 139 | break; 140 | case WDTO_60MS: 141 | Serial.print(F("60MS")); 142 | correction = duration - 60; 143 | wdtTime = WDTO_120MS; 144 | break; 145 | case WDTO_120MS: 146 | Serial.print(F("120MS")); 147 | correction = duration - 120; 148 | wdtTime = WDTO_250MS; 149 | break; 150 | case WDTO_250MS: 151 | Serial.print(F("250MS")); 152 | correction = duration - 250; 153 | wdtTime = WDTO_500MS; 154 | break; 155 | case WDTO_500MS: 156 | Serial.print(F("500MS")); 157 | correction = duration - 500; 158 | wdtTime = WDTO_1S; 159 | break; 160 | case WDTO_1S: 161 | Serial.print(F("1S")); 162 | correction = duration - 1000; 163 | wdtTime = WDTO_2S; 164 | break; 165 | case WDTO_2S: 166 | Serial.print(F("2S")); 167 | correction = duration - 2000; 168 | wdtTime = WDTO_4S; 169 | break; 170 | case WDTO_4S: 171 | Serial.print(F("4S")); 172 | correction = duration - 4000; 173 | wdtTime = WDTO_8S; 174 | break; 175 | case WDTO_8S: 176 | Serial.print(F("8S")); 177 | correction = duration - 8000; 178 | wdtTime = FINISHED; 179 | break; 180 | default: 181 | wdtTime = FINISHED; 182 | } 183 | 184 | avgCorrectionSum[avgCorrectionSumIndex] += correction; 185 | Serial.print(F("_CORRECTION ")); 186 | Serial.print(avgCorrectionSum[avgCorrectionSumIndex] / repeat); 187 | // Serial.print(F(" ")); 188 | // Serial.print(duration); 189 | Serial.println(); 190 | avgCorrectionSumIndex++; 191 | } 192 | 193 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For DeepSleepScheduler 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | scheduler KEYWORD1 10 | Runnable KEYWORD1 11 | TaskTimeout KEYWORD1 12 | 13 | ####################################### 14 | # Methods and Functions (KEYWORD2) 15 | ####################################### 16 | 17 | run KEYWORD2 18 | schedule KEYWORD2 19 | scheduleOnce KEYWORD2 20 | scheduleDelayed KEYWORD2 21 | scheduleAt KEYWORD2 22 | scheduleAtFrontOfQueue KEYWORD2 23 | isScheduled KEYWORD2 24 | getScheduleTimeOfCurrentTask KEYWORD2 25 | removeCallbacks KEYWORD2 26 | acquireNoSleepLock KEYWORD2 27 | releaseNoSleepLock KEYWORD2 28 | doesSleep KEYWORD2 29 | setTaskTimeout KEYWORD2 30 | getMillis KEYWORD2 31 | setSupervisionCallback KEYWORD2 32 | taskWdtReset KEYWORD2 33 | execute KEYWORD2 34 | 35 | ####################################### 36 | # Constants (LITERAL1) 37 | ####################################### 38 | 39 | TIMEOUT_15Ms LITERAL1 40 | TIMEOUT_30MS LITERAL1 41 | TIMEOUT_60MS LITERAL1 42 | TIMEOUT_120MS LITERAL1 43 | TIMEOUT_250MS LITERAL1 44 | TIMEOUT_500MS LITERAL1 45 | TIMEOUT_1S LITERAL1 46 | TIMEOUT_2S LITERAL1 47 | TIMEOUT_4S LITERAL1 48 | TIMEOUT_8S LITERAL1 49 | NO_SUPERVISION LITERAL1 50 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=DeepSleepScheduler 2 | version=3.3.0 3 | author=Pete 4 | maintainer=Pete 5 | sentence=Lightweight, cooperative task scheduler with configurable sleep and task supervision. 6 | paragraph=Provides an easy to use API to schedule tasks, supervise them with the hardware watchdog on AVR and puts the CPU to sleep while no task is running. Tasks can be schedule from interrupts and it supports multiple CPU architectures with the same API. 7 | category=Other 8 | url=https://github.com/PRosenb/DeepSleepScheduler 9 | architectures=avr,esp32,esp8266 10 | --------------------------------------------------------------------------------