wdt_disable()を呼び出してウォッチドッグタイマーをオフにしようとすると、AVRがリセットされるのはなぜですか?


34

AVR ATtiny84Aでウォッチドッグシーケンスを無効にすると、タイマーには十分な時間が残っているはずですが、実際にチップがリセットされるという問題があります。これは一貫性がなく、多くの物理パーツで同じコードを実行しているときに発生します。毎回リセットされるものもあれば、時々リセットされるものと、決してリセットされないものもあります。

問題を実証するために、私は簡単なプログラムを書きました...

  1. 1秒のタイムアウトでウォッチドッグを有効にします
  2. ウォッチドッグをリセットします
  3. 白色LEDを0.1秒間点滅させます
  4. 白色LEDを0.1秒間点滅させた
  5. ウォッチドッグを無効にします

ウォッチドッグの有効化と無効化の間の合計時間は0.3秒未満ですが、無効化シーケンスの実行時にウォッチドッグリセットが発生する場合があります。

コードは次のとおりです。

#define F_CPU 1000000                   // Name used by delay.h. We are running 1Mhz (default fuses)

#include <avr/io.h>
#include <util/delay.h>
#include <avr/wdt.h>


// White LED connected to pin 8 - PA5

#define WHITE_LED_PORT PORTA
#define WHITE_LED_DDR DDRA
#define WHITE_LED_BIT 5


// Red LED connected to pin 7 - PA6

#define RED_LED_PORT PORTA
#define RED_LED_DDR DDRA
#define RED_LED_BIT 6


int main(void)
{
    // Set LED pins to output mode

    RED_LED_DDR |= _BV(RED_LED_BIT);
    WHITE_LED_DDR |= _BV(WHITE_LED_BIT);


    // Are we coming out of a watchdog reset?
    //        WDRF: Watchdog Reset Flag
    //        This bit is set if a watchdog reset occurs. The bit is reset by a Power-on Reset, or by writing a
    //        logic zero to the flag

    if (MCUSR & _BV(WDRF) ) {

        // We should never get here!


        // Light the RED led to show it happened
        RED_LED_PORT |= _BV(RED_LED_BIT);

        MCUCR = 0;        // Clear the flag for next time
    }

    while(1)
    {
        // Enable a 1 second watchdog
        wdt_enable( WDTO_1S );

        wdt_reset();          // Not necessary since the enable macro does it, but just to be 100% sure

        // Flash white LED for 0.1 second just so we know it is running
        WHITE_LED_PORT |= _BV(WHITE_LED_BIT);
        _delay_ms(100);
        WHITE_LED_PORT &= ~_BV(WHITE_LED_BIT);
        _delay_ms(100);

        // Ok, when we get here, it has only been about 0.2 seconds since we reset the watchdog.

        wdt_disable();        // Turn off the watchdog with plenty of time to spare.

    }
}

起動時に、プログラムは前回のリセットがウォッチドッグタイムアウトによって発生したかどうかを確認し、発生した場合は赤色のLEDを点灯してウォッチドッグリセットフラグをクリアし、ウォッチドッグリセットが発生したことを示します。このコードは決して実行されるべきではなく、赤いLEDが点灯することはないと信じていますが、それでも頻繁に実行されます。

ここで何が起こっていますか?


7
あなたがこの問題についてあなた自身のQ&Aをここに書くことに決めたなら、私はそれを発見するために必要であった痛みと苦しみを想像できます。
ウラジミールクラベロ

3
あなたは賭けます!このバグで12時間。しばらくの間、バグはサイト外でのみ発生します。ボードをデスクトップに持って行くと、おそらく温度の影響のためにバグがなくなります(私の場所は寒いため、ウォッチドッグ発振器はシステムクロックに比べてわずかに遅くなります)。それを再現し、ビデオの行為でそれをキャッチするために30以上のトライアルが必要でした。
bigjosh

