ESP-IDFを使ってみる

WatchDog TimerとBrownout detection


ESP-IDF は、割り込みウォッチドッグ タイマー(IWDT)と、タスク ウォッチドッグ タイマー(TWDT)の 2種類のウォッチドッグをサポートしています。
ウォッチドッグの詳細はこ ちらに公開されています。

IWDTは、割り込みサービスルーチン(ISR)が長時間にわたって実行をブロックしていることを検出する機能を持っています。
割り込みサービスルーチン内で、所定の時間(デフォルトでは300ミリ秒)を超えて処理を行うと、割り込みウォッチドッグ タイマーにより以下のアラートが発生します。
割り込みサービスルーチンは、可能な限り最小限の時間内に処理を終える必要があります。
割り込みサービスルーチン内で使用することが出来るAPIは限られています。
割り込みサービスルーチン内で使用できる通知関数(xQueueSendFromISR、xMessageBufferSendFromISR、 xTaskNotifyFromISRなど)が用意されていて、
これらを使ってタスク側に割り込みが有ったことを通知します。
Guru Meditation Error: Core  0 panic'ed (Interrupt wdt timeout on CPU0).

Core  0 register dump:
PC      : 0x40090f7b  PS      : 0x00060535  A0      : 0x80165cd1  A1      : 0x3ffbfab0 
0x40090f7b: vListInsert at /home/q/WorkSpace/esp/esp-idf/components/freertos/FreeRTOS-Kernel/list.c:183 (discriminator 3)

A2      : 0x3fff7a3c  A3      : 0x3ffb7b34  A4      : 0xb33fffff  A5      : 0x00060523 
A6      : 0x3ffc35d8  A7      : 0x0000cdcd  A8      : 0x3ffb7b34  A9      : 0x00000019 
A10     : 0x00000019  A11     : 0x00000000  A12     : 0xb33fffff  A13     : 0x00060523 
A14     : 0x00000001  A15     : 0x0000cdcd  SAR     : 0x0000001d  EXCCAUSE: 0x00000005 
EXCVADDR: 0x00000000  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0xffffffff

TWDTは、長期間にわたって中断せずに実行されているタスクのインスタンスを検出する機能を持っています。
通常、実行中のタスクはタスク ウォッチドッグ タイマーを定期的にリセットしますが、タスクがハングしたり、永久ループすると、このタイマーのリセット処理が止まります。
タスク ウォッチドッグ タイマーが一定時間を超えると、タスク ウォッチドッグアラートが発生します。
タスク ウォッチドッグアラートが発生すると、以下のアラートを表示します。
こちらはマルチコアの場合のタスク ウォッチドッグアラート発生時の表示で、CPU 0側のmainタスクに異常が発生したことが分かります。
E (220315) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (220315) task_wdt:  - IDLE0 (CPU 0)
E (220315) task_wdt: Tasks currently running:
E (220315) task_wdt: CPU 0: main
E (220315) task_wdt: CPU 1: IDLE1
E (220315) task_wdt: Print CPU 0 (current core) backtrace

こちらはシングルコアの場合のタスク ウォッチドッグアラート発生時の表示で、CPU 1側の表示は有りません。
E (10277) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (10277) task_wdt:  - IDLE (CPU 0)
E (10277) task_wdt: Tasks currently running:
E (10277) task_wdt: CPU 0: main
E (10277) task_wdt: Print CPU 0 (current core) backtrace

sdkconfig.defaultsに以下を定義することで、タスク ウォッチドッグ タイマー を無効にすることができます。
TFT等に全速力で描画するときなどは、タスク ウォッチドッグ タイマー を無効にする必要が有ります。
割り込みウォッチドッグ タイマーも無効にしたり、タイムアウト時間を変更することができますが、そもそも割り込みハンドラーを長時間実行するのは、
RTOS的によろしくないので、割り込みウォッチドッグによるアラートが発生した時は、処理を見直すべきです。
# Disable Interrupt Watchdog Timer
# CONFIG_ESP_INT_WDT=n

# Disable Task Watchdog Timer
CONFIG_ESP_TASK_WDT_EN=n

