Medición del período PIC a veces 1 conteo de desbordamiento incorrecto

Información breve: estoy usando un PIC18F4580 para medir el período de tiempo de dos pulsos positivos usando el CCP 1 en la MCU. Y luego escribir el resultado en una pantalla LCD. Cuando pruebo, a veces obtengo un resultado que es un conteo de desbordamiento incorrecto (32,768ms incorrecto => 65536 timer1 ticks wong => 1 conteo de desbordamiento).

Detalles: La precisión debe ser mínimo 1us. Estoy codificando en C y usando el compilador xc8. Se utiliza un cristal de 8 MHz para ejecutar el PIC, por lo que Fosc/4 = 2 MHz. El temporizador 1 se ejecuta sin preescalador para obtener una precisión del temporizador de 0,5 us. Timer2 se utiliza para sondear el estado del botón pulsador, cada 10 ms. El código está bien documentado y espero que sea fácil de seguir.

La forma en que estamos probando es con un generador de onda cuadrada con frecuencias entre 10 Hz y 400 Hz.

Primero tuve los guardados en t1 y t2 en el bucle principal, pero eso no funcionó bien en frecuencias más altas.

He intentado verificar el indicador de desbordamiento del temporizador 1 en la rutina de interrupción de CCP1 para no perderme un desbordamiento al guardar la "marca de tiempo".

¿Alguna idea sobre qué está creando este error?

Código a continuación, bits de configuración e incluye omitido

/********************* Variables *********************/
unsigned long t1 = 0;
unsigned long t1Count = 0;
unsigned long t2 = 0;
unsigned long t2Count = 0;
volatile unsigned long timeBuff = 0;
float elapsedTime = 0;
volatile unsigned long overflowCountTMR1 = 0;
volatile unsigned long overflowCountBuff = 0;

volatile int pulseCount = 0;
volatile int btnCount = 0;

char ms[20];

/********************* Function declarations *********************/

void interrupts();
void noInterrupts();
void resetValues();

/********************* Main function *********************/
void main() {

TRISBbits.RB5 = 1;                    // Input push button

// Set up CCP module, capture rising
TRISCbits.RC2 = 1;                   // Set RC2/CPP1 pin as input
PORTC = 0x00;                        // Set all pins to low
CCP1CON = 0x05;                      // Capture rising edge
PIR1bits.CCP1IF = 0;                 // Reset interrupt flag

// Set-up timer1 for counting periodtime
T1CON = 0x00;                        // Disable Timer1 under set-up
T3CON = 0x00;                        // Set Timer1 as capture source for CCP1 
PIR1bits.TMR1IF = 0;                 // Reset interrupt flag
T1CON = 0x81;                        // Start Timer1 with 16-bits read/write 

// Set-up timer2 for polling push button state
T2CON = 0x00;                        // Disable Timer1 under set-up
PIR1bits.TMR2IF = 0;                 // Reset interrupt flag
PR2 = 125;
T2CON = 0x4F;                        // Prescaler: 16 Postscaler: 10 => Interrupt every 10ms (WHEN PR2 = 125)

// Set up LCD
CMCONbits.CM = 0x07;                // Disable comperators at RB2 & RB3
TRISD = 0x00;                       // Set up D ports as output
Lcd_Init();
Lcd_Clear();
Lcd_Set_Cursor(1,1);

interrupts();                       // Enable interrupts

while(1)
{
    if(btnCount == 10){
        noInterrupts();
        resetValues();
        Lcd_Clear();
        interrupts();
    }


    // Calculate period from timestamps
    if (pulseCount == 2){              // We got 2 pulses, calculate elapsed time

        noInterrupts();
        // T2-T1 don't forget the overflow count variable
        elapsedTime = ( (t2 - t1) + ((t2Count-t1Count)*65536) )/2.0;   // In us (microseconds)
        pulseCount++;                 // Increment so we wont calculate again

        // Write results to LCD in ms
        sprintf(ms,"%4.3f",(elapsedTime/1000.0));
        Lcd_Set_Cursor(1,1);
        Lcd_Write_String(ms);
        interrupts();
    }

  }  
}

/*********************  Interrupt functions *********************/
// Handles interrupts. Timer interrupt to keep track of nr of overflows.
// Check if we got a pulse, save value if we did 
void interrupt ISR_handeler(){
    if(PIR1bits.CCP1IF == 1){
        timeBuff = CCPR1;
        int saveFlag = PIR1bits.TMR1IF;
        if(saveFlag == TRUE){
            timeBuff = CCPR1;
            overflowCountTMR1++;
            PIR1bits.TMR1IF == 0;
        }
        pulseCount++;
        if(pulseCount == 1){
            t1 = timeBuff;
            t1Count = overflowCountTMR1;
        } else if (pulseCount == 2){
            t2 = timeBuff;
            t2Count = overflowCountTMR1;
            PIE1bits.TMR1IE = 0;                // We only want to calculate between two pulses
        }
        PIR1bits.CCP1IF = 0;
    }

    if(PIR1bits.TMR1IF == 1){                  //Count number of Timer1 overflows
        overflowCountTMR1++;
        PIR1bits.TMR1IF = 0;
    }

    if(PIR1bits.TMR2IF == 1){
        if(PORTBbits.RB5 == 0){
            btnCount++;
        }
        PIR1bits.TMR2IF = 0;
    }

}

/********************* Functions *********************/

