Utilisez AVR Watchdog comme un ISR normal

17

J'essaie de comprendre la minuterie du chien de garde de la série ATTinyX5. Donc, les choses que j'ai lues donnaient l'impression que vous pouviez l'utiliser pour que le programme fasse quelque chose de spécifique jamais N secondes mais n'a jamais vraiment montré comment. D'autres ont fait croire qu'il ne ferait que réinitialiser la puce à moins que quelque chose dans le code réinitialise son compte entre-temps (ce qui semble être l'utilisation "normale").

Existe-t-il un moyen d'utiliser le WDT comme vous le feriez pour TIMER1_COMPA_vect ou similaire. J'ai remarqué qu'il a un mode de temporisation de 1 seconde et j'aimerais vraiment pouvoir l'utiliser pour que quelque chose se produise toutes les 1 seconde dans mon code (et de préférence dormir entre les deux).

Pensées?

* Mise à jour: * Depuis qu'on lui a demandé, je fais référence à la section 8.4 de la fiche technique ATTinyX5 . Non pas que je le comprenne bien, c'est mon problème ...

Adam Haile
la source
1
+1 pour sortir des sentiers battus. Coup de pouce supplémentaire si vous ajoutez un lien vers la fiche technique de l'AVR.
jippie

Réponses:

23

Vous le pouvez très certainement. Selon la fiche technique, le temporisateur de surveillance peut être configuré pour réinitialiser le MCU ou provoquer une interruption lors de son déclenchement. Il semble que vous soyez plus intéressé par la possibilité d'interruption.

Le WDT est en fait plus facile à configurer qu'un minuteur normal pour la même raison qu'il est moins utile: moins d'options. Il fonctionne sur une horloge de 128 kHz calibrée en interne, ce qui signifie que sa synchronisation n'est pas affectée par la vitesse d'horloge principale du MCU. Il peut également continuer à fonctionner pendant les modes de sommeil les plus profonds pour fournir une source de réveil.

Je vais passer en revue quelques exemples de fiches techniques ainsi que du code que j'ai utilisé (en C).

Fichiers et définitions inclus

Pour commencer, vous souhaiterez probablement inclure les deux fichiers d'en-tête suivants pour que les choses fonctionnent:

#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros 
#include <avr/sleep.h>      // Supplied AVR Sleep Macros

En outre, j'utilise la macro <_BV (BIT)> qui est définie dans l'un des en-têtes AVR standard comme suit (qui pourrait être plus familiale pour vous):

#define _BV(BIT)   (1<<BIT)

Début du code

Lorsque le MCU est démarré pour la première fois, vous initialisez généralement les E / S, configurez des temporisateurs, etc. une boucle instable.

if(MCUSR & _BV(WDRF)){            // If a reset was caused by the Watchdog Timer...
    MCUSR &= ~_BV(WDRF);                 // Clear the WDT reset flag
    WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
    WDTCSR = 0x00;                      // Disable the WDT
}

Configuration WDT

Ensuite, après avoir configuré le reste de la puce, refaites le WDT. La configuration du WDT nécessite une "séquence chronométrée", mais c'est vraiment facile à faire ...

// Set up Watch Dog Timer for Inactivity
WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
WDTCSR =   _BV(WDIE) |              // Enable WDT Interrupt
           _BV(WDP2) | _BV(WDP1);   // Set Timeout to ~1 seconds

Bien sûr, vos interruptions doivent être désactivées pendant ce code. Assurez-vous de les réactiver par la suite!

cli();    // Disable the Interrupts
sei();    // Enable the Interrupts

Routine de service d'interruption WDT La prochaine chose dont vous devez vous soucier est la manipulation de l'ISR WDT. Cela se fait comme tel:

ISR(WDT_vect)
{
  sleep_disable();          // Disable Sleep on Wakeup
  // Your code goes here...
  // Whatever needs to happen every 1 second
  sleep_enable();           // Enable Sleep Mode
}

Veille MCU

Plutôt que de mettre le MCU en veille à l'intérieur du WDT ISR, je recommande simplement d'activer le mode veille à la fin de l'ISR, puis de laisser le programme MAIN mettre le MCU en veille. De cette façon, le programme quitte réellement l'ISR avant de s'endormir, et il se réveillera et retournera directement dans l'ISR WDT.

// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Set Sleep Mode: Power Down
sleep_enable();                     // Enable Sleep Mode  
sei();                              // Enable Interrupts 

/****************************
 *  Enter Main Program Loop  *
 ****************************/
 for(;;)
 {
   if (MCUCR & _BV(SE)){    // If Sleep is Enabled...
     cli();                 // Disable Interrupts
     sleep_bod_disable();   // Disable BOD
     sei();                 // Enable Interrupts
     sleep_cpu();           // Go to Sleep

 /****************************
  *   Sleep Until WDT Times Out  
  *   -> Go to WDT ISR   
  ****************************/

   }
 }
