Qball's Weblog

Hitting a new low-power record with the esp32c3 - part 2

Tags ESP32  Low Power  RiscV 

To get to the low power usage of the previous blog post there several steps I took. The main goal off most changes is to ‘race to deep sleep’. Many of these tricks are obvious, but it never hurts to list them again. All the below steps are done when using the esp-idf SDK.

In my test example application I started at around 500ms uptime.

Configuration options

Reduce debug output of your program

Given the default UART output runs at 115200, you want to remove all unneeded debug output from the module. I tend to use the ESP_LOGX macros so we can disable messages using the menuconfig system. This is the Component config -> Log output -> Default log verbosity setting.

By using the macros its easy to have lot of output when developing/debugging and turn them off when in production.

Reduce debug output of bootloader

Using Bootloader config -> Bootloader log verbosity option you can reduce the output of the bootloader.

This saves around 100ms, on a total of ~200ms, this is significant.

Compiler flags bootloader

Using Bootloader config -> Bootloader optimization Level you can set the compiler to optimize for speed instead of size.

This saves me around 6-10ms, its not a lot but it adds up.

Application optimization flags

Using Compiler options -> optimization Level you can also change the optimization settings for the application. If this helps, and how much depends a lot on your application.

Disable image validation when waking from deep sleep

This is a bit of more low-level optimization, but you can tell the bootloader not to validate (checksum) the image. For my application this saves around 97ms seconds from boot.

With these options we reduced the bootloader time (on deep sleep wakeup) by 200ms. While the chip is not in its highest power consumption state, it still saves a lot.

At this point in time, we are now down from 500ms to 190ms.

I’ve made ‘waketime’ (time it took to start sending the mqtt message) a default part of the payload. This helps optimizing things. Especially with WiFi it can help to move the sensor to get a better connection and therefor a faster drop off.


These are more things I do to lower the power.

Enable DVFS and light-sleep

Even when you are online for only a short amount of time, this can help. The settings for these options are different for each chip. But in general you want to enable PM_ENABLE, DFS, ESP_SYSTEM_PM_POWER_DOWN_CPU and TICKLESS_IDLE in menuconfig. Then when initializing the code you can enable it with f.e.:

esp_pm_config_esp32c3_t pm_config = {
    .max_freq_mhz = 80,
    .min_freq_mhz = 10,
    .light_sleep_enable = true

This one can be a bit tricky, as this might scale the clock to peripherals and they won’t function correctly anymore. Depending on the chip, you can tell the peripheral to use a clock-source that does not scale. In my experience, the esp32s2 is more flexible for this then the esp32c3. I suggest to checkout the datasheet of the used chip.

Use multiple tasks

This is more about structuring code, but I normally start multiple tasks:

One to connect to WiFi and MQTT, then another one to start sampling the sensor.

The sensor task sets up the sensors, sleeps until it stabilizes and has taken the sample. It then puts this into an async queue to the WiFi task.

The other task has started the WiFi and goes to sleep (listening to async queue). The WIFI callback msg the task when connected. The task wakes up again, and connects to async MQTT. Once MQTT reports back its connected we repeat this async game to send the sensor value via MQTT wait till its send and go to deep sleep.

If you play this game completely async, without polling and using light-sleep and the tickless idle setting it tends to pretty energy efficient. This can be so low power, that if you have a slow sensor with a high sample rate, it might be beneficial to keep running in this idle mode instead of deep-sleeping and booting/connecting every time. For me in a setup with 2 ds18s20 sensors, this was the case.

I sometimes add a 3rd task, that works as a watchdog and puts the chip to sleep if it did not managed to send something in X time.

I normally draw out a small state machine to make sure I got this orchestration correct. If I have a sensor that is very quick to get a sample, I often merge this into the WiFi task. I start the sensor while WiFi is connecting, once everything is connected I sample it.


There are many more knobs to tweak, but with these small things I managed to extend battery life of my sensors to year(s). The next step is to see how I can make them smaller. The huge 340mA spikes makes it difficult to use small batteries, even if we have a very low average consumption.

comments powered by Disqus