Interrupt infinite loop

Thread Starter

zbblanton

Joined Jan 14, 2014
5
So I'm working on controller servos with TLC5940 LED Driver and this is my first time using interrupts. The interrupt is on Timer 2 match with PR2. I know the interrupt is working because of some testing blinking an LED but it appears that the interrupt is called over and over again.

All my while loop does at the end of main is blink an LED. The LED turns on but never off, I assume this is when the first interrupt happens but it seems like every time the interrupt returns to the main it seems to be called again.

Rich (BB code):
//Microcontroller: PIC18F4520
//8MHz internal clock
//Complier: xc8 v1.20
#include <xc.h>
#include "tlc5940.h"

#define _XTAL_FREQ 8000000
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))

#define OSCCON = 0b01110000
#define ANSEL = 0b00000000
#pragma config LVP = OFF

#pragma config OSC = HS, WDT=OFF, PWRT=OFF, BOREN=OFF, CP0=OFF,CP1=OFF,CP2=OFF,CP3=OFF, MCLRE=ON

volatile int count;

void delay_sec() //actually, half a second
{
    for(int i = 0; i < 50; i++)
    {
        __delay_ms(10);
    }
}

void interrupt blank_pulse(void)
{
    if(PIE1bits.TMR2IE && PIR1bits.TMR2IF)
    {
        if(count >= 520) //572
        {
            tlc_blank = 1;
            __delay_us(1);
            tlc_blank = 0;
            count = 0; //Reset count
        }
        count++;
        PIR1bits.TMR2IF = 0;
//        if(PIR1bits.TMR2IF == 0)//Testing if there is a loop happening
//        {
//            LATDbits.LD3 = 1;
//            delay_sec();
//            LATDbits.LD3 = 0;
//            delay_sec();
//        }
    }
}

void spi_init()
{
    SSPSTATbits.SMP = 0; //Input data sampled at middle of data output time
    SSPSTATbits.CKE = 1; //Transmit on transition from active to Idle clock state
    SSPCON1bits.CKP = 0; //Idle state for clock is a low level
    SSPCON1bits.SSPM0 = 0;//Sets SPI in master mode and sets clock to FOSC/4
    SSPCON1bits.SSPM1 = 0;
    SSPCON1bits.SSPM2 = 0;
    SSPCON1bits.SSPM3 = 0;
    SSPCON1bits.SSPEN = 1; //Enables the serial port

    TRISCbits.RC5 = 0; //Set SDO to ouput
    TRISCbits.RC3 = 0; //Set SCK to output
}

int main()
{
    init_TMR2();
    count = 0;

    PIE1bits.TMR2IE = 1;
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
    T2CONbits.TMR2ON = 0; //Turn Timer on

    TRISD = 0b00000000;
    PORTD = 0b00000000;
    TRISB = 0b00100000;
    PORTB = 0b00000000;

    TRISA = 0b00001111;
    PORTA = 0b00001111;
    TRISE = 0b00001000;
    PORTE = 0b00000000;

    SSPSTATbits.SMP = 0; //Input data sampled at middle of data output time
    SSPSTATbits.CKE = 1; //Transmit on transition from active to Idle clock state
    SSPCON1bits.CKP = 0; //Idle state for clock is a low level
    SSPCON1bits.SSPM0 = 0;//Sets SPI in master mode and sets clock to FOSC/4
    SSPCON1bits.SSPM1 = 0;
    SSPCON1bits.SSPM2 = 0;
    SSPCON1bits.SSPM3 = 0;
    SSPCON1bits.SSPEN = 1; //Enables the serial port


    T2CONbits.TMR2ON = 0; //Turn Timer on

    //dot correction
    //delay_sec();
    init_dot_correction();
    //delay_sec();
    
    //delay_sec();
    init_grayscale();
    //delay_sec();
    update_grayscale();

    T2CONbits.TMR2ON = 1; //Turn Timer on
//    while(1) //Old function before new interrupt
//    {
//        while(counter < 572) //Wait until ~40 ticks have passed //was 5 888
//        {
//            if(TMR2 == PR2)
//            {
//                counter = counter + 1;
//            }
//        }
//        tlc_blank = 1;
//        __delay_us(1);
//        tlc_blank = 0;
//        counter = 0; //Reset counter
//    }
    while(1)
    {
        LATDbits.LD3 = 1;
        delay_sec();
        LATDbits.LD3 = 0;
        delay_sec();

    }
}
 

atferrari

Joined Jan 6, 2004
4,764
Have you tried to simulate it?

