Estoy tratando de entender el temporizador de vigilancia en la serie ATTinyX5. Entonces, las cosas que he leído hacen que parezca que podría usarlo para hacer que el programa haga algo específico cada N segundos, pero nunca mostró cómo. Otros hicieron que pareciera que solo restablecería el chip a menos que algo en el código lo restablezca mientras tanto (lo que parece ser el uso "normal").
¿Hay alguna forma de usar el WDT como lo haría con TIMER1_COMPA_vect o similar? Noté que tiene un modo de tiempo de espera de 1 segundo y realmente me encantaría poder usarlo para hacer que suceda algo cada 1 segundo en mi código (y preferiblemente dormir en el medio).
¿Pensamientos?
* Actualización: * Dado que se preguntó, a lo que me refiero es a la sección 8.4 de la hoja de datos de ATTinyX5 . No es que lo entienda del todo, que es mi problema...
Ciertamente puedes. De acuerdo con la hoja de datos, el temporizador de vigilancia se puede configurar para restablecer la MCU o provocar una interrupción cuando se activa. Parece que estás más interesado en la posibilidad de interrupción.
El WDT es en realidad más fácil de configurar que un temporizador normal por la misma razón 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 funcionando durante los modos de suspensión más profundos para proporcionar una fuente de activación.
Repasaré un par de ejemplos de hojas de datos, así como algunos códigos que he usado (en C).
Archivos incluidos y definiciones
Para comenzar, probablemente querrá 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, utilizo la Macro <_BV(BIT)> que se define en uno de los encabezados estándar de AVR de la siguiente manera (que podría resultarle más familiar):
#define _BV(BIT) (1<<BIT)
Comienzo del código
Cuando la MCU se inicia 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 volver a hacerlo, 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 muy 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 estar deshabilitadas durante este código. ¡Asegúrate de volver a habilitarlos después!
cli(); // Disable the Interrupts
sei(); // Enable the Interrupts
Rutina de servicio de interrupción de WDT Lo siguiente que debe preocuparse es manejar el ISR de WDT. Esto se hace así:
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
}
Suspensión de MCU
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 y luego hacer que el programa PRINCIPAL ponga la MCU en suspensión. De esa manera, el programa en realidad está saliendo del ISR antes de que se vaya a dormir, y se despertará y regresará 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
****************************/
}
}
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 que hace que el bit de habilitación de interrupción se deshabilite (interrupción deshabilitada). El próximo tiempo de espera reiniciará su CPU. Si habilita la interrupción directamente después de que se haya ejecutado, el siguiente tiempo de espera activará (nuevamente) solo una interrupción.
También puede simplemente habilitar la interrupción y no habilitar el reinicio en absoluto. Deberá configurar el bit WDIE cada vez que se active la interrupción.
Esto es mucho más fácil de lo sugerido anteriormente y en otros lugares.
Siempre que el WDTON
fusible no esté programado (no está programado de forma predeterminada), solo necesita...
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. Dado que nunca habilitamos el restablecimiento del perro guardián, nunca tenemos que jugar con las secuencias cronometradas para deshabilitarlo. El indicador de interrupción de vigilancia se borra automáticamente cuando se llama al ISR.
Si desea un período diferente a cada 1 segundo, puede usar estos valores aquí para establecer los bits apropiados en WDTCR
...
Tenga en cuenta que necesita 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
yippie