Makuna / Task

Arduino Nonpreemptive multitasking library. Please refer to the Wiki for more details. Please use the GitHub Discussions to ask questions as the GitHub Issues feature is used for bug tracking.
GNU Lesser General Public License v3.0
115 stars 34 forks source link

Sleep and delay() #35

Open skorokithakis opened 4 years ago

skorokithakis commented 4 years ago

I'm using this excellent library to run tasks, but I want the ESP to sleep otherwise. I have implemented light sleep, but that only runs on a delay() call. If my fastest task is 50ms and I don't care about accuracy (I'm fine being 20ms over), can I add a 20ms delay() call in the loop? The only other thing that runs is the Task scheduler.

Would it be possible to have Task optionally call delay() in its downtime, so I don't have to do it?

Makuna commented 4 years ago

esp32 or Esp8266?

#if defined(ARDUINO_ARCH_ESP8266)
    // must have GPIO16 tied to RST
    void EnterSleep(uint32_t microSeconds = 0,
        void* state = NULL,
        uint16_t sizeofState = 0,
        WakeMode mode = WAKE_RF_DEFAULT);
    bool RestartedFromSleep(void* state = NULL,
        uint16_t sizeofState = 0 );
#elif defined(ARDUINO_ARCH_ESP32)
    // ESP32 support for sleep not implemented yet

Back when I implemented this the ESP32 sleep features were not stabile enough to use them. I don't know how much the esp8266 sleep as changed and needs some love.

taskmanager.cpp line 110, (for esp8266) adding a delay(n) where n = 0,1,2 would be fine for the idle sleep (light sleep). Something similar for ESP32 should be added.

But, this general area is where an automatic idle level sleep would be triggered. Esp8266 there is the EnterSleep method also, for the real sleep modes.

skorokithakis commented 4 years ago

Sorry, ESP8266. My use case is one where I update an OLED screen every 50ms, so I delay(20) to take advantage of light sleep, but since Task can know exactly when the next task is going to trigger, it could sleep for longer.

This is assuming that delaying for longer has power advantages, otherwise I guess just adding a delay of 1 ms in my code would be equivalent.

Makuna commented 4 years ago

I don't believe calling delay causes any power advantage on esp8266. You have to enter sleep to get any advantages; which is an explicit call. But we can ask on the esp8266 Arduino gitter channel and get a quick answer for that.

Note that they request calling delay() or yield() when in tight loops. But the way you are supposed to call the taskmanager.Loop() within the sketches loop() means that it quickly enters and leaves the sketches loop() and thus there is no need to call delay() or yield().

skorokithakis commented 4 years ago

If you're on light sleep, calling delay does save power, it took me from 80 mA down to 60ish. In another program I wrote that used the Task library, I noticed that some interrupts wouldn't get processed sometimes unless I called delay(1) in the loop, for some reason, it was very odd.

Makuna commented 4 years ago

What about calling delay(0) or just yield()?

If either of these will cause the same ma drop, then I would be all in for putting that into the location I mentioned above.

skorokithakis commented 4 years ago

I will measure various delays and report back, though I think it only benefits the CPU for the amount of time it's sleeping. Will check, though.

skorokithakis commented 4 years ago

Alright, so:

delay() ms amp draw
None 77mA
0 77 mA
1 26 mA
5 22-43 mA (very spiky)
20 22 mA
50 22 mA

Unfortunately I don't have very reliable equipment, these tests were done with a Ruideng UM25C USB meter, so the refresh frequency is pretty low. Anything under 20 was pretty spiky, though, so I ended up setting the delay to 20ms for my needs.

If you want to look at the code, it's here: https://gitlab.com/stavros/do-not-be-alarmed

Makuna commented 4 years ago

Ok, with this knowledge, then I would not include it in my library.

BUT it does warrant a FAQ entry in the wiki and maybe an update to the samples to include a delay(1) with a comment in the main loop() after calling taskmanager.Loop(). like

void loop() {
...
taskManager.Loop();
delay(1); // (1-100) used to allow the processor to idle, depending on sketch this value can be changed
}
skorokithakis commented 4 years ago

That works, I was mainly wondering because you advise against calling delay() in the user code.

My other question would be if, since you know when the next task is going to run, you could delay() for that amount in the scheduler, so the CPU yields. So, for example, if I had two tasks, one every 50ms and the other every 90ms, you'd usually delay(50) inside your loop, except for cases where you knew the next task was less than 50ms away. Would that be easy/valuable, or would it just be too much work because you don't exactly know when to wake up?

Makuna commented 4 years ago

It does know "the time interval to the next task".
The issue are:

skorokithakis commented 4 years ago

The time interval could be in microseconds or it could be in milliseconds; depending on the TASK_MICRO_RESOLUTION define.

Ah okay, I don't know why this is a problem but maybe you could just skip the delay if it's less than a few ms, where it wouldn't be effective anyway?

The time interval could be in microseconds or it could be in milliseconds; depending on the TASK_MICRO_RESOLUTION define.

Yes, I do this in other applications and this would definitely need to be an option when instantiating.

I am positive the results you are seeing are Esp8266 specific, most architectures will not like having the delay; but there is a spot for esp8266 specific code where it would go.

This is very likely (although I think even Arduino recommends adding a delay(0) in the loop function so interrupts can be processed, but I'm not sure), but it could possibly be an ESP-specific thing, as you say.

Either way, this isn't a big deal, I can just delay() outside the code, but you could do it with more precision (since you know when the next task will run). The larger issue I had was with clarifying in the README whether "don't call delay() in your code!" was something we should absolutely never do or if we'd just miss events, as one would expect (and it seems to be the latter).

Makuna commented 4 years ago

I really think you should put together a minimal sketch (without task) and check an empty loop versus a loop with a delay(1) and then go to the esp8266 team and ask why this is so. Since they are calling loop, they already service all the normal things behind the scenes.

Makuna commented 4 years ago

Feature request: Expose a user provided feature to inject action when next task is greater than wait period and would possibly enter an "idle mode" for N milliseconds. Today AVR will enter a sleep mode but other architectures it maybe more sketch specific as outlined above.