STM32 もうひとつのウォッチドッグタイマー WWDG

  • 2020.06.09
  • HAL
STM32 もうひとつのウォッチドッグタイマー WWDG

前回は独立型ウォッチドッグタイマーについてお話ししました。
今回はウィンドウウォッチドッグタイマーについて見ていきます。

前回の記事と少し重複している部分がありますこと、ご容赦ください。

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

PC:Windows10 OS
IDE: STM32CubeIDE Version1.3.0
Configurator: STM32CubeMX Version5.5.0
Board: STM32Nucleo-F401RE

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

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

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

STM32では2種類のウォッチドッグタイマーが存在します。

IWDG:
独立型ウォッチドッグタイマー

先頭の I は independence の略ですね。
どちらかと言えば、こちらが一般的なのではないかと思います。
IWDGはカウンタを動かしてオーバーフローした時にリセットをかけるものです。
独立型と言われるのは、タイマーを動かすクロックがマイコンのメインクロックとは別にあるからです。

何かの障害でマイコンのクロックが停止してしまった場合でも、IWDGが動いていればリセットをかけることができます。

WWDG:
ウィンドウ・ウォッチドッグタイマー

言うまでもなく先頭の W は window の略ですね。

ウィンドウの意味は窓というよりは窓枠です。
WWDGはカウンタがオーバーフローすることに加えて、そのタイミングが想定よりも早くなることも検出します。

より堅牢なシステムでは、これら2つのWDTを組み合わせて使うことが推奨されているようです。

前回書き損じてしまったことを追記しておきます。
WWDGに関することなので、こちらにだけ記述しておけば良いでしょう。

ウィンドウ・ウォッチドッグタイマーはマイコンのクロックで動くので、停止することが可能です。
ですから間欠システムでウォッチドッグタイマーが必要な場合には有効な手段になりそうです。
低消費電力を売りにするARMマイコンですから間欠システムで使われることも多いでしょう。

ウィンドウウォッチドッグタイマー

今回はウィンドウウォッチドッグタイマーの挙動について確認してみます。

いつものように IDE (STM32CubeIDE) でプロジェクトを作成します。

File – New – STM32 Project を選択します。

Board Selector で NUCLEO-F401RE を選択し Next を押します。

Project Name に F401WWDT と入力して Finish を押します。

Initialize all peripherals with their default Mode ? と聞いてくるので Yes を押します。

(周辺機能をデフォルト値で初期化しますか?)

This kind of project is associated with the STM32CubeMx perspective.
Do you want to open this perspective now? と聞いてくるので Yes を押します。

(この種のプロジェクトはSTM32CubeMXパースペクティブに関連づけられています。今開きますか?)

この意味がわかりにくいのですが「コンフィグレーターであるSTM32CubeMXをIDEのレイアウトに取り込みますか?」と解釈しました。

これで、このボード用のある仕様で周辺機能の初期化まで行ったプロジェクトがつくられました。

Pinout & Configuration – Categories – System Core の WWDG をクリックします。

Mode – Activated にチェックを入れます。

Project Explorer でプロジェクト名 F401IWDT をクリックしておき、 Project – Build Project を選択しビルドします。

main()の中に MX_WWDG_Init()が作成されました。

MX_WWDG_Init()をダブルクリックして選択し、右クリックメニューから Open Declaration を選択することで関数の中を見ることができます。

WWDT 初期化のコード

static void MX_WWDG_Init(void)
{
  hwwdg.Instance = WWDG;
  hwwdg.Init.Prescaler = WWDG_PRESCALER_1;
  hwwdg.Init.Window = 64;
  hwwdg.Init.Counter = 64;
  hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;
  if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
  {
    Error_Handler();
  }
}

コメントを省くと上のコードが書かれています。

hwwdg は WWDG_HandleTypeDef型の構造体です。

これをダブルクリックして選択し右クリックメニューから Open Declaration すると宣言部分に飛んでいき構造体について確認することができます。

初期化処理の途中でリセットがかかってしまう

原因がわからないのですが HAL_WWDG_Init()の中でリセットがかかってしまいます。

リセット系のブロック図は以下の通りになっています。

次にコンフィグレーターが生成したコードです。
以下の部分をステップ実行し上の行の WWDGのCR レジスタに値を書き込んだ瞬間にリセットがかかります。

WRITE_REG(hwwdg->Instance->CR, (WWDG_CR_WDGA | hwwdg->Init.Counter));
WRITE_REG(hwwdg->Instance->CFR, (hwwdg->Init.EWIMode | hwwdg->Init.Prescaler | hwwdg->Init.Window));

これは順番を逆にすべきではないかと思います。
条件を揃える前にウォッチドッグタイマーをイネーブルにしているからです。

そこで以下のようにコードを書くと初期化処理の途中でリセットがかかることはなくなりました。

WRITE_REG(hwwdg->Instance->CR, hwwdg->Init.Counter);
WRITE_REG(hwwdg->Instance->CFR, (hwwdg->Init.EWIMode | hwwdg->Init.Prescaler | hwwdg->Init.Window));

if ((hwwdg->Instance->CR & 0x7f) < (hwwdg->Instance->CFR & 0x7f))
{
  WRITE_REG(hwwdg->Instance->CR, (WWDG_CR_WDGA | hwwdg->Init.Counter));
}

if()の中はカウンタの値がウィンドウの値より小さいことを確認しています。
カウンタの値がウィンドウの値より大きい場合に CRレジスタに値を書くとリセットがかかってしまうからです。

実はここも、やや疑問がありますが動いてしまったので結果オーライにしました。

何が疑問かというと、if()の条件が成立しないとウォッチドッグタイマーが有効になりません。
でも必ずif()の中を通るので「まぁ、いいか」となってしまいました。

でもif()の条件をとるとリセットしてしまうのです(謎)

もうひとつオマケです。
以下のコードがベストではないかと思うのですが、こちらを使うとリセットがかかってしまいます。これも謎です。
カウンターはフリーランニングしているので、このコードで良さそうに思いますけれど。

while (1)
{
  if ((hwwdg->Instance->CR & 0x7f) < (hwwdg->Instance->CFR & 0x7f))
  {
    WRITE_REG(hwwdg->Instance->CR, (WWDG_CR_WDGA | hwwdg->Init.Counter));
    return HAL_OK;
  }
}

カウンターをリフレッシュするコードを追加する

リセットがかからないようにリフレッシュするコードをmain()関数内に追加してみます。

while (1)
{
  /* USER CODE END WHILE */
  if ((hwwdg.Instance->CR & 0x7f) < (hwwdg.Instance->CFR & 0x7f))
  {
   HAL_WWDG_Refresh(&hwwdg);
  }
  /* USER CODE BEGIN 3 */
}

これでプログラムを実行すると HAL_Init()には最初の一度だけしか来ないことが確認でき、カウンターのリフレッシュ動作が効いていることがわかります。

リフレッシュの処理はwhile(1)ループで頻繁に行う必要はなく、定期的に処理するなどシステムによって検討する必要があります。

WWDGはクリティカルで非常に使いにくい印象を受けました。

興味のある方は、ぜひ試して、はまってみてください(笑)

HALカテゴリの最新記事