Pic16 Timer0 puzzle

3

INTRO

El punto central de un temporizador con interrupción por 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 he encontrado con el siguiente comportamiento y no lo entiendo. En resumen, se llamó a una de mis rutinas desde dentro de la interrupción y causó que la interrupción retrasara el encendido la próxima vez. Es como si la rutina de alguna manera hace que Timer0 se detenga mientras se está ejecutando. He reducido el código de problema tanto como puedo.

EMPEZANDO CON LO QUE FUNCIONA

Primero, el código básico sin adornos establece el procesador en 2MHz, inicia Timer0 sin preescalador, y en la rutina de interrupción, el pin de prueba (C3) se conmuta para verificar que todo esté funcionando. (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 alterna una vez cada 132 µs con o sin el retraso.

CÓMO RODARLO

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, solo 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?

EDIT1:

CORRECCIÓN, el reloj está configurado a 4MHz, por lo que el reloj funciona a 1uS (1 / reloj de instrucciones). 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 el código 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 obtener la sincronización correcta, habiendo tenido en cuenta todo lo que sucede después se dispara la interrupción, pero antes tengo la oportunidad de restablecer el temporizador. factor de fudge mágico de 11. En otras palabras, estoy tratando de predecir cuál es el valor del temporizador en el momento en que lo preestablecí.

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

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

Esto produjo 135uS pulsos, debido a la sentencia 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 al reducir el tiempo en 3, también agregué un par de operaciones que tomaron aún más tiempo. Así que intenté 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 en exactamente de la misma manera cada vez. Algo debe estar deteniendo la interrupción a veces. Entonces, aunque esto es una solución, me deja sin saber exactamente cómo funciona el mecanismo de interrupción del temporizador.

¿Alguna idea?

    
pregunta Lance Beasley

2 respuestas

4

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

  1. Lee la hoja de datos.

  2. Vea el punto 1.

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

  4. Ver el punto 1 de nuevo.

Has fallado en los puntos 1 y 3. Si hubieras leído cuidadosamente la sección del temporizador 0, habrías visto que se detiene durante dos ciclos después de cualquier escritura. 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 para dos ciclos de instrucción inmediatamente después de la escritura.

A medida que eventualmente se da cuenta, no desea establecer TMR0 en un valor fijo en la interrupción, sino agregarle un valor fijo. Dado que el agregado es relativo al valor actual, no importa en qué parte de la rutina de interrupción se realice el agregado, siempre que la rutina de interrupción no exceda el período del temporizador deseado.

Al hacer el complemento, se deben tener en cuenta los tres ciclos de pérdida de incremento. 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 el temporizador se queda solo. El valor agregado al temporizador es la cantidad de ciclos que se reduce el período de 259. Si desea un período de 132 ciclos, agregue 256 + 3 - 132 = 127 a TMR0 en cada período.

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

TMR0 = TMR0 + (256 + 3 - period);

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

         movf    tmr0, w
         addlw   256 + 3 - period
         movwf   tmr0

Quiere escribir esto usted mismo:

         movlw   256 + 3 - period
         addwf   tmr0

Aún mejor es envolver todo esto en una macro. Con el fin de documentar mejor la intención en su código de interrupción y convertirlo en algo que solo tiene que descifrar una vez y luego utilizarlo como recurso enlatado en nuevos proyectos. Hice esto hace mucho tiempo. Aquí está mi macro:

;********************
;
;   Macro TIMER0_PER cy
;
;   Update timer 0 so that it next wraps CY cycles from the previous wrap.  This
;   can be useful in a timer 0 interrupt routine to set the exact number of
;   cycles until the next timer 0 interrupt.  Timer 0 is assumed to be running
;   from the instruction clock.  The appropriate value is added into timer 0,
;   so this macro does not need to be invoked a fixed delay after the last
;   timer 0 wrap.  CY must be a constant.
;
;   The timer sets its interrupt flag when counting from 255, which wraps back
;   to 0.  If left alone, the timer therefore has a period of 256 instruction
;   cycles.  When adding a value into the timer, the increment is lost during
;   the add instruction, and the timer is not incremented for two additional
;   cycles when the TMR0 register is written to.  This effectively adds 3 more
;   cycles to the timer 0 wrap period.  These additional cycles are taken
;   into account in computing the value to add to TMR0.
;
timer0_per macro cy
         dbankif tmr0
         movlw   256 + 3 - (cy)
         addwf   tmr0
         endm

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

    
respondido por el Olin Lathrop
0

Esta línea: -

TMR0 = 255 - 121;

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

El aspecto que debería tener tu 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. Dependiendo de qué código crea el compilador y qué instrucción está ejecutando la MCU en ese momento, la cantidad de ciclos entre una activación de interrupción y la ejecución del código puede variar. Para compensar esto, está restando el número de ciclos tomados para llegar allí. Pero este número no se puede determinar de manera confiable porque no tienes 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 toma más tiempo que el temporizador.   intervalo.

Esto se cumple solo si la latencia de la interrupción es menor a 1 marca (no todo el intervalo de tiempo), o si nunca vuelve a cargar el temporizador. Si tiene que volver a cargar el temporizador para generar el intervalo que desea y está marcando más rápido que el tiempo necesario para volver a cargarlo, 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 se desplace por cada 256 tics y usar el prescaler para establecer el intervalo de tiempo que desee. Desafortunadamente, eso significa que no puede obtener 132us con un reloj del sistema de 4MHz, pero podría obtener 128us con 8MHz.

    
respondido por el Bruce Abbott

Lea otras preguntas en las etiquetas