2
\$\begingroup\$

Short info: I'm using a PIC18F4580 to measure the period time of two positive pulses using the CCP 1 on the MCU. And then writing the result to an LCD. When testing I sometimes get a result thats one overflow count wrong.(32,768ms wrong => 65536 timer1 ticks wong => 1 overflow count).

Details: Accuracy needs to be minimum 1us. I'm coding in C and using xc8 compiler. A 8MHz crystal is used to run the PIC so Fosc/4 = 2MHz. Timer1 runs with no prescaler to get a timer accuracy of 0.5us. Timer2 is used to poll the state of the push button, every 10ms. The code is well documented and i hope its easy to follow.

The way we are testing is with a squarewave generator with frequencies between 10Hz to 400Hz.

First i had the saves to t1 and t2 in the main loop but that didn't work good at higher frequencies.

I have tried to check to timer1 overflow flag in the CCP1 interrupt routine so that I dont miss a overflow when saving the "timestamp".

Any ideas on what is creating this error?

Code below, comfig bits and includes omitted

/********************* 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;
}

EDIT: Added question.

\$\endgroup\$
  • \$\begingroup\$ Do you have a specific question? \$\endgroup\$ – Andy aka May 31 '18 at 13:28
1
\$\begingroup\$

Okay, based on a quick look...

Reading the overflow flag is not quite enough. The overflow flag happens when the free-running counter rolls over, so you actually don't know whether the capture was triggered before or after the roll over.

So what you can do is to look at the MS bit of timeBuff after you read the capture register and if it is 1 (indicating a rollover was, at the time of capture, relatively close and in the future) then don't increment overflowCountxx and let the other ISR do that work. If it is 0 then you can safely assume it rolled over before the capture and correct overflowCountxx.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Should i check both the flag and MS bit of timeBuff to get a correct reading? Because the other ISR maybe already handled the overflow and incremented overflowCountxx and MSB of timeBuff is 0. \$\endgroup\$ – Martin May 31 '18 at 14:43
  • \$\begingroup\$ Yes, add to what you've done. \$\endgroup\$ – Spehro Pefhany May 31 '18 at 14:50
  • 1
    \$\begingroup\$ Great, I will test it tomorrow when Im back at work. \$\endgroup\$ – Martin May 31 '18 at 14:53
  • \$\begingroup\$ I added a check of MSB inside the if(saveFlag == TRUE) block, but it didnt help. Still getting a wrong readin sometimes. \$\endgroup\$ – Martin Jun 1 '18 at 5:34
  • 2
    \$\begingroup\$ Found an error in the code that fixed it. There was an = to much in the CCP1 isr when reseting the TMR1IF. Changed PIR1bits.TMR1IF == 0 to PIR1bits.TMR1IF = 0 and with your solution it now works. \$\endgroup\$ – Martin Jun 1 '18 at 6:45
1
\$\begingroup\$

Your basic problem is that the maximum period the hardware can measure with this setup is 32.768 ms, but apparently some of the periods you need to measure are longer than that.

The way to address this is to extend timer 1 in firmware. You already have a 10 ms interrupt, which could be used for this. Every 10 ms, you check for whether timer 1 overflowed in the previous 10 ms, and increment a firmware-maintained counter if it did.

When the capture occurs, you have to do a little more work. You first have to check whether timer 1 overflowed since the last 10 ms interrupt. If so, you need to increment the extended high bits of timer 1 first. You basically perform the same logic that the 10 ms interrupt code would.

Once you have a valid "wide" timer 1, you do the unsigned subtract of the current value minus the previous to get elapsed time as usual.

By keeping only 1 byte of timer 1 overflow counts (effectively extending timer 1 by 8 bits), the overflow time is 8.4 seconds. If that's enough, you can stop there. Otherwise, keep using more bytes for the extended part of timer 1 until the whole "wide" timer has a wrap period exceeding any period you have to measure.

I actually did this in a PIC 16 many years ago for a flow meter application, and it worked very nicely.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks, but I'm already counting the Timer1 overflows with a ISR when the Timer1 interrupt occurs. Do i really need to check it every 10ms? Also in the interrupt for CCP1 I'm checking the overflow flag before saving the value of the overflow counter. Is it not enough/ not to often? \$\endgroup\$ – Martin May 31 '18 at 13:43
  • \$\begingroup\$ @Mar: No, checking for overflow by the time the capture happens is too late. You lose information whenever nothing is looking at the timer for a whole wrap period. The easiest way to fix that is to look at it periodically a bit faster than that. Your 10 ms interrupt would be very convenient for that. Keeping the previous timer state, checking for overflow, possibly incrementing a counter, and updating the previous timer state is only a few instructions. \$\endgroup\$ – Olin Lathrop May 31 '18 at 15:12
  • \$\begingroup\$ Then if I am checking for overflow every 10ms the interrupt for Timer1 is not needed anymore, and I can disable it? \$\endgroup\$ – Martin Jun 1 '18 at 5:53
  • \$\begingroup\$ @Mar: No, not at all. The hardware timer is then providing the 16 low bits of a wider timer. You still need the hardware to capture the timer each event to get down to the 500 ns resolution of your clock. \$\endgroup\$ – Olin Lathrop Jun 1 '18 at 11:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.