3
\$\begingroup\$

Using a PIC16F886, I am trying to generate interrupts every 100 milliseconds using TMR0 clocked from the internal oscillator, but I am getting some really strange behaviour.

This is a battery powered circuit, so I am using the 125KHz internal oscillator, selected via:

OSCCON = 0b00010000; // select 125 KHz Int. Osc. = IRCF<2:0>=001

I then assign the prescaler to TMR0 and set a prescaler value of 1:2:

T0CS = 0;   // TMR0 Clock Source: Internal instruction cycle clock (FOSC/4)
PSA = 0;    // Prescaler is assigned to TMR0
PS2 = 0;
PS1 = 0;    // > TMR0 Rate: 1:2;
PS0 = 0; 

So now, according to my calculations each 'tick' should take ((1/125 000) / 4) / 2 = 1.0 × 10^-6 seconds, If I preload the timer with 155, it will take 100 'ticks' to overflow, generating an interrupt every 100uS.

My interrupt service routine consists of:

if(T0IE)
{
    ticks++;
    if (ticks >= 999){
        ticks = 0;

        PORTB = ~PORTB;
    }

    TMR0 = 155; 
    return;     
}

And it does function, but the timing is not exactly right.

When I simulate it using the MPLAB SIM, It takes around 85mS and on real hardware it seems take longer than 100mS.

The full code listing can be found here: http://pastebin.ca/1928766

It is quite possibly I am miscalculating something, so any pointers/corrections would be much appreciated.

\$\endgroup\$
5
\$\begingroup\$

I believe your calculations are mixing frequency and time periods.

1 / 125000 Hz equals a period of 8 us. FOSC/4 divides the frequency further down (or multiplies the period). You cannot multiply a frequency without special circuitry. The prescaler also divides the frequency, or multiples the period. So instead of dividing the 8 us by 4*2, you need to multiply it by 8: 8 * 4 * 2 = a period of 64 us, or 64 times what you thought it was.

Unfortunately, 1000 doesn't divide evenly by 64, so you can't get a exact 1 ms interrupt. Instead you should probably use a clock frequency of 1 MHz (OSCCON = 0b01000000) for a period of 1 us. Then 8 * 1 us equals 8 us, and you can use a clock preset of 256-125=134 or 0x86 and get an interrupt every 1 ms. Count every 100 of those for your 100 ms timing.

As an alternative, if power consumption is an issue, you could set the clock frequency to 250 KHz (just double what you were using), and get an interrupt every 4 ms. Then count 25 of those for 100 ms timing.

\$\endgroup\$
  • \$\begingroup\$ (I have completely forgotten that I've posted this here, sorry!) Thanks for your answer, shortly after asking the question I've realised the silly mistake :) \$\endgroup\$ – Hamza Dec 14 '10 at 12:22
2
\$\begingroup\$

They're right, each T0 tick is 64 us. In 100 ms, there will be 1,562.5 T0 ticks. Each T0 overflow takes 256 ticks, so there will be 6 overflows. Then T0 tick number 26.5 corresponds to your 100'th millisecond.

if (T0IE&&T0IF)
{
    // clear interrupt flag
    T0IF=0;
    ticks++;
    if (ticks == 6)
    {
        // Prepare fractional overflow of 26 ticks
        TMR0 = (256 - 26);
    }
    else if (ticks == 7)
    {   
        ticks = 0;
        PORTB = ~PORTB;
    }   

    // don't need return
}
\$\endgroup\$
1
\$\begingroup\$

I get the same thing with the math as tcrosley - you can't divide the period by 4 for a clock divider. You multiply the period (divided frequency == multiplied period). Also, do you need to clear the interrupt flag in the service routine? I don't see you doing it if you do.

\$\endgroup\$
1
\$\begingroup\$