Kurt E. Clothier
la source
WOW ... très détaillé. Merci! Je suis un peu confus par la section sommeil où vous montrez la boucle principale (si (MCUCR & _BV (SE)) {// Si le sommeil est activé ... etc.), je ne comprends pas pourquoi dans l'aspect principal vous le feriez désactiver et activer en permanence les interruptions. Et la partie en haut de cette section (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) Où est-ce censé fonctionner?
Adam Haile
OK, la partie "set_sleep_mode (MODE)" doit être dans la partie principale AVANT la boucle principale, idéalement dans l'autre code d'initialisation où vous configurez les ports d'E / S, les temporisateurs, etc. Vous n'avez pas vraiment besoin d'activer_sleep (); à ce stade, car cela sera fait après votre premier déclencheur WDT. À L'INTÉRIEUR de la boucle principale, ce code de veille ne s'exécutera que si la veille est activée, et il n'est pas entièrement nécessaire de désactiver / réactiver les interruptions là-bas, uniquement si vous effectuez la fonction sleep_bod_disable (); Cette instruction IF entière pourrait être en bas (mais toujours à l'intérieur) de la boucle MAIN après tout autre code que vous y avez exécuté.
Kurt E. Clothier
Ok ... ça a plus de sens maintenant. La dernière chose sur laquelle je suis floue est ce qu'est cette "séquence chronométrée" ...
Adam Haile
Note latérale: Je sais pourquoi vous voudriez vous endormir, mais je suppose que vous n'en avez pas besoin lorsque vous utilisez le WDT?
Adam Haile
Jetez un œil à cette petite section de la fiche technique: 8.4.1. Fondamentalement, pour modifier le registre WDT, vous devez définir le bit de modification, puis définir les bits WDTCR appropriés dans autant de cycles d'horloge. Le code que j'ai fourni dans la section Configuration WDT le fait en activant d'abord le bit de changement WD. Et non, vous n'avez pas du tout besoin d'utiliser la fonctionnalité de veille avec le WDT. Il peut s'agir d'une source d'interruption temporisée standard pour la raison que vous souhaitez.
Kurt E. Clothier
1

Selon la fiche technique, c'est possible. Vous pouvez même activer les deux, une interruption et la réinitialisation. Si les deux sont activés, le premier délai d'attente du chien de garde déclenchera l'interruption, ce qui provoquera la désactivation du bit d'activation d'interruption (interruption désactivée). Le prochain timeout réinitialisera alors votre CPU. Si vous activez l'interruption directement après son exécution, le prochain délai d'attente ne déclenchera (à nouveau) qu'une interruption.

Vous pouvez également simplement activer l'interruption et ne pas activer la réinitialisation du tout. Vous devrez régler le bit WDIE à chaque fois que l'interruption s'est déclenchée.

Tom L.
la source
Hmmm .... Je pense que cela a du sens. Je vais vous donner un coup de feu. J'aime toujours utiliser des choses pour ce à quoi elles n'étaient pas destinées :)
Adam Haile
En fait, je pense que c'est un design intelligent. Vous enregistre une minuterie tout en conservant la fonctionnalité de surveillance.
Tom L.
2
Ce délai d'expiration initial du WDT et l'interruption suivante peuvent également être utilisés de manière avantageuse pour certaines applications qui activent le WDT pour une récupération de raccrochage réelle. On peut regarder l'adresse de retour empilée dans le WDT ISR pour déduire ce que le code essayait de faire lorsque le "timeout inattendu" s'est produit.
Michael Karas
1

C'est beaucoup plus facile que suggéré ci-dessus et ailleurs.

Tant que le WDTONfusible n'est pas programmé (il n'est pas programmé par défaut), alors il vous suffit de ...

  1. Définissez le bit d'activation d'interruption du chien de garde et le délai d'expiration dans le registre de contrôle du chien de garde.
  2. Activez les interruptions.

Voici un exemple de code qui exécutera un ISR une fois toutes les 16 ms ...

ISR(WDT_vect) {
   // Any code here will get called each time the watchdog expires
}

void main(void) {
   WDTCR =  _BV(WDIE);    // Enable WDT interrupt, leave existing timeout (default 16ms) 
   sei();                                           // Turn on global interrupts
   // Put any code you want after here.
   // You can also go into deep sleep here and as long as 
   // global interrupts are eneabled, you will get woken 
   // up when the watchdog timer expires
   while (1);
}

C'est vraiment ça. Étant donné que nous n'activons jamais la réinitialisation du chien de garde, nous n'avons jamais à jouer avec les séquences chronométrées pour la désactiver. L'indicateur d'interruption du chien de garde est automatiquement effacé lorsque l'ISR est appelé.

Si vous souhaitez une période différente de chaque 1 seconde, vous pouvez utiliser ces valeurs ici pour définir les bits appropriés dans WDTCR...

entrez la description de l'image ici

Notez que vous devez exécuter la séquence chronométrée pour modifier le délai d'expiration. Voici le code qui définit le délai d'attente à 1 seconde ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
bigjosh
la source
Ne pas exécuter la séquence chronométrée pendant l'installation enregistre une ligne de code - une opération de lecture-modification-écriture. La séquence initiale est recommandée dans la fiche technique "Si le chien de garde est accidentellement activé, par exemple par un pointeur de fuite ou une condition de brunissement" et le reste de mon code est spécifique à l'utilisation du WDT en combinaison avec un mode veille, comme demandé par l'OP. Votre solution n'est pas plus simple, vous avez juste négligé le code de plaque de chaudière recommandé / requis.
Kurt E. Clothier
@ KurtE.Clothier Désolé, j'essaye juste de donner l'exemple de travail le plus simple!
bigjosh