cembeddedpic

PIC16F877A timer1 interrupt time is not as expected


I implemented an interrupt function on TIMER1 on a PIC16F877A microcontroller on a PIC-DIP40 development board. I configured the timer prescaler to 1 and set the auto preload value to 55536, so that the interrupt time was 0.01 second. It is using a counter of 100 to count 1 second intervals. The Fosc is 4 MHz. So my calculation is:

interrupt time = (4 / Fosc) * (65536 - 55536) = (4/4000000) * (65536 - 55536) = 0.01 second

And I used a counter of 100 to generate a 1-second interval. Currently, I don't have any oscilloscope to test the actual 1-second interval, so I am blinking an LED (LED2) on the timer interrupt and another LED (LED1) on the same time interval 1 second using the __delay_ms(1000); function.

So as expected, the two LEDs will blink synchronously (turn ON and OFF at the same time). But for some first iterations, they blink synchronously. After some iterations, there is a clear difference in time between their blinking time (turning ON and OFF time). After several minutes, the difference is almost 1 second. So the timer interrupt is not working as expected.

So is my calculation wrong for interrupt time or am I missing something in the timer1 configuration?

The overall goal is to generate a 1 second time interval and test the validity without using an oscilloscope.

Here is my code:

// Configuration
#pragma config FOSC = HS    // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF   // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF  // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF  // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF    // Low-Voltage (Single-Supply) In-Circuit
                            // Serial Programming Enable bit (RB3 is
                            // digital I/O, HV on MCLR must be used
                            // for programming)

#pragma config CPD = OFF    // Data EEPROM Memory Code Protection bit
                            // (Data EEPROM code protection off)

#pragma config WRT = OFF    // Flash Program Memory Write Enable bits (Write
                            // protection off; all program memory may be
                            // written to by EECON control)

#pragma config CP = OFF     // Flash Program Memory Code Protection
                            // bit (Code protection off)

#include <xc.h>
#include <pic16f877a.h>

#define _XTAL_FREQ  4000000

#define LED1_ON PORTDbits.RD7 = 0
#define LED1_OFF PORTDbits.RD7 = 1
#define LED2_ON PORTDbits.RD6 = 0
#define LED2_OFF PORTDbits.RD6 = 1

#define LED2_TOGGLE PORTDbits.RD6 = ~PORTDbits.RD6

uint16_t preloadValue = 55536;
uint16_t counter = 0;
uint16_t secCounter1 = 100;

void io_config() {
    // RD7 and RD6 are output LEDs
    TRISD  &= ~((1 << _PORTD_RD7_POSITION) | (1 << _PORTD_RD6_POSITION));
}

void timer1_init() {
    TMR1 = preloadValue; // Loading the preload value

    // Prescaler is 1 clock is Fosc
    T1CON &= ~((1 << _T1CON_T1CKPS1_POSN) | (1 << _T1CON_T1CKPS0_POSN) | (1 << _T1CON_TMR1CS_POSN));

    T1CONbits.TMR1ON = 1; // Timer 1 is ON
    LED2_ON;
}

void interrupt_en_configure() {

    // Global and peripheral interrupt on
    INTCON |=  (1 << _INTCON_GIE_POSITION) | (1 << _INTCON_PEIE_POSITION);

    PIE1 |= _PIE1_TMR1IE_MASK; // Timer 1 interrupt enable
    TMR1IF = 0; // Clearing the interrupt flag
}

void __interrupt() ISR() {
    if(TMR1IF) {
        counter ++;
        if (counter == secCounter1) {
            counter = 0;
            LED2_TOGGLE;
        }

        TMR1 = preloadValue;
        TMR1IF = 0;
    }
}

void main(void) {
    io_config();
    interrupt_en_configure();
    timer1_init();

    while (1) {
        LED1_ON;
        __delay_ms(1000);
        LED1_OFF;
        __delay_ms(1000);
    }
}

Solution

  • __delay_ms() delays by running an empty loop, but it commonly cannot be exact. You would need to look into the actual machine code that is run to calculate the real delay. BTW, this is not rocket science and a great learning task. (I have been there, done that.)

    Now the rest of your loop (LED switching and looping) adds to this. Therefore, your pure software driven blinker is not exact.

    However, your interrupt driven blinker is not, either. You reset the timer at the end of the ISR, after several clock cycles have passed. You need to take this into account, and don't forget the interrupt latency. Even worse, depending on the conditional statement, the reset happens at different times after the timer overflow.

    Producing exact timing is difficult, especially with such a simple device.

    The solution is to avoid software at all for the reset of the timer. Please read chapter 8 of the data sheet and use the capture/compare/PWM module to reset the timer on the appropriate value.

    The worst thing that could still happen is some jitter, just because the ISR might have different latencies. But the timer runs as exactly as your system's crystal. On average, your LED will blink correctly.

    Anyway, if your timing requirements are not that hard, consider to live with some inaccuracy. Then use the most simple solution you like best.