DeepSleepScheduler is a lightweight, cooperative task scheduler library with configurable sleep and task supervision.
https://github.com/PRosenb/DeepSleepScheduler
DeepSleepScheduler is a lightweight, cooperative task scheduler library with configurable sleep and task supervision.
SLEEP_MODE_PWR_DOWN
or SLEEP_MODE_IDLE
while no task is running (on AVR)Simple blink:
#include <DeepSleepScheduler.h>
#ifdef ESP32
#include <esp_sleep.h>
#endif
bool ledOn = true;
void toggleLed() {
if (ledOn) {
ledOn = false;
digitalWrite(LED_BUILTIN, HIGH);
} else {
ledOn = true;
digitalWrite(LED_BUILTIN, LOW);
}
scheduler.scheduleDelayed(toggleLed, 1000);
}
void setup() {
#ifdef ESP32
// ESP_PD_DOMAIN_RTC_PERIPH needs to be kept on
// in order for the LED to stay on during sleep
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
pinMode(LED_BUILTIN, OUTPUT);
scheduler.schedule(toggleLed);
}
void loop() {
scheduler.execute();
}
Simple blink with Runnable:
#include <DeepSleepScheduler.h>
#ifdef ESP32
#include <esp_sleep.h>
#endif
class BlinkRunnable: public Runnable {
private:
bool ledOn = true;
const byte ledPin;
const int delay;
public:
BlinkRunnable(byte ledPin, int delay) : ledPin(ledPin), delay(delay) {
pinMode(ledPin, OUTPUT);
}
virtual void run() {
if (ledOn) {
ledOn = false;
digitalWrite(ledPin, HIGH);
} else {
ledOn = true;
digitalWrite(ledPin, LOW);
}
scheduler.scheduleDelayed(this, delay);
}
};
void setup() {
#ifdef ESP32
// ESP_PD_DOMAIN_RTC_PERIPH needs to be kept on
// in order for the LED to stay on during sleep
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
BlinkRunnable *blinkRunnable = new BlinkRunnable(LED_BUILTIN, 1000);
scheduler.schedule(blinkRunnable);
}
void loop() {
scheduler.execute();
}
The following example sketches are included in the DeepSleepScheduler library.
You can also see them in the Arduino Software (IDE) in menu File->Examples->DeepSleepScheduler.
SLEEP_DELAY
to allow serial write to finish before entering sleep/**
Schedule the callback method as soon as possible but after other tasks
that are to be scheduled immediately and are in the queue already.
@param callback: the method to be called on the main thread
*/
void schedule(void (*callback)());
/**
Schedule the Runnable as soon as possible but after other tasks
that are to be scheduled immediately and are in the queue already.
@param runnable: the Runnable on which the run() method will be called on the main thread
*/
void schedule(Runnable *runnable);
/**
Schedule the callback method as soon as possible and remove all other
tasks with the same callback. This is useful if you call it
from an interrupt and want one execution only even if the interrupt triggers
multiple times.
@param callback: the method to be called on the main thread
*/
void scheduleOnce(void (*callback)());
/**
Schedule the Runnable as soon as possible and remove all other
tasks with the same Runnable. This is useful if you call it
from an interrupt and want one execution only even if the interrupt triggers
multiple times.
@param runnable: the Runnable on which the run() method will be called on the main thread
*/
void scheduleOnce(Runnable *runnable);
/**
Schedule the callback after delayMillis milliseconds.
@param callback: the method to be called on the main thread
@param delayMillis: the time to wait in milliseconds until the callback shall be made
*/
void scheduleDelayed(void (*callback)(), unsigned long delayMillis);
/**
Schedule the callback after delayMillis milliseconds.
@param runnable: the Runnable on which the run() method will be called on the main thread
@param delayMillis: the time to wait in milliseconds until the callback shall be made
*/
void scheduleDelayed(Runnable *runnable, unsigned long delayMillis);
/**
Schedule the callback uptimeMillis milliseconds after the device was started.
Please be aware that uptimeMillis is stopped when no task is pending. In this case,
the CPU may only wake up on an external interrupt.
@param callback: the method to be called on the main thread
@param uptimeMillis: the time in milliseconds since the device was started
to schedule the callback.
*/
void scheduleAt(void (*callback)(), unsigned long uptimeMillis);
/**
Schedule the callback uptimeMillis milliseconds after the device was started.
Please be aware that uptimeMillis is stopped when no task is pending. In this case,
the CPU may only wake up on an external interrupt.
@param runnable: the Runnable on which the run() method will be called on the main thread
@param uptimeMillis: the time in milliseconds since the device was started
to schedule the callback.
*/
void scheduleAt(Runnable *runnable, unsigned long uptimeMillis);
/**
Schedule the callback method as next task even if other tasks are in the queue already.
@param callback: the method to be called on the main thread
*/
void scheduleAtFrontOfQueue(void (*callback)());
/**
Schedule the callback method as next task even if other tasks are in the queue already.
@param runnable: the Runnable on which the run() method will be called on the main thread
*/
void scheduleAtFrontOfQueue(Runnable *runnable);
/**
Check if this callback is scheduled at least once already.
This method can be called in an interrupt but bear in mind, that it loops through
the run queue until it finds it or reaches the end.
@param callback: callback to check
*/
bool isScheduled(void (*callback)()) const;
/**
Check if this runnable is scheduled at least once already.
This method can be called in an interrupt but bear in mind, that it loops through
the run queue until it finds it or reaches the end.
@param runnable: Runnable to check
*/
bool isScheduled(Runnable *runnable) const;
/**
Returns the scheduled time of the task that is currently running.
If no task is currently running, 0 is returned.
*/
unsigned long getScheduleTimeOfCurrentTask() const;
/**
Cancel all schedules that were scheduled for this callback.
@param callback: method of which all schedules shall be removed
*/
void removeCallbacks(void (*callback)());
/**
Cancel all schedules that were scheduled for this runnable.
@param runnable: instance of Runnable of which all schedules shall be removed
*/
void removeCallbacks(Runnable *runnable);
/**
Acquire a lock to prevent the CPU from entering sleep.
acquireNoSleepLock() supports up to 255 locks.
You need to call releaseNoSleepLock() the same amount of times
to allow the CPU to enter sleep again.
*/
void acquireNoSleepLock();
/**
Release the lock acquired by acquireNoSleepLock(). Please make sure you
call releaseNoSleepLock() the same amount of times as acquireNoSleepLock(),
otherwise the CPU is not allowed to enter sleep.
*/
void releaseNoSleepLock();
/**
return: true if the CPU is currently allowed to enter sleep, false otherwise.
*/
bool doesSleep() const;
/**
Configure the supervision of future tasks. Can be deactivated with NO_SUPERVISION.
Default: TIMEOUT_8S
@param taskTimeout: The task timeout to be used
*/
void setTaskTimeout(TaskTimeout taskTimeout);
/**
Resets the task watchdog. After this call returns, the currently running
Task can run up to the configured TaskTimeout set by setTaskTimeout().
*/
void taskWdtReset();
/**
return: The milliseconds since startup of the device where the sleep time was added.
This value does not consider the time when the CPU is in infinite deep sleep
while nothing is in the queue.
*/
unsigned long getMillis() const;
/**
Sets the runnable to be called when the task supervision detects a task that runs too long.
The run() method will be called from the watchdog interrupt what means, that
e.g. the method delay() does not work.
On AVR, when run() returns, the CPU will be restarted after 15ms.
On ESP32, the interrupt service routine as a whole has a time limit and calls
abort() when returning from this method.
See description of SUPERVISION_CALLBACK and SUPERVISION_CALLBACK_TIMEOUT.
@param runnable: instance of Runnable where the run() method is called
*/
void setSupervisionCallback(Runnable *runnable);
/**
This method needs to be called from your loop() method and does not return.
*/
void execute();
enum TaskTimeout {
TIMEOUT_15Ms,
TIMEOUT_30MS,
TIMEOUT_60MS,
TIMEOUT_120MS,
TIMEOUT_250MS,
TIMEOUT_500MS,
TIMEOUT_1S,
TIMEOUT_2S,
TIMEOUT_4S,
TIMEOUT_8S,
NO_SUPERVISION
};
#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.All following options are to be set before the include where no LIBCALL_DEEP_SLEEP_SCHEDULER
is defined.
#define SLEEP_DELAY
: Prevent the CPU from entering sleep for the specified amount of milliseconds after finishing the previous task.#define SUPERVISION_CALLBACK
: Allows to specify a callback Runnable
to be called when a task runs too long. When
the callback returns, the CPU is restarted after 15 ms by the watchdog. The callback method is called directly
from the watchdog interrupt. This means that e.g. delay()
does not work.#define SUPERVISION_CALLBACK_TIMEOUT
: Specify the timeout of the callback until the watchdog resets the CPU. Defaults to WDTO_1S
.#define AWAKE_INDICATION_PIN
: Show on a LED if the CPU is active or in sleep mode.#define SLEEP_MODE
: Specifies the sleep mode entered when doing deep sleep. Default is SLEEP_MODE_PWR_DOWN
.#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.#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 and example AdjustSleepTimeCorrections.#ESP32_TASK_WDT_TIMER_NUMBER
: Specifies the timer number to be used for task supervision. Default is 3.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.#define
. You can still include the header file in multiple files of a project by using #define LIBCALL_DEEP_SLEEP_SCHEDULER
. See Define Options.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.SLEEP_MODE_PWR_DOWN
and for task supervision. It can therefore not be used for other means.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.SLEEP_TIME_XXX_CORRECTION
(see Define Options and example AdjustSleepTimeCorrections).SLEEP_MODE_PWR_DOWN
) while scheduling with tight time constraints. To do so, use the methods acquireNoSleepLock()
and releaseNoSleepLock()
(see Methods). Please report values back to me if you do time measuring, thanks.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.Enhancements and improvements are welcome.
Arduino DeepSleepScheduler Library
Copyright (c) 2018 Peter Rosenberg (https://github.com/PRosenb).
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.