Citra Dewi
Citra Dewi

Reputation: 343

Exception handling for `Brownout detector was trigerred`

I'm using ESP-IDF as framework.

I know that Brownout detector was trigerred error is come from low voltage detector that detect low voltage occurs. Usually the MCU will restart automatically when that error occurs.

Yes that detector can be setup, but can I handle that error in software like how esp-idf handle error with using convention esp_err_t? So I can just continue the runtime in my MCU without restarted by such error.

What I mean handle is like how high level programming using try-catch concept.

Upvotes: 2

Views: 2978

Answers (4)

Deyaa
Deyaa

Reputation: 1

I faced the same issue and then detected that the problem was due to the bad soldering.

Upvotes: 0

Craige
Craige

Reputation: 21

https://www.esp32.com/viewtopic.php?t=6855 says look at the components/esp_system/port/brownout.c for how to catch the interrupt. Set the high 2.8V threshold so you'll have a bit of time. The default is the low 2.43V threshold and often doesn't have time to print the entire ~40 character message. Pre-erase flash if you are trying to save something.

Use a bigger power supply capacitor--100uF is probably too small.

A use-case for this might look like a GPS that wants to save some warm-start data if it can, but doesn't want to wear out the flash by saving it all the time. If the data is missing or corrupt, there is a recovery procedure that just takes longer.

Anti-pattern: don't depend on the brownout detector to put a system in a safe state. Those big capacitors get smaller over time.

edit: actual testing, 4700uf does not provide a reliable 1/30 second, but 2 X 4700uF does. (Just a wroom32, nothing else.) I think 10,000uF is a heavy lift for my wall-wart to start up.

0 idle0=12954   idle1=25465   FPS=29.9967  sampleRate=    306822.9
0 idle0=12954   idle1=25465   FPS=30.0003  sampleRate=    306836.3

2.80V Brownout...warning...
1 idle0=14692   idle1=23489   FPS=30.0012  sampleRate=    306848.5
1 idle0=14692   idle1=23489   FPS=29.9976  sampleRate=    306859.1

2.43V Brownout...restart...
ets Jul 29 2019 12:21:46

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:6664
load:0x40078000,len:14848
load:0x40080400,len:3792
0x40080400: _init at ??:?

entry 0x40080694
I (27) boot: ESP-IDF v4.4.3-dirty 2nd stage bootloader
I (27) boot: compile time 07:37:04
I (27) boot: chip revision: 3
I (30) boot_comm: chip revision: 3, min. bootload�

The 2 X 4700uF carried the CPU pretty far into the reboot.

Here's the quick and dirty test code; it needs some thought about what should happen if there is a temporary brownout that doesn't trip the reset, and maybe a bit more understanding of how to return from the interrupt. It was retriggering immediately before I set the lower voltage in the isr. The calling code can test the static counter in the 30 FPS loop; above it is the 0 or 1 at the start of the line.

// modified from components/esp_system/port/brownout.c

// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#include "esp_private/system_internal.h"
#include "driver/rtc_cntl.h"

#include "esp_rom_sys.h"

#include "soc/soc.h"
#include "soc/cpu.h"
#include "soc/rtc_periph.h"
#include "hal/cpu_hal.h"

#include "hal/brownout_hal.h"

#include "sdkconfig.h"

#if defined(CONFIG_ESP32_BROWNOUT_DET_LVL)
#define BROWNOUT_DET_LVL CONFIG_ESP32_BROWNOUT_DET_LVL
#elif defined(CONFIG_ESP32S2_BROWNOUT_DET_LVL)
#define BROWNOUT_DET_LVL CONFIG_ESP32S2_BROWNOUT_DET_LVL
#elif defined(CONFIG_ESP32S3_BROWNOUT_DET_LVL)
#define BROWNOUT_DET_LVL CONFIG_ESP32S3_BROWNOUT_DET_LVL
#elif defined(CONFIG_ESP32C3_BROWNOUT_DET_LVL)
#define BROWNOUT_DET_LVL CONFIG_ESP32C3_BROWNOUT_DET_LVL
#elif defined(CONFIG_ESP32H2_BROWNOUT_DET_LVL)
#define BROWNOUT_DET_LVL CONFIG_ESP32H2_BROWNOUT_DET_LVL
#else
#define BROWNOUT_DET_LVL 0
#endif

#if SOC_BROWNOUT_RESET_SUPPORTED
#define BROWNOUT_RESET_EN true
#else
#define BROWNOUT_RESET_EN false
#endif // SOC_BROWNOUT_RESET_SUPPORTED

int sBrownOut = 0;
extern void esp_brownout_disable(void);
extern void my_esp_brownout_disable(void);

#ifndef SOC_BROWNOUT_RESET_SUPPORTED
static void my_rtc_brownout_isr_handler(void *arg)
{
    sBrownOut++;
    brownout_hal_intr_clear();
    // change to level 0...prevents immediate retrigger...
    brownout_hal_config_t cfg = {
        .threshold = 0,//BROWNOUT_DET_LVL, 
        .enabled = true,
        .reset_enabled = BROWNOUT_RESET_EN,
        .flash_power_down = true,
        .rf_power_down = true,
    };

    brownout_hal_config(&cfg);
    if(sBrownOut==1){
        esp_rom_printf("\r\n2.80V Brownout...warning...\r\n");
    }
    else{
        esp_cpu_stall(!cpu_hal_get_core_id());
        esp_reset_reason_set_hint(ESP_RST_BROWNOUT);
        esp_rom_printf("\r\n2.43V Brownout...restart...\r\n");
        esp_restart_noos();
    }
    return; // dead code follows...

    /* Normally RTC ISR clears the interrupt flag after the application-supplied
     * handler returns. Since restart is called here, the flag needs to be
     * cleared manually.
     */
    brownout_hal_intr_clear();
    /* Stall the other CPU to make sure the code running there doesn't use UART
     * at the same time as the following esp_rom_printf.
     */
    esp_cpu_stall(!cpu_hal_get_core_id());
    esp_reset_reason_set_hint(ESP_RST_BROWNOUT);
    esp_rom_printf("\r\n***Brownout detector was triggered\r\n\r\n");
    esp_restart_noos();
}
#endif // not SOC_BROWNOUT_RESET_SUPPORTED

