]> granicus.if.org Git - esp-idf/commitdiff
Initial addition of wdt and brownout code
authorJeroen Domburg <git@j0h.nl>
Fri, 21 Oct 2016 09:59:57 +0000 (17:59 +0800)
committerJeroen Domburg <git@j0h.nl>
Fri, 21 Oct 2016 09:59:57 +0000 (17:59 +0800)
components/esp32/Kconfig
components/esp32/brownout.c [new file with mode: 0644]
components/esp32/cpu_start.c
components/esp32/include/esp_brownout.h [new file with mode: 0644]
components/esp32/include/esp_int_wdt.h [new file with mode: 0644]
components/esp32/include/esp_task_wdt.h [new file with mode: 0644]
components/esp32/int_wdt.c [new file with mode: 0644]
components/esp32/task_wdt.c [new file with mode: 0644]
components/freertos/include/freertos/FreeRTOSConfig.h

index 535df23eb5e824dc3f519345c74cd8e85ec5228f..c40d7930d077c2feb9c1961da94fe5745e66b6d8 100644 (file)
@@ -140,4 +140,117 @@ config ULP_COPROC_RESERVE_MEM
     default 0
     depends on !ULP_COPROC_ENABLED
 
+menu "Watchdogs & brown-out detection"
+
+config INT_WDT
+    bool "Interrupt watchdog"
+    default y
+    help
+        This watchdog timer can detect if the FreeRTOS tick interrupt has not been called for a certain time,
+        either because a task turned off interrupts and did not turn them on for a long time, or because an
+        interrupt handler did not return. It will try to invoke the panic handler first and failing that
+        reset the SoC.
+
+config INT_WDT_TIMEOUT_MS_MIN
+    default (2000/CONFIG_FREERTOS_HZ)
+
+config INT_WDT_TIMEOUT_MS
+    int "Interrupt watchdog timeout (ms)"
+    depends on INT_WDT
+    default 100
+    range INT_WDT_TIMEOUT_MS_MIN 10000
+    help
+        The timeout of the watchdog, in miliseconds.
+
+config TASK_WDT
+    bool "Task watchdog"
+    default y
+    help
+        This watchdog timer can be used to make sure individual tasks are still running.
+
+config TASK_WDT_PANIC
+    bool "Invoke panic handler when Task Watchdog is triggered"
+    depends on TASK_WDT
+    default n
+    help
+        Normally, the Task Watchdog will only print out a warning if it detects it has not
+        been fed. If this is enabled, it will invoke the panic handler instead, which
+        can then halt or reboot the chip.
+
+config TASK_WDT_TIMEOUT_S
+    int "Task watchdog timeout (seconds)"
+    depends on TASK_WDT
+    range 1 60
+    default 5
+    help
+        Timeout for the task WDT, in seconds.
+
+config TASK_WDT_CHECK_IDLE_TASK
+    bool "Task watchdog watches idle tasks"
+    depends on TASK_WDT
+    default y
+    help
+        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.
+
+config BROWNOUT_DET
+    bool "Hardware brownout detect & reset"
+    default y
+    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
+        behaviour.
+
+choice BROWNOUT_DET_LVL_SEL
+    prompt "Brownout voltage level"
+    depends on BROWNOUT_DET
+    default BROWNOUT_DET_LVL_SEL_25
+    help
+        The brownout detector will reset the chip when the supply voltage is below this level.
+
+config BROWNOUT_DET_LVL_SEL_0
+    bool "2.1V"
+config BROWNOUT_DET_LVL_SEL_1
+    bool "2.2V"
+config BROWNOUT_DET_LVL_SEL_2
+    bool "2.3V"
+config BROWNOUT_DET_LVL_SEL_3
+    bool "2.4V"
+config BROWNOUT_DET_LVL_SEL_4
+    bool "2.5V"
+config BROWNOUT_DET_LVL_SEL_5
+    bool "2.6V"
+config BROWNOUT_DET_LVL_SEL_6
+    bool "2.7V"
+config BROWNOUT_DET_LVL_SEL_7
+    bool "2.8V"
+endchoice
+
+config BROWNOUT_DET_LVL
+    int
+    default 0 if BROWNOUT_DET_LVL_SEL_0
+    default 1 if BROWNOUT_DET_LVL_SEL_1
+    default 2 if BROWNOUT_DET_LVL_SEL_2
+    default 3 if BROWNOUT_DET_LVL_SEL_3
+    default 4 if BROWNOUT_DET_LVL_SEL_4
+    default 5 if BROWNOUT_DET_LVL_SEL_5
+    default 6 if BROWNOUT_DET_LVL_SEL_6
+    default 7 if BROWNOUT_DET_LVL_SEL_7
+
+
+config BROWNOUT_DET_RESETDELAY
+    int "Brownout reset delay (in uS)"
+    depends on BROWNOUT_DET
+    range 0 6820
+    default 1000
+    help
+        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
+
+
+
 endmenu
diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c
new file mode 100644 (file)
index 0000000..173d63a
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright 2015-2016 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 "sdkconfig.h"
+#include "soc/soc.h"
+#include "soc/rtc_cntl_reg.h"
+
+
+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);
+
+
+    WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 
+            RTC_CNTL_BROWN_OUT_ENA | (4 << RTC_CNTL_DBROWN_OUT_THRES_S) |
+            RTC_CNTL_BROWN_OUT_RST_ENA | (0x3FF << 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
index 7b2ccdc609610a977f21657c0be9c45b110ab8f7..6a482c539c0ff7f01cd2fd93b5dabe85921f1a37 100644 (file)
@@ -43,6 +43,8 @@
 #include "esp_ipc.h"
 #include "esp_log.h"
 
+#include "esp_brownout.h"
+
 void start_cpu0(void) __attribute__((weak, alias("start_cpu0_default")));
 void start_cpu0_default(void) IRAM_ATTR;
 #if !CONFIG_FREERTOS_UNICORE
@@ -137,6 +139,16 @@ void start_cpu0_default(void)
     do_global_ctors();
     esp_ipc_init();
     spi_flash_init();
+#if CONFIG_BROWNOUT_DET
+    esp_brownout_init();
+#endif
+#if CONFIG_INT_WDT
+    int_wdt_init()
+#endif
+#if CONFIG_TASK_WDT
+    task_wdt_init()
+#endif
+
     xTaskCreatePinnedToCore(&main_task, "main",
             ESP_TASK_MAIN_STACK, NULL,
             ESP_TASK_MAIN_PRIO, NULL, 0);
diff --git a/components/esp32/include/esp_brownout.h b/components/esp32/include/esp_brownout.h
new file mode 100644 (file)
index 0000000..acce05e
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef BROWNOUT_H
+#define BROWNOUT_H
+
+void esp_brownout_init();
+
+#endif
\ No newline at end of file
diff --git a/components/esp32/include/esp_int_wdt.h b/components/esp32/include/esp_int_wdt.h
new file mode 100644 (file)
index 0000000..81404e3
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef INT_WDT_H
+#define INT_WDT_H
+
+void int_wdt_init();
+
+#endif
\ No newline at end of file
diff --git a/components/esp32/include/esp_task_wdt.h b/components/esp32/include/esp_task_wdt.h
new file mode 100644 (file)
index 0000000..9163e69
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef TASK_WDT_H
+#define TASK_WDT_H
+
+void task_wdt_feed();
+void task_wdt_delete();
+void task_wdt_init();
+
+#endif
\ No newline at end of file
diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c
new file mode 100644 (file)
index 0000000..392ed6b
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright 2015-2016 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.
+
+
+/*
+This routine enables a watchdog to catch instances of processes disabling
+interrupts for too long, or code within interrupt handlers taking too long. 
+It does this by setting up a watchdog which gets fed from the FreeRTOS
+task switch interrupt. When this watchdog times out, initially it will call
+a high-level interrupt routine that will panic FreeRTOS in order to allow
+for forensic examination of the state of the CPU. When this interrupt 
+handler is not called and the watchdog times out a second time, it will 
+reset the SoC.
+
+This uses the TIMERG1 WDT.
+*/
+
+#include "sdkconfig.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include <esp_types.h>
+#include "esp_err.h"
+#include "esp_intr.h"
+#include "soc/timer_group_struct.h"
+
+#include "esp_int_wdt.h"
+
+#if CONFIG_INT_WDT
+
+
+#define WDT_INT_NUM 24
+
+
+#define WDT_WRITE_KEY 0x50D83AA1
+
+static void esp_int_wdt_isr(void *arg) {
+       abort();
+}
+
+
+void int_wdt_init() {
+       TIMERG1.wdt_wprotect=WDT_WRITE_KEY;
+       TIMERG1.wdt_config0.sys_reset_length=7;                         //3.2uS
+       TIMERG1.wdt_config0.cpu_reset_length=7;                         //3.2uS
+       TIMERG1.wdt_config0.level_int_en=1;
+       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
+       TIMERG1.wdt_config0.en=1;
+       TIMERG1.wdt_feed=1;
+       TIMERG1.wdt_wprotect=0;
+       ESP_INTR_DISABLE(WDT_INT_NUM);
+       intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM);
+       xt_set_interrupt_handler(WDT_INT_NUM, int_wdt_isr, NULL);
+       ESP_INTR_ENABLE(WDT_INT_NUM);
+}
+
+
+
+void vApplicationTickHook(void) {
+       TIMERG1.wdt_wprotect=WDT_WRITE_KEY;
+       TIMERG1.wdt_feed=1;
+       TIMERG1.wdt_wprotect=0;
+}
+
+#endif
\ No newline at end of file
diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c
new file mode 100644 (file)
index 0000000..7ae3dfb
--- /dev/null
@@ -0,0 +1,158 @@
+// Copyright 2015-2016 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.
+
+
+/*
+This routine enables a more general-purpose task watchdog: tasks can individually
+feed the watchdog and the watchdog will bark if one or more tasks haven't fed the
+watchdog within the specified time. Optionally, the idle tasks can also configured
+to feed the watchdog in a similar fashion, to detect CPU starvation.
+
+This uses the TIMERG0 WDT.
+*/
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "sdkconfig.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include <esp_types.h>
+#include "esp_err.h"
+#include "esp_intr.h"
+#include "soc/timer_group_struct.h"
+#include "esp_log.h"
+
+#include "esp_task_wdt.h"
+
+#if CONFIG_TASK_WDT
+
+static const char* TAG = "task_wdt";
+
+
+typedef struct wdt_task_t wdt_task_t;
+struct wdt_task_t {
+       TaskHandle_t task_handle;
+       bool fed_watchdog;
+       wdt_task_t *next;
+};
+
+
+static wdt_task_t *wdt_task_list=NULL;
+
+#define WDT_INT_NUM 24
+
+
+#define WDT_WRITE_KEY 0x50D83AA1
+
+static void task_wdt_isr(void *arg) {
+       abort();
+}
+
+
+void task_wdt_feed() {
+       wdt_task_t *wdttask=wdt_task_list;
+       bool found_task=false, do_feed_wdt=true;
+       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) {
+               //See if we are at the current task.
+               if (wdttask->task_handle == handle) {
+                       wdttask->fed_watchdog=true;
+                       found_task=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) {
+               //This is the first time the task calls the task_wdt_feed function. Create a new entry for it in
+               //the linked list.
+               wdt_task_t *newtask=malloc(sizeof(wdt_task_t));
+               memset(newtask, 0, sizeof(wdt_task_t));
+               newtask->task_handle=handle;
+               newtask->fed_watchdog=true;
+               if (wdt_task_list == NULL) {
+                       wdt_task_list=newtask;
+               } else {
+                       wdttask=wdt_task_list;
+                       while (!(wdttask->next == NULL)) wdttask=wdttask->next;
+                       wdttask->next=wdttask;
+               }
+       }
+       if (do_feed_wdt) {
+               //All tasks have checked in; time to feed the hw watchdog.
+               TIMERG0.wdt_wprotect=WDT_WRITE_KEY;
+               TIMERG0.wdt_feed=1;
+               TIMERG0.wdt_wprotect=0;
+       }
+}
+
+void task_wdt_delete() {
+       TaskHandle_t handle=xTaskGetCurrentTaskHandle();
+       wdt_task_t *wdttask=wdt_task_list;
+       //Wdt task list can't be empty
+       if (!wdt_task_list) {
+               ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?");
+               return;
+       }
+       if (handle==wdt_task_list) {
+               //Current task is first on list.
+               wdt_task_list=wdt_task_list->next;
+               free(wdttask);
+       } else {
+               //Find current task in list
+               while (wdttask->next!=NULL && wdttask->next->task_handle!=handle) wdttask=wdttask->next;
+               if (!wdttask->next) {
+                       ESP_LOGE(TAG, "task_wdt_delete: Task never called task_wdt_feed!");
+                       return;
+               }
+               wdt_task_t *freeme=wdttask->next;
+               wdttask->next=wdttask->next->next;
+               free(freeme);
+       }
+}
+
+void task_wdt_init() {
+       TIMERG0.wdt_wprotect=WDT_WRITE_KEY;
+       TIMERG0.wdt_config0.sys_reset_length=7;                         //3.2uS
+       TIMERG0.wdt_config0.cpu_reset_length=7;                         //3.2uS
+       TIMERG0.wdt_config0.level_int_en=1;
+       TIMERG0.wdt_config0.stg0=1;                                                     //1st stage timeout: interrupt
+       TIMERG0.wdt_config0.stg1=3;                                                     //2nd stage timeout: reset system
+       TIMERG0.wdt_config1.clk_prescale=80*500;                        //Prescaler: wdt counts in ticks of 0.5mS
+       TIMERG0.wdt_config2=CONFIG_TASK_WDT_TIMEOUT_S*2000;             //Set timeout before interrupt
+       TIMERG0.wdt_config3=CONFIG_TASK_WDT_TIMEOUT_S*4000;             //Set timeout before reset
+       TIMERG0.wdt_config0.en=1;
+       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);
+       xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL);
+       ESP_INTR_ENABLE(ETS_T0_WDT_INUM);
+}
+
+
+#if CONFIG_TASK_WDT_CHECK_IDLE_TASK
+void vApplicationIdleHook(void) {
+       task_wdt_feed();
+}
+#endif
+
+#endif
\ No newline at end of file
index d1958e7701b6f67473469d4bcadc4d1c9e2baea5..a5483d6bbddc84e7c90574afde5d1abe8bf1d29d 100644 (file)
  *----------------------------------------------------------*/
 
 #define configUSE_PREEMPTION                   1
-#define configUSE_IDLE_HOOK                            0
+#define configUSE_IDLE_HOOK                            ( CONFIG_TASK_WDT_CHECK_IDLE_TASK )
 
-#define configUSE_TICK_HOOK                            0
+#define configUSE_TICK_HOOK                            ( CONFIG_INT_WDT )
 
 #define configTICK_RATE_HZ                             ( CONFIG_FREERTOS_HZ )