Pic16 Temporizador0 rompecabezas

INTRODUCCIÓN

El objetivo de un temporizador con interrupción de desbordamiento es que la interrupción se activará en un intervalo de tiempo preciso, siempre que el código ejecutado en la interrupción no tome más tiempo que el intervalo del temporizador.

EL PROBLEMA

En un PIC16LF1823, me encontré con el siguiente comportamiento y no lo entiendo. En resumen, una de mis rutinas estaba siendo llamada desde dentro de la interrupción y estaba causando que la interrupción retrasara el disparo la próxima vez. Es como si la rutina de alguna manera causara que Timer0 se detenga mientras se está ejecutando. He reducido el código del problema tanto como puedo.

COMENZANDO CON LO QUE FUNCIONA

Primero, el código básico sin florituras configura el procesador a 2 MHz, inicia Timer0 sin escalador previo y, en la rutina de interrupción, se activa el pin de prueba (C3) para verificar que todo funciona. (Por cierto, estoy usando mikroC). Este código funciona como se esperaba:

void main() {
    //2MHz
    SPLLEN_bit = 0;
    SCS1_bit = 1;
    SCS0_bit = 0;
    IRCF3_bit = 1;
    IRCF2_bit = 1;
    IRCF1_bit = 0;
    IRCF0_bit = 1;

    //Oscilloscope probe on C3
    ANSC3_bit = 0;
    TRISC3_bit = 0;
    LATC3_bit = 1;

    //Start Timer0
    TMR0CS_bit = 0; //Internal clock
    PSA_bit = 1;    //No prescaler assigned to Timer0

    //Enable timer interrups
    TMR0IE_bit = 1;

    //Enable general interupt

    PEIE_bit = 0;
    GIE_bit = 1;
    LATC0_bit = ~LATC0_bit;
}


void interrupt(void){

      if (TMR0IF_bit) {
         //The interrupt fires every 132uS
          TMR0 = 255 - 121;
          TMR0IF_bit = 0;

          LATC3_bit = ~LATC3_bit;
          //Delay_us(100); //<<<<Uncommenting this widens the pulse, but the pulses are still 132uS apart
          LATC3_bit = ~LATC3_bit;
      }
}

He verificado que esto funciona. El pin C3 cambia una vez cada 132 µs con o sin demora.

COMO ROMPERLO

Sin embargo, en el siguiente código, la interrupción ahora se dispara cada 136 µs, en lugar de 132 µs. La única diferencia es la adición de dos funciones (rutina1 y rutina2) y la declaración de un corto sin firmar. Mientras hago esta pregunta, simplemente eliminar el valor inicializado ('= 0') soluciona el problema.

void main() {
    //2MHz
    SPLLEN_bit = 0;
    SCS1_bit = 1;
    SCS0_bit = 0;
    IRCF3_bit = 1;
    IRCF2_bit = 1;
    IRCF1_bit = 0;
    IRCF0_bit = 1;

    //Oscilloscope probe on C3
    ANSC3_bit = 0;
    TRISC3_bit = 0;
    LATC3_bit = 1;

    //Start Timer0
    TMR0CS_bit = 0; //Internal clock
    PSA_bit = 1;    //No prescaler assigned to Timer0

    //Enable timer interrups
    TMR0IE_bit = 1;

    //Enable general interupt

    PEIE_bit = 0;
    GIE_bit = 1;
    LATC0_bit = ~LATC0_bit;
}


unsigned short mode=0; //<<<<<<<<<<Removing the initiator ('=0') fixes the problem!!!
void routine1(void){
    unsigned short result;

    mode = 0;
    switch(mode) {
    case 99:
        //result = someFunction();
        if (result == 0xFF) {
            //Do something
        }
        break;
    }
}

void routine2(void){
    unsigned int result;

    if (result == 0xFF) {
       //Do something
    }
}

void interrupt(void){
int tmp;

      if (TMR0IF_bit) {
         //The interrupt fires every 132uS
          TMR0 = 255 - 121;
          TMR0IF_bit = 0;


          LATC3_bit = ~LATC3_bit;
          //Delay_us(100);
          LATC3_bit = ~LATC3_bit;


          routine2();
      }
}

¿Alguien puede explicar esto?


EDITAR1:

CORRECCIÓN, el reloj está configurado a 4MHz, por lo que el reloj está funcionando a 1uS (1 / reloj de instrucción). El comentario del código anterior es incorrecto.

El comentario de Bruce y m. Alin sobre el número mágico me hizo pensar. Debería poder codificar 132. Y luego se me ocurrió en el código original:

void interrupt(void){

    if (TMR0IF_bit) {
        TMR0 = 255 - 121; //Produces 132uS, but proves unpredictable       
        TMR0IF_bit = 0;

        LATC3_bit = ~LATC3_bit;
        LATC3_bit = ~LATC3_bit;
    }
}

la línea:

TMR0 = 255 - 121;

es un intento de acertar en el tiempo, teniendo en cuenta todo lo que sucede después de que se dispara la interrupción, pero antes de que tenga la oportunidad de restablecer el temporizador, por lo tanto, el factor mágico de 11. En otras palabras, estoy tratando de predecir qué el valor del temporizador está en el momento en que lo preestablecí.

El hecho es que conozco el valor: se encuentra en TMR0. Así que probé la siguiente línea a continuación:

TMR0 = TMR0 - 132; //Predictable but off by 3uS

Esto produjo pulsos de 135uS, debido a la instrucción if y la resta. Entonces, resté 3 de 132:

TMR0 = TMR0 - 132 - 3; //This produces 142uS pulses (132uS + 10uS for the extra operation)

Pero, por supuesto, eso no funcionó del todo porque mientras reduje el tiempo en 3, también agregué un par de operaciones que tomaron aún más tiempo. Así que probé lo siguiente...

TMR0 = TMR0 - 129; //This produces 132uS pulses (132 - 3)

Esto parece haber solucionado el problema. Esto debe significar que la interrupción no se dispara exactamente de la misma manera cada vez. Algo debe estar retrasando la interrupción a veces. Entonces, aunque esto es una solución, no sé exactamente cómo funciona el mecanismo de interrupción del temporizador.

¿Algunas ideas?

Necesita un while(1)al final de su función principal. Es probable que su código simplemente restablezca el uC cada vez que llega al final de esa función. Tenga cuidado con el perro guardián también.
El while(1) no ayudó. Además, no era necesario para el código de prueba simple.
Cuando restablece el valor del temporizador, debe tener en cuenta los tics del reloj que se producen entre la interrupción del temporizador y el momento en que lo restablece.
@LanceBeasley esto es incorrecto. Siempre es necesario detener la ejecución llegando al final de main() o el procesador se reiniciará.
@LanceBeasley, en realidad, si no hay nada al final de main, el resultado es impredecible: la ejecución simplemente se ejecutará al final de las instrucciones al final de main, tal vez en su controlador de interrupciones, tal vez no. solo porque el código parece funcionar, no necesariamente funciona de la manera esperada...

Respuestas (2)

Al administrar hardware a un nivel bajo como este, debe:

  1. Lea la hoja de datos.

  2. Ver punto 1.

  3. Asegúrese de saber qué instrucciones de máquina se están utilizando. La forma más fácil de hacer esto es escribir código crítico en ensamblador.

  4. Véase el punto 1 de nuevo.

Ha fallado en los puntos 1 y 3. Si hubiera leído detenidamente la sección sobre el temporizador 0, habría visto que se detiene durante dos ciclos después de escribirle. Esto se describe claramente en la página 175, sección 20.1.1 Modo de temporizador de 8 bits , párrafo 2:

Cuando se escribe TMR0, el incremento se inhibe durante dos ciclos de instrucción inmediatamente después de la escritura.

Como finalmente descubrió, no desea establecer TMR0 en un valor fijo en la interrupción, sino agregarle un valor fijo. Dado que la adición es relativa al valor actual, no importa en qué parte de la rutina de interrupción se realice la adición, siempre que la rutina de interrupción no exceda el período de tiempo deseado.

Al hacer la adición, se debe tener en cuenta la pérdida de incremento de tres ciclos. Dicho de otra manera, al agregar al temporizador cada período, el período base se convierte en 259 ciclos, no en los 256 ciclos cuando se deja solo el temporizador. El valor agregado en el temporizador es cuántos ciclos se acorta el período desde 259. Si desea un período de 132 ciclos, agregue 256 + 3 - 132 = 127 a TMR0 cada período.

Esto, por supuesto, debe hacerse en ensamblador para garantizar que el temporizador se escriba incrementalmente de la manera correcta. No tiene garantía de qué instrucción exactamente el código C