I asume you have available MPSIM. If you proceed step by step, every register that has changed will have the value in red. If you expect a change in a certain register, and it did not happen, the value in that one will remain in black. Basic but useful.
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
IF you do not "PIR1bits.TMR2IF = 0;" whenever "PIE1bits.TMR2IE == 1" then you cannot leave the ISR, it will retrigger if you try to leave. It seems you may have that part correct but it is hard to tell with so much of the code commented out, not matching comments, or irrelevant to the problem.

Try cleaning up your code ("T2CONbits.TMR2ON = 0;" and "T2CONbits.TMR2ON = 1;" cannot both "//Turn Timer on") ("void delay_sec() //actually, half a second" ORLY?), and slim it down to the smallest piece that can reproduce the "problem."

Post that, along with a very clear description of the problem and someone else may be able to help.

To help yourself learn how to use a debugger. PICkits can do in-circuit debugging of live code as it runs in your PIC. If not, there is always the simulator in MPLAB(X) which will do a fine job of simulating timer interrupts.
 

t06afre

Joined May 11, 2009
5,934
Could it be that simple. I can not see any return in your ISR.
Example
Rich (BB code):
void interrupt tc_int(void) { if (TMR0IE && TMR0IF) { TMR0IF=0; ++tick_count; return; }​
}
I also HIGHLY agree with others. Recommending learning to use the debugging options you have at hand. The software simulator is a very good friend then it comes to learning PIC programming
 

JohnInTX

Joined Jun 26, 2012
4,787
Could it be that simple. I can not see any return in your ISR.
Example
Rich (BB code):
void interrupt tc_int(void) { if (TMR0IE && TMR0IF) { TMR0IF=0; ++tick_count; return; }​
}
You would not use a 'return' at the end of an interrupt routine. The compiler will generate 'retfie' after restoring context.

Delays should not be used in an interrupt routine. It slows the system down and the delay functions are likely not re-entrant (although XC8 may generate copies for each context - don't know).

What does your init_TMR2() code look like?
Be sure that the interrupt priority setup agrees with your interrupt vector.
 
Last edited:

tshuck

Joined Oct 18, 2012
3,534
The delay in your interrupt (after clearing the interrupt flag) is very likely causing the timer to overflow again, before returning from interrupt context.

This is why special attention must be given to interrupt sequencing. Clearing the flag should be one of the last things you do.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
The delay in your interrupt (after clearing the interrupt flag) is very likely causing the timer to overflow again, before returning from interrupt context.

This is why special attention must be given to interrupt sequencing. Clearing the flash should be one if the last things you do.
That delay is commented out but that would be a concern.

I ran it up in MPSIM after adding a TMR2 init and commenting out the tlc_stuff. It toggles the LED in the main-while loop so the basic interrupt is there.

FWIW: looking at the disassembly listing, having delay_sec() in both contexts (main and interrupt) gets two copies of delay_sec() to avoid re-entrancy problems.
 
Last edited:

tshuck

Joined Oct 18, 2012
3,534
That delay is commented out but that would be a concern.

I ran it up in MPSIM after adding a TMR2 init and commenting out the tlc_stuff. It toggles the LED in the main-while loop so the basic interrupt is there.

FWIW: looking at the disassembly listing, having delay_sec() in both contexts (main and interrupt) gets two copies of delay_sec() to avoid re-entrancy problems.
Ah, I didn't scroll all the way to the left...:(

Double check that you are generating a hex file and that the generated hex file is what is making it to the PIC, considering John's working simulation...
 
Last edited:

Thread Starter

zbblanton

Joined Jan 14, 2014
5
Wow was not expecting such feedback :)

IF you do not "PIR1bits.TMR2IF = 0;" whenever "PIE1bits.TMR2IE == 1" then you cannot leave the ISR, it will retrigger if you try to leave. It seems you may have that part correct but it is hard to tell with so much of the code commented out, not matching comments, or irrelevant to the problem.

Try cleaning up your code ("T2CONbits.TMR2ON = 0;" and "T2CONbits.TMR2ON = 1;" cannot both "//Turn Timer on") ("void delay_sec() //actually, half a second" ORLY?), and slim it down to the smallest piece that can reproduce the "problem."

Post that, along with a very clear description of the problem and someone else may be able to help.

To help yourself learn how to use a debugger. PICkits can do in-circuit debugging of live code as it runs in your PIC. If not, there is always the simulator in MPLAB(X) which will do a fine job of simulating timer interrupts.
Yes I'm aware its only half second ;). It was a placeholder that just hasn't been cleaned up yet.

