Pourquoi mon AVR se réinitialise-t-il lorsque j'appelle wdt_disable () pour essayer de désactiver le minuteur de surveillance?

34

J'ai un problème lorsque l'exécution d'une séquence de surveillance désactivée sur un AVR ATtiny84A réinitialise réellement la puce, même si la minuterie doit disposer de suffisamment de temps. Cela se produit de manière incohérente et lorsque vous exécutez le même code sur de nombreuses parties physiques. certains réinitialisent à chaque fois, d'autres parfois, et d'autres jamais.

Pour démontrer le problème, j'ai écrit un programme simple qui ...

  1. Active le chien de garde en 1 seconde
  2. Réinitialise le chien de garde
  3. Fait clignoter la LED blanche pendant 0,1 seconde
  4. La LED blanche s'est éteinte pendant 0,1 seconde
  5. Désactive le chien de garde

Le temps total entre l'activation et la désactivation du chien de garde est inférieur à 0,3 seconde. Cependant, une réinitialisation du chien de garde se produit parfois lorsque la séquence de désactivation est exécutée.

Voici le 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.

    }
}

Au démarrage, le programme vérifie si la réinitialisation précédente a été provoquée par un délai d'attente du chien de garde et, le cas échéant, allume le voyant rouge et efface l'indicateur de réinitialisation du chien de garde pour indiquer qu'une réinitialisation du chien de garde s'est produite. Je crois que ce code ne doit jamais être exécuté et que le voyant rouge ne doit jamais s'allumer, mais c'est souvent le cas.

Qu'est-ce qui se passe ici?

bigjosh
la source
7
Si vous décidez de rédiger ici votre propre Q & R sur ce problème, je peux imaginer la douleur et la souffrance nécessaires pour le découvrir.
Vladimir Cravero
3
Tu paries! 12 heures sur ce bug. Pendant un certain temps, le bogue ne surviendrait que hors site. Si j'apportais les cartes sur mon bureau, le bogue disparaîtrait, probablement à cause des effets de la température (mon endroit est froid, ce qui rend l'oscillateur de chien de garde légèrement plus lent que l'horloge système). Il a fallu plus de 30 essais pour le reproduire et le prendre en flagrant délit en vidéo.
bigjosh
Je peux presque sentir la douleur. Je ne suis pas un ancien et navigué EE mais je me suis parfois retrouvé dans de telles situations. Bonne prise, prendre une bière et continuer à résoudre des problèmes;)
Vladimir Cravero

Réponses:

41

Il y a un bogue dans la routine de bibliothèque wdt_reset ().

Voici le 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" \
)

La quatrième ligne se développe pour ...

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

Le but de cette ligne est d'écrire un 1 dans le WD_CHANGE_BIT, ce qui permettra à la ligne suivante d'écrire un 0 dans le bit d'activation du chien de garde (WDE). De la fiche technique:

Pour désactiver un minuteur de surveillance activé, procédez comme suit: 1. Lors de la même opération, écrivez une logique sur WDCE et WDE. Un numéro logique 1 doit être écrit dans WDE quelle que soit la valeur précédente du bit WDE. 2. Dans les quatre cycles d'horloge suivants, dans la même opération, écrivez les bits WDE et WDP comme vous le souhaitez, mais en désactivant le bit WDCE.

Malheureusement, cette affectation a également pour effet de mettre à 0 les 3 bits inférieurs du registre de contrôle de chien de garde (WDCE). Ceci place immédiatement le prescaler à sa valeur la plus courte. Si le nouveau compteur a déjà été déclenché au moment de l'exécution de cette instruction, le processeur est réinitialisé.

Étant donné que la minuterie du chien de garde fonctionne avec un oscillateur à 128 kHz physiquement indépendant, il est difficile de prédire quel sera l'état du nouveau dispositif de dimensionnement par rapport au programme en cours d'exécution. Cela explique le large éventail de comportements observés où le bogue peut être corrélé à la tension d'alimentation, à la température et au lot de fabrication, dans la mesure où tous ces facteurs peuvent affecter la vitesse de l'oscillateur de surveillance et de l'horloge système de manière asymétrique. C'était un bug très difficile à trouver!

Voici un code mis à jour qui évite ce problème ...

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

L' wdrinstruction supplémentaire réinitialise le temporisateur du chien de garde. Ainsi, lorsque la ligne suivante bascule potentiellement vers un autre diviseur de valeur, il est garanti que le délai n'a pas encore expiré.

Cela pourrait également être corrigé en effectuant une opération OR sur les bits WD_CHANGE_BIT et WDE dans WD_CONTROL_REGISTER, comme suggéré dans les fiches techniques ...

; 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

... mais cela nécessite plus de code et un registre de travail supplémentaire. Comme le compteur du chien de garde est réinitialisé quand il est désactivé, la réinitialisation supplémentaire n’obstrue rien et n’a pas d’effets secondaires involontaires.

bigjosh
la source
7
J'aimerais aussi vous donner des accessoires, car lorsque je suis allé vérifier la liste des problèmes d'avril-libc, il semble que vous l'ayez déjà soumise ( savannah.nongnu.org/bugs/?44140
vicatcu le
1
ps "josh.com" est réel ... impressionnant
vicatcu