void my_esp_brownout_init(void)
{
    esp_brownout_disable();
    brownout_hal_config_t cfg = {
        .threshold = 7,//BROWNOUT_DET_LVL, // level 7 is the highest voltage, earliest possible warning, most time left...
        .enabled = true,
        .reset_enabled = BROWNOUT_RESET_EN,
        .flash_power_down = false, // if this does what it says,
        .rf_power_down = false, // probably don't want it first time
    };

    brownout_hal_config(&cfg);


#ifndef SOC_BROWNOUT_RESET_SUPPORTED
    rtc_isr_register(my_rtc_brownout_isr_handler, NULL, RTC_CNTL_BROWN_OUT_INT_ENA_M);

    brownout_hal_intr_enable(true);
#endif // not SOC_BROWNOUT_RESET_SUPPORTED
}

void my_esp_brownout_disable(void)
{
    brownout_hal_config_t cfg = {
        .enabled = false,
    };

    brownout_hal_config(&cfg);

#ifndef SOC_BROWNOUT_RESET_SUPPORTED
    brownout_hal_intr_enable(false);

    rtc_isr_deregister(my_rtc_brownout_isr_handler, NULL);
#endif // not SOC_BROWNOUT_RESET_SUPPORTED
}

Upvotes: 1

romkey
romkey

Reputation: 7069

It doesn't make any sense to try to "catch" a brownout.

When brownout detection triggers, it means that the ESP32 isn't getting enough power to run reliably. If it can't run reliably, it's not helpful to try to catch an exception indicating that because the exception handler also wouldn't run reliably.

If you're seeing this problem, there's one fix for it and that's to supply enough power to your ESP32 and whatever circuitry you have it connected to. That's it, that's what you do. That means figure out how much current the entire project draws and use a power source that's rated to supply more than that amount of current. If you're using a "wall wart" AC/DC adapter, use one that's rated for a lot more current as many of them can't deliver what they promise to.

Upvotes: 2

Sercan
Sercan

Reputation: 5081

The CPU is reset after this error occurs. There may be a way to find out the reason for the reset when the CPU restarts. As with STM32 MCUs, the RCC (Reset and Clock Controller) register can be read. During my research I found a solution that can be used with ESP32.

#include <rom/rtc.h>

void print_reset_reason(RESET_REASON reason)
{
  switch (reason)
  {
    /**<1, Vbat power on reset*/
    case 1 : Serial.println ("POWERON_RESET");break;          
    
    /**<3, Software reset digital core*/
    case 3 : Serial.println ("SW_RESET");break;               
    
    /**<4, Legacy watch dog reset digital core*/
    case 4 : Serial.println ("OWDT_RESET");break;             
    
    /**<5, Deep Sleep reset digital core*/
    case 5 : Serial.println ("DEEPSLEEP_RESET");break;        
    
    /**<6, Reset by SLC module, reset digital core*/
    case 6 : Serial.println ("SDIO_RESET");break;             
    
    /**<7, Timer Group0 Watch dog reset digital core*/
    case 7 : Serial.println ("TG0WDT_SYS_RESET");break;       
    
    /**<8, Timer Group1 Watch dog reset digital core*/
    case 8 : Serial.println ("TG1WDT_SYS_RESET");break;       
    
    /**<9, RTC Watch dog Reset digital core*/
    case 9 : Serial.println ("RTCWDT_SYS_RESET");break;       
    
    /**<10, Instrusion tested to reset CPU*/
    case 10 : Serial.println ("INTRUSION_RESET");break;       
    
    /**<11, Time Group reset CPU*/
    case 11 : Serial.println ("TGWDT_CPU_RESET");break;       
    
    /**<12, Software reset CPU*/
    case 12 : Serial.println ("SW_CPU_RESET");break;          
    
    /**<13, RTC Watch dog Reset CPU*/
    case 13 : Serial.println ("RTCWDT_CPU_RESET");break;      
    
    /**<14, for APP CPU, reseted by PRO CPU*/
    case 14 : Serial.println ("EXT_CPU_RESET");break;         
    
    /**<15, Reset when the vdd voltage is not stable*/
    case 15 : Serial.println ("RTCWDT_BROWN_OUT_RESET");break;
    
    /**<16, RTC Watch dog reset digital core and rtc module*/
    case 16 : Serial.println ("RTCWDT_RTC_RESET");break;      
    
    default : Serial.println ("NO_MEAN");
  }
}

void setup() {
  Serial.begin(115200);
  delay(2000);

  Serial.println("CPU0 reset reason: ");
  print_reset_reason(rtc_get_reset_reason(0));

  Serial.println("CPU1 reset reason: ");
  print_reset_reason(rtc_get_reset_reason(1));
}

void loop() {}

Related Links

Upvotes: 2

Related Questions