TMR0 = TMR0 + (256 + 3 - período);

Generará. Por ejemplo, podría hacer un lío:

         movf tmr0, w
         adicional 256 + 3 - punto
         movwf tmr0

Quieres escribir esto tú mismo:

         movlw 256 + 3 - punto
         añadirwf tmr0

Aún mejor es envolver todo esto en una macro. Eso documenta mejor la intención en su código de interrupción y lo convierte en algo que solo tiene que descubrir una vez con cuidado, luego utilícelo como un recurso enlatado en nuevos proyectos. Hice esto hace mucho tiempo. Aquí está mi macro:

;********************
;
; Macro TIMER0_PER ciclo
;
; Actualice el temporizador 0 para que a continuación reinicie los ciclos CY desde el reinicio anterior. Este
; puede ser útil en una rutina de interrupción del temporizador 0 para establecer el número exacto de
; ciclos hasta la siguiente interrupción del temporizador 0. Se supone que el temporizador 0 está funcionando
; del reloj de instrucciones. El valor apropiado se agrega al temporizador 0,
; por lo que no es necesario invocar esta macro con un retraso fijo después de la última
; temporizador 0 envolver. CY debe ser una constante.
;
; El temporizador establece su indicador de interrupción cuando cuenta desde 255, que se reinicia
; a 0. Si se deja solo, el temporizador tiene un período de 256 instrucciones
; ciclos Al agregar un valor en el temporizador, el incremento se pierde durante
; la instrucción de adición, y el temporizador no se incrementa por dos adicionales
; ciclos cuando se escribe en el registro TMR0. Esto efectivamente agrega 3 más
; cicla al período de ajuste del temporizador 0. Estos ciclos adicionales se toman
; en cuenta al calcular el valor a agregar a TMR0.
;
timer0_per macro cy
         dbankif tmr0
         movlw 256 + 3 - (cy)
         añadirwf tmr0
         fin

Esta es una de las muchas macros de utilidad en STD.INS.ASPIC en mi entorno de desarrollo PIC.

Gracias por la información. ¡En este proyecto, tengo un LIBRO sólido de hojas de datos!

Esta línea:-

TMR0 = 255 - 121;

es tu problema. Timer0 hace tic en intervalos de 1 us, cuenta hasta 255 y luego genera una interrupción cuando llega a 0. Establecer Timer0 en 256-132 provocaría una interrupción 132 tics más tarde, pero no hay 132 en su código. En su lugar, vemos un número 'mágico', 121. ¿Cómo obtiene esto una interrupción cada 132us? Tu comentario no nos dice.

El aspecto que debería tener su código es algo como esto:

// fudge factor to account for interrupt latency - determined by experiment 
#define MY_FUDGE_FACTOR 11

//The interrupt fires every 132uS
TMR0 = 255 - (132 - MY_FUDGE_FACTOR);     

Ahora podemos ver el problema. Según el código que cree el compilador y la instrucción que ejecute la MCU en ese momento, la cantidad de ciclos entre la activación de una interrupción y la ejecución de su código puede variar. Para compensar esto, está restando el número de ciclos necesarios para llegar allí. Pero este número no se puede determinar de manera confiable porque no tiene control sobre qué código de máquina produce el compilador.

la interrupción se activará en un intervalo de tiempo preciso, siempre que el código ejecutado en la interrupción no tome más tiempo que el intervalo del temporizador.

Esto es cierto solo si la latencia de interrupción es segura menos de 1 tic (no el intervalo cronometrado completo), o si nunca recarga el temporizador. Si tiene que recargar el temporizador para generar el intervalo que desea y está corriendo más rápido que el tiempo necesario para recargarlo, puede ser sensible al tiempo de ejecución de su código.

La forma más fácil de evitar esto es simplemente dejar que el temporizador pase cada 256 tics y usar el preescalador para establecer el intervalo de tiempo que desea. Desafortunadamente, eso significa que no puede obtener 132us con un reloj de sistema de 4MHz, pero podría obtener 128us con 8MHz.

"pero no hay 132 en su código. En su lugar, vemos un número 'mágico', 121. ¿Cómo obtiene esto una interrupción cada 132us?" ¿El 132 us no viene del 255 - 121 = 134 (que es casi 132)?