I just noticed another problem I hadn't noticed before: you are neither checking nor clearing TMR0IF within your interrupt routine. The way the PIC works, any time it's about to execute an instruction, GIE is set, and any peripheral interrupt flag is set along with its corresponding enable (e.g. TMR0IF and TMR0IE) are set, it will clear GIE and call the interrupt routine. Clearing GIE allows the instructions within the interrupt routine to run (otherwise any time the system was about to execute the second instruction of the interrupt routine, it would generate another call to the first instruction). Returning from the interrupt re-sets GIE. If at that moment there is still a peripheral interrupt flag which is set along with its corresponding enable, the system will again clear GIE and generate a call to the interrupt routine.

There are two strategies via which an interrupt routine can solve this problem:

  1. If there's more than one interrupt that may be enabled, the interrupt routine should check the flag for one of them and, if the flag is set, clear that flag and handle the condition it represents. After the first flag is found to be clear or its condition has been handled, check the flag for the second interrupt and, if set, clear that flag and handle its condition. Repeat for all of the interrupts you're using. If an unexpected interrupt gets enabled, the main-line code will never execute but interrupts will behave as normal (whenever no expected interrupt condition exists, the interrupt routine will continuously check all interrupt conditions, return, and restart, until an expected interrupting condition arises).
  2. If there's only one interrupt that will ever be enabled, one may skip the condition check and simply unconditionally clear the flag when the condition occurs. Note that if some other interrupt somehow becomes enabled, the code may erroneously run the interrupt routine 'as fast as possible', without regard for how often the code should actually run. If such erroneous execution would pose a safety hazard, one must guard against it. In many cases, however, the fact that the main-line code can't execute will be enough of a problem (hopefully yielding an eventual watchdog reset) that it won't really matter what the interrupt routine does.

Try changing the first line of your interrupt code to "if (T0IF && T0IE)", and add to the conditional clause "T0IE=0". That should cause your interrupt frequency to become closer to being correct. It won't be quite right, though, unless you use something like the code in my other answer.

\$\endgroup\$
0
\$\begingroup\$

To get precise timing with timer 0, the divide ratio must either be less than about 259, or else must be a power of two. Because of Microchip's unfortunate decision not to allow TMR0 to be written without clearing the prescalar, it is impossible to get precise timings when adjusting TMR0 with the prescalar enabled.

To achieve precise timings with 'short' timer intervals, one need only add

  TMR0 += (259-interval);

Provided the code generates an "ADDWF _TMR0,f" instruction (as it should with any typical compiler) it should yield a precise timer rate of 'interval' clock cycles.

If one needs an interrupt action to be performed at some rate slower than once every 259 cycles that isn't a power of two, and if the interrupt action might take more than 256 cycles, adding a little assembly-language before the standard interrupt handler may be 'just the ticket'. For best efficiency, this approach requires a non-banked variable to hold the execution counter. Before the interrupt handler, insert:

; Example assumes code should run once every thousand cycles
  bcf    _INTCON,2  ; TMR0IF
  decfsz _intCounter,f  ; Must be in unbanked RAM!
   retfie
  ... Proveed to normal interrupt handler.  Then in C code:
  TMR0 += (3+24); /* Three cycles for RTCC 'loss'; advance 24 cycles;
                     so we execute main interrupt once every 1000 cycles
                     rather than once every 1024 */
  GIE = 1;  /* Interrupts are okay now */
  ... Do main interrupt code; at end of interrupt:
  intCounter += 4;
  /* Note that if the timer tick interrupt triggers more than three times while
     running the main interrupt routine, intCounter will be zero or will be greater
     than four.  This code ignores that possibility. */

Note that this code allows nested interrupts, but the "quick" interrupts don't disturb the flags, nor do they disturb any registers other than _intCounter. Consequently, there's no need to save/restore registers, and no re-entrancy problems if the "quick" timer-tick interrupts happen while the main timer-tick routine is running. Problems only occur if four or more timer-tick interrupts occur while the main timer-tick code is running.

\$\endgroup\$

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.