I can clean the code up once I get off work and repost it, not a problem. Sorry for that messy code.

Yes I really need to take full advantage of my PICKIT 3.

That delay is commented out but that would be a concern.

I ran it up in MPSIM after adding a TMR2 init and commenting out the tlc_stuff. It toggles the LED in the main-while loop so the basic interrupt is there.

FWIW: looking at the disassembly listing, having delay_sec() in both contexts (main and interrupt) gets two copies of delay_sec() to avoid re-entrancy problems.
Thanks for the advice from both of you on the delay in interrupt that makes sense.

Sorry I should have posted my timer function, ill post that once I get off work as well.

So it ran ok for you in the sim? I'll have to see if I can do that and then track through my code.

To me it just seems like it exits the interrupt correctly but is just immediately called back.

Would the two dalays cause a problem?

Ah, I didn't scroll all the way to the left...:(

Double check that you are generating a hex file and that the generated hex file is what is making it to the PIC, considering John's working simulation...
Yes it is for sure the generated hex file. Tested it by applying some changes to the main and saw change in the LED.
 

JohnInTX

Joined Jun 26, 2012
4,787
So it ran ok for you in the sim? I'll have to see if I can do that and then track through my code.

To me it just seems like it exits the interrupt correctly but is just immediately called back.
It did the same for me with the first quickie TMR2 setup. By the time it was done with the IRQ service, TMR2 had counted up to PR2. I added (lots of) time using the pre/postscalers and it worked. Perhaps your TMR2 is running too fast?

Before you put it on the chip, be sure to do the rest of the CONFIG regs (XC8 will complain until you do). The easy way to do this is to select Configuration Bits on the lower view window. That will bring up a nice list of configs with drop-down options. Set up ALL of the options then click Generate Source Code to Output. You will get a window full of wonderful #pragmas. Copy/paste to your source and you are ready to go.

FWIW here's the code I stepped in MPSIM - attached zip is the same. If you set breakpoints on each LED transition in main and monitor LATD in the SFR window, you'll see the port bit change on every breakpoint.
Have fun!

Rich (BB code):
/* 
 * File:   zzblanton.c
 * Author: John
 *
 * Created on January 14, 2014, 9:54 AM
 */

//Microcontroller: PIC18F4520
//8MHz internal clock
//Complier: xc8 v1.20
#include <xc.h>
//#include "tlc5940.h"

#define _XTAL_FREQ 8000000
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))

#define OSCCON = 0b01110000
#define ANSEL = 0b00000000
#pragma config LVP = OFF

#pragma config OSC = HS, WDT=OFF, PWRT=OFF, BOREN=OFF, CP0=OFF,CP1=OFF,CP2=OFF,CP3=OFF, MCLRE=ON

volatile int count;

void init_TMR2(void)
{
    T2CON = 0b01111010; // quick and dirty init
    PR2 = 160;   // 
    TMR2 = 0;

}
void delay_sec() //actually, half a second
{
    for(int i = 0; i < 50; i++)
    {
        __delay_ms(10);
    }
}

void interrupt blank_pulse(void)
{
    if(PIE1bits.TMR2IE && PIR1bits.TMR2IF)
    {
        if(count >= 520) //572
        {
    //        tlc_blank = 1;
            __delay_us(1);
            delay_sec();
     //       tlc_blank = 0;
            count = 0; //Reset count
        }
        count++;
        PIR1bits.TMR2IF = 0;
//        if(PIR1bits.TMR2IF == 0)//Testing if there is a loop happening
//        {
//            LATDbits.LD3 = 1;
//            delay_sec();
//            LATDbits.LD3 = 0;
//            delay_sec();
//        }
    }
}

void spi_init()
{
    SSPSTATbits.SMP = 0; //Input data sampled at middle of data output time
    SSPSTATbits.CKE = 1; //Transmit on transition from active to Idle clock state
    SSPCON1bits.CKP = 0; //Idle state for clock is a low level
    SSPCON1bits.SSPM0 = 0;//Sets SPI in master mode and sets clock to FOSC/4
    SSPCON1bits.SSPM1 = 0;
    SSPCON1bits.SSPM2 = 0;
    SSPCON1bits.SSPM3 = 0;
    SSPCON1bits.SSPEN = 1; //Enables the serial port

    TRISCbits.RC5 = 0; //Set SDO to ouput
    TRISCbits.RC3 = 0; //Set SCK to output
}

