8000 ESP32 enhancement: support automatic light-sleep / dynamic freq scaling · Issue #5459 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

ESP32 enhancement: support automatic light-sleep / dynamic freq scaling #5459

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

Open
tve opened this issue Dec 27, 2019 · 8 comments
Open

ESP32 enhancement: support automatic light-sleep / dynamic freq scaling #5459

tve opened this issue Dec 27, 2019 · 8 comments

Comments

@tve
Copy link
Contributor
tve commented Dec 27, 2019

On the esp32 automatic light sleep / dynamic frequency scaling / power management puts the CPU to sleep when all tasks are idle thereby reducing the power consumption to about 1mA instead of around 40mA @ 160Mhz. The special feature of auto light sleep is that it can maintain a Wifi association by waking up at the appropriate times to receive AP beacons and respond to them. This ticket proposes to discuss how to enable auto light-sleep.

The first issue is that FREERTOS_USE_TICKLESS_IDLE needs to be enabled in sdkconfig.h and that machine.freq() must set pm.light_sleep_enable = true.

The next issue is that mp_hal_stdin_rx_chr and mp_hal_delay_ms in mphalport.h do not call vTaskDelay and instead call ulTaskNotifyTake which does not seem to enable auto light sleep (need to dig into this). Specifically, in the idle loop without wifi enabled ulTaskNotifyTake almost always returns after 100ms, which seems to be too little for light sleep to kick in.

The use of vTaskDelay got switched to ulTaskNotifyTake as part of 14ab81e because it affects interrupt latency too much, see #3894.

Adding a call to vTaskDelay(4) in there for test purposes results in the expected power savings (a value of 4 is the minimum for light sleep to kick in). However, it messes up gpio: they all(?) go low, which is unexpected (I expected ESP-IDF to handle this appropriately). The gpio issue also affects the uart: the console no longer works unless one manages to get a char in while mp is polling. Mysteries...

Update: I had not understood the second parameter of ulTaskNotifyTake: setting that to 4 also causes light sleep to kick in.

@tve
Copy link
Contributor Author
tve commented Dec 27, 2019

Update: I had not fully understood how much the auto-light-sleep mode affects gpio pins as well as uarts. My sense is that this makes its use very questionable in a general-purpose firmware such as micropython. Clever coding and getting some settings just right can probably work around many issues, but in the end it's also likely to create a never ending stream of issues and unmaintainable tricky code.

I looked into something related, which is the dynamic frequency scaling, e.g. setting a 10Mhz lower clock frequency. That seems like a more productive direction because, with some caveats, most peripherals continue to work properly. The main caveat is that the clocks need to be chosen carefully so they're not affected by CPU freq scaling. Another related option may be to run single-core and turn one CPU off altogether.

My plan is:

  • add a min_freq named parameter to machine.freq() as well as a named auto_light_sleep parameter, but document the latter as being highly experimental / not-really-usable.
  • look into single core operation
  • document some power consumption results

One open issue I don't quite know how to resolve is the FreeRTOS tickless-idle: I don't know whether there is a downside to enabling it in general. I believe there isn't, but I don't know enough about its side-effects.

@tve
Copy link
Contributor Author
tve commented Dec 29, 2019

Proposal for implementing what I've figured out so far: #5473

tannewt added a commit to tannewt/circuitpython that referenced this issue Oct 13, 2021
@peter9477
Copy link

Just a note on FreeRTOS tickless idle. I've been using it for a few years in an nRF52832 project, and at several points have had to delve into the relevant code a bit to explore while troubleshooting some issues (which turned out not to be directly related). Possibly a few comments will help others understand it better, assuming the implementation on nRF is similar on your own system.

Roughly speaking, tickless idle consists of a mechanism whereby while the code is active (i.e. not attempting to go idle) the RTC is allowed to generate interrupts every tick, which increments the system tick counter and which processes any completed time-based events. When the system wants to go idle, because there's nothing known to be active except some delays that are far enough in the future to warrant it, the per-tick RTC interrupt is shut off, and a comparator interrupt is turned on instead for the future time, and a sleep instruction is issued (__WFE, on Cortex-M4).

When the system wakes up, either because the RTC has overflowed or hit the comparator value, or because some other unexpected event occurred (e.g. other peripheral interrupt), the code figures out how long it was actually asleep and fixes up the tick counter, and then things continue normally.

So basically, it's capable of turning off the tick interrupt when it wants to idle sleep ("tickless idle"), and making suitable adjustments when it comes out of idle sleep again, and that's about it.

@EternityForest
Copy link

gpio_hold_en can apparently maintain GPIO state, right?

What if you had a new user function to manually enter auto light sleep, with the understanding that you won't actually be able to do much.

From there you could sit in an idle loop waiting for commands, and manually exit when you get one.

@EternityForest
Copy link

Update: I've got auto light sleep working perfectly well under manual control, snd it saves a ton of power.

I've patched the time.sleep() function to calculate the longest possible block of time, so that auto light sleep can kick in, and I'm working on a function cancel_sleep() to call from an interrupt to stop a time.sleep().

I don't have that fully working, but even just being able to sleep for a fixed interval without losing the WiFi connection is a really big deal, I'm still saving a lot of power just by waking up once a second to see if the user is holding the wake button.

Coming from Arduino, there's a vast number of sketches based on the "Sleep for a while, do something, sleep some more" pattern, and I think an auto_sleep(val) covers a lot of the most common use cases.

https://github.com/EternityForest/micropython/tree/dev-branch

@jonnor
Copy link
Contributor
jonnor commented Jul 13, 2024

@EternityForest were you able to get further on your auto_sleep functionality?

@EternityForest
Copy link

@jonnor I haven't had time to work on this one in a long time, so pretty much everything is just in the(Very far out of sync) repo.

If there's actually interest in merging something like this to upstream I could possibly work on a PR, but it seemed like everyone was too busy to think much about it, so I didn't go any further.

@jonnor
Copy link
Contributor
jonnor commented Jul 13, 2024

Thanks for the quick response @EternityForest !

I am not a maintainer, so I cannot speak for them. But I found your suggested approach with a manual function to enter/leave the mode to be a pragmatic way forward. I just today wanted to build a low-power Bluetooth, and the current state of MicroPython is quite limiting. Being able to access the ESP32 autosleep functionality would seemingly make a large difference, and manually calling machine.autosleep() seems like it would work great in my case. So I would at least be willing to test and give feedback on a PR :)

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

No branches or pull requests

5 participants
0