knowit / snappysense

3 stars 1 forks source link

Incorporate deep sleep mode, investigate power usage and running off battery #5

Open lars-t-hansen opened 1 year ago

lars-t-hansen commented 1 year ago

With deep sleep mode, it should be possible to run the device off battery and not off USB power. But this requires redesigning the main loop, and it may require spinning down some devices. There's some PoC code from Gunnar to do all of this.

lars-t-hansen commented 1 year ago

The code now has a lightweight sleep mode: it powers off peripherals when the sleep window in the scheduler becomes long enough (which it usually will be in production mode). A deep sleep mode competes (for lowest power usage) with this lightweight sleep mode.

lars-t-hansen commented 1 year ago

Experiment: running off battery, and being careful about powering down peripherals and wifi and having a 1h rest period after communication and before monitoring, and having fairly short communication and monitoring and slideshow windows so that power is "on" for about 3-4m of every hour, and turning on the wifi only every few hours, the battery lasts a little more than 30h. (I don't know if the battery was fully charged but the device had been powered up enough before the test that that seems likely.) This is with the latest Arduino firmware, which basically ignores the Arduino runloop entirely and uses FreeRTOS timers for everything; when nothing happens, the firmware is blocked on xQueueReceive in all tasks. Only the FreeRTOS idle loop should be getting any time, as it's the only runnable task, and it should at least in principle be able to wait for timer interrupts and put the CPU into a low-power state (but I don't know yet if it does that).

This build had serial-line logging enabled, but of course nobody listening on that line; I don't know whether disabling that would be better. More experimentation needed.

lars-t-hansen commented 1 year ago

Probably important to look at how FreeRTOS is configured and whether we can configure it dynamically to use light-sleep mode [1]. Maybe important to make sure the ledc subsystem is spun down when this happens. Maybe important in general to make sure that WiFi.disconnect() is sufficient to spin down the wifi radio, TBD. Gunnar sketched out a deep-sleep mode, for this also see [2], but this is more invasive so it would be interesting to see how far we can get with light-sleep. (Would be useful to be able to measure the power draw instead of having the system run until the battery's depleted...)

[1] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/power_management.html [2] https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/

lars-t-hansen commented 1 year ago

Data point: Gunnar has a power meter. When the device is on and sensing and displaying the slideshow, it draws about 140 mA. When the peripherals are off and the device is just waiting for a FreeRTOS timer callback, it draws about 40mA. This is a nice reduction but I think it ought to be possible to get it much further down. The HUZZAH32 v1 we're using is not very low-power friendly supposedly but I've seen data suggesting we should be able to get it down to about 7mA: This site thinks 40 mA is normal for "awake" and 6.6 mA is normal for "asleep" for the HUZZAH32.

lars-t-hansen commented 1 year ago

The power management feature of IDF is disabled for Arduino (CONFIG_PM_ENABLE), which renders the esppm functions inert. There's supposedly a way to use arduino and idf together, https://github.com/platformio/platform-espressif32/tree/master/examples/espidf-arduino-blink?utm_source=platformio.org&utm_medium=docs. It's a little confusing but looks like the secret is to request both arduino and idf frameworks in the platformio.ini file. Unfortunately this does not work on my M1 mac, there are mysterious python errors.

lars-t-hansen commented 1 year ago

there are mysterious python errors.

Known problem with VS Code and/or platformio: https://github.com/platformio/platform-espressif32/issues/1045

lars-t-hansen commented 1 year ago

There are fixes for that (including hand-copying Python libraries across the file system) and subsequent problems (modifying files outside the project, gross). When this unmaintainable mess has been put together we end up in another mess of link errors. There are creative suggestions on the 'net for how to fix that, but given that this is basically an Arduino problem that one would not run into with the use of plain IDF I'm not sure it's really worth going after.

lars-t-hansen commented 1 year ago

Since it's hard to get the Arduino firmware to play nicely with a custom FreeRTOS config, here are some initial experiments with the IDF firmware with CONFIG_PM_ENABLE turned on but no other important changes. It turns out that with CONFIG_PM_ENABLE we also get CONFIG_PM_DFS_INIT_AUTO, that is, frequency scaling with sensible parameters.

Under normal operations, when it's monitoring and displaying the slideshow, the power usage is about 40-70 mA. This is much better than the Arduino firmware, which is running around 140 mA at that point.

When it's sleeping (ie actually idling), it's at about 19-20 mA, half of what we get with the Arduino firmware idling.

But after it wakes up from idling and performs monitoring again it does not jump back to 40-70, but stays in the range 25-40. I don't know why that is. It is consistent with eg a situation where the device powers up at max clock speed but after idling finds that that clock speed is never required and stays at a lower speed. (This might change once we add a radio to the IDF firmware.)

lars-t-hansen commented 1 year ago

When further configured with CONFIG_FREERTOS_USE_TICKLESS_IDLE (aka configUSE_TICKLESS_IDLE) with the default value of "3" for configEXPECTED_IDLE_TIME_BEFORE_SLEEP, and with a call to esp_pm_configure() to enable light sleep mode:

(I still have to figure out how to keep the wakeup button enabled during light sleep but supposedly this is doable.)

lars-t-hansen commented 1 year ago

firmware-idf code on the branch low_power now has the desired behavior: it goes into 6.4mA light sleep mode in the sleep window, behaves normally outside the sleep window, and the button wakes it up in the sleep window. Outside the sleep window the power draw is 25-40 mA.

It is important that light sleep mode is not enabled outside the sleep window, because this makes the device very sluggish, button events are lost, and so on - the device keeps going to sleep while it's waiting for events, and though this sounds "right" and it probably saves power, the UX is bad.

lars-t-hansen commented 1 year ago

firmware-idf code on the branch low_power

Merged to main #91.

lars-t-hansen commented 1 year ago

Work on deep sleep for the Arduino firmware is ongoing on branch arduino_deep_sleep. This code works, and power usage is around 5.2 mA when the system is sleeping, which is pretty good and about the best we can hope for given the stated specs of the Feather module. Power usage during normal monitoring operation is still high, around 80 mA, unsurprising.

The logic is mainly done, except for bits tagged TODO DEEP_SLEEP in the code. The main missing functionality is that the button is currently unable to wake the device from deep sleep, probably a minor configuration error.