ESP-IDFにはマイクロ秒単位でスリープするesp_rom_delay_us()関数が有ります。
今まで、この関数を定期的に呼び出せば、タスク ウォッチドッグアラートを防ぐことが出来ると思っていましたが、
最近になって、この関数ではタスク ウォッチドッグアラートを防ぐことが出来ないことが分かりました。

以下のコードを実行すると、タスク ウォッチドッグアラートが発生します。
esp_rom_delay_us(10000) も vTaskDelay(1) も、待ち時間は10ミリ秒で同じですが、vTaskDelay(1) に変更すると、タスク ウォッチドッグアラートは発生しなくなります。
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char *TAG = "MAIN";

void WaitForTimeout(unsigned long timeout)
{
    //TaskHandle_t TaskHandle = xTaskGetCurrentTaskHandle();
    TickType_t start = xTaskGetTickCount();
    int counter = 0;

    while(xTaskGetTickCount() - start < timeout) {
        esp_rom_delay_us(10000);
        //vTaskDelay(1);
        counter++;
    }
    ESP_LOGI(TAG, "counter=%d", counter);
}

void app_main(void)
{
    ESP_LOGI(TAG, "app_main start");
    for (int i=0;i<10;i++) {
        ESP_LOGI(TAG, "i=%d", i);
        WaitForTimeout(500);
    }
}

割り込みウォッチドッグアラートが発生するとアラートの内容を標準出力に表示し、パニックハンドラーを呼び出します。
パニックハンドラーのデフォルトの動作は以下の様になっています。


タスクウォッチドッグアラートが発生するとアラートの内容を標準出力に表示しますが、パニックハンドラーが無効になっています。
従って、デフォルトの動作では、タスクウォッチドッグアラートが発生しても、アプリの実行を続行する前に警告とバックトレースを出力するだけで す。
この動作はタスクウォッチドッグアラート時のパニックハンドラを有効にすることで変更する事ができます。


これで、タスクウォッチドッグアラートが発生すると、処理継続ではなくリセットに変わります。
次にパニックハンドラーの動作を以下の様に変更します。


タスク ウォッチドッグアラート、または割り込みウォッチドッグアラートが発生すると、リセットではなくHaltに変わります。
E (5315) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (5315) task_wdt:  - IDLE0 (CPU 0)
E (5315) task_wdt: Tasks currently running:
E (5315) task_wdt: CPU 0: main
E (5315) task_wdt: CPU 1: IDLE1
E (5315) task_wdt: Aborting.
E (5315) task_wdt: Print CPU 0 (current core) backtrace




Backtrace: 0x40008544:0x3ffb4d70 0x400d5266:0x3ffb4d90 0x400d5292:0x3ffb4db0 0x400e2c20:0x3ffb4dd0 0x40085fd5:0x3ffb4e00
0x40008544: ets_delay_us in ROM
0x400d5266: WaitForTimeout at /home/nop/rtos/watchdog-test/main/main.c:15
0x400d5292: app_main at /home/nop/rtos/watchdog-test/main/main.c:25 (discriminator 1)
0x400e2c20: main_task at /home/nop/esp-idf/components/freertos/app_startup.c:208
0x40085fd5: vPortTaskWrapper at /home/nop/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:134





ELF file SHA256: 6f5a83fab

CPU halted.

以下のコードを追加すると、タスク ウォッチドッグアラートが発生した時にユーザのコードを実行することができます。
この関数が呼ばれたのちに、configに従ったパニック処理が行われます。
void esp_task_wdt_isr_user_handler(void)
{
    esp_rom_printf("called esp_task_wdt_isr_user_handler\n");
}



ウォッチドッグによるパニックとよく似たパニックに、Brownout detection eventが有ります。
ESP-IDF では、電圧低下検出機能を使用してパニックをトリガーし、何が起こっているのかをユーザーに知らせますが、その直後に再起動します。
以下の項目を無効にすることで、Brownout detection(BOD)を無効にすることができますが、電源電圧が低下した状態で使い続けると、
正常な動作は期待できなくなります。
Brownout detectionは有効にしておいて、これが発生した時は、電源関係を見直した方がいいです。
カメラ付きのESP-CAMは消費電力が大きいので、安物のUSB変換アダプターを使うと、直ぐにBrownout detection eventが発生します。


続く...