int main()
{
    init_TMR2();
    count = 0;

    PIE1bits.TMR2IE = 1;
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
    T2CONbits.TMR2ON = 0; //Turn Timer on

    TRISD = 0b00000000;
    PORTD = 0b00000000;
    TRISB = 0b00100000;
    PORTB = 0b00000000;

    TRISA = 0b00001111;
    PORTA = 0b00001111;
    TRISE = 0b00001000;
    PORTE = 0b00000000;

    SSPSTATbits.SMP = 0; //Input data sampled at middle of data output time
    SSPSTATbits.CKE = 1; //Transmit on transition from active to Idle clock state
    SSPCON1bits.CKP = 0; //Idle state for clock is a low level
    SSPCON1bits.SSPM0 = 0;//Sets SPI in master mode and sets clock to FOSC/4
    SSPCON1bits.SSPM1 = 0;
    SSPCON1bits.SSPM2 = 0;
    SSPCON1bits.SSPM3 = 0;
    SSPCON1bits.SSPEN = 1; //Enables the serial port


    T2CONbits.TMR2ON = 0; //Turn Timer on

    //dot correction
    //delay_sec();
//    init_dot_correction();
    //delay_sec();

    //delay_sec();
  //  init_grayscale();
    //delay_sec();
  //  update_grayscale();

    T2CONbits.TMR2ON = 1; //Turn Timer on
//    while(1) //Old function before new interrupt
//    {
//        while(counter < 572) //Wait until ~40 ticks have passed //was 5 888
//        {
//            if(TMR2 == PR2)
//            {
//                counter = counter + 1;
//            }
//        }
//        tlc_blank = 1;
//        __delay_us(1);
//        tlc_blank = 0;
//        counter = 0; //Reset counter
//    }
    while(1)
    {
        LATDbits.LD3 = 1;
        delay_sec();
        LATDbits.LD3 = 0;
        delay_sec();

    }
}
 

Attachments

Thread Starter

zbblanton

Joined Jan 14, 2014
5
Ok so I ran through it through the simulator and it loops in the interrupt.

I tried to remove everything except the PIR1bits.TMR2IF = 0 and it still would loop.

All of you guys said it work for you, but... you all set up the timer. So maybe there is something I'm doing wrong with it.

It did the same for me with the first quickie TMR2 setup. By the time it was done with the IRQ service, TMR2 had counted up to PR2. I added (lots of) time using the pre/postscalers and it worked. Perhaps your TMR2 is running too fast?
I think your right, I think the timer is running to fast.

Here is a clean up code with the timer function

Rich (BB code):
//Microcontroller: PIC18F4520
//8MHz internal clock
//Complier: xc8 v1.20
#include <xc.h>
#include "tlc5940.h"

#define _XTAL_FREQ 8000000
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))

#define OSCCON = 0b01110000
#define ANSEL = 0b00000000
#pragma config LVP = OFF

#pragma config OSC = HS, WDT=OFF, PWRT=OFF, BOREN=OFF, CP0=OFF,CP1=OFF,CP2=OFF,CP3=OFF, MCLRE=ON

volatile int count;

void delay_sec() //actually, half a second
{
    for(int i = 0; i < 50; i++)
    {
        __delay_ms(10);
    }
}

void interrupt blank_pulse(void)
{
    if(PIE1bits.TMR2IE && PIR1bits.TMR2IF)
    {
        if(count >= 520) //572
        {
            tlc_blank = 1;
            __delay_us(1);
            tlc_blank = 0;
            count = 0; //Reset count
        }
        count++;
        PIR1bits.TMR2IF = 0;
    }
}

void spi_init()
{
    SSPSTATbits.SMP = 0; //Input data sampled at middle of data output time
    SSPSTATbits.CKE = 1; //Transmit on transition from active to Idle clock state
    SSPCON1bits.CKP = 0; //Idle state for clock is a low level
    SSPCON1bits.SSPM0 = 0;//Sets SPI in master mode and sets clock to FOSC/4
    SSPCON1bits.SSPM1 = 0;
    SSPCON1bits.SSPM2 = 0;
    SSPCON1bits.SSPM3 = 0;
    SSPCON1bits.SSPEN = 1; //Enables the serial port

    TRISCbits.RC5 = 0; //Set SDO to ouput
    TRISCbits.RC3 = 0; //Set SCK to output
}

void TMR2_init()
{   
    //Set to 100,000hz with 50% duty cycle. Prescaler is 4.
    PR2 = 0b00000100 ;
    T2CON = 0b00000101 ;
    CCPR1L = 0b00000010 ;
    CCP1CON = 0b00101100 ;
    TRISC = 0b00000000;
    PORTC = 0b00000000;
    T2CONbits.TMR2ON = 0; //Turn Timer off
}

