看门狗机制用于监控嵌入式系统运行并在发生不可知的软硬件故障时将系统复位。系统正常运行时,看门狗定时器溢出之前会被重置计数值,也就是“喂狗”。定时器溢出意味着无法“喂狗”,系统异常。
0:BUG
前一阵在没事开着ESP32测试运行的时候,较长时间后会有很小的概率出现异常,Log未保存,大概是:“Task watchdog got triggered. Thefollowing tasks did not reset the watchdog in time:”,也就是说,系统内的任务看门狗未在指定时间内被复位(喂狗)。
出现这个问题以后,基本系统就不正常运行了。
因此关注ESP32的任务看门狗定时器(TaskWDT),以便于问题再出现时有些对策。
一些说明:
WDT:看门狗定时器
MWDT0/1:ESP32定时器0/1内置的看门狗定时器
RWDT:ESP32 RTC内置的看门狗定时器
TWDT:任务看门狗定时器,注意是ESP32 SDK软件上实现的软件定时器功能组件,并非硬件定时器模块。
1:TWDT源码路径
路径:
\esp-idf-v3.0-rc1\components\esp32\include\Esp_task_wdt.h TWDT API头文件
\esp-idf-v3.0-rc1\components\esp32\task_wdt.c TWDT源文件
重要API:
esp_err_t esp_task_wdt_init(uint32_ttimeout, bool panic); 初始化TWDT
esp_err_t esp_task_wdt_add(TaskHandle_thandle); 任务添加到TWDT链表
void esp_task_wdt_feed() __attribute__((deprecated)); 喂狗
2:TWDT数据结构
整个TWDT的数据结构是链表,基本的数据类型是twdt_task_t,头结点包含有扩展数据,类型是twdt_config_t,重要成员数据涵义如下:
TaskHandle_t task_handle:任务句柄
bool has_reset:标识位。标识该任务的是否已进行喂狗操作。
bool panic:标识位。若TWDT有溢出,置位则生成系统panic。
intr_handle_t intr_handle:函数指针,指向TWDT溢出中断处理函数。
uint32_t timeout:TWDT计时溢出的周期,单位秒。
基本上从数据结构已经可以看出TWDT是如何运作的。
3:TWDT初始化
3.1esp_task_wdt_init
esp_err_t esp_task_wdt_init(uint32_ttimeout, bool panic)实现TWDT模块的初始化。
参数timeout设定了TWDT溢出的时间,单位是秒;
参数panic设置当TWDT溢出时是否生成系统panic。
esp_task_wdt_init的代码部分很好理解,也就是初始化TWDT的链表头,填充相关数据的数据等。
TWDT是基于ESP32定时器0的WDT实现定时,不得不说这个形式的定时器是笔者见过比较复杂的(也可能是已经过气的原因),因此特别关注一下:
3.2ESP32 MWDT0
文档《esp32_technical_reference_manual_cn》Chapter19有关于ESP32定时器模块的描述,概要如下:
<1>ESP32内有三个WDT,两个通用定时器0/1模块内各一个WDT(MWDT0& MWDT1),RTC模块内一个(RWDT);
<2>MWDT与RWDT使能后循环工作,且每个循环周期可被配置为4个阶段,阶段0~阶段3。
每个阶段的超时时间与超时动作都可以单独配置。当软件喂狗时,WDT重置回阶段0。
<3>以下动作可以被配置为WDT定时时间接近某个阶段预设定时值时的执行的动作:
A:触发WDT溢出中断
B:复位指定的CPU(ESP32包含两个CPU):
复位 MWDT0只能复位 PRO CPU,复位 MWDT1只能复位APP CPU;
根据不同配置,复位 RWDT 可以复位两个,一个或不复位 CPU 内核。
C:复位主系统:复位CPU以及MWDT在内的外设,RTC除外。
D:复位主系统与RTC:复位CPU以及全部外设,只能通过RWDT实现。
E:Do Nothing
这里再通过esp_task_wdt_init解析一下TWDT的配置:
TIMERG0.wdt_config0.sys_reset_length=7;
CPU复位长度信号选择 7:3.2us
TIMERG0.wdt_config0.cpu_reset_length=7;
系统复位长度信号选择 7:3.2us
TIMERG0.wdt_config1.clk_prescale=80*500;
定时器0预分频器值,分辨率 = 12.5ns* 80 * 500 = 500000ns = 0.5ms
TIMERG0.wdt_config2=twdt_config->timeout*2000;
时钟周期0超时时间:timeout * 2000* 0.5ms = timeout(S)
TIMERG0.wdt_config3=twdt_config->timeout*4000;
时钟周期1超时时间:timeout * 4000* 0.5ms = timeout * 2(S)
可见,MWDT0每个计时周期被设置为两个阶段:
阶段0:计时时间0 - timeout(S),若期间MWDT被喂狗,则重新计时;若超时,则触发WDT超时中断。
阶段1:计时时间timeout(S)–2*timeout(S),若期间MWDT被喂狗,则重新回到阶段0;若超时,异常,复位主系统。
3.3阶段与超时动作
MWDT0的计时周期被分为阶段0与阶段1。
阶段1超时的处理是复位主系统,因此无需多说;
阶段0超时触发的中断则是在
ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE,0, task_wdt_isr, NULL, &twdt_config->intr_handle))关联到了函数task_wdt_isr。
task_wdt_isr做的事情其实很简单,就是在TWDT链表内遍历,找到位于链表内但未进行喂狗操作的任务打印出来。另外,若esp_task_wdt_init的panic参数被设置为true,则产生一个系统panic:
这里也就是为本文开头所述BUG出现的地方了。必然是系统内有任务被阻塞到无法喂狗,TWDT超时,最后因为配置了panic,设备无法正常运行。
4:向TWDT添加任务与任务喂狗
4.1函数接口
TWDT完成初始化时,其链表内并没有任务数据。这时用户要把需要被TWDT监控的任务添加到TWDT链表内,其接口是:esp_task_wdt_add
与此同时,用户要在自己的任务处理函数中进行喂狗操作,其接口时esp_task_wdt_feed
这里截取一部分esp_task_wdt_feed代码:
很显然,用户任务调用esp_task_wdt_feed喂狗时,esp_task_wdt_feed会找到当前运行的任务,也就是执行喂狗操作的任务。若该任务不存在于TWDT的链表,说明这个任务未被TWDT监控,直接返回;否则的话,所谓的喂狗操作就是简单的设置了该任务的喂狗标识位。
值得注意的是蓝色部分,当任务链表内的所有任务的喂狗标识位都被设置,会复位MWDT0并且将TWDT内所有的任务的喂狗标识位重新设置为“未喂狗”状态。
4.2ESP32 SDK默认TWDT监控系统IDLE任务
搜索esp_task_wdt_feed却并未发现有任何任务进行喂狗操作。那么为何导致本文开头所述的BUG?
原因是ESP32 SDK通过esp_task_wdt_add将FreeRTOS的空闲任务加入了TWDT的监控链表,并且还通过esp_register_freertos_idle_hook_for_cpu将函数idle_hook_cb注册为空闲任务的任务处理函数。而idle_hook_cb所做的事情已经没有悬念了,就是喂狗咯。
这也就意味着,出现本文开头所述的BUG时,当时系统在TWDT溢出时间内从未让出过CPU让IDLE任务得以执行。这里随手看一下正常运行时任务CPU占有率(两个IDLE任务是因为ESP32双核):
正常情况下,整个系统98%的时间基本都是IDLE的。
所以这个BUG出现时到底发生了什么,比较可怕。只能等下一次出现。
5:ESP32 SDK TWDT的配置
ESP32 SDK的TWDT组件是可以配置的:
Make menuconfig后按照路径找:
![](https://i-blog.csdnimg.cn/blog_migrate/0238b25f0b057e4b3b270db2d3cf52ca.png)
当前设置超时周期5S。
6:使用TWDT监控重要任务
TBD