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 は含まれている。

6 件のコメント:

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

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

      削除
  2. コメント失礼します.
    上記の件はArduino pro miniでも通じるでしょうか?
    緑の部分のコードを追加しても無限リセットになってしまうのですが,,

    返信削除
  3. すみません!! コメントを見落としていたためずっと公開されていませんでした。
    実は Arduino を使っていないためわかりません。
    緑の部分は、プログラム本体の実行前に実行される特殊な処理だと思います。
    Arduino ではそのままではだめなのですね。
    Arduino での解決策をご存知の方がいましたら教えてください。

    返信削除
  4. Arduino UNO R3 において、ウォッチドッグタイマーリセット時に無限ループ(立ち上がってもすぐにリセット)になり、調べていてたところ、ここへたどり着きました。
    こちらに書かれている対策では私も、Arduino ではダメでした。

    おそらくですが、Arduino IDEでコンパイルするときに、無効化または、組み込まれていないのではないかと思っています。

    発生する条件を観察したところ、
    ①Arduino IDEで通常の方法で書き込みを行うと無限ループは発生しない。
    ②Arduino IDEで通常の方法で書き込みを行うとブートローダも書き込まれる(起動が遅く、書いたプログラム実行開始まで1.5秒程度の待ち時間が必要)
    ③Arduino IDEでAtmel ICEにてSPI端子から書込みを行うと無限ループが発生する。
    ④Arduino IDEでAtmel ICEにてSPI端子から書込みを行うとブートローダーは書き込まれない(起動が早い。0.5秒程度でプログラム実行開始)
    ⑤ブートローダにはウォッチドッグタイマーリセットが発生した場合は、ウォッチドッグタイマーの無効化と以降のブートローダ実行をスキップするように書かれている。


    解消策
    SPI端子から書込みを行う場合にブートローダーも書き込むようにする。

    具体策
    ①Arduino IDE→「スケッチ」→「コンパイルしたバイナリを出力」を実行する。

    スケッチ(プログラム)の置かれているフォルダに
    「XXXX.ino.standard.hex」と「XXXX.ino.with_bootloader.standard.hex」が作成される。

    ②「XXXX.ino.with_bootloader.standard.hex」をAtmel studio7などでSPI端子から書込みを行うと無限ループが解消される。
    (「XXXX.ino.standard.hex」を上記方法で書き込むと、無限ループが発生する)

    ※解決策は見つかりませんでしたが、解消できましたのでこちらに書かせてもらいました。

    返信削除
    返信
    1. 解消策の報告、どうもありがとうございました。
      Arduino を使っていないため確認できないのですが、同じ問題で困っている方の助けになると思います。

      削除