0
\$\begingroup\$

I tried to setup Timer0 on a Microchip PIC18F46K22 so that it will trigger an interrupt and toggle an LED. However, it takes at least 42 µs for the LED to toggle.

At an increment rate of Fosc/4 (= 8 MHz/4 = 2 MHz), a TMR0 preload of 0xFF and a two cycle loss when writing to TMR0 I expect to get an interrupt every 1.5 µs, at least on paper. Even if I factor into some instruction cycles for my LED operations, I can't figure out why it takes 42 µs. What's wrong here?

Here's my code:

volatile unsigned char ledStatus;


void greenLEDon(void)
{
    LATCbits.LATC5 = 0;
}

void greenLEDoff(void)
{
    LATCbits.LATC5 = 1;
}


void interrupt isr(void)
{
    if(TMR0IE && TMR0IF)
    {
        if(ledStatus == 0)
        {
            ledStatus = 1;
            greenLEDon();
        }
        else
        {
            ledStatus = 0;
            greenLEDoff();
        }

        TMR0IF = 0;            // Clear interrupt flag
        TMR0 = 0xFF;           // preset for timer register
    }
}


void setup(void)
{
    OSCCON = 0b01100010;    // 8 MHz, internal

    // General outputs
    TRISCbits.TRISC5 = 0;   // RC5 (green LED)

    INTCON = 0b01000000;    // PEIE on (Peripheral Interrupt Enable)
    INTCONbits.GIE = 1;     // General Interrupt Enable


    // Timer0 Registers
    T0CONbits.T0CS = 0;   // TMR0 Clock Source Select bit: 0 = Internal Clock (CLKO)
    T0CONbits.T0SE = 0;   // TMR0 Source Edge Select bit: 0 = low/high
    T0CONbits.PSA  = 1;   // Prescaler Assignment bit: 0 = No Prescaler is assigned to the Timer0
    TMR0 = 0xFF;           // preset for timer register
    T0CONbits.TMR0ON = 1;
    T0CONbits.T08BIT = 1;
    INTCONbits.TMR0IE = 1;
}


void main(void)
{
    setup();

    while (1)
    {
    }
}

enter image description here

\$\endgroup\$
4
\$\begingroup\$

Stop and think about what it means to set timer 0 to FFh. That means it will overflow and set the interrupt condition on the very next clock. The clock is stopped for a couple of cycles when TMR0 is written, so this will occur 3 cycles (if I remember right) after the write. How can you possibly expect the interrupt mechanism to repond that fast. The return from interrupt (RETFIE) takes 2 cycles alone, and the entry into the interrupt routine at least another 2 cycles (it's essentially a call), then there is the code to save and restore context. This is all overhead even if your application code does nothing.

Expecting a PIC 18 to interrupt every 3 cycles is rediculous, as even a cursory look at the datasheet should have made obvious. Using a compiler on top of that only makes the minimum interrupt frequency that can actually be handled worse.

If you want to set up a periodic interrupt, start with timer 2, not timer 0. That timer has a period register for exactly this purpose.

Even if you are stuck with timer 0 to create a periodic interrupt, you should be adding a value into TMR0, not setting it to a fixed value. That way there is no accumulating error and the number of cycles from the timer 0 overflow to the instruction that writes it to a new value doesn't matter.

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

You should try to take a look at the equivalent assembly language code produced by your C code upon compilation. The actual code produced will give you a better picture of why certain things take a given number of cycles. With the fast interrupt rate you are attempting the actual processing rate that you achieve is the sum of:

  1. Duration to timer overflow if time of exit of ISR back to main() is shorter than timer programmed duration. This is probably close to zero cycles in your example.
  2. The time of interrupting at least one instruction fetch in main().
  3. The time of the MCU responding to the interrupt and fetching the interrupt vector.
  4. The total execution time of the ISR itself as next interrupt cannot happen till current one is finished.

You could get it faster by simplifying the code in the interrupt routine. Look into trying to just XOR the port bit itself in the ISR instead of using the copy variable and the extra subroutines.

Code such as:

LATCbits.LATC5 ^= 1;

Or possibly even faster:

LATC ^= 0x20;

Welcome to the world of programming in the embedded realm on low performance hardware where your CPU does not run at 3.27GHz.

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

I see one thing that might cause unexpected results here: you set TMR0 to 0xff in the ISR. I don't understand why you do that. You want the timer to increment on itself, you don't need to write it yourself. In fact, it isn't even needed to set the TMR0 to 0xff in the setup() - it doesn't really matter what the value on startup is.

Now, let's calculate this again. You're using an 8MHz clock, that's 2MIPS. The prescaler is set to 1:2, so that means your TMR0 increments 1M times per second. This means your ISR will be called 1M/256 times per second, if you remove the TMR0 = 0xff from the ISR. This is 256us or 3906 times per second.

You should not take in calculations for calling the ISR or the LED. This is because while you do this, the timer is still running. When you exit the ISR, the timer isn't 0 anymore. Therefore, the ISR is called exactly at the rate we just calculated.

\$\endgroup\$
  • \$\begingroup\$ Sorry for the misleading oddment. PSA bit is already cleared and the prescaler is not assigned to Timer0. The 0xFF in TMR0 should give me the shortest interrupt time possible. \$\endgroup\$ – Norbert Jun 25 '13 at 8:57
  • \$\begingroup\$ @Norbert you're right, thanks. I updated my answer. \$\endgroup\$ – user17592 Jun 25 '13 at 9:00
  • \$\begingroup\$ Theres overhead to interrupt handling. Registers need to be copied and saved. To get your desired functionality, you might need a faster clock (doesn't this chip have a Pll?), use FAST interrupts, if available, or write your own ISR \$\endgroup\$ – Scott Seidman Jun 25 '13 at 11:00
0
\$\begingroup\$

In addition to the above, copies are made of a variety of registers by the interrupt handler so that they can be restored on return. Minimally, the return address needs to be saved to the stack, even if you're willing to throw away the value of every register. See section 9.12 in the datasheet for better description.

To cut down on errors of this sort, clock faster. With your 8Mhz crystal, you can use the 4x PLL to clock at 32MHz. With a faster crystal, the PLL lets the uC go at 64MHz. Your error wont go away but it will go down by a factor of 8.

Another approach would be to move to the dsPIC33 line, which seems really optimized for low latency interrupts, and can clock at 80MHz for a 40MIPS rate (the instruction cycle on those uC's is 2 clock ticks!). Of course, this might have implications for your tool chain.

Lastly, if its really critical, you might consider actually using logic chips to accomplish your task, which doesn't seem all that complex. You can maintain the uC, but just use hardware logic external to the uC to do the real speedy stuff. FPGA would be yet another really fast possibility, but with definite tool chain issues.

\$\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.