Use AVR Watchdog como ISR normal

17

Estoy tratando de entender el temporizador de vigilancia de la serie ATTinyX5. Entonces, las cosas que he leído parecían que podría usarlo para hacer que el programa haga algo específico cada N segundos, pero en realidad nunca mostró cómo. Otros hicieron que pareciera que solo restablecería el chip a menos que algo en el restablecimiento del código se cuente mientras tanto (que parece ser el uso "normal").

¿Hay alguna forma de usar el WDT como lo haría con TIMER1_COMPA_vect o similar? Me di cuenta de que tiene un modo de tiempo de espera de 1 segundo y realmente me encantaría poder usarlo para hacer que algo suceda cada 1 segundo en mi código (y preferiblemente dormir entre ellos).

Pensamientos?

* Actualización: * Como se le preguntó, a lo que me refiero es a la sección 8.4 de la hoja de datos ATTinyX5 . No es que lo entienda completamente, que es mi problema ...

Adam Haile
fuente
1
+1 por pensar fuera de la caja. Aprobación adicional si agrega un enlace a la hoja de datos para el AVR.
jippie

Respuestas:

23

Ciertamente puedes. Según la hoja de datos, el temporizador de vigilancia se puede configurar para restablecer la MCU o causar una interrupción cuando se dispara. Parece que estás más interesado en la posibilidad de interrupción.

El WDT es realmente más fácil de configurar que un temporizador normal por la misma razón por la que es menos útil: menos opciones. Funciona con un reloj de 128 kHz calibrado internamente, lo que significa que su sincronización no se ve afectada por la velocidad del reloj principal de la MCU. También puede continuar ejecutándose durante los modos de reposo más profundos para proporcionar una fuente de activación.

Revisaré un par de ejemplos de hojas de datos, así como algunos códigos que he usado (en C).

Archivos incluidos y definiciones

Para comenzar, es probable que desee incluir los siguientes dos archivos de encabezado para que las cosas funcionen:

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

Además, uso la Macro <_BV (BIT)> que se define en uno de los encabezados AVR estándar como la siguiente (que podría ser más familiar para usted):

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

Comienzo del Código

Cuando se inicia MCU por primera vez, normalmente inicializaría la E / S, configuraría temporizadores, etc. En algún lugar aquí es un buen momento para asegurarse de que el WDT no provocó un reinicio porque podría hacerlo nuevamente, manteniendo su programa en Un bucle inestable.

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
}

Configuración WDT

Luego, después de haber configurado el resto del chip, vuelva a hacer el WDT. Configurar el WDT requiere una "secuencia cronometrada", pero es realmente fácil de hacer ...

// 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

Por supuesto, sus interrupciones deben deshabilitarse durante este código. ¡Asegúrese de volver a habilitarlos después!

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

Rutina de servicio de interrupción de WDT Lo siguiente de qué preocuparse es manejar el WDT ISR. Esto se hace como tal:

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
}

MCU Sleep

En lugar de poner la MCU en suspensión dentro del WDT ISR, recomiendo simplemente habilitar el modo de suspensión al final de la ISR, luego hacer que el programa PRINCIPAL ponga la MCU en suspensión. De esa manera, el programa en realidad está dejando el ISR antes de que se vaya a dormir, y se despertará y volverá directamente al WDT ISR.

