default 0
depends on !ULP_COPROC_ENABLED
-menu "Watchdogs & brown-out detection"
config INT_WDT
bool "Interrupt watchdog"
int "Interrupt watchdog timeout (ms)"
depends on INT_WDT
default 10
- range INT_WDT_TIMEOUT_MIN 10000
+ range 10 10000
help
The timeout of the watchdog, in miliseconds. Make this higher than the FreeRTOS tick rate.
With this turned on, the task WDT can detect if the idle task is not called within the task
watchdog timeout period. The idle task not being called usually is a symptom of another
task hoarding the CPU. It is also a bad thing because FreeRTOS household tasks depend on the
- idle task getting some runtime every now and then.
+ idle task getting some runtime every now and then. Take Care: With this disabled, this
+ watchdog will trigger if no tasks register themselves within the timeout value.
+#The brownout detector code is disabled (by making it depend on a nonexisting symbol) because the current revision of ESP32
+#silicon has a bug in the brown-out detector, rendering it unusable for resetting the CPU.
config BROWNOUT_DET
bool "Hardware brownout detect & reset"
default y
+ depends on NEEDS_ESP32_NEW_SILICON_REV
help
The ESP32 has a built-in brownout detector which can detect if the voltage is lower than
a specific value. If this happens, it will reset the chip in order to prevent unintended
help
The brownout detector will reset the chip when the supply voltage is below this level.
+#The voltage levels here are estimates, more work needs to be done to figure out the exact voltages
+#of the brownout threshold levels.
config BROWNOUT_DET_LVL_SEL_0
bool "2.1V"
config BROWNOUT_DET_LVL_SEL_1
The brownout detector can reset the chip after a certain delay, in order to make sure e.g. a voltage dip has entirely passed
before trying to restart the chip. You can set the delay here.
-endmenu
#include "soc/rtc_cntl_reg.h"
+#if CONFIG_BROWNOUT_DET
+/*
+This file ins included in esp-idf, but the menuconfig option for this is disabled because a silicon bug
+prohibits the brownout detector from functioning correctly on the ESP32.
+*/
+
void esp_brownout_init() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG,
RTC_CNTL_BROWN_OUT_ENA | (CONFIG_BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S) |
RTC_CNTL_BROWN_OUT_RST_ENA | (((CONFIG_BROWNOUT_DET_RESETDELAY*150)/1000) << RTC_CNTL_BROWN_OUT_RST_WAIT_S) |
RTC_CNTL_BROWN_OUT_PD_RF_ENA|RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA);
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
#define WDT_INT_NUM 24
-
#define WDT_WRITE_KEY 0x50D83AA1
void int_wdt_init() {
TIMERG1.wdt_config0.stg0=1; //1st stage timeout: interrupt
TIMERG1.wdt_config0.stg1=3; //2nd stage timeout: reset system
TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
- TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt
- TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset
+ //The timer configs initially are set to 5 seconds, to make sure the CPU can start up. The tick hook sets
+ //it to their actual value.
+ TIMERG1.wdt_config2=10000;
+ TIMERG1.wdt_config3=10000;
TIMERG1.wdt_config0.en=1;
TIMERG1.wdt_feed=1;
TIMERG1.wdt_wprotect=0;
+ TIMERG1.int_clr_timers.wdt=1;
+ TIMERG1.int_ena.wdt=1;
ESP_INTR_DISABLE(WDT_INT_NUM);
intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM);
//We do not register a handler for the interrupt because it is interrupt level 4 which
}
-
void vApplicationTickHook(void) {
TIMERG1.wdt_wprotect=WDT_WRITE_KEY;
+ TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt
+ TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset
TIMERG1.wdt_feed=1;
TIMERG1.wdt_wprotect=0;
}
#include <esp_types.h>
#include "esp_err.h"
#include "esp_intr.h"
+#include "esp_attr.h"
#include "soc/timer_group_struct.h"
#include "esp_log.h"
static const char* TAG = "task_wdt";
-
typedef struct wdt_task_t wdt_task_t;
struct wdt_task_t {
TaskHandle_t task_handle;
wdt_task_t *next;
};
-
static wdt_task_t *wdt_task_list=NULL;
+//We use this interrupt number on whatever task calls task_wdt_init.
#define WDT_INT_NUM 24
-
#define WDT_WRITE_KEY 0x50D83AA1
-static void task_wdt_isr(void *arg) {
+static void IRAM_ATTR task_wdt_isr(void *arg) {
+ wdt_task_t *wdttask;
+ const char *cpu;
+ //Feed the watchdog so we do not reset
+ TIMERG0.wdt_wprotect=WDT_WRITE_KEY;
+ TIMERG0.wdt_feed=1;
+ TIMERG0.wdt_wprotect=0;
+ //Ack interrupt
+ TIMERG0.int_clr_timers.wdt=1;
+ //Watchdog got triggered because at least one task did not report in.
+ ets_printf("Task watchdog got triggered. The following tasks did not feed the watchdog in time:\n");
+ for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) {
+ if (!wdttask->fed_watchdog) {
+ cpu=xTaskGetAffinity(wdttask->task_handle)==0?"CPU 0":"CPU 1";
+ if (xTaskGetAffinity(wdttask->task_handle)==tskNO_AFFINITY) cpu="CPU 0/1";
+ printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu);
+ }
+ }
+#if CONFIG_TASK_WDT_PANIC
+ ets_printf("Aborting.\n");
abort();
+#endif
}
TaskHandle_t handle=xTaskGetCurrentTaskHandle();
//Walk the linked list of wdt tasks to find this one, as well as see if we need to feed
//the real watchdog timer.
- while (wdttask!=NULL) {
+ for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) {
//See if we are at the current task.
if (wdttask->task_handle == handle) {
wdttask->fed_watchdog=true;
}
//If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog.
if (!wdttask->fed_watchdog) do_feed_wdt=false;
- //Next entry.
- wdttask=wdttask->next;
}
if (!found_task) {
if (wdt_task_list == NULL) {
wdt_task_list=newtask;
} else {
- wdttask=wdt_task_list;
- while (!(wdttask->next == NULL)) wdttask=wdttask->next;
- wdttask->next=wdttask;
+ for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) ;
+ wdttask->next=newtask;
}
}
if (do_feed_wdt) {
TIMERG0.wdt_wprotect=WDT_WRITE_KEY;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
+ //Reset fed_watchdog status
+ for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) wdttask->fed_watchdog=false;
}
}
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
ESP_INTR_DISABLE(ETS_T0_WDT_INUM);
- intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM);
+ intr_matrix_set(xPortGetCoreID(), ETS_TG0_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM);
xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL);
+ TIMERG0.int_clr_timers.wdt=1;
+ TIMERG0.int_ena.wdt=1;
ESP_INTR_ENABLE(ETS_T0_WDT_INUM);
}
-
#if CONFIG_TASK_WDT_CHECK_IDLE_TASK
void vApplicationIdleHook(void) {
task_wdt_feed();
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
+#define INCLUDE_pcTaskGetTaskName 1
#if CONFIG_ENABLE_MEMORY_DEBUG
#define configENABLE_MEMORY_DEBUG 1
#ifndef PANIC_H
#define PANIC_H
+
+#define PANIC_RSN_NONE 0
+#define PANIC_RSN_DEBUGEXCEPTION 1
+#define PANIC_RSN_DOUBLEEXCEPTION 2
+#define PANIC_RSN_KERNELEXCEPTION 3
+#define PANIC_RSN_COPROCEXCEPTION 4
+#define PANIC_RSN_INTWDT 5
+#define PANIC_RSN_MAX 5
+
+
+#ifndef __ASSEMBLER__
+
void setBreakpointIfJtag(void *fn);
+#endif
#endif
\ No newline at end of file
#include "soc/io_mux_reg.h"
#include "soc/dport_reg.h"
#include "soc/rtc_cntl_reg.h"
+#include "soc/timer_group_struct.h"
#include "gdbstub.h"
+#include "panic.h"
+
+#define WDT_WRITE_KEY 0x50D83AA1
+
/*
Panic handlers; these get called when an unhandled exception occurs or the assembly-level
}
void panicHandler(XtExcFrame *frame) {
+ int *regs=(int*)frame;
+ //Please keep in sync with PANIC_RSN_* defines
+ const char *reasons[]={
+ "Unknown reason",
+ "Unhandled debug exception",
+ "Double exception",
+ "Unhandled kernel exception",
+ "Coprocessor exception",
+ "Interrupt wdt timeout"
+ };
+ const char *reason=reasons[0];
+ //The panic reason is stored in the EXCCAUSE register.
+ if (regs[20]<=PANIC_RSN_MAX) reason=reasons[regs[20]];
haltOtherCore();
panicPutStr("Guru Meditation Error: Core ");
panicPutDec(xPortGetCoreID());
- panicPutStr(" panic'ed.\r\n");
+ panicPutStr(" panic'ed (");
+ panicPutStr(reason);
+ panicPutStr(")\r\n");
if (inOCDMode()) {
asm("break.n 1");
}
+//Disables all but one WDT, and allows enough time on that WDT to do what we need to do.
+static void reconfigureAllWdts() {
+ TIMERG0.wdt_wprotect=WDT_WRITE_KEY;
+ TIMERG0.wdt_feed=1;
+ TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS
+ TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS
+ TIMERG0.wdt_config0.stg0=3; //1st stage timeout: reset system
+ TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
+ TIMERG0.wdt_config2=2000; //1 second before reset
+ TIMERG0.wdt_config0.en=1;
+ TIMERG0.wdt_wprotect=0;
+ //Disable wdt 1
+ TIMERG1.wdt_wprotect=WDT_WRITE_KEY;
+ TIMERG1.wdt_config0.en=0;
+ TIMERG1.wdt_wprotect=0;
+}
+
+static void disableAllWdts() {
+ TIMERG0.wdt_wprotect=WDT_WRITE_KEY;
+ TIMERG0.wdt_config0.en=0;
+ TIMERG0.wdt_wprotect=0;
+ TIMERG1.wdt_wprotect=WDT_WRITE_KEY;
+ TIMERG1.wdt_config0.en=0;
+ TIMERG0.wdt_wprotect=0;
+}
+
+
/*
We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the
serial port and either jump to the gdb stub, halt the CPU or reboot.
"A6 ","A7 ","A8 ","A9 ","A10 ","A11 ","A12 ","A13 ",
"A14 ","A15 ","SAR ","EXCCAUSE","EXCVADDR","LBEG ","LEND ","LCOUNT "};
+ //Feed the watchdogs, so they will give us time to print out debug info
+ reconfigureAllWdts();
+
panicPutStr("Register dump:\r\n");
for (x=0; x<24; x+=4) {
panicPutStr("\r\n");
}
#if CONFIG_FREERTOS_PANIC_GDBSTUB
+ disableAllWdts();
panicPutStr("Entering gdb stub now.\r\n");
gdbstubPanicHandler(frame);
#elif CONFIG_FREERTOS_PANIC_PRINT_REBOOT || CONFIG_FREERTOS_PANIC_SILENT_REBOOT
for (x=0; x<100; x++) ets_delay_us(1000);
software_reset();
#else
+ disableAllWdts();
panicPutStr("CPU halted.\r\n");
while(1);
#endif
/*-----------------------------------------------------------*/
#if ( INCLUDE_pcTaskGetTaskName == 1 )
-
char *pcTaskGetTaskName( TaskHandle_t xTaskToQuery ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
TCB_t *pxTCB;
*******************************************************************************/
#include "xtensa_rtos.h"
+#include "panic.h"
/*
Define for workaround: pin no-cpu-affinity tasks to a cpu when fpu is used.
jx a3
#else
wsr a0, EXCSAVE+XCHAL_DEBUGLEVEL /* save original a0 somewhere */
+ movi a0,PANIC_RSN_DEBUGEXCEPTION
+ wsr a0,EXCCAUSE
call0 _xt_panic /* does not return */
rfi XCHAL_DEBUGLEVEL /* make a0 point here not later */
#endif
#if XCHAL_HAVE_DEBUG
break 1, 4 /* unhandled double exception */
#endif
+ movi a0,PANIC_RSN_DOUBLEEXCEPTION
+ wsr a0,EXCCAUSE
call0 _xt_panic /* does not return */
rfde /* make a0 point here not later */
#if XCHAL_HAVE_DEBUG
break 1, 0 /* unhandled kernel exception */
#endif
+ movi a0,PANIC_RSN_KERNELEXCEPTION
+ wsr a0,EXCCAUSE
call0 _xt_panic /* does not return */
rfe /* make a0 point here not there */
#if XCHAL_HAVE_DEBUG
break 1, 1 /* unhandled user exception */
#endif
+ movi a0,PANIC_RSN_COPROCEXCEPTION
+ wsr a0,EXCCAUSE
call0 _xt_panic /* not in a thread (invalid) */
/* never returns */
off and the CPU should panic. */
rsr a0, EXCSAVE_4
wsr a0, EXCSAVE_1 /* panic handler reads this register */
+ movi a0,PANIC_RSN_INTWDT
+ wsr a0,EXCCAUSE
call0 _xt_panic