Bootloader Wake Time Improvements

A project log for ESP-NOW Weather Station

An ESP32 based weather station investigating ESP32's Deep Sleep and ESP-NOW

Kevin KesslerKevin Kessler 09/19/2020 at 02:500 Comments

Both the anemometer and the rain gauge use reed switches to send pulses to the microcontroller indicate the value of the sensor.  The anemometer closes the switch on each rotation, so that a 2.4 km/h breeze will cause the switch to close once per second. The tip bucket rain gauge will cycle the reed switch on every .2794mm of rain. My initial approach was to connect each of these to a GPIO that would wake the ESP32, which would then update the appropriate counter and go back to sleep.

The ESP32 wakes from deep sleep is level triggered instead of edge triggered, and if you set the uC to wake on a low input, for example, and the input is still low when you the ESP is put to sleep, it will immediately wake. The result of this is in order to get an accurate measurement of counts you need to emulate an edge triggered interrupt.  To do this, you need to set the wakeup signal to a low on the GPIO, programmatically note the low event when it occurs, then set the the wakeup signal to a high on the GPIO. When the high wakes the ESP32, the count can be recorded. The switch is hardware debounced with a low pass filter, and it would have been a good idea to put some time based software debouncing in, but it turns out that the ESP32 is so slow to wake, it isn't necessary.

On doing some initial testing, I found that it takes the ESP32 around 192ms to wake from deep sleep. Having to wake twice on every count would limit the ESP32's anemometer reading to about 6km/h. Assuming 100km/h as the max wind speed I need to measure (pretty strong wind which might tear the station off its poll), I would require the ability to handle about 84 wake events per second, so this is not close to meeting the requirement. The fastest rainfall recorded in the US was 17.5mm in one minute, which generate a wake event about twice a second, which would be doable (although, under that much rain, something else would probably break).  I set about trying to decrease the wake time.

To measure the baseline bootloader time, I wrote a program which toggled a GPIO, set a wake up timer for 1uS, and went to sleep:

#include <Arduino.h>

void setup() {


void loop() {
  // put your main code here, to run repeatedly:

Then I could monitor the GPIO with my oscilloscope to determine the speed at which the ESP32 can cycle through deep sleep.

Since 18uS pulses are not very visible in the image with 50mS per division, I turned on measurements, and you can see the period between program runs is 191 mS (lower left).

This is a list of things I've done to try to speed things up:

I'm using PlatformIO as my IDE, and the first 2 settings are made with the platformio.ini options 

board_build.flash_mode = qio
board_build.f_flash = 80000000L

The rebuild of the bootloader consisted loading up the Espressif IDF native SDK, and running menuconfig. Under PlatformIO this just consists of creating an ESP32 project with the espidf framework, so the hard work is done for you. You then can run:

pio run -t menuconfig 

In order to get all the bootloader configuration options, though, you need to be using the newest espressif32 platform, so the platform directive in platformio should read:

platform =

in order to get the yet to be released 4.1.0 version.

In menuconfig, under "Bootloader Config", I enabled "Skip Image Validation when waking from Deep Sleep" (which I think made the most difference), set the compiler optimization to -O2 for speed, and turned off all logging. Under the "Serial Flasher Config", I set the mode to QIO and speed to 80MHz, and under "Component Config" I set log output to None. Build the project and you end up with a bootloader.bin in the project_folder/.pio/build/esp32dev directory.

To get this bootloader into an Arduino Framework project, you first must understand how Platformio (and probably the Arduino IDE as well) find a bootloader to load into your ESP32. Normally a group of prebuilt bootloaders are found under $HOME/.platformio/packages/framework-arduinoespressif32/tools/sdk/bin, and they are named with the convention bootloader_mode_flashspeed.bin, so with the configuration of mode=qio and flash speed=80MHz, the targeted bootloader is bootloader_qio_80m.bin.

Now you could copy the newly compiled bootloader.bin to the folder under the home directory, but these bootloaders are shared by all PlatformIO projects, and I didn't want to screw up some other project. You can create a private copy of the packages directory with the following directive in platformio.ini:


This way you can copy the new bootloader.bin to project_dir/ packages_dir/framework-arduinoespressif32/tools/sdk/bin/bootloader_qio_80m.bin, and the next time the ESP32 is flashed, it will get the new bootloader.

 The final test I ran was to rewrite my Arduino test code into native ESP IDF code:

#include <stdio.h>
#include "driver/gpio.h"
#include "esp_sleep.h"
#include "sdkconfig.h"

void app_main() {


This code ran with a period of 28mS, which means that there is still something inside the Arduino code which is eating up a lot of time. The actual time between the GPIO going high and going low in the Arudino Code was 18uS and the IDF code was 7uS, so the time is being lost somewhere less obvious. Even 28mS is not fast enough for the requirements of the anemometer, so the anemometer counter will have to be accomplished with some sort of event counting IC.