¿Cronometraje preciso con un microcontrolador PIC18?

Estoy tratando de escribir una implementación en serie de software y tengo problemas con el tiempo. Estoy usando Timer0 en un PIC18F242 y, por alguna razón, no parece ser muy preciso.

He estado tratando de depurar el problema alternando un pin en el microcontrolador y observando la frecuencia en mi osciloscopio. El osciloscopio muestra que el período es mayor de lo que debería ser, y no sé por qué.

Con mi código completo, el período fue algo así como 10us demasiado largo, si no recuerdo mal, pero incluso con un ejemplo muy simple, como el que se muestra a continuación, el tiempo está fuera de lugar.

Aquí hay un ejemplo:

#include <p18f242.h>

// fuse configurations
#pragma config OSC = HSPLL
#pragma config OSCS = OFF 
#pragma config PWRT = OFF 
#pragma config BOR = OFF 
#pragma config WDT = OFF
#pragma config LVP = OFF 
#pragma config CP0 = OFF

// CALCULATIONS:
// 8Mhz crystal with PLL enabled
// FOSC = 4*8Mhz = 32MHz
// TOSC = 1/FOSC = 31.25ns
// TCY = 4*TOSC = 125 ns
// timer has 1:8 prescaler, therefore timer increments every 8*TOSC = 1us.

main(void)
{
    // port configuration
    TRISBbits.RB1 = 0; // make RB1 an output

    // configure Timer0
    T0CONbits.TMR0ON = 0; // disable (stop) the timer
    T0CONbits.T08BIT = 0; // 16-bit timer
    T0CONbits.T0CS   = 0; // use internal instruction clock
    T0CONbits.PSA    = 0; // use prescaler (prescaler assigned below)
    T0CONbits.T0PS0  = 0; // 010 = 1:8 prescaler
    T0CONbits.T0PS1  = 1;
    T0CONbits.T0PS2  = 0;
    T0CONbits.TMR0ON = 1; // enable (start) the timer

    while(1) 
    {
        if(TMR0L >= 100)
        {
            PORTBbits.RB1 ^= 1;

            // reset the timer
            TMR0H = 0; // MUST SET TMR0H FIRST
            TMR0L = 0;
        }   
    }
}

Mi osciloscopio dice que el período de esta señal es 204.0us, lo que significa que el pin cambia cada 102.0us. Si mi osciloscopio es correcto, entonces el pin cambia 2us demasiado tarde cada vez.

Curiosamente, cambiar if(TMR0L >= 100)a if(TMR0L >= 102)resultados en el mismo período, pero ir por debajo 100o por encima 102da como resultado que el período disminuya y aumente, respectivamente.

¿Por qué está pasando esto?

Además, como mencioné, tener código adicional en el ciclo principal parece exacerbar el problema. ¿Por qué sería eso?

Respuestas (6)

Si desea una temporización precisa, utilice interrupciones (es decir, restablezca el valor del temporizador en su rutina de interrupción).
Tal como lo tiene, solo verifica el valor del temporizador una vez por ciclo, por lo que si agrega código en el ciclo, las comprobaciones se dispersan.
La interrupción cambiará de contexto tan pronto como el temporizador se desborde, por lo que no importa cuánto código haya en su bucle en ese momento.

Lo que ve aquí es el retraso entre el momento en que el temporizador llega a 100 (lo que significa que su condición si es verdadera) y cuando lo restablece. En su caso, se trata de 2 tics de temporizador, lo que significa que obtiene un período más largo. Como ya se dijo, si desea tener una sincronización precisa, use interrupciones. Alternativamente, puede configurar su temporizador para que se desborde exactamente en el momento adecuado. De esa manera, solo necesita verificar que se produzca un desbordamiento y no necesita restablecerlo.

Esta no es la forma más efectiva de usar un temporizador. Si desea una sincronización precisa, configure una interrupción para cuando el temporizador se detenga. La rutina de interrupción debe establecer un indicador de temporizador y hacer cualquier otra cosa que sea rápida que deba ser tratada de una manera crítica en el tiempo, y restablecer el conteo del temporizador (leyendo el valor actual del temporizador para que pueda tener en cuenta cualquier retraso asociado con el servicio de interrupción). Todo lo que no es rápido, lo pones en un ciclo if que verifica el indicador del temporizador. Lo primero que hace en ese ciclo es restablecer el indicador del temporizador y luego hacer todas las cosas que no son críticas en el tiempo asociadas con su ciclo.

El retraso adicional se debe al tiempo que lleva ejecutar las instrucciones while, if compare, toggle y reset, porque cuando pone a cero el temporizador, pierde todos esos conteos, dependiendo de cuándo exactamente el temporizador llegó a 100 en ese ciclo en particular. . Tcy es 125 ns, por lo que solo se necesitan 16 instrucciones de ensamblaje para compensar el retraso adicional de 2us. Eche un vistazo a la lista de ensamblaje y se dará cuenta de la cantidad de gastos generales que está agregando.

Como han dicho otros, use una interrupción para la menor incertidumbre y nerviosismo. En lugar de reiniciar el temporizador, deje que se desborde y siga contando, de esa manera no pone a cero los conteos, que se suman al período total.

Si no quiere usar interrupciones (pero debería hacerlo), configure también el período para que se desborde y marque el indicador de desbordamiento. Esto le permitirá no poner a cero el temporizador y preservará el período calculado, pero tendrá fluctuaciones.

Si usa un preescalar con el temporizador 0, no es posible generar interrupciones con precisión a tasas que no sean múltiplos de 256 en potencia de dos. Si desea generar una tasa que no sea múltiplo de 256 en potencia de dos , su mejor opción probablemente sea usar el temporizador 2, que se puede configurar para cualquier número de la forma (A*B*C) donde A es cualquier número del 1 al 256, B es cualquier número del 1 al 16 y C es una potencia permitida de dos (se me olvida cuales están permitidos). De lo contrario, si desea usar el temporizador 0 de manera eficiente en modo de 8 bits sin preescalar, digaTMR0L += Navanzará el temporizador en N-3 ciclos. Por lo tanto, si desea que el temporizador 0 se reinicie cada 200 ciclos, podría agregarle (259-200) ciclos después de cada vez que finaliza (ya que avanzar el temporizador en 56 ciclos después de que finaliza dejaría 256-56 ciclos, es decir, 200). Tenga en cuenta que al usar esta técnica, no importa precisamente cuándo se produce el "agregado", siempre que ocurra lo suficientemente pronto como para que no empuje el temporizador "sobre el borde".

Puede ser una molestia si no conoce el ensamblaje, pero insertar rutinas de ensamblaje en el código C puede ayudarlo a lograr una sincronización precisa. Si recuerdo correctamente para insertar el ensamblaje en PIC C, usa

_asm

escriba su código de ensamblaje

_endasm