-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Should schedule_function() call esp_schedule()? #7661
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
Comments
Here is a quick hack for a test-proposal:
static inline void esp_yield_within_cont() __attribute__((always_inline));
static void esp_yield_within_cont() {
+ run_scheduled_recurrent_functions();
cont_yield(g_pcont);
s_cycles_at_yield_start = ESP.getCycleCount();
- run_scheduled_recurrent_functions();
}
extern "C" void __esp_yield() {
if (can_yield()) {
esp_yield_within_cont();
}
} With this patch, (recurrent) scheduled functions are called before effectively yielding instead of after. Background: recurrent scheduled functions are scheduled functions:
edit: embedded Ticker library does not use recurrent scheduled functions. |
Thanks for your suggestion. However, I think it does not solve the problem yet, since recurrent functions are also only called when the loop is either running, or about to be suspended with I guess that this does mean that my original request to call I haven't tested this yet (no hardware nearby), but IIUC you suggest changing my tests sketch like this:
AFAICS, even with your suggested patch to the core, this will not run the recurrent function? Given that the recurrent function can be run at a later time as well, it could also replace the Ticker completely. However, AFAICS that won't fix the problem (now nothing runs after 5 seconds, rather than just the ticker firing and scheduling a function that does not run). Also, AFAICS recurrent functions do not actually set timers, so they cannot cause wakeup from sleep, unlike Ticker (which is the reason I'm For completeness, I think this would look like this:
|
Why are you calling esp_yield inside the loop? remove that.
I would add logic to the loop to detect the start and end of your network transaction.
|
From my original post:
And:
|
See my edit above. |
When I press a button, usually minutes apart.
To stay connected to the AP and have the fastest possible response time after pressing the button combined with the lowest power usage (battery powered application).
Yes, but that would have longer response time due to needing to reassociate to the network.
Because doing network traffic (i.e. an HTTP request and waiting from the response) did not work from inside the button ISR (which makes sense), but also not from the Ticker handler IIRC (which I think is just another task and not a timer ISR, but it still didn't work). It's been a while since I dug into this, but IIRC some of the networking code was written in a way that only worked from inside the main loop / CONT.
Note that my actual case is slightly complicated since the work is done 1000ms after the interrupt (which is what I'm using Ticker for now), but the flow could be the same (ISR triggers Ticker, ticker triggers start condition). But this would also require the interrupt to call But the main point of this request is that IMHO |
That's not what I meant by "why a scheduled function at all". You can't do network ops from an ISR, and the Ticker cb executes in SYS, not in CONT, so also no. But that doesn't mean a scheduled function. What I meant was: why are you set on a scheduled function at all? There are other ways to trigger loop code, such as a global flag, which is what I'm suggesting with the start/end. You can think of it as a state machine.
Of course, I'm simplifying. There could be more states needed in your code, that's something you would need to figure out. |
Thanks for that suggestion, I can see how that would indeed work. However, that's not the way I want to structure my code, scheduling functions to be called ASAP with Any response to the suggestion itself? If the answer to my suggestion is "No, |
At top level, you're not supposed to use esp_yield/esp_schedule yourself. Those are low level functions that deal with the interactions between SYS and CONT. The user is supposed to only call yield/delay, which wraps the details. As such, if you do decide to use them, strictly speaking you are responsible for doing it correctly. Having said that, your general idea seems to have merit, and I would say it's a valid use case. Changing how the scheduled functions are currently called on switching to SYS and back could cause trouble. We've already gone through several incarnations of that, and I'd prefer to keep it as it is, i. e. call on returning. There is also the fact that the interrupt could come in while there is an ongoing delay, in which case things might get... interesting. Let's see if your current approach can be improved. |
From OP:
After rereading, there is a misunderstanding from my side.
The Scheduled function will not work (*) if there is an (*) except if delay is 0 and recurrent scheduled functions are used, that leads to your first answer: If we claim that every
should be written like:
Thus, I agree with you about this:
And disagree with that:
.. because
You had the answer before I made my own way through it:
That's indeed my POV. And your use-case should go into or update an already existing example. |
Thanks for more in-depth replies, much appreciated. Responding to @devyte first:
Yeah, I guessed as much, but I couldn't think of any way to get the light sleeping to work correctly without
Ideally, there would be a well-supported/well-defined API to support this usecase, if
Do you mean the case where, inside the delay CONT is currently suspended but will be resumed after a timer, so calling This one-on-one mapping is something that might indeed be tricky to take into account (In my original sketch that I did not show here, these "extra resumes" might also occur, though since the loop does not do anything except Thinking about this, maybe a higher level API for this might also solve the one-on-one mapping problem, by exposing a suspend_loop() function that suspends the loop and resume_loop() that resumes the loop only if it is currently suspended? I.e. something like:
The idea as that there is a separate mechanism (the However, after writing the above, I realize that this does not actually work like this yet, i.e. if you Then, @d-a-v writes:
I think both of these statements suggest that you're thinking about calling Another way to look at it, is to note that However, I do now realize that my suggestion could only possibly work for
Yeah, I think I can imagine how that would be required. Then having a function that maybe calls
That would indeed make sense. Maybe the documentation could also be somewhat updated about how this stuff works internally (even if a better API for this would be added). |
This API is tricky.
Historically, scheduled functions are triggerred "ASAP" meaning at next
Someone may correct me about what's above. Indeed that should be documented. That is (probably) mainly the result of fine hacking nonos-sdk to cope with both its requirement and arduino needs (just check |
@matthijskooijman Did we answer to the issue you opened ? edit:
by running |
Well, I think the answer is "No, schedule_function() should not call esp_schedule(), since that messes up the balance", but then I think a new API (for suspending the mainloop and returning to it from elsewhere) might be useful to better support my usecase. I'm not quite sure how I'd want this to work yet, though, that needs a bit more thought. Would it be ok to leave this issue open for further discussion on this subject? |
We didn't consider a call to That would break the "one We could add a (edit: to be clear: that would be a new method from ticker with this functionality: "schedule_function()+esp_schedule() in ISR") |
That would work, but I was thinking of (also) adding some extra API to be called from the |
@matthijskooijman This is taken from the Sketch source:
Output:
pushing button
This suggests, that no, there is no need to call |
So here's another one, with the buzzer as "proof" that at least the PWM/waveform timer gets restored. Actual effect on power consumption is pending, @Tech-TX will add that info shortly:
|
@mcspr AFAIK the changes in PR #7956 don't contain any fixes to the internals of the power saving modes, only the API is being cleaned up. If that's right, please let me point out that WIFI_SHUTDOWN / WIFI_RESUME do not work with light sleep, the MCU does not return from light sleep when the pin is pulled low. As you can tell from the sketch above, WIFI_OFF and the full connect sequence do work at present. |
Current during Timed/Forced Light Sleep is roughly 0.49mA, well within the range for Light Sleep. That's for the second chunk of code. I'll test the first one presently. I'm running debug level = CORE, should I disable that? It tracks WiFi connections and the sleep mode commands, but may be corrupting the output as the core tries to tell us it's going to sleep.
When it wakes up, there's junk in the TX buffer, so apparently that didn't get flushed. Oh, THAT'S rude. I turned off debug, and got an illegal instruction as it was initially trying to connect WiFi. It only did it once, it was OK after that.
BTW, turning off debug DID eliminate the random junk in the TX buffer when it came out of Light Sleep. |
One can see that I switched from |
Not sure of the ping and the example above. Following the note about the isr, this works... as expected? #include <Arduino.h>
#include <Ticker.h>
#include <ESP8266WiFi.h>
bool isr { false };
bool fpm { false };
void isr_wakeup() IRAM_ATTR;
void isr_wakeup() {
isr = true;
}
void fpm_wakeup() IRAM_ATTR;
void fpm_wakeup() {
fpm = true;
}
void setup() {
#if 1
enableWiFiAtBootTime();
#endif
Serial.begin(115200);
WiFi.persistent(false);
WiFi.mode(WIFI_OFF);
pinMode(5, INPUT_PULLUP);
attachInterrupt(5, isr_wakeup, ONLOW_WE);
static Ticker sleep;
sleep.once_ms(100, []() {
Serial.println("opening fpm");
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
wifi_fpm_open();
wifi_fpm_set_wakeup_cb(fpm_wakeup);
wifi_fpm_do_sleep(0xFFFFFFF);
});
}
void loop() {
delay(100);
if (isr) {
isr = false;
Serial.println("triggered isr wakeup cb");
}
if (fpm) {
fpm = false;
Serial.println("triggered fpm wakeup cb");
}
Serial.print('.');
} On setup() system is put to a halt, touching GND with GPIO5 wakes things up and starts flooding the console with dots |
@mcspr Um. No. That's an obfuscation. Using a timer when timers need to be stopped. The way that the |
I have not read the example, just the comment about the ONLOW_WE mode. |
You don't think you code example is open to race conditions? What are you trying to prove with it, irrelevant loop, no schedule_function? |
This may be a blueprint for inclusion as a light-sleep convenience API. It also attempts to show how ISRs can be switched between wakeup from sleep mode and regular working mode. There is a small window where bouncing signals on the GPIO may prevent the wakeup mode to enter correctly, in that case, the MCU sleeps depending on the timeout parameter.
|
@dok-net Works fine for me Dirk, 0.495mA when it's in Sleep, wakes on timer expire or with external interrupt. |
@dok-net, thanks for your suggestions. Took me a while to find time to look at all comments, working my way through them now. It's been a while since I worked with ESP8266 code for this project, so when I say things that do not make sense, I might be working from faulty memory, so apologies in advance :-p In the first version of your sketch (found in the edit history of #7661 (comment)), you used a Your last example seems to indeed solve this by using (through Looking at your last example, I believe that instead of letting However, I do have a few questions about your code:
|
@matthijskooijman It's good seeing that you mostly agree with the way I've implemented things. With regard to where and when the wakeup callback does or does not get invoked, I would like to ask you to run the supplied example sketches and see for yourself - testing and reporting back from another person beats me checking my own code any time. Clearing and restoring the timer_list was previously discussed elsewhere, I've also performed testing on it. I would like to point on that enforcement of the forced light sleep is the stated purpose of the API, so no, it's not permitted for any other timers but the RTC timer, to end the forced light sleep. In fact, it would only happen as a race condition anyway, given how these timers cannot trigger when light sleep is truly engaged, stopping the CPU and in consequence the responsible timer clock for them. Now, concerning your observations about |
I now see that my comment |
Basic Infos
I tried, but the latest master requires GCC10 and I'm not sure how to get this installed.
Platform
Settings in IDE
-fqbn=esp8266:esp8266:huzzah:xtal=80,vt=flash,exception=legacy,ssl=all,eesz=4M2M,ip=lm2f,dbg=Serial,lvl=CORE,wipe=none,baud=460800
Problem Description
I'm building a sketch that essentially always sleeps, but needs to wakeup on a pin interrupt and then do some network traffic.
Some observations & assumptions:
esp_yield()
in my loop for when it has nothing left to do (to ensure the CPU can sleep, rather than continuously having to keep callingloop()
).attachInterrupt()
with e.g. theONHIGH_WE
.schedule_function
to call some code after the next loop iteration.esp_yield()
, will not return untilesp_schedule()
is called elsewhere.schedule_function()
to schedule some code to run later, andesp_schedule()
to actually make sure the loop function continues and the scheduled function can run.schedule_function()
(e.g.Ticker.once_scheduled()
) that I have no control over, this breaks (so e.g. for Ticker I have to do a non-scheduledTicker.once()
with a function that calls bothschedule_function()
andesp_schedule()
, but that might not be always possible with other interfaces).Maybe I'm misunderstanding how to approach my original problem, but if the above approach is correct, then maybe it would be good if
schedule_function()
would just always callesp_schedule()
? (or, if it is harmful to call it when already inside the loop, then maybe the call can be done only when outside the main loop, e.g. by checkingcan_yield()
or so?).Doing so would mean that
schedule_function()
would just always work to ensure the given function is called, even when the mainloop is not currently running?Sketch
To illustrate the problem, here's something that I think should turn the led on pin 0 on (
LOW
), and schedule a function that turns the led off after 5 seconds. I left out the actual sleeping and interrupt handling out, to simplify the example. If it helps to clarify, I can provide a more complete example with those as well.Debug Messages
(nothing else appears on serial)
The text was updated successfully, but these errors were encountered: