¿Por qué se restablece mi AVR cuando llamo a wdt_disable () para intentar apagar el temporizador de vigilancia?

31

Tengo un problema al ejecutar una secuencia de vigilancia deshabilitada en un AVR ATtiny84A en realidad está restableciendo el chip incluso aunque el temporizador debería tener un montón de tiempo en él. Esto sucede de manera inconsistente y cuando se ejecuta el mismo código en muchas partes físicas; algunos se reinician cada vez, algunos se reinician a veces y otros nunca.

Para demostrar el problema, he escrito un programa simple que ...

  1. Habilita el watchdog con un tiempo de espera de 1 segundo
  2. Restablece el perro guardián
  3. Enciende el LED blanco durante 0.1 segundos
  4. Apagó el LED blanco durante 0.1 segundos
  5. Deshabilita el perro guardián

El tiempo total entre la habilitación y la inhabilitación del watchdog es inferior a 0,3 segundos, aunque a veces se produce un reinicio del watchdog cuando se ejecuta la secuencia de inhabilitación.

Aquí está el código:

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

    }
}

Al inicio, el programa verifica si el reinicio anterior fue causado por un tiempo de espera de vigilancia, y si es así, enciende el LED rojo y borra el indicador de reinicio de vigilancia para indicar que ocurrió un reinicio de vigilancia. Creo que este código nunca debería ejecutarse y el LED rojo nunca debería encenderse, aunque a menudo lo hace.

¿Qué está pasando aquí?

    
pregunta bigjosh

1 respuesta

38

Hay un error en la rutina de la biblioteca wdt_reset ().

Aquí está el código ...

__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 cuarta línea se expande hacia ...

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

La intención de esta línea es escribir un 1 en WD_CHANGE_BIT, lo que permitirá que la siguiente línea escriba un 0 en el bit de habilitación de vigilancia (WDE). De la hoja de datos:

  

Para deshabilitar un temporizador de vigilancia habilitado, el siguiente procedimiento debe ser   seguido:   1. En la misma operación, escriba una lógica en WDCE y WDE. Una lógica se debe escribir en WDE independientemente del valor anterior de WDE   poco.   2. Dentro de los siguientes cuatro ciclos de reloj, en la misma operación, escriba los bits WDE y WDP como se desee, pero con el bit WDCE borrado.

Lamentablemente, esta asignación tiene el efecto secundario de establecer también los 3 bits inferiores del Watchdog Control Register (WDCE) en 0. Esto establece inmediatamente el valor de preescala en su valor más corto. Si el nuevo prescaler ya se ha activado en el momento en que se ejecuta esta instrucción, el procesador se reiniciará.

Dado que el temporizador de vigilancia se ejecuta en un oscilador de 128 kHz físicamente independiente, es difícil predecir cuál será el estado del nuevo prescaler en relación con el programa en ejecución. Esto explica la amplia gama de comportamientos observados en los que el error se puede correlacionar con la tensión de alimentación, la temperatura y el lote de fabricación, ya que todas estas cosas pueden afectar la velocidad del oscilador de vigilancia y el reloj del sistema de forma asimétrica. ¡Este fue un error muy difícil de encontrar!

Aquí hay un código actualizado que evita este problema ...

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

La instrucción wdr adicional restablece el temporizador de vigilancia, por lo que cuando la siguiente línea cambie potencialmente a un preescalador diferente, se garantiza que aún no se ha agotado el tiempo de espera.

Esto también podría solucionarse mediante la operación de los bits WD_CHANGE_BIT y WDE en WD_CONTROL_REGISTER como se sugiere en las hojas de datos ...

; 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

... pero esto requiere más código y un registro adicional. Dado que el contador de vigilancia se reinicia cuando está deshabilitado de todos modos, el reinicio adicional no obstruye nada y no tiene efectos secundarios involuntarios.

    
respondido por el bigjosh

Lea otras preguntas en las etiquetas