Search code examples
timerinterrupt-handlingpic24

Timer accuracy and interrupts


I have a PIC24F Curiosity Board (PIC24FJ128GA204) and I'm trying to get an accurate second timing with the TIM1. As a source I'm using the secondary oscillator, which uses a 32678 kHz xtl.

The timer is configured with a 32 period, which corresponds to 1 ms

void TMR1_Initialize (void)
{
    //TMR1 0; 
    TMR1 = 0x0000;
    //Period = 0.001 s; Frequency = 32000 Hz; PR1 32; 
    PR1 = 0x0020;
    //TCKPS 1:1; TON enabled; TSIDL disabled; TCS External; TECS SOSC; TSYNC enabled; TGATE disabled; 
    T1CON = 0x8006;


    IFS0bits.T1IF = false;
    IEC0bits.T1IE = true;

    tmr1_obj.timerElapsed = false;

}

So, every 1 ms the following interrupt is called, where the count of ms is stored on a uint32_t variable

void __attribute__ ( ( interrupt, no_auto_psv ) ) _T1Interrupt (  )
{    
    tmr1_obj.count++;
    tmr1_obj.timerElapsed = true;
    IFS0bits.T1IF = false;
}

In the main loop I do a busy wait on the tmr1_obj.count variable, and when it reaches 1000 a message is sent in UART.

int main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    TMR1_Start();


    char msg[] = "A\r\n";        
    while (1)
    {
        // Add your application code
        int value = TMR1_SoftwareCounterGet();

        if (value == 1000) {                                
            printf("%s", msg);
            TMR1_SoftwareCounterClear();                                  
        }        
    }

    return -1;
}

On the other side of the UART I have an application that reads the message and logs the time when it was received and the difference in milliseconds with respect to the previous message.

The problem is that I have an accumulation of ~8ms for each message.

22:05.026   1008
22:06.035   1009
22:07.045   1010
22:08.054   1008
22:09.063   1008

Suspecting it could be something related to the UART transmission being often interrupted I tried to send the message every 10 seconds instead of 1. The delay was consistently accumulated to ~80ms each 10 seconds.

Finally, it could be that the 32 ticks do not correspond to 1 ms but to 1.x ms. To test that I changed the period to be 32768, that is, 1 second. I expected the results to be roughly the same, but they weren't

31:15.216   999
31:16.216   999
31:17.216   1000
31:18.216   999
31:19.216   999
31:20.215   999
31:21.216   1000

It seems that the period affects the accuracy of the timer. And I don't understand how. Could it be that for each interruption some microseconds were lost ? I don't see anything on the data sheet, but I can't find any other explanation.


Solution

  • Keep in mind that when setting the period of the PIC24 that you have to count "0" as a tick, so you want to set your period to 1 less tick than the period you want (31 in this case).

    Consider if you wanted to have a peroid of 5 ticks. If you set the period register to 5 it will do:

    0->1 = 1 tick
    1->2 = 2 ticks
    2->3 = 3 ticks
    3->4 = 4 ticks
    4->5 = 5 ticks
    5->0 = 6 ticks
    

    So you really want to set the value to 4 to get 5 ticks (or 31 to get 32 ticks).

    Now on to your 8ms. Since you set it to 32, you get 33 ticks. Each tick is: 1/32768 seconds

    33 of those is 33/32768 or which is roughly 1.007ms. 1000 of those gives you an extra 7 ms, which is pretty close to your 8. So your real problem is that you are trying to use a value that is inexact and it accumulates error over time. Even if you set the period to 31 (giving 32 ticks) you will be off because you can't get exactly 1ms from a 32768 Hz crystal.

    One thing you can do is set your period to 31 (or 32 ticks) and compare to 1024 ticks (instead of 1000). That should be much closer to 1 second if not exact (tolerances not withstanding). Your interrupts won't be at 1ms exactly, but your 1 second will be pretty darn close.