Warum wird mein AVR zurückgesetzt, wenn ich wdt_disable () aufrufe, um zu versuchen, den Watchdog-Timer auszuschalten?

34

Ich habe ein Problem, bei dem das Ausführen einer Watchdog-Deaktivierungssequenz auf einem AVR ATtiny84A den Chip zurücksetzt, obwohl der Timer noch genügend Zeit haben sollte. Dies geschieht inkonsistent und wenn auf vielen physischen Teilen derselbe Code ausgeführt wird. Einige werden jedes Mal zurückgesetzt, andere manchmal und wieder andere nie.

Um das Problem zu demonstrieren, habe ich ein einfaches Programm geschrieben, das ...

  1. Aktiviert den Watchdog mit einem Timeout von 1 Sekunde
  2. Setzt den Watchdog zurück
  3. Blinkt die weiße LED für 0,1 Sekunden
  4. Blinkt die weiße LED für 0,1 Sekunden aus
  5. Deaktiviert den Watchdog

Die Gesamtzeit zwischen dem Aktivieren und Deaktivieren des Watchdogs beträgt weniger als 0,3 Sekunden. Manchmal tritt jedoch ein Watchdog-Reset auf, wenn die Deaktivierungssequenz ausgeführt wird.

Hier ist der Code:

#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.

    }
}

Beim Start prüft das Programm, ob der vorherige Reset durch ein Watchdog-Timeout verursacht wurde. In diesem Fall leuchtet die rote LED und das Flag zum Zurücksetzen des Watchdogs wird gelöscht, um anzuzeigen, dass ein Watchdog-Reset stattgefunden hat. Ich bin der Meinung, dass dieser Code niemals ausgeführt werden sollte und die rote LED niemals aufleuchten sollte, was jedoch häufig der Fall ist.

Was geht hier vor sich?

bigjosh
quelle
7
Wenn Sie sich dazu entschlossen haben, hier Ihre eigenen Fragen und Antworten zu diesem Problem zu verfassen, kann ich mir die Schmerzen und Leiden vorstellen, die erforderlich waren, um es zu entdecken.
Vladimir Cravero
3
Wetten Sie? 12 Stunden an diesem Bug. Für eine Weile würde der Fehler NUR außerhalb der Site auftreten. Wenn ich die Boards auf meinen Desktop bringe, verschwindet der Fehler wahrscheinlich aufgrund von Temperatureffekten (mein Platz ist kalt, wodurch der Watchdog-Oszillator im Vergleich zur Systemuhr etwas langsamer läuft). Es dauerte über 30 Versuche, um es zu reproduzieren und auf Video festzuhalten.
Bigjosh
Ich kann den Schmerz fast spüren. Ich bin kein alter und navigierter EE, aber manchmal befand ich mich in solchen Situationen. Toller Fang, trink ein Bier und löse weiter Probleme;)
Vladimir Cravero

Antworten:

41

In der Bibliotheksroutine wdt_reset () ist ein Fehler aufgetreten.

Hier ist der Code ...

__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" \
)

Die vierte Zeile erweitert sich zu ...

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

Die Absicht dieser Zeile ist es, eine 1 in das WD_CHANGE_BIT zu schreiben, wodurch die folgende Zeile aktiviert wird, um eine 0 in das Watchdog-Aktivierungsbit (WDE) zu schreiben. Aus dem Datenblatt:

Um einen aktivierten Watchdog-Timer zu deaktivieren, muss wie folgt vorgegangen werden: 1. Schreiben Sie in derselben Operation eine logische Eins an WDCE und WDE. Unabhängig vom vorherigen Wert des WDE-Bits muss eine logische Eins in WDE geschrieben werden. 2. Schreiben Sie innerhalb der nächsten vier Taktzyklen in derselben Operation die gewünschten WDE- und WDP-Bits, wobei jedoch das WDCE-Bit gelöscht wird.

Leider hat diese Zuweisung den Nebeneffekt, dass auch die unteren 3 Bits des Watchdog- Steuerregisters (WDCE) auf 0 gesetzt werden. Dadurch wird der Vorteiler sofort auf den kürzesten Wert gesetzt. Wenn der neue Vorteiler zum Zeitpunkt der Ausführung dieses Befehls bereits ausgelöst wurde, wird der Prozessor zurückgesetzt.

Da der Watchdog-Timer von einem physikalisch unabhängigen 128-kHz-Oszillator ausgeführt wird, ist es schwierig vorherzusagen, wie der Status des neuen Prescaler in Bezug auf das ausgeführte Programm sein wird. Dies erklärt die Vielzahl der beobachteten Verhaltensweisen, bei denen der Fehler mit der Versorgungsspannung, der Temperatur und dem Herstellungsstapel korreliert werden kann, da all diese Faktoren die Geschwindigkeit des Watchdog-Oszillators und den Systemtakt asymmetrisch beeinflussen können. Dies war ein sehr schwer zu findender Fehler!

Hier ist aktualisierter Code, der dieses Problem vermeidet ...

__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" \
)

Der zusätzliche wdrBefehl setzt den Watchdog-Timer zurück. Wenn also die folgende Zeile möglicherweise auf einen anderen Prescaler wechselt, ist garantiert, dass noch keine Zeitüberschreitung vorliegt.

Dies könnte auch durch ODER-Verknüpfung der Bits WD_CHANGE_BIT und WDE mit dem WD_CONTROL_REGISTER behoben werden, wie in den Datenblättern vorgeschlagen ...

; 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

... aber das erfordert mehr Code und ein zusätzliches Scratch-Register. Da der Watchdog-Zähler zurückgesetzt wird, wenn er ohnehin deaktiviert ist, stört das zusätzliche Zurücksetzen nichts und hat keine unbeabsichtigten Nebenwirkungen.

bigjosh
quelle
7
Ich würde Ihnen auch gerne Requisiten geben, denn als ich die Liste der avr-libc-
Probleme durchgesehen habe
1
ps "josh.com" ist echt ... beeindruckend
vicatcu