私はほとんど痛みを感じることができます。私は古くてナビゲートされたEEではありませんが、そのような状況に時々気づきました。素晴らしいキャッチ、ビールを飲み、問題を解決し続けます;)
ウラジミールクラベロ

回答:


41

wdt_reset()ライブラリルーチンにバグがあります。

コードは次のとおりです...

__asm__ __volatile__ ( \
   "in __tmp_reg__, __SREG__" "\n\t" \
   "cli" "\n\t" \
   "out %0, %1" "\n\t" \
   "out %0, __zero_reg__" "\n\t" \
   "out __SREG__,__tmp_reg__" "\n\t" \
   : /* no outputs */ \
   : "I" (_SFR_IO_ADDR(_WD_CONTROL_REG)), \
   "r" ((uint8_t)(_BV(_WD_CHANGE_BIT) | _BV(WDE))) \
   : "r0" \
)

4行目は次のように展開されます...

out _WD_CONTROL_REG, _BV(_WD_CHANGE_BIT) | _BV(WDE)

この行の目的は、WD_CHANGE_BITに1を書き込むことです。これにより、次の行でウォッチドッグイネーブルビット(WDE)に0を書き込むことができます。データシートから:

有効なウォッチドッグタイマーを無効にするには、次の手順に従う必要があります。1.同じ操作で、WDCEおよびWDEにロジック1を書き込みます。WDEビットの以前の値に関係なく、論理1をWDEに書き込む必要があります。2.次の4クロックサイクル内で、同じ操作で、WDEおよびWDPビットを必要に応じて書き込みますが、WDCEビットをクリアします。

残念ながら、この割り当てには、ウォッチドッグ制御レジスタ(WDCE)の下位3ビットも0に設定するという副作用があります。これにより、プリスケーラがすぐに最短値に設定されます。この命令の実行時に新しいプリスケーラが既に起動されている場合、プロセッサはリセットされます。

ウォッチドッグタイマーは物理的に独立した128 kHz発振器で動作するため、実行中のプログラムに関連して新しいプリスケーラーの状態がどうなるかを予測することは困難です。これは、バグが電源電圧、温度、および製造バッチと相関する可能性のあるさまざまな観察された動作を説明します。これらのすべてがウォッチドッグ発振器とシステムクロックの速度に非対称的に影響する可能性があるためです。これは見つけるのが非常に難しいバグでした!

この問題を回避する更新されたコードは次のとおりです...

__asm__ __volatile__ ( \
   "in __tmp_reg__, __SREG__" "\n\t" \
   "cli" "\n\t" \
   "wdr" "\n\t" \
   "out %0, %1" "\n\t" \
   "out %0, __zero_reg__" "\n\t" \
   "out __SREG__,__tmp_reg__" "\n\t" \
   : /* no outputs */ \
   : "I" (_SFR_IO_ADDR(_WD_CONTROL_REG)), \
   "r" ((uint8_t)(_BV(_WD_CHANGE_BIT) | _BV(WDE))) \
   : "r0" \
)

余分なwdr命令はウォッチドッグタイマーをリセットするため、次の行が別のプリスケーラーに切り替わる可能性がある場合、まだタイムアウトしていないことが保証されます。

これは、データシートに示されているように、WD_CHANGE_BITビットとWDEビットをWD_CONTROL_REGISTERにOR結合することでも解決できます。

; Write logical one to WDCE and WDE
; Keep old prescaler setting to prevent unintentional Watchdog Reset
in r16, WDTCR
ori r16, (1<<WDCE)|(1<<WDE)
out WDTCR, r16

...しかし、これにはより多くのコードと追加のスクラッチレジスタが必要です。ウォッチドッグカウンターは無効になったときにリセットされるため、余分なリセットは何も上書きせず、意図しない副作用はありません。


7
私はAVR-libcの問題のリストを確認するために行ったとき、それは(おそらくあなた)あなたのようなので、私はそれが既に存在提出あなたが小道具与えるだろうとも似savannah.nongnu.org/bugs/?44140
vicatcu

1
ps「josh.com」は本当に...印象的です
-vicatcu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.