Al configurar el puerto en el bucle principal, ISR se muere de hambre

3

El código de ejemplo

  • inicializa un temporizador asíncrono que se dispara cada segundo
  • establece dos puertos como salidas (PA4, PA6 - LED conectados)
  • el temporizador ISR conmuta el pin PA4
  • establece permanentemente el pin PA6 en 1 en el ciclo while ()

Si el pin PA6 se conmuta usando

PORTA |= (1 << PA6);

todo funciona como se espera. El LED en el pin PA4 se alterna exactamente cada segundo. Sin embargo, si el puerto está configurado de esta manera

PORTA |= (1 << PinNumber);

el pin PA4 solo se alterna muy esporádicamente (se observa hasta 22 segundos entre alternaciones). Esto me indica que, por algún motivo, el controlador está "ocupado" o que el indicador ISR de desbordamiento no está configurado en absoluto.

EDITAR: Si agrego otro código de uso intensivo de ciclo como _delay_us (10); la situación mejora de tal manera que los archivos y menos interrupciones se perderán cuanto mayor sea el retraso. Esto tiene sentido si asumimos que la operación de cambio de pin está en algún punto bloqueando de alguna manera la entrada ISR.

Dado que la ejecución de ISR siempre debe tener prioridad sobre el código en el bucle principal, no entiendo por qué puede suceder esto y cómo exactamente hace una diferencia si el número pin se pasa como una constante o como una variable. / p>

Comprendo que el compilador probablemente pueda realizar una buena optimización si se usa una constante, pero el uso de una variable no debería dar como resultado algún código de bloqueo de ISR.

Esto está en GCC 4.8.1.

#include "stdlib.h"
#include "avr/interrupt.h"

uint8_t PinNumber;  

// asynch ISR with external clock source, fires every second
ISR (TIMER2_OVF_vect) {
    PORTA ^= (1 << PA4);
}

int main(void) {        
    DDRA |= (1 << PA6) | (1 << PA4);    // set PA6 and PA4 as output
    PinNumber = PA6;      // variable that just holds PA6 for demonstration

    cli();                      // global interrupt disable during init
    ASSR    |= (1<<AS2);        // Asynchronous Timer/Counter2 from external clock
    TCNT2   = 0x00;
    OCR2A   = 0x00;
    OCR2B   = 0x00;
    TCCR2A  = 0x00;
    TCCR2B  = 0x05;             // set divider for one second
    TIMSK2  |= (1 << TOIE2);    // enable TIMER2_OVF_vect
    sei();                      // global interrupt enable

    while(1) {
        //PORTA |= (1 << PinNumber);    // this somehow causes the interrupt to "starve"
        PORTA |= (1 << PA6);            // this works as expected
    }

}

EDITAR: El fondo práctico es que observamos que el ISR no se ejecuta correctamente cuando se cambian repetidamente varios puertos de salida, donde el número de pin es variable en el tiempo de ejecución.

    
pregunta Rev1.0

2 respuestas

1

Como se señaló en los comentarios, el problema es la secuencia de lectura-modificación-escritura que se interrumpe, lo que provoca que la instrucción en el bucle principal borre inadvertidamente PA4.

Cuando se usa una constante, GCC parece optimizarla a una sola instrucción sbi. Cuando se utiliza una variable, el estado del puerto se copia antes de que se realice la operación OR. Si el ISR se dispara entre esas instrucciones, el cambio del ISR esencialmente se pierde.

    
respondido por el Rev1.0
0

Probablemente el acceso a la memoria, que es bastante más lento, está retrasando las cosas. Tal vez también es mejor cambiar el estado del puerto en un solo lugar, para evitar la sincronización de lectura-modificación-escritura mencionada en los comentarios. Mi sugerencia:

#include "stdlib.h"
#include "avr/interrupt.h"

uint8_t PinMask = 1 << PA6;
volatile uint8_t PAMask = 0;  

// asynch ISR with external clock source, fires every second
ISR (TIMER2_OVF_vect) {
    PAMask ^= (1 << PA4);
}

int main(void) {        
    DDRA |= (1 << PA6) | (1 << PA4);    // set PA6 and PA4 as output

    cli();                      // global interrupt disable during init
    ASSR    |= (1<<AS2);        // Asynchronous Timer/Counter2 from external clock
    TCNT2   = 0x00;
    OCR2A   = 0x00;
    OCR2B   = 0x00;
    TCCR2A  = 0x00;
    TCCR2B  = 0x05;             // set divider for one second
    TIMSK2  |= (1 << TOIE2);    // enable TIMER2_OVF_vect
    sei();                      // global interrupt enable

    while(1) {
        PORTA = PAMask;
    }

}
    
respondido por el fceconel

Lea otras preguntas en las etiquetas