// 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
fuente
WOW ... muy detallado. ¡Gracias! Estoy un poco confundido por la sección de reposo donde muestra el bucle principal (if (MCUCR & _BV (SE)) {// Si el sueño está habilitado ... etc.) Estoy confundido por qué en el aspecto principal Deshabilitar continuamente y habilitar interrupciones. Y la parte en la parte superior de esa sección (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) ¿Dónde se supone que debe ejecutarse?
Adam Haile
OK, la parte "set_sleep_mode (MODE)" debe estar en main ANTES del loop principal, idealmente en el otro código de inicialización donde configura los puertos de E / S, temporizadores, etc. Realmente no necesita enable_sleep (); en ese punto, ya que se hará después de su primer desencadenador WDT. DENTRO del bucle principal, ese código de suspensión solo se ejecutará SI la suspensión está habilitada, y no es completamente necesario deshabilitar / volver a habilitar las interrupciones allí, solo si está haciendo la función sleep_bod_disable (); Esa declaración IF completa podría estar en la parte inferior (pero aún dentro) del bucle PRINCIPAL después de cualquier otro código que esté ejecutando allí.
Kurt E. Clothier
Ok ... eso tiene más sentido ahora. Lo último en lo que estoy confuso es en qué es esta "secuencia cronometrada" ...
Adam Haile
Nota al margen: Sé por qué querrías ir a dormir, pero supongo que no tienes que hacerlo cuando utilizas el WDT.
Adam Haile
Eche un vistazo a esta pequeña sección de la hoja de datos: 8.4.1. Básicamente, para cambiar el registro WDT, debe establecer el bit de cambio y luego establecer los bits WDTCR adecuados dentro de tantos ciclos de reloj. El código que proporcioné en la sección Configuración de WDT hace esto habilitando primero el bit de cambio de WD. Y no, no tiene que usar la funcionalidad de suspensión en absoluto con el WDT. Podría ser una fuente de interrupción temporizada estándar por el motivo que desee.
Kurt E. Clothier
1

Según la hoja de datos es posible. Incluso puede habilitar ambos, una interrupción y el reinicio. Si ambos están habilitados, el primer tiempo de espera de vigilancia activará la interrupción, lo que ocasionará que el bit de interrupción habilitada se desactive (interrupción deshabilitada). El siguiente tiempo de espera restablecerá su CPU. Si habilita la interrupción directamente después de que se ejecutó, el siguiente tiempo de espera (nuevamente) activará solo una interrupción.

También puede habilitar la interrupción y no habilitar el reinicio en absoluto. Deberá establecer el bit WDIE cada vez que se active la interrupción.

Tom L.
fuente
Hmmm ... creo que eso tiene sentido. Lo intentaré. Siempre me gusta usar las cosas para lo que no estaban destinadas :)
Adam Haile
En realidad creo que es un diseño inteligente. Le ahorra un temporizador mientras mantiene la funcionalidad de vigilancia.
Tom L.
2
Este tiempo de espera inicial del WDT y la interrupción posterior también se pueden utilizar para aprovechar algunas aplicaciones que están habilitando el WDT para la recuperación real del bloqueo. Uno puede mirar la dirección de retorno apilada en el WDT ISR para inferir lo que el código estaba tratando de hacer cuando ocurrió el "tiempo de espera inesperado".
Michael Karas
1

Esto es mucho más fácil que lo sugerido anteriormente y en otros lugares.

Mientras el WDTONfusible no esté programado (no está programado por defecto), solo necesita ...

  1. Establezca el bit de habilitación de interrupción de vigilancia y el tiempo de espera en el registro de control de vigilancia.
  2. Habilitar interrupciones.

Aquí hay un ejemplo de código que ejecutará un ISR una vez cada 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);
}

Eso es realmente Como nunca habilitamos el reinicio de watchdog, nunca tenemos que perder el tiempo con las secuencias temporizadas para deshabilitarlo. El indicador de interrupción de vigilancia se borra automáticamente cuando se llama al ISR.

Si desea un período diferente de cada 1 segundo, puede usar estos valores aquí para establecer los bits apropiados en WDTCR...

ingrese la descripción de la imagen aquí

Tenga en cuenta que debe ejecutar la secuencia cronometrada para cambiar el tiempo de espera. Aquí hay un código que establece el tiempo de espera en 1 segundo ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
bigjosh
fuente
No realizar la secuencia cronometrada durante la configuración ahorra una línea de código: una operación de lectura-modificación-escritura. La secuencia inicial se recomienda en la hoja de datos "Si el Watchdog se activa accidentalmente, por ejemplo, por un puntero desbocado o una condición de oscurecimiento" y el resto de mi código es específico para usar el WDT en combinación con un modo de suspensión, según lo solicitado por el OP. Su solución no es más simple, simplemente descuidó el código de placa de caldera recomendado / requerido.
Kurt E. Clothier
@ KurtE.Clothier Lo siento, ¡solo trato de dar el ejemplo de trabajo más simple!
bigjosh