ESP32失败的喂狗经历
概述
采用ARDUINO IDE为ESP32进行编程的资料非常有限,中国的乐鑫也没有太多的资料支持。但从乐鑫的官网还是可以看到对FreeRTOS的介绍,试着对Task Watchdog Timer进行理解和编程,最终归于失败,用vTaskDelay()函数防止中断触发,但如果想让程序更快执行没有实现,记录失败过程供大家参考。
Task Watchdog Timer
在ESP32的硬件配置中有中断看门狗和任务看门狗。把中断看门狗的介绍文字贴在这里,这些文字很难理解,肯定不是中国人写的:The interrupt watchdog makes sure the FreeRTOS task switching interrupt isn’t blocked for a long time. This is bad because no other tasks, including potentially important ones like the WiFi task and the idle task, can’t get any CPU runtime. A blocked task switching interrupt can happen because a program runs into an infinite loop with interrupts disabled or hangs in an interrupt. 粘贴到google得到的翻译如下:
中断看门狗确保FreeRTOS任务切换中断不会长时间被阻止。 这很不好,因为没有其他任务(包括可能重要的任务,如WiFi任务和空闲任务)无法获得任何CPU运行时间。 可能会发生阻塞的任务切换中断,因为程序在禁用中断或陷入中断的情况下进入了无限循环。
这部分的描述是不是一头雾水。不知道这些文字是从中文翻译成英文还是从意大利文翻译成英文就这个样子了。最简单的一条是中断看门狗的作用就是不要在任务切换时不能长时间占用CPU的时间。
任务看门狗定时器的描述也不是容易理解的事情,英文也贴在这里:The Task Watchdog Timer (TWDT) is responsible for detecting instances of tasks running for a prolonged period of time without yielding. This is a symptom of CPU starvation and is usually caused by a higher priority task looping without yielding to a lower-priority task thus starving the lower priority task from CPU time. This can be an indicator of poorly written code that spinloops on a peripheral, or a task that is stuck in an infinite loop. 用google翻译看看我们的智能翻译的路其实还有很多路要走:
任务看门狗定时器(TWDT)负责检测长时间运行的任务实例而不会屈服。 这是CPU饥饿的症状,通常是由较高优先级的任务循环导致的,而没有产生较低优先级的任务,因此使较低优先级的任务因CPU时间而饿死。 这可能表明外围设备上的自旋循环编写不佳的代码,或者卡在无限循环中的任务。
这部分看起来也不轻松。
控制看门狗的函数
控制看门狗的函数在程序中找到了,在sdk的目录下面,我机器上的目录是:
C:\用户<User Name>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\sdk\include\esp32.
头文件名称为:esp_int_wdt.h
乐鑫的文档说明地址:
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/wdts.html
在程序中这些函数都可以使用,编译也可以通过,但就是总重启,无法在任务循环时间小于1秒时喂狗。比如我使用:
esp_err_tesp_task_wdt_init(uint32_t timeout, bool panic); 得到的结果是ESP_OK. 然后再使用:
esp_err_tesp_task_wdt_add(TaskHandle_thandle); 得到的结果也是ESP_OK. 只是在循环过程中用:
esp_err_tesp_task_wdt_reset(void); 无法实现喂狗。程序在循环时总重新启动。
最后没有办法就是在任务中的循环中添加vTaskDelay(1)来避免重启。当选择vTaskDelay(0)时就会使TWDT中断超时并触发以下信息:
ARDUINO_RUNNING_CORE = 1
setup() running on the core 1
TaskBlink on the core 0
E (5812) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (5812) task_wdt: – IDLE0 (CPU 0)
E (5812) task_wdt: Tasks currently running:
E (5812) task_wdt: CPU 0: TaskBlink
E (5812) task_wdt: CPU 1: loopTask
E (5812) task_wdt: Aborting.
abort() was called at PC 0x400d5bd3 on core 0
今天使用Ticker时钟为程序编程,当使用1ms作为定时器时也出现了没有喂狗的问题。
21:31:25.785 -> Timer = 8E (10760) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
21:31:25.785 -> E (10760) task_wdt: – IDLE0 (CPU 0)
21:31:25.820 -> E (10760) task_wdt: Tasks currently running:
21:31:25.820 -> E (10760) task_wdt: CPU 0: esp_timer
21:31:25.820 -> E (10760) task_wdt: CPU 1: loopTask
21:31:25.820 -> E (10760) task_wdt: Aborting.
21:31:25.820 -> abort() was called at PC 0x400d3737 on core 0
这个问题虽然是我的程序有问题,但还是不知道为啥会出现喂狗的问题。看样子Ticker的定时器是运行在CPU0当中的。使用的是esp_timer.
希望了解的大咖加以指点。
后记
今天看了知乎上的一个大咖的文章,实验了一下,可以解决了。其实在网友留言(锋仔2)的解决方案就可以解决这个问题。将文章做了简单的修改粘贴在这里:
/* 库函数在这里添加 "soc/rtc_wdt.h", 这个库函数的地址在哪里? */
#include "soc/rtc_wdt.h" // 设置看门狗应用
#define digitalToggle(x) digitalWrite(x, !digitalRead(x))
void setup()
{
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
rtc_wdt_protect_off(); // 看门狗写保护关闭,关闭后可以喂狗
rtc_wdt_enable(); // 启动看门狗
rtc_wdt_set_time(RTC_WDT_STAGE0, 800); // 设置看门狗超时 800ms,超时重启
}
void loop() {
while(true)
{
rtc_wdt_feed(); //喂狗函数
digitalToggle(LED_BUILTIN);
delay(500); // 设置延时500 ms可以正常运行,如果设置1000 ms会不断重启
Serial.println("Perseverance52");
}
}
这样可以利用看门狗监视程序运行,如果超过了一定的时间会重新启动。
后后记
使用ESP32-SOLO-1板子时又出现了看门狗的问题,还是不断重启,用上面的函数也没有用。使用的版本是Arduino 1.8.19. core版本为1.0.6. 只有在loop程序中增加一个delay(1)的函数才不会重复重启。
关于网友们说的”soc/rtc_wdt.h”的库的位置,我把我安装的ESP32的库的位置放在这里供大家参考。这个库是在安装了ESP32情况下会自动安装的。
安装ESP32时,如果不自己设置的话,会默认安装在:
C:\用户\用户名\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\tools\sdk\include\soc\soc
这个文件目录很长,要耐心寻找。至于说网友们咋找到的我也不清楚,也不知道他们有啥教程或者是文章介绍了。总之,感谢互联网的伟大。
rtc_wtd.h抄录如下:
// Copyright 2018 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.
/* Recommendation of using API RTC_WDT.
1) Setting and enabling rtc_wdt:
@code
rtc_wdt_protect_off();
rtc_wdt_disable();
rtc_wdt_set_length_of_reset_signal(RTC_WDT_SYS_RESET_SIG, RTC_WDT_LENGTH_3_2us);
rtc_wdt_set_stage(RTC_WDT_STAGE0, RTC_WDT_STAGE_ACTION_RESET_SYSTEM); //RTC_WDT_STAGE_ACTION_RESET_SYSTEM or RTC_WDT_STAGE_ACTION_RESET_RTC
rtc_wdt_set_time(RTC_WDT_STAGE0, 7000); // timeout rtd_wdt 7000ms.
rtc_wdt_enable();
rtc_wdt_protect_on();
@endcode
* If you use this option RTC_WDT_STAGE_ACTION_RESET_SYSTEM then after reset you can see these messages.
They can help to understand where the CPUs were when the WDT was triggered.
W (30) boot: PRO CPU has been reset by WDT.
W (30) boot: WDT reset info: PRO CPU PC=0x400xxxxx
... function where it happened
W (31) boot: WDT reset info: APP CPU PC=0x400xxxxx
... function where it happened
* If you use this option RTC_WDT_STAGE_ACTION_RESET_RTC then you will see message (rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT))
without description where were CPUs when it happened.
2) Reset counter of rtc_wdt:
@code
rtc_wdt_feed();
@endcode
3) Disable rtc_wdt:
@code
rtc_wdt_disable();
@endcode
*/
#ifndef _SOC_RTC_WDT_H
#define _SOC_RTC_WDT_H
#include <stdint.h>
#include <stdbool.h>
#include "soc/rtc_cntl_reg.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C"
{
#endif
/// List of stage of rtc watchdog. WDT has 4 stage.
typedef enum {
RTC_WDT_STAGE0 = 0, /*!< Stage 0 */
RTC_WDT_STAGE1 = 1, /*!< Stage 1 */
RTC_WDT_STAGE2 = 2, /*!< Stage 2 */
RTC_WDT_STAGE3 = 3 /*!< Stage 3 */
} rtc_wdt_stage_t;
/// List of action. When the time of stage expires this action will be triggered.
typedef enum {
RTC_WDT_STAGE_ACTION_OFF = RTC_WDT_STG_SEL_OFF, /*!< Disabled. This stage will have no effects on the system. */
RTC_WDT_STAGE_ACTION_INTERRUPT = RTC_WDT_STG_SEL_INT, /*!< Trigger an interrupt. When the stage expires an interrupt is triggered. */
RTC_WDT_STAGE_ACTION_RESET_CPU = RTC_WDT_STG_SEL_RESET_CPU, /*!< Reset a CPU core. */
RTC_WDT_STAGE_ACTION_RESET_SYSTEM = RTC_WDT_STG_SEL_RESET_SYSTEM, /*!< Reset the main system includes the CPU and all peripherals. The RTC is an exception to this, and it will not be reset. */
RTC_WDT_STAGE_ACTION_RESET_RTC = RTC_WDT_STG_SEL_RESET_RTC /*!< Reset the main system and the RTC. */
} rtc_wdt_stage_action_t;
/// Type of reset signal
typedef enum {
RTC_WDT_SYS_RESET_SIG = 0, /*!< System reset signal length selection */
RTC_WDT_CPU_RESET_SIG = 1 /*!< CPU reset signal length selection */
} rtc_wdt_reset_sig_t;
/// Length of reset signal
typedef enum {
RTC_WDT_LENGTH_100ns = 0, /*!< 100 ns */
RTC_WDT_LENGTH_200ns = 1, /*!< 200 ns */
RTC_WDT_LENGTH_300ns = 2, /*!< 300 ns */
RTC_WDT_LENGTH_400ns = 3, /*!< 400 ns */
RTC_WDT_LENGTH_500ns = 4, /*!< 500 ns */
RTC_WDT_LENGTH_800ns = 5, /*!< 800 ns */
RTC_WDT_LENGTH_1_6us = 6, /*!< 1.6 us */
RTC_WDT_LENGTH_3_2us = 7 /*!< 3.2 us */
} rtc_wdt_length_sig_t;
/**
* @brief Get status of protect of rtc_wdt.
*
* @return
* - True if the protect of RTC_WDT is set
*/
bool rtc_wdt_get_protect_status();
/**
* @brief Set protect of rtc_wdt.
*/
void rtc_wdt_protect_on();
/**
* @brief Reset protect of rtc_wdt.
*/
void rtc_wdt_protect_off();
/**
* @brief Enable rtc_wdt.
*/
void rtc_wdt_enable();
/**
* @brief Enable the flash boot protection procedure for WDT.
*
* Do not recommend to use it in the app.
* This function was added to be compatibility with the old bootloaders.
* This mode is disabled in bootloader or using rtc_wdt_disable() function.
*/
void rtc_wdt_flashboot_mode_enable();
/**
* @brief Disable rtc_wdt.
*/
void rtc_wdt_disable();
/**
* @brief Reset counter rtc_wdt.
*
* It returns to stage 0 and its expiry counter restarts from 0.
*/
void rtc_wdt_feed();
/**
* @brief Set time for required stage.
*
* @param[in] stage Stage of rtc_wdt.
* @param[in] timeout_ms Timeout for this stage.
*
* @return
* - ESP_OK In case of success
* - ESP_ERR_INVALID_ARG If stage has invalid value
*/
esp_err_t rtc_wdt_set_time(rtc_wdt_stage_t stage, unsigned int timeout_ms);
/**
* @brief Get the timeout set for the required stage.
*
* @param[in] stage Stage of rtc_wdt.
* @param[out] timeout_ms Timeout set for this stage. (not elapsed time).
*
* @return
* - ESP_OK In case of success
* - ESP_ERR_INVALID_ARG If stage has invalid value
*/
esp_err_t rtc_wdt_get_timeout(rtc_wdt_stage_t stage, unsigned int* timeout_ms);
/**
* @brief Set an action for required stage.
*
* @param[in] stage Stage of rtc_wdt.
* @param[in] stage_sel Action for this stage. When the time of stage expires this action will be triggered.
*
* @return
* - ESP_OK In case of success
* - ESP_ERR_INVALID_ARG If stage or stage_sel have invalid value
*/
esp_err_t rtc_wdt_set_stage(rtc_wdt_stage_t stage, rtc_wdt_stage_action_t stage_sel);
/**
* @brief Set a length of reset signal.
*
* @param[in] reset_src Type of reset signal.
* @param[in] reset_signal_length A length of reset signal.
*
* @return
* - ESP_OK In case of success
* - ESP_ERR_INVALID_ARG If reset_src or reset_signal_length have invalid value
*/
esp_err_t rtc_wdt_set_length_of_reset_signal(rtc_wdt_reset_sig_t reset_src, rtc_wdt_length_sig_t reset_signal_length);
/**
* @brief Return true if rtc_wdt is enabled.
*
* @return
* - True rtc_wdt is enabled
*/
bool rtc_wdt_is_on();
#ifdef __cplusplus
}
#endif
#endif // _SOC_RTC_WDT_H