2014年6月3日火曜日

AVR がウォッチドッグタイマ発動後に再起動を繰り返す問題

AVR マイコンにはウォッチドッグタイマが搭載されている。 ウォッチドッグタイマとは、メインプログラムの実行とは独立に勝手にカウントアップされていくカウンタで、そのカウンタがオーバーフローすると強制的にマイコンにリセットがかかるというものである。よってウォッチドッグタイマを有効にしたときには、メインプログラム側で、定期的にウォッチドッグタイマの値を0にクリアする命令を実行しないといけない。

これが何の役に立つかというと、例えば異常が発生してメインプログラムが無限ループにはまった時には、ウォッチドッグタイマがオーバーフローして自動的にリセットしてくれる。よって、完全に固まるという最悪の状況だけは避けることができる。

※ もっとも、運悪く無限ループの中にウォッチドッグタイマの値を0にクリアする命令が含まれていた場合は、ウォッチドッグタイマを有効にしていたとしても固まってしまうが。

他の使い方もある。例えば AVR にはリセット命令がないので、ソフトウェアからリセットするには、ウォッチドッグタイマを有効にした上で無限ループを実行するのが正規のやり方だそうである。

今回、赤外線リモコンの制作で、AVR が暴走した時のために、ATTiny85 のウォッチドッグタイマを 有効にした。AVR-libc のマクロ (avr/wdt.h) を使えば簡単に設定できる。実際に無限ループに陥った時には、ウォッチドッグタイマはきちんと動作して、リセットがかかってくれた。ところがその後がよくない。リセットがかかった直後にまたウォッチドッグタイマによるリセットが再発生し、再起動を繰り返してしまう。

これは一部の新しい AVR のみに発生する現象のようで、AVR-libc のマニュアルに回避策が書いてあった。ただ意味がちょっとわかりにくかったので、その部分をチョー訳してみた。

ソースファイルのどこかに下記の緑色の部分のコードを書いておけば、ウォッチドッグタイマによるリセットの後に、自動的にウォッチドッグタイマが無効になる。main() の途中で改めて wdt_enable() を使ってウォッチドッグタイマを有効にすればよい。

AVR-libc のドキュメントWatchdog timer (日本語)より


(原文)
Detailed Description
#include <avr/wdt.h>
This header file declares the interface to some inline macros handling the watchdog timer present in many AVR devices. In order to prevent the watchdog timer configuration from being accidentally altered by a crashing application, a special timed sequence is required in order to change it. The macros within this header file handle the required sequence automatically before changing any value. Interrupts will be disabled during the manipulation.

Note:
Depending on the fuse configuration of the particular device, further restrictions might apply, in particular it might be disallowed to turn off the watchdog timer.

Note that for newer devices (ATmega88 and newer, effectively any AVR that has the option to also generate interrupts), the watchdog timer remains active even after a system reset (except a power-on condition), using the fastest prescaler value (approximately 15 ms). It is therefore required to turn off the watchdog early during program startup, the datasheet recommends a sequence like the following:

    
#include <stdint.h>
#include <avr/wdt.h>
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) __attribute__((naked)) __attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

(日本語訳)
詳細説明
#include <avr/wdt.h>
このヘッダファイルには、多くのAVRデバイスでサポートされているウォッチドッグタイマを扱うためのインラインマクロへのインタフェースが書かれている。アプリケーションがクラッシュして誤った警報を出さないように、ウォッチドッグタイマの設定を変更する時は特殊なタイミングの手続きが必要である。このヘッダファイルにあるマクロを使って変更すれば、変更前にその手続きが自動的に行われる。 変更中は割り込みは禁止される。

注意:
デバイスのヒューズの設定によっては、とくにウォッチドッグタイマの無効化が禁止されている場合には、さらなる制約がかかることがある。

新しいデバイス(ATmega88 以降。割り込みを生成するオプションがあるデバイス)では、(電源 ON の直後を除く)システムリセットの後でもなお、ウォッチドッグタイマが有効のままになり、最速のプリスケーラ値(約15ms)が設定されることに注意が必要である。そのため、データシートでは、プログラムの起動時の最初の方で、次のようなシーケンスでウォッチドッグを無効にするように要求している。
    
#include <stdint.h>
#include <avr/wdt.h>
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) __attribute__((naked)) __attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}
 



さて、この問題が起こる「新しいデバイス」とはどのデバイスのことなのか。マニュアルの記載からはわかりにくいが、ウォッチドッグタイマのオーバーフロー割り込みを持つ AVR マイコンという事だと思う。

同じく AVR-libc のマニュアルの「interrupt」の所に、各割り込みを持つデバイスはどれかの表がある。表によれば、下記の (1)~(3) に名前があるデバイスには上記の対応が必要のようだ。

(1) WATCHDOG_vect (SIG_WATCHDOG_TIMEOUT)
ATtiny24, ATtiny44, ATtiny84

(2) WDT_OVERFLOW_vect (SIG_WATCHDOG_TIMEOUT, SIG_WDT_OVERFLOW)
ATtiny2313

(3) WDT_vect (SIG_WDT, SIG_WATCHDOG_TIMEOUT)
AT90PWM3, AT90PWM2, AT90PWM1, ATmega1284P, ATmega168P, ATmega328P, ATmega32HVB, ATmega406, ATmega48P, ATmega88P, ATmega168, ATmega48, ATmega88, ATmega640, ATmega1280, ATmega1281, ATmega2560, ATmega2561, ATmega324P, ATmega164P, ATmega644P, ATmega644, ATmega16HVA, ATtiny13, ATtiny43U, ATtiny48, ATtiny45, ATtiny25, ATtiny85, ATtiny261, ATtiny461, ATtiny861, AT90USB162, AT90USB82, AT90USB1287, AT90USB1286, AT90USB647, AT90USB646

たとえば、自分の使っているデバイスでは、ATmega8, ATmega64 はこのリストにないが、ATmega88, ATtiny85 は含まれている。

2 件のコメント:

  1. 電子牛乳さん、いつも貴重な情報ありがとうございます。
    本件非常に役立ちました。
    最初Mega128にてソフトウェアリセットするロジックを組み想定通りの動きとなっていたのにMega1281へ移殖後にソフトウェアリセット後、繰り返しリセット発動現象が始まりデバッグするにもリセットしてしまうためにっちもさっちもいかず困り果ててた時に、電子牛乳さんの記述にまさに答えがありました。マニュアル意外と端から端まで読んでるつもりでもこのような情報に目がいかないことがほとんどなので、このような情報を的確につかんでる電子牛乳さんはやはり尊敬します。今後も本HP楽しみに読ませていただきます。

    返信削除
    返信
    1. コメントどうもありがとうございました。この挙動は直感的でなく、また AVR の機種によって発生したりしなかったりするので厄介ですね。私の場合は main() の開始時に BEEP 音が鳴る回路だったため、BEEP音が止まらなくなってこの現象に気づきましたが、回路によっては大変気づきにくいと思います。お役に立ったようでよかったです。

      削除