int main()
{
    TRISD = 0b00000000;
    PORTD = 0b00000000;
    TRISB = 0b00100000;
    PORTB = 0b00000000;
    TRISA = 0b00001111;
    PORTA = 0b00001111;
    TRISE = 0b00001000;
    PORTE = 0b00000000;

    TMR2_init();
    count = 0;

    PIE1bits.TMR2IE = 1;
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
    T2CONbits.TMR2ON = 0; //Turn Timer on

    spi_init();
    init_dot_correction();
    init_grayscale();
    update_grayscale();

    T2CONbits.TMR2ON = 1; //Turn Timer on
    while(1)
    {
        LATDbits.LD3 = 1;
        delay_sec();
        LATDbits.LD3 = 0;
        delay_sec();

    }
}
 

JohnInTX

Joined Jun 26, 2012
4,787
I tried to remove everything except the PIR1bits.TMR2IF = 0 and it still would loop.
Don't forget that it takes instructions to get into/out of an interrupt with context saving etc.

Timer 2 is interrupting every 32 Tcyc (prescaler=4 * PR2=8), that's not a lot of instructions and probably is why you are stuck in the interrupt routine. When I sim'd your new code, it eventually broke at the port output but it took a long time to do it.

So.. you want a 50% duty cycle PWM with a 100KHz (10us) period. Doable but why the interrupt so often? Could you factor the 520 into the postscaler? See FIG 13-1 in the datasheet.

When figuring out the duty cycle don't forget to include CCPxCON <5:4>. They are the LSBits in the calculations.

BTW when you init T2CON, you are turning TMR2 on. Leave it off until its ready to run.

EDIT: and actually, with Prescaler=4, PR2=8, 8MHz, the Lil' Professor says the period is 62.5KHz so I guess we got some 'splainin' to do.
 
Last edited:

Thread Starter

zbblanton

Joined Jan 14, 2014
5
So.. you want a 50% duty cycle PWM with a 100KHz (10us) period. Doable but why the interrupt so often? Could you factor the 520 into the postscaler? See FIG 13-1 in the datasheet.
Not a 100% sure what you mean on the period. I ended up using an online calculator for it.

It has to be that fast, but yes I can try the postscaler out and ill let you guys know how it works.
 

JohnInTX

Joined Jun 26, 2012
4,787
//Set to 100,000hz with 50% duty cycle. Prescaler is 4.
That's what I understood your comment to be..

What are you actually trying to do? Maybe I'm misunderstanding some things here. The PWM period is how many Tcyc (Tosc/4 = 500ns) TMR2/PR2 counts. The duty cycle set in CCP1 is how many of those counts makes 50%.

For yours: 100KHz is a period of 10us. With an input clock of 500ns period, you count to 20 for 10us. 4Prescaler * (4+1)* PR2 = 20 total count. For a 50% duty cycle, CCP1 is set to .5*20 = 10 counts. The CCP1 hardware output will run at 100KHz 50% duty cycle.

The interrupt rate is 10us * the POSTscaler (which doesn't affect the PWM). Set the postscaler to some multiple of 10us i.e. set to 10 for 100us etc. Rather than count to 520*10us in the interrupt routine can you count to 52*100us?

The upshot is that 10us only gives you 20 instruction cycles to work with. If you took a look at the disassembled output (the generated code from the C) you would find that the interrupt routine takes longer than that. For a 4520 with the errata handling that XC8 adds it takes 7 Tcycs just to vector to the interrupt, fix the errata and retfie. Add context save and you are way over 10us.

That's why it seems to spend all of its time in the interrupt.
 

Thread Starter

zbblanton

Joined Jan 14, 2014
5
What are you actually trying to do? Maybe I'm misunderstanding some things here. The PWM period is how many Tcyc (Tosc/4 = 500ns) TMR2/PR2 counts. The duty cycle set in CCP1 is how many of those counts makes 50%.
The TLC5940 LED Driver has a blank pin that you must toggle high to reset its grayscale counter. So that is what the interrupt is performing. Its really the only downside of this controller.

The interrupt rate is 10us * the POSTscaler (which doesn't affect the PWM). Set the postscaler to some multiple of 10us i.e. set to 10 for 100us etc. Rather than count to 520*10us in the interrupt routine can you count to 52*100us?
That did it! Set the post scale to 4 and now is running perfectly.


Thank you all for your help. I learned more then I even came here for :)
 
Top