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

34

Tengo un problema en el que ejecutar una secuencia de desactivación de vigilancia en un AVR ATtiny84A en realidad está restableciendo el chip a pesar de que el temporizador debería tener suficiente tiempo. 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. Permite al perro guardián con un tiempo de espera de 1 segundo
  2. Restablece el perro guardián
  3. Parpadea el LED blanco durante 0,1 segundos.
  4. Parpadeó el LED blanco apagado durante 0.1 segundos
  5. Deshabilita el perro guardián

El tiempo total entre la activación y desactivación de watchdog es inferior a 0.3 segundos, aunque a veces se produce un reinicio de watchdog cuando se ejecuta la secuencia de desactivació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, de ser así, enciende el LED rojo y borra la bandera de reinicio de vigilancia para indicar que se produjo un reinicio de vigilancia. Creo que este código nunca debería ejecutarse y el LED rojo nunca debería encenderse, pero a menudo lo hace.

¿Que esta pasando aqui?

bigjosh
fuente
77
Si decidió escribir sus propias preguntas y respuestas aquí sobre este problema, puedo imaginar el dolor y el sufrimiento necesarios para descubrirlo.
Vladimir Cravero
3
Usted apuesta! 12 horas con este error. Por un tiempo, el error SOLO ocurriría fuera del sitio. Si traje las placas a mi escritorio, el error desaparecería, probablemente debido a los efectos de la temperatura (mi lugar está frío, lo que hace que el oscilador de vigilancia funcione un poco más lento en relación con el reloj del sistema). Se necesitaron más de 30 pruebas para reproducirlo y captarlo en el acto en video.
bigjosh
Casi puedo sentir el dolor. No soy un EE antiguo y navegado, pero a veces me encuentro en tales situaciones. Gran captura, tomar una cerveza y seguir resolviendo problemas;)
Vladimir Cravero

Respuestas:

41

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

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, se debe seguir el siguiente procedimiento: 1. En la misma operación, escriba uno lógico en WDCE y WDE. Se debe escribir uno lógico en WDE independientemente del valor anterior del bit WDE. 2. Dentro de los siguientes cuatro ciclos de reloj, en la misma operación, escriba los bits WDE y WDP como desee, pero con el bit WDCE borrado.

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

Dado que el temporizador de vigilancia funciona con un oscilador de 128 kHz físicamente independiente, es difícil predecir cuál será el estado del nuevo preescalador en relación con el programa en ejecución. Esto explica la amplia gama de comportamientos observados en los que el error puede correlacionarse con el voltaje de suministro, 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. ¡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 wdrinstrucción adicional restablece el temporizador de vigilancia, por lo que cuando la siguiente línea cambia potencialmente a un preescalador diferente, se garantiza que aún no ha excedido el tiempo de espera.

Esto también se puede solucionar ORinging 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 de memoria virtual adicional. Desde el mostrador de vigilancia se restablece cuando se trata de todos modos desactivado, el reajuste adicional no clobber nada y no tiene efectos secundarios no intencionales.

bigjosh
fuente
77
También me gustaría darle accesorios porque cuando fui a revisar la lista de problemas de avr-libc, parece que usted (presumiblemente) lo envió allí ya savannah.nongnu.org/bugs/?44140
vicatcu
1
ps "josh.com" es real ... impresionante
vicatcu