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?
Best Answer
When managing hardware at a low level like this you must:
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:
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
will generate. For example it could make a mess:
You want to write this yourself:
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:
This is one of the many utility macros in STD.INS.ASPIC in my PIC Development Environment.