void noInterrupts(){
// Disable global and peripheral interrupts
INTCONbits.GIE = 0;
INTCONbits.PEIE = 0;
PIE1bits.CCP1IE = 0;                // Disable CPP1 interrupt
PIE1bits.TMR1IE = 0;                // Disable Timer1 Overflow interrupt
PIE1bits.TMR2IE = 0;                // Disable Timer2 Overflow interrupt
}

void interrupts(){
// Enable global and peripheral interrupts
INTCONbits.GIE = 1;
INTCONbits.PEIE = 1;
PIE1bits.CCP1IE = 1;                // Enable CPP1 interrupt
PIE1bits.TMR1IE = 1;                // Enable Timer1 Overflow interrupt
PIE1bits.TMR2IE = 1;                // Enable Timer2 Overflow interrupt
}

void resetValues() {
pulseCount = 0;
overflowCountTMR1 = 0;
btnCount = 0;
}

EDITAR: pregunta añadida.

¿Tienes una pregunta específica?

Respuestas (2)

Vale, basándome en un vistazo rápido ...

Leer el indicador de desbordamiento no es suficiente. El indicador de desbordamiento ocurre cuando el contador de ejecución libre se da la vuelta, por lo que en realidad no sabe si la captura se activó antes o después de la vuelta.

Entonces, lo que puede hacer es mirar el bit MS de timeBuff después de leer el registro de captura y si es 1 (lo que indica que un rollover estaba, en el momento de la captura, relativamente cerca y en el futuro) entonces no incremente overflowCountxx y deje que el otro ISR haga ese trabajo. Si es 0, puede asumir con seguridad que se revirtió antes de la captura y corregir overflowCountxx.

¿Debo verificar tanto la bandera como el bit de MS de timeBuff para obtener una lectura correcta? Debido a que el otro ISR tal vez ya manejó el desbordamiento y el overflowCountxx incrementado y el MSB de timeBuff es 0.
Sí, añade a lo que has hecho.
Genial, lo probaré mañana cuando vuelva al trabajo.
Agregué una verificación de MSB dentro del bloque if (saveFlag == TRUE), pero no ayudó. Todavía obtengo una lectura incorrecta a veces.
Encontré un error en el código que lo arregló. Hubo un = demasiado en el CCP1 isr al restablecer el TMR1IF. Cambió PIR1bits.TMR1IF == 0 a PIR1bits.TMR1IF = 0 y con su solución ahora funciona.
He cambiado a un cristal de 20Mhz y ahora recibo el mismo error en las frecuencias más altas nuevamente. ¿Hay alguna manera de mejorar el código aún más? Intenté verificar si el timebuff (captura de CPP) está por debajo de un umbral (probé valores entre 100 y 300), pero luego empeora.

Su problema básico es que el período máximo que el hardware puede medir con esta configuración es de 32,768 ms, pero aparentemente algunos de los períodos que necesita medir son más largos que eso.

La forma de abordar esto es extender el temporizador 1 en el firmware. Ya tiene una interrupción de 10 ms, que podría usarse para esto. Cada 10 ms, verifica si el temporizador 1 se desbordó en los 10 ms anteriores e incrementa un contador mantenido por firmware si lo hizo.

Cuando se produce la captura, hay que trabajar un poco más. Primero debe verificar si el temporizador 1 se desbordó desde la última interrupción de 10 ms. Si es así, primero debe incrementar los bits altos extendidos del temporizador 1. Básicamente, realiza la misma lógica que el código de interrupción de 10 ms.

Una vez que tenga un temporizador 1 "amplio" válido, haga la resta sin signo del valor actual menos el anterior para obtener el tiempo transcurrido como de costumbre.

Manteniendo solo 1 byte de los recuentos de desbordamiento del temporizador 1 (ampliando efectivamente el temporizador 1 en 8 bits), el tiempo de desbordamiento es de 8,4 segundos. Si eso es suficiente, puede detenerse allí. De lo contrario, siga usando más bytes para la parte extendida del temporizador 1 hasta que todo el temporizador "ancho" tenga un período de finalización que exceda cualquier período que tenga que medir.

De hecho, hice esto en un PIC 16 hace muchos años para una aplicación de medidor de flujo, y funcionó muy bien.

Gracias, pero ya estoy contando los desbordamientos de Timer1 con un ISR cuando ocurre la interrupción de Timer1. ¿Realmente necesito comprobarlo cada 10 ms? También en la interrupción para CCP1 estoy comprobando el indicador de desbordamiento antes de guardar el valor del contador de desbordamiento. ¿No es suficiente/no con demasiada frecuencia?
@Mar: No, verificar el desbordamiento en el momento en que ocurre la captura es demasiado tarde. Pierde información cada vez que nada está mirando el temporizador durante un período de finalización completo. La forma más fácil de solucionarlo es mirarlo periódicamente un poco más rápido que eso. Su interrupción de 10 ms sería muy conveniente para eso. Mantener el estado del temporizador anterior, verificar el desbordamiento, posiblemente incrementar un contador y actualizar el estado del temporizador anterior son solo algunas instrucciones.
Entonces, si estoy verificando el desbordamiento cada 10 ms, la interrupción para Timer1 ya no es necesaria, ¿y puedo deshabilitarla?
@Mar: No, para nada. El temporizador de hardware proporciona entonces los 16 bits bajos de un temporizador más amplio. Todavía necesita el hardware para capturar el temporizador de cada evento para llegar a la resolución de 500 ns de su reloj.