3
\$\begingroup\$

INTRO

The whole point of a timer with overflow interrupt is that the interrupt will trigger on a precisely timed interval - so long as the code executed in the interrupt doesn't take longer than the timer interval.

THE PROBLEM

On a PIC16LF1823, I have run into the following behavior and I don't understand it. In short, one of my routines was being called from within the interrupt and was causing the interrupt to delay firing the next time. Its as though the routine somehow causes Timer0 to pause while its running. I've reduced the problem code as much as I can.

STARTING WITH WHAT WORKS

First, the basic code with no frills sets the processor to 2 MHz, starts Timer0 with no pre-scaler, and in the interrupt routine the test pin (C3) is toggled to verify everything is working. (Incidentally, I'm using mikroC). This code works as expected:

void main() {
    //2MHz
    SPLLEN_bit = 0;
    SCS1_bit = 1;
    SCS0_bit = 0;
    IRCF3_bit = 1;
    IRCF2_bit = 1;
    IRCF1_bit = 0;
    IRCF0_bit = 1;

    //Oscilloscope probe on C3
    ANSC3_bit = 0;
    TRISC3_bit = 0;
    LATC3_bit = 1;

    //Start Timer0
    TMR0CS_bit = 0; //Internal clock
    PSA_bit = 1;    //No prescaler assigned to Timer0

    //Enable timer interrups
    TMR0IE_bit = 1;

    //Enable general interupt

    PEIE_bit = 0;
    GIE_bit = 1;
    LATC0_bit = ~LATC0_bit;
}


void interrupt(void){

      if (TMR0IF_bit) {
         //The interrupt fires every 132uS
          TMR0 = 255 - 121;
          TMR0IF_bit = 0;

          LATC3_bit = ~LATC3_bit;
          //Delay_us(100); //<<<<Uncommenting this widens the pulse, but the pulses are still 132uS apart
          LATC3_bit = ~LATC3_bit;
      }
}

I have verified this works. Pin C3 toggles once every 132 µs with or without the delay.

HOW TO BREAK IT

However, in the following code, the interrupt now fires every 136 µs, instead of 132 µs. The only difference is the addition of two function (routine1 and routine2), and the declaration of an unsigned short. As I'm asking this question, just removing the initialized value ('=0') fixes the problem.

void main() {
    //2MHz
    SPLLEN_bit = 0;
    SCS1_bit = 1;
    SCS0_bit = 0;
    IRCF3_bit = 1;
    IRCF2_bit = 1;
    IRCF1_bit = 0;
    IRCF0_bit = 1;

    //Oscilloscope probe on C3
    ANSC3_bit = 0;
    TRISC3_bit = 0;
    LATC3_bit = 1;

    //Start Timer0
    TMR0CS_bit = 0; //Internal clock
    PSA_bit = 1;    //No prescaler assigned to Timer0

    //Enable timer interrups
    TMR0IE_bit = 1;

    //Enable general interupt

    PEIE_bit = 0;
    GIE_bit = 1;
    LATC0_bit = ~LATC0_bit;
}


unsigned short mode=0; //<<<<<<<<<<Removing the initiator ('=0') fixes the problem!!!
void routine1(void){
    unsigned short result;

    mode = 0;
    switch(mode) {
    case 99:
        //result = someFunction();
        if (result == 0xFF) {
            //Do something
        }
        break;
    }
}

void routine2(void){
    unsigned int result;

    if (result == 0xFF) {
       //Do something
    }
}

void interrupt(void){
int tmp;

      if (TMR0IF_bit) {
         //The interrupt fires every 132uS
          TMR0 = 255 - 121;
          TMR0IF_bit = 0;


          LATC3_bit = ~LATC3_bit;
          //Delay_us(100);
          LATC3_bit = ~LATC3_bit;


          routine2();
      }
}

Can anyone explain this?


EDIT1:

CORRECTION, the clock is set to 4MHz, so the clock is running at 1uS (1 / instruction clock). The code comment above is wrong.

Bruce's and m.Alin's comment about the magic number got me to thinking. I should be able to hard code 132. And then it occurred to me in the original code:

void interrupt(void){

    if (TMR0IF_bit) {
        TMR0 = 255 - 121; //Produces 132uS, but proves unpredictable       
        TMR0IF_bit = 0;

        LATC3_bit = ~LATC3_bit;
        LATC3_bit = ~LATC3_bit;
    }
}

the line:

TMR0 = 255 - 121;

is an attempt to get the timing right, having taken into account anything that happens after the interrupt fires but before I get a chance to reset the timer- thus the magic fudge factor of 11. In other words, I'm trying to predict what the value of the timer is at the time I preset it.

The fact is I know the value- its held in TMR0. So I tried the following line next:

TMR0 = TMR0 - 132; //Predictable but off by 3uS

This produced 135uS pulses, because of the if statement, and the subtraction. So, I subtracted 3 from 132:

TMR0 = TMR0 - 132 - 3; //This produces 142uS pulses (132uS + 10uS for the extra operation)

But of course, that didn't quite work because while reducing the time by 3, I also added a couple of operations which took even more time. So I tried the following...

TMR0 = TMR0 - 129; //This produces 132uS pulses (132 - 3)

This seems to have fixed the problem. This must mean the interrupt isn't firing in exactly the same way every time. Something must be holding the interrupt off at times. So even though this is a fix, it leaves me not know exactly how the timer interrupt mechanism works.

Any ideas?

\$\endgroup\$
5
  • 3
    \$\begingroup\$ You need a while(1) at the end of your main function. It's likely your code is simply resetting the uC every time it hits the end of that function. Be careful about the watchdog too. \$\endgroup\$
    – David
    Commented Jul 13, 2016 at 22:42
  • \$\begingroup\$ The while(1) didn't help. Besides, it wasn't necessary for the simple test code. \$\endgroup\$ Commented Jul 13, 2016 at 23:00
  • \$\begingroup\$ When you reset the timer value, you need to account for clock ticks that occur between the timer interrupt and when you reset it \$\endgroup\$ Commented Jul 14, 2016 at 0:15
  • 1
    \$\begingroup\$ @LanceBeasley this is incorrect. It is always necessary to stop execution reaching the end of main() or the processor will reset. \$\endgroup\$
    – David
    Commented Jul 14, 2016 at 6:04
  • \$\begingroup\$ @LanceBeasley, actually, if there is nothing at the end of main, the result is unpredictable - execution will just run off the end of the instructions at the end of main - maybe into your interrupt handler, maybe not. just because code seems to work, it isn't necessarily working the way you expect... \$\endgroup\$ Commented Jul 14, 2016 at 8:00

2 Answers 2

3
\$\begingroup\$

When managing hardware at a low level like this you must:

  1. Read the datasheet.

  2. See point 1.

  3. Be sure you know what machine instructions are being used. The easiest way to do this is to write critical code in assembler.

  4. See point 1 again.

You failed on points 1 and 3. If you had carefully read the section on timer 0, you would have seen that it stops for two cycles after any write to it. This is clearly described on page 175, section 20.1.1 8-Bit Timer Mode, paragraph 2:

When TMR0 is written, the increment is inhibited for two instruction cycles immediately following the write.

As you eventually figured out, you don't want to set TMR0 to a fixed value in the interrupt, but rather add a fixed value to it. Since the add is relative to the current value, it doesn't matter where in the interrupt routine the add is done, so long as the interrupt routine doesn't exceed the desired timer period.

When doing the add, the three cycle loss of increment needs to be taken into account. Put another way, when adding to the timer each period, the base period becomes 259 cycles, not the 256 cycles when the timer is left alone. The value added into the timer is how many cycles the period is shortened by from 259. If you want 132 cycle period, then you add 256 + 3 - 132 = 127 to TMR0 each period.

This, of course, needs to be done in assembler to guarantee the timer is incrementally written to the right way. You have no guarantee what instruction exactly the C code

TMR0 = TMR0 + (256 + 3 - period);

will generate. For example it could make a mess:

         movf    tmr0, w
         addlw   256 + 3 - period
         movwf   tmr0

You want to write this yourself:

         movlw   256 + 3 - period
         addwf   tmr0

Even better is to wrap all this into a macro. That better documents the intent in your interrupt code and make it something you only have to figure out once carefully, then use as a canned resource on new projects. I did this long ago. Here is my macro:

;********************
;
;   Macro TIMER0_PER cy
;
;   Update timer 0 so that it next wraps CY cycles from the previous wrap.  This
;   can be useful in a timer 0 interrupt routine to set the exact number of
;   cycles until the next timer 0 interrupt.  Timer 0 is assumed to be running
;   from the instruction clock.  The appropriate value is added into timer 0,
;   so this macro does not need to be invoked a fixed delay after the last
;   timer 0 wrap.  CY must be a constant.
;
;   The timer sets its interrupt flag when counting from 255, which wraps back
;   to 0.  If left alone, the timer therefore has a period of 256 instruction
;   cycles.  When adding a value into the timer, the increment is lost during
;   the add instruction, and the timer is not incremented for two additional
;   cycles when the TMR0 register is written to.  This effectively adds 3 more
;   cycles to the timer 0 wrap period.  These additional cycles are taken
;   into account in computing the value to add to TMR0.
;
timer0_per macro cy
         dbankif tmr0
         movlw   256 + 3 - (cy)
         addwf   tmr0
         endm

This is one of the many utility macros in STD.INS.ASPIC in my PIC Development Environment.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the insight. On this project, I have a solid BOOK of datasheets! \$\endgroup\$ Commented Jul 14, 2016 at 14:08
0
\$\begingroup\$

This line:-

TMR0 = 255 - 121;

is your problem. Timer0 ticks at 1us intervals, counting up to 255 and then generating an interrupt when it rolls over to 0. Setting Timer0 to 256-132 would cause an interrupt 132 ticks later, but there is no 132 in your code. Instead we see a 'magic' number, 121. How does this get an interrupt every 132us? Your comment doesn't tell us.

What your code should look like is something like this:-

// fudge factor to account for interrupt latency - determined by experiment 
#define MY_FUDGE_FACTOR 11

//The interrupt fires every 132uS
TMR0 = 255 - (132 - MY_FUDGE_FACTOR);     

Now we can see the problem. Depending on what code the compiler creates and which instruction the MCU is executing at the time, the number of cycles between an interrupt firing and your code executing may vary. To compensate for this you are subtracting the number of cycles taken to get there. But this number cannot be reliably determined because you have no control over what machine code the compiler produces.

the interrupt will trigger on a precisely timed interval - so long as the code executed in the interrupt doesn't take longer than the timer interval.

This is true only if the interrupt latency is safely less than 1 tick (not the entire timed interval), or if you never reload the timer. If you have to reload the timer to generate the interval you want and it is ticking faster than the time taken to reload it, it may be sensitive to the execution timing of your code.

The easiest way around this is to just let the timer roll over every 256 ticks and use the prescaler to set the time interval you want. Unfortunately that means you can't get 132us with a 4MHz system clock, but you could get 128us with 8MHz.

\$\endgroup\$
1
  • \$\begingroup\$ "but there is no 132 in your code. Instead we see a 'magic' number, 121. How does this get an interrupt every 132us?" Doesn't the 132 us comes from the 255 - 121 = 134 (which is almost 132)? \$\endgroup\$
    – m.Alin
    Commented Jul 14, 2016 at 8:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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