Skip to content

Feature request: MCU sleep, when all tasks in delay() #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
hattesen opened this issue Dec 24, 2018 · 7 comments
Closed

Feature request: MCU sleep, when all tasks in delay() #31

hattesen opened this issue Dec 24, 2018 · 7 comments

Comments

@hattesen
Copy link

For devices where power consumption should be minimized (e.g. battery operated equipment), it would be a great if the Scheduler library would be able to identify situations, where all the executing tasks are inside a delay(), and in those situations enter a low-power sleep mode.

In embedded systems, it is often the case, that many tasks only need to run once every tens or hundreds of milliseconds, and when reading temperature sensors, one reading per second is often more than enough.

If the code for the tasks would include a call to delay() (possibly at the end of the taskLoop() function) rather than a call to yield(), it would result in all tasks executing code inside the delay() implementation (measuring elapsed time and calling yield()), effectively busy-waiting, as no tasks require any task code to be run.

Implementation

It would be fairly trivial to provide an option (#define DELAY_SLEEP) that, when enabled, at the start of the delay() implementation, would traverse the task list and identify if all tasks are currently executing a delay(), and if so, determine the number of microseconds left until the first task is scheduled to exit the delay() function, and return to the taskLoop() code. The MCU could, at that point, be put into one of the sleep modes supported by the MCU.

The optimal sleep mode depends on the concrete application and MCU architecture, and should be chosen based on a number of parameters:

  • Requirement to wake up from internal interrupts (e.g. pin-change)
  • Required active peripheral interfaces (UART, SPI, ...)
  • Use of Analog comparators
    Choosing the optimal sleep mode will always be a balance between power consumption and responsiveness to events.

My proposal is to leave it up to the Scheduler library user, to provide a suitable implementation (function pointer) to put the MCU into a suitable sleep mode, for a specified number of micro-seconds sleep(microSec). It would be up to this user provided sleep implementation, to...

  1. Set up a timer to generate an interrupt when the sleep duration (requested number of microseconds) have passed.
  2. Enter the appropriate SLEEP mode.
  3. Upon waking up: if wake-up was caused by something other than the sleep-duration timer, the SLEEP mode should be re-entered to prevent a premature return to the delay() task executions.
@mikaelpatel
Copy link
Owner

@hattesen Thanks for your Christmas Feature Request. This functionality was available in an early implementation (and actually available in Cosa). To minimize the size of the library this was removed as it required 1) a background task to perform the sleep mode, 2) alternative delay() implementation.
I arrived at the conclusion that this type of functionality is best implemented by the application programmer, e.g. the main-task in the sketch could detect the state and perform the power-down with the necessary sleep mode. I see now that I have not added an example that shows how to do this in detail. Cheers! Mikael

@hattesen
Copy link
Author

@mikaelpatel wasn't aware of the previous implementation and the rationale for removing it. It is always a delicate balance between features, complexity and memory footprint.
Could the sleep feature not be enabled/included using a #define, and thus not have any impact when not enabled?

A user-domain example of how to manage MCU sleep mode would be good. Intercepting calls to delay() would be able to maintain an isInDelay for each task, and the main task could then enter sleep mode, which would be awoken by the next millisecond timer "system tick". So in effect, the main task would never call delay(), but rather enter sleep mode at each loop pass, provided the other tasks are all inside the delay loop, and otherwise call yield().

I may have a go at an implementation myself, and issue a pull request.

@mikaelpatel
Copy link
Owner

mikaelpatel commented Dec 26, 2018

@hattesen
Alternative delay() implementation is difficult in current cores. Please see issue https://github.com/arduino/Arduino/issues/8370.

@mikaelpatel
Copy link
Owner

mikaelpatel commented Dec 26, 2018

Sketch of implementation:

  1. Introduce variable that counts number of running tasks. Incremented by startLoop() for each new task.
  2. Add Scheduler.delay(ms) which decrements number of tasks before waiting and increments afters. If the number of running tasks is zero a power down callback function is called instead of yield().

@hattesen
Copy link
Author

hattesen commented Dec 27, 2018

The implementation sketch that you have provided, keeping a running count of the number of tasks, NOT currently executing a delay(), and executing sleep() rather than yield() if that count is 0.

The implementation Scheduler.delay(ms) works for any tasks that explicitly calls this implementation from within their taskLoop(), but unless library calls to Arduino Core delay() can be intercepted (by overriding the delay() implementation), such calls will not allow sleep mode, while being executed, as they are not being tracked by the above mentioned method.

As I understand the problem, the Arduino Core delay() function declaration cannot be overridden, until the original declaration is made weak (ref: arduino/ArduinoCore-avr#57). Given, that the toolchain for Arduino development relies on GCC, would it be possible to use the --wrap linker option to coerce calls to Arduino Core's delay() to be redirected to a __wrap_delay() implementation provided by the Scheduler library, that supports task count housekeeping as well as determining whether sleep() or yield() should be called during the delay execution.

ld ... -Wl,--wrap=delay

... obviously, it would require a way to be able to "inject" linker options into the build process, which is probably not straightforward, or even possible, when using the Arduino IDE.

@infinity-computers-dot-net

Any movement on this?

@mikaelpatel
Copy link
Owner

@infinity-computers-dot-net
I think this discussion is actually over. The result was that this issue should be solved with an application level task. What remains is (maybe) to add an example. Cheers, Mikael.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants