¿Por qué el lugar donde borro TMR0IF cambia el comportamiento del programa?

6

Estoy programando un microcontrolador PIC16F877A en C, compilando con Microchip XC8 1.44.

He configurado una interrupción TMR0 por cada 800 microsegundos que escanea a través de una matriz de LED, iluminando cada columna de 8 LED a la vez usando la configuración de la siguiente manera:

    OPTION_REG = 0x04; // TMR0 prescaler (1:32)
    GIE    = 1;
    TMR0IE = 1;

Y este es mi ISR:

void interrupt isr()
{
    static const uint16_t divs[4] = { 1000, 100, 10, 1 };
    if (TMR0IF)
    {
        PORTB = 0x00; // PORTB is the 8 LED column
        if (i)
        {
            PORTAbits.RA0 = 1; // pin that clocks CD4017 for next column
            __nop(); // give a brief delay just for good measure
            PORTAbits.RA0 = 0;
        }
        else
        {
            PORTAbits.RA1 = 1; // pin that resets CD4017 to first column
            __nop();
            PORTAbits.RA1 = 0;
        }

        // pattern to be displayed
        PORTB = font[i % 8U + 8U * ((c / divs[i / 8U]) % 10U)];
        i = (i + 1U) % 31U; // increment i for next iteration

        TMR0 = TMR0_OFFSET; // 240 for 4MHz crystal for 800us delay according to my calculations (I am bad at calculations)
        TMR0IF = 0;
    }
}

Básicamente, c (para 'contador') mantendrá un número entre 0 y 9999, y la matriz de 31 columnas LED lo muestra utilizando los datos en font para dibujar los números.

Ahora, este código funciona correctamente. Pero si muevo TMR0IF = 0; al principio del bloque, sucede algo extraño:

El programa parece ser lento, como si el reloj se retrasara por un factor de 100 o más, y el bucle del programa principal falla y, por eso, dejo de recibir las señales externas que utilizo para aumentar o restablecer el valor de c , solo permanece el ISR y mantiene la matriz de LED encendida, pero parpadea debido a la caída del reloj.

Si muevo TMR0IF = 0; al final como en el código anterior, todo parece funcionar normalmente y el mundo es perfecto. Pero ¿por qué?

En todas partes veo la referencia del código para el uso de interrupciones del Temporizador 0, la gente establece TMR0IF = 0; de inmediato, ¿por qué está causando todos los problemas en mi código?

    
pregunta Havenard

3 respuestas

13

El problema básico es que su código de interrupción tarda más tiempo en ejecutarse que el tiempo entre interrupciones. Esto probablemente se deba a uno de dos problemas:

  1. El período de interrupción no es lo que crees que es. La condición de interrupción en realidad se está activando con más frecuencia de la prevista.

  2. Estás tomando demasiados ciclos en la rutina de interrupción.

La razón por la que parece trabajar cuando se borra la condición de interrupción al final de la ISR es que luego hay un tiempo para que el código de primer plano se ejecute antes de la siguiente interrupción. Sin embargo, eso NO es una solución al problema. Cuando mueve la eliminación de la condición de interrupción al inicio del ISR donde debería estar, el ISR tarda tanto que la siguiente interrupción se toma inmediatamente cuando finaliza el ISR. No, esto no desborda la pila como otra respuesta reclama. Sin embargo, sí bloquea el código de primer plano para que no obtenga ciclos.

Siempre elimine la condición de interrupción al comienzo de la rutina de interrupción. Si usa el temporizador 0 para el período de interrupción, NO configure TMR0 en un valor fijo para cada interrupción. En su lugar, agregue la cantidad apropiada cada interrupción. De esa manera, no perderá tiempo en función de la latencia de fluctuación de fase de interrupción.

Además, no utilice el prescaler cuando agregue un desplazamiento en el temporizador. Si usa el temporizador 0, establezca agregar un desplazamiento en cada interrupción para obtener un período específico con el prescaler 1, o use solo el período de ejecución libre resultante con el prescaler sin unidad.

Si desea períodos de interrupción más arbitrarios, use el temporizador 2. Para eso es.

Me he dado cuenta de que escribiste tu rutina de interrupción en C. He visto un código bastante horrible inflado debido a que el compilador de C no sabía lo que realmente necesitaba ser guardado y, por lo tanto, estaba ahorrando muchas cosas. Personalmente, escribo rutinas de interrupción PIC 16 en el ensamblador. También hace que sea más fácil entender qué está pasando en casos como este, porque no hay compilador entre usted y el hardware.

Acabo de escanear su código ISR. Estás haciendo divisiones en el ISR! No es de extrañar que esté tardando más de lo previsto.

Este es un gran ejemplo de por qué en microcontroladores pequeños como este, los compiladores nunca deben usarse como sustituto para comprender la máquina en el nivel de instrucción. El compilador solo debe ser un atajo para crear un montón de código para usted, pero aún necesita tener una comprensión básica de qué es ese código y qué debe hacer la máquina para implementar lo que pide.

Dicho de otra manera, tienes que diseñar el código mientras piensas en las capacidades en bruto de la máquina. Una vez que haga eso, usar un compilador para generar algo del código real para usted puede ser un atajo legítimo. Incluso en ese caso, el ISR es la única rutina que más se debe tener en cuenta al escribir en el ensamblador.

Ve a escribir esta rutina de interrupción en el ensamblador. Eso no es porque necesariamente tiene que estar en el ensamblador, sino porque necesita aprender la máquina. No tiene por qué estar aquí sin entender el conjunto de instrucciones y las otras capacidades de bajo nivel del hardware. Nunca habrías intentado hacer división en un ISR si realmente entendieras el conjunto de instrucciones.

    
respondido por el Olin Lathrop
5

No creo que debas poner TMR0IF = 0 (borra el indicador de interrupción) al comienzo de tu ISR.

Creo que lo que está sucediendo es que su rutina ISR nos está llevando más de 800 o su TMR0 se está activando más rápido de lo que cree. Al poner TMR0IF = 0 al frente, está eliminando la interrupción y permitiendo que otra interrupción TMR0 interrumpa la interrupción. La MCU inserta la información actual en la pila y vuelve a llamar a la misma interrupción. Eventualmente, su pila se desbordará y su sistema se bloqueará.

  1. Mantenga TMR0IF = 0 como la última instrucción
  2. Calcule su ISR para ver si es más corto que 800 nosotros.
  3. Verifique si TMR0 se está ejecutando a la velocidad que usted piensa.

Programo mis rutinas al conducir un GPIO alto al comienzo y al mismo GPIO antes de salir de la rutina. Use un o'scope en ese GPIO y puede verificar a) si su ISR es realmente inferior a 800, b) si su TMR0 realmente está llamando a la velocidad que usted cree.

El ancho del pulso le dirá cuánto tiempo está tomando su ISR. El período le dirá con qué frecuencia se llama a su ISR. Si el período de la señal de o'scope es errático, su ISR está tomando más tiempo que los períodos de temporizador de TMR0 (es decir, se está saltando los períodos de TMR0)

    
respondido por el Vince Patron
1

La respuesta ya está en la otra publicación y en el comentario (un "LOT" de div, y el ISR se está demorando mucho en hacer las cosas), pero me gustaría señalar que resulta ser grosero.

¿Estamos todavía en la Edad de piedra (bromeando)? La mayoría de los ejemplos en internet te dan un ejemplo, ¿por qué? Porque es más fácil de entender y se puede cambiar más libremente que un código de ensamblador. Claro, el código escrito en el ensamblador es más rápido (quizás de alguien que ha pasado mucho tiempo programando con él), pero para una tarea simple, el compilador también hace un buen trabajo.

El problema, en mi humilde opinión, es la fuente de la habilidad de programación de la operación, si está acostumbrado a desarrollar software, no se da cuenta de cuántos recursos está utilizando el programa, porque está trabajando con un hardware de mayor rendimiento, y no con un pic16 limitado.

Div y mod, están seguros de tomar mucho tiempo para ejecutarse (en la foto), pero si optimizas el proceso, tal vez en algún caso sea viable. Por ejemplo:

i % 8U -> i&7
i / 8U -> i>>3

Algunos div y mod, pueden simplificarse usando el cambio o la operación lógica (en algunos casos donde el número es mod 2), pero aprendes trucos probando cosas y, en ocasiones, cometiendo errores.

Con la carga de trabajo en la ISR, es mejor como se sugiere, establecer una marca y, en general, actualizar sus datos, pero tratar de mejorar su código, lo ayuda en un proyecto futuro.

No quiero ser duro, pero todo debe ser evaluado para el alcance final. En este caso, abordar la carga de trabajo de la imagen debe ser la forma correcta de señalar el problema, reescribiendo en ensamblador lo que se logra. Agregar más complejidad no lo resuelve, por ejemplo: su horno (micro) puede cocinar 1 pizza (n instrucciones) cada 10 minutos (antes de que se vuelva a llamar al ISR), pero está solicitando cocinar 10 pizza al mismo tiempo. , probablemente da una idea concreta del problema.

Solo ten en cuenta lo que estás usando.

    
respondido por el Rurikred

Lea otras preguntas en las etiquetas