Disabling the ESP32 Brownout detector

I hate it when things fail when they are not supposed to. I’ve now got some code that uses deep sleep on an ESP 32 to drop power consumption to a tiny amount between the readings made by the environmental sensor hardware that Brian has built.

Today I was doing some battery testing and I hit a snag. When the device is running from battery power the deep sleep mode breaks. The device does a power on reset rather than waking from its deep sleep as it is supposed to. Of course, this only happens when it is not connected to a computer, so I can’t use any of my debugging tools to find out what is going on. In the end I had to resort to flashing the LED on the device to indicate the reason for the reboot.

void flashLed(int flashes)
{
    pinMode(LED_BUILTIN, OUTPUT);
    for (int i = 0; i < flashes; i++)
    {
        digitalWrite(LED_BUILTIN, true);
        delay(500);
        digitalWrite(LED_BUILTIN, false);
        delay(500);
    }
}

void flashBootReasonOnBuiltInLed()
{
    esp_reset_reason_t reset_reason = esp_reset_reason();
    flashLed(reset_reason);

    delay(2000);

    esp_sleep_wakeup_cause_t wakeup_reason;
    wakeup_reason = esp_sleep_get_wakeup_cause();

    flashLed(wakeup_reason);
}

This is the code that I ended up writing. On usb power I get a reset reason of 8 flashes (deepsleep) and a wakeup reason of 4 flashes (timer). All good. On battery power I get a reset reason of 9 (brownout).

OK, so what is a brownout? A brownout is when the supply voltage drops a bit low. It’s something you need to worry about because low voltage can cause your processor to do some very strange things. It might get its sums wrong or forget stuff. The ESP32 has hardware that checks the supply voltage and resets the processor if it detects that the power supply is failing. Software in the device can then detect this and go about shutting down safely.

In the case of our sensor the brownout is caused by one specific event. It occurs when the program turns on the power supply that drives the particle sensor. The particle sensor has a fan in it, which takes quite a chunk of current just as it spins up. This sudden load causes the battery voltage to drop for a fraction of a second. The sequence that we have is as follows:

  1. Sensor wakes up from a deep sleep.

  2. Sensor runs the code that turns on the power to the particle sensor.

  3. Supply voltage drops and triggers the brownout sensor which resets the ESP32.

  4. The ESP32 wakes up as from a brownout reset, not a deep sleep.

  5. Sensor runs the code that turns on the power again, but this time it doesn’t trigger the brownout sensor because the power supply has a little bit of residual power in it from the recent (failed) startup.

I’ve fixed the problem by turning off the brownout detector when I turn the power on.

WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector   

// turn on the power
digitalWrite(powerControlSettings.powerControlOutputPin, HIGH);

// let things power up
delay(500);

WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector

This is the code that does the deed. The power is controlled by a pin that is lifted high to turn it on. So the program turns of the brownout detector, turns the power on, waits half a second for things to settle and then turns the brownout detector back on again.

I’m not sure whether to be proud of this code or not. But in the end I think I am. If you accuse me of masking a hardware problem with a bit of software then I’m going to plead guilty as charged. I could probably fix the hardware cause by adding a wacking great capacitor over the supply to stop the voltage dropping. But this might lead to other problems. And anyway, if you look at the programs in any of the devices that you use on a daily basis you will find bits of code like this. Little bits of behaviour that are only there because the hardware doesn’t work without them.

Oh, and as a bonus (and so I can find it again if I ever need it) here’s a chunk of C that prints out the reset reason for an ESP32.

void printBootReason()
{
    esp_reset_reason_t reset_reason = esp_reset_reason();

    switch (reset_reason)
    {
    case ESP_RST_UNKNOWN:    Serial.println("Reset reason can not be determined"); break;
    case ESP_RST_POWERON:    Serial.println("Reset due to power-on event"); break;
    case ESP_RST_EXT:        Serial.println("Reset by external pin (not applicable for ESP32)"); break;
    case ESP_RST_SW:         Serial.println("Software reset via esp_restart"); break;
    case ESP_RST_PANIC:      Serial.println("Software reset due to exception/panic"); break;
    case ESP_RST_INT_WDT:    Serial.println("Reset (software or hardware) due to interrupt watchdog"); break;
    case ESP_RST_TASK_WDT:   Serial.println("Reset due to task watchdog"); break;
    case ESP_RST_WDT:        Serial.println("Reset due to other watchdogs"); break;
    case ESP_RST_DEEPSLEEP:  Serial.println("Reset after exiting deep sleep mode"); break;
    case ESP_RST_BROWNOUT:   Serial.println("Brownout reset (software or hardware)"); break;
    case ESP_RST_SDIO:       Serial.println("Reset over SDIO"); break;
    }

    if (reset_reason == ESP_RST_DEEPSLEEP)
    {
        esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();

        switch(wakeup_reason)
        { 
            case ESP_SLEEP_WAKEUP_UNDEFINED:    Serial.println("In case of deep sleep: reset was not caused by exit from deep sleep"); break;
            case ESP_SLEEP_WAKEUP_ALL:          Serial.println("Not a wakeup cause: used to disable all wakeup sources with esp_sleep_disable_wakeup_source"); break;
            case ESP_SLEEP_WAKEUP_EXT0:         Serial.println("Wakeup caused by external signal using RTC_IO"); break;
            case ESP_SLEEP_WAKEUP_EXT1:         Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
            case ESP_SLEEP_WAKEUP_TIMER:        Serial.println("Wakeup caused by timer"); break;
            case ESP_SLEEP_WAKEUP_TOUCHPAD:     Serial.println("Wakeup caused by touchpad"); break;
            case ESP_SLEEP_WAKEUP_ULP:          Serial.println("Wakeup caused by ULP program"); break;
            case ESP_SLEEP_WAKEUP_GPIO:         Serial.println("Wakeup caused by GPIO (light sleep only)"); break;
            case ESP_SLEEP_WAKEUP_UART:         Serial.println("Wakeup caused by UART (light sleep only)"); break;
        }
    }
}

You’re welcome.