ESP32 RTOSでウォッチドッグタイマーを使ってみる (Arduino)

  • 2022.08.12
  • WDT
ESP32 RTOSでウォッチドッグタイマーを使ってみる (Arduino)

今回も Arduino のフレームワークで RTOS を使います。
今回はウォッチドッグタイマーを使ってみます。

この記事は JTAG でデバッグすることを前提にして書いています。
環境構築については こちら をご覧になってください。

投稿時の開発環境を記しておきます。

PC:
Windows10 OS

開発ボード :
ESP32-DevKitCーVE
(Soc : ESP32-D0WD-V3)

デバッガー(H/W):
FT2232D

デバッガー (S/W) :
Visual Studio Code + PlatformIO + Arduino Framework

オシロスコープ : 波形確認用

FreeRTOS

こちら が本家ですが、ESP32用としては このへん を見るのが良さそうです。

プロジェクトをつくる

VSCodeで、もし使っていたプロジェクトを開いていたら、File – Close Folder して閉じておきます。

その後にVSCodeからPlatformIOをOpenします。

以下の内容でプロジェクトを新規に作成します。

Name : ESP32A-RTOS-WDT
Board : Espressif ESP32 Dev Module
Framework : Arduino Framework

Name : ESP32A の “A” は Framework (Arduino Framework)の頭文字を示しています。

(後から見てわかるように、Arduinoを使うことを明示しています)

platformio.ini に以下の4行を追加して、 Ctrl + s で保存しておきます。

COM[8]の8の部分はデバイスマネージャーのポート(COMとLPT)で Silicon Labs CP210x から始まるCOMの番号を記述します。

upload_port はプログラムをアップロードする時に使うCOMポートを指定します。
monitor_port は printf()出力をモニターするCOMポートを指定します。

debug_tool = minimodule
upload_port = COM[8]
monitor_port = COM[8]
monitor_speed = 115200

ウォッチドッグタイマーとは?

番犬タイマーと呼べば良いでしょうか、WDTと省略して呼ばれることがあります。
カウンタを監視していて、オーバーフローするとマイコンにリセットをかける等するものです。
WDTは例えばプログラムが暴走して動かなくなった場合にリセットをかけて正常に動作させる役割を持ちます。
通常はリセットがかからないようにカウンタをリフレッシュ(リセット)してあげます。

ひと昔前までは、電源電圧の低下を監視する機能なども含んだ専用のICが主流でしたが今ではマイコンに内蔵しているものが多くなっています。
一長一短あると思いますがコスト面を考えると、こちらを使わない手はありません。

一般的にウォッチドッグタイマーとRTOSは無関係ですが、ESP32 のシステムでは関連付けているようです。
まずはRTOSのタスクを作りながら動作を確認してみることにします。

タスクに関連したウォッチドッグタイマーなのでTask Watchdog Timer (TWDT)と呼ばれています。

今回使うウォッチドッグタイマーに関連するAPI

esp_task_wdt_init()

機能:タスクWDTを初期化する
第1引数:タイムアウト時間を指定する(単位:秒)
第2引数:trueでパニックを有効にしてESP32を再起動する(trueを指定する)
戻り値:初期化が成功したかどうか

(注)一応リンクを張りましたが、古い情報なのか引数が構造体になっています。
実際には2つの引数(タイムアウト時間、パニック)を指定します。

コアとウォッチドッグタイマー

コア0のウォッチドッグタイマーはデフォルトで有効ですが、コア1は無効になっています。
コア1のウォッチドッグタイマーを使うためには設定用の関数を呼ぶ必要があります。

コア0のリセット動作

まず最初にコア0でタスクをつくり、カウンタをリフレッシュしない場合にマイコンがリセットすることを確認してみます。

( xTaskCreatePinnedToCore()の第7引数に PRO_CPU_NUM を指定することでコア0にタスクをつくることができます )

ソースコード main.cpp は以下の通りです。
オシロスコープでタスクの挙動を確認できるようにGPIO出力をパタパタさせています。
マイコンが再起動したことがわかるように setup()内で GPIO27 を 50msecの間 LOW にしています。
ESP32では vTaskDelay()を呼ぶことで、TWDTをリフレッシュ(リセット)することができます。
この例では core0a()内で定期的に vTaskDelay()を呼ぶ必要があります。
vTaskDelay()が呼ばれずにTWDTのタイムアウト時間に達するとマイコンがリセットします。

TWDTが有効なコア0のタスクcore0a() 内の vTaskDelay() をコメントにしているので、リセットがかかることになります。
リセットがかからないようにするには // をはずして vTaskDelay() を有効にしてください。

ではビルドして実行してみましょう。

#include <Arduino.h>

#include "esp_task_wdt.h"

TaskHandle_t taskHandle[3];

#define IO25 25
#define IO26 26
#define IO27 27

void core0a(void *args) {
  digitalWrite(IO25, HIGH);

  while (true) {
    digitalWrite(IO25, LOW);
    digitalWrite(IO25, HIGH);
//    vTaskDelay(1);
  }
}

void setup() {
  pinMode(IO25, OUTPUT);
  pinMode(IO26, OUTPUT);
  pinMode(IO27, OUTPUT);
  digitalWrite(IO25, LOW);
  digitalWrite(IO26, LOW);
  digitalWrite(IO27, LOW);
  delayMicroseconds(50000); // 50msec
  digitalWrite(IO27, HIGH);

  xTaskCreatePinnedToCore(core0a, "core0a", 4096, NULL, 2, &taskHandle[0],
                          PRO_CPU_NUM);
}

void loop() {
  digitalWrite(IO26, HIGH);

  while (true) {
    digitalWrite(IO26, LOW);
    digitalWrite(IO26, HIGH);
  }
}

マイコンがリセットしている様子をロジアナに記録したのでご覧ください。
上から GPIO25, 26, 27 の順番です。
マイコンは約5秒でリセットしています。

タイムアウト時間を延ばしてみる

ソースコード main.cpp は以下の通りです。
setup()の先頭で以下の関数を呼ぶコードを追加しました。

esp_task_wdt_init(WDT_TIMEOUT, true);

WDT_TIMEOUT=7 なのでタイムアウト時間は7秒です。

おそらくsetup()やloop()に来る前に、一度この関数が呼ばれています。
そこでタイムアウト時間を5秒に指定しているのだと思います。

この関数は上書き可能なのでタイムアウト時間が7秒に変更されることが確認できました。

5秒でも充分な長さだと思いますが、もし重い処理があって時間が不足するような場合には対策として有効です。
もちろん5秒が長すぎる場合には3秒(5秒未満の値)に変更することも可能です。

#include <Arduino.h>
#include "esp_task_wdt.h"

TaskHandle_t taskHandle[3];

#define IO25 25
#define IO26 26
#define IO27 27

#define WDT_TIMEOUT 7

void core0a(void *args) {
  digitalWrite(IO25, HIGH);

  while (true) {
    digitalWrite(IO25, LOW);
    digitalWrite(IO25, HIGH);
//    vTaskDelay(1);
  }
}

void setup() {
  esp_task_wdt_init(WDT_TIMEOUT, true);

  pinMode(IO25, OUTPUT);
  pinMode(IO26, OUTPUT);
  pinMode(IO27, OUTPUT);
  digitalWrite(IO25, LOW);
  digitalWrite(IO26, LOW);
  digitalWrite(IO27, LOW);
  delayMicroseconds(50000); // 50msec
  digitalWrite(IO27, HIGH);

  xTaskCreatePinnedToCore(core0a, "core0a", 4096, NULL, 2, &taskHandle[0],
                          PRO_CPU_NUM);
}

void loop() {
  digitalWrite(IO26, HIGH);

  while (true) {
    digitalWrite(IO26, LOW);
    digitalWrite(IO26, HIGH);
  }
}

vTaskDelay() をコメントにしているので、リセットがかかることになります。
マイコンは約7秒でリセットしています。
リセットがかからないようにするには// をはずして vTaskDelay() を有効にします。

コア1のTWDTを有効にする

次の関数をsetup()の中に記述するとコア1のTWDTが有効になります。
enableCore1WDT();

コア1にタスクをつくる

ソースコード main.cpp は以下の通りです。

loop()を含めて2つのタスクがあるので、双方に vTaskDelay() を配置する必要があります。
そうしないとリセットがかかってしまうことに注意してください。

以下のコードでは、loop()側の vTaskDelay()をコメントにしています。
約7秒でタイムアウトすることが確認できました。

( xTaskCreatePinnedToCore()の第7引数に APP_CPU_NUM を指定することでコア1にタスクをつくることができます )

#include <Arduino.h>
#include "esp_task_wdt.h"

TaskHandle_t taskHandle[3];

#define IO25 25
#define IO26 26
#define IO27 27

#define WDT_TIMEOUT 7

void core1a(void *args) {
  digitalWrite(IO25, HIGH);

  while (true) {
    digitalWrite(IO25, LOW);
    digitalWrite(IO25, HIGH);
    vTaskDelay(1);
  }
}

void setup() {
  enableCore1WDT();
  esp_task_wdt_init(WDT_TIMEOUT, true);

  pinMode(IO25, OUTPUT);
  pinMode(IO26, OUTPUT);
  pinMode(IO27, OUTPUT);
  digitalWrite(IO25, LOW);
  digitalWrite(IO26, LOW);
  digitalWrite(IO27, LOW);
  delayMicroseconds(50000); // 50msec
  digitalWrite(IO27, HIGH);

  xTaskCreatePinnedToCore(core1a, "core1a", 4096, NULL, 1, &taskHandle[0],
                          APP_CPU_NUM);
}

void loop() {
  digitalWrite(IO26, HIGH);

  while (true) {
    digitalWrite(IO26, LOW);
    digitalWrite(IO26, HIGH);
//    vTaskDelay(1);
  }
}

こちらもマイコンが約7秒でリセットしていることを確認できました。
(波形は省略します)

いかがでしたか?
皆さまは、うまく動かすことができたでしょうか。
お疲れさまでした。

WDTカテゴリの最新記事