Interrupt Service Routine Executes Twice ATmega88 - c

I have been looking all of for an answer to this problem and have not been able to find anything. When my ISR is triggered, it goes through and does everything that it is supposed to perfectly fine, then before exiting and returning back to the main loop the ISR executes again. Once it has gone though the second time it then returns back to the main loop. This only happens when I use a 115V relay to operate the interrupt.
I am trying to detect when there is a power outage or when the power comes back on. I am using a Pin change interrupt to sense if the relay is closed or open. When the power goes out the relay will open and will trigger the ISR. If I connect this setup to a normal push button or switch everything works as needed and there is no problem, it is only when it is connected to the relay that there is a problem.
Here is the code that I have:(I know I don't need cli I have just been trying everything)
ISR(PCINT2_vect){
cli();
sbi(PORTC,5);
_delay_ms(6000);
cbi(PORTC,5);
for(delay_counter=0;delay_counter<2;delay_counter++)
{
_delay_ms(6000);
}
sbi(PORTC,5);
_delay_ms(6000);
if(bit_is_set(PIND,2))
{
lcd_clrscr();
lcd_puts("Sending SMS");
usart_print("at");
USART_Transmit('\r');
_delay_ms(6000);
for(i=0;i<=1;i++)
{
usart_print("at*smsm2m=");
USART_Transmit('"');
for(j=0;j<11;j++)
{
USART_Transmit(Alert_Numbers[i][j]);
}
usart_print(" Power has been lost");
USART_Transmit('"');
USART_Transmit('\r');
_delay_ms(6000);
}
lcd_clrscr();
lcd_puts("SMS Sent");
_delay_ms(6000);
lcd_clrscr();
lcd_puts("Status:NO POWER");
cbi(PORTC,5);
}
else if(bit_is_clear(PIND,2))
{
lcd_clrscr();
lcd_puts("System Reset");
_delay_ms(6000);
_delay_ms(6000);
usart_print("at");
USART_Transmit('\r');
_delay_ms(6000);
for(i=0;i<=1;i++)
{
usart_print("at*smsm2m=");
USART_Transmit('"');
for(j=0;j<11;j++)
{
USART_Transmit(Alert_Numbers[i][j]);
}
usart_print(" Pump regained power");
USART_Transmit('"');
USART_Transmit('\r');
_delay_ms(6000);
}
lcd_clrscr();
lcd_puts("POWER ON");
_delay_ms(6000);
lcd_clrscr();
lcd_puts("Status: Good");
}
else
{
}
}
int main(void)
{ /*Initializations*/
DDRC = 0x20; // PORTC,5 is now output
sbi(PORTC,5);
USART_Init(51);
lcd_init(LCD_DISP_ON);
lcd_clrscr();
/*Set interrupts*/
DDRD = 0b11111011; // set PD2 to input
PORTD = 0b00000100; // set PD2 to high
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << PCINT0);
PCICR |= (1<<PCIE2);
PCMSK2 |= (1<<PCINT18);
sei();
lcd_clrscr();
lcd_puts("Status: Good");
/*Main Program Loop: NOP*/
while(1)
{
lcd_clrscr();
lcd_puts("MAIN LOOP");
for(delay_counter=0;delay_counter<3;delay_counter++)
{
_delay_ms(6000);
}
}
}

I am using a Pin change interrupt to sense if the relay is closed or
open.
Don't do that. Seriously. Do not try to hook mechanical switches to an interrupt pin to trigger ISRs.
If you insist on doing so, at least make sure the switch's signal is decently debounced in hardware before it hits the µC's input pin.
Besides, waiting of any kind (_delay_ms(6000);) is not something one wants to have in an ISR.

The reason your code fails: When the signal is bouncing, even if you wait 6 seconds in your interrupt, the flag is set again (while you are still in your ISR). The quick and dirty solution would be to reset the flag just before you exit the ISR.
Two (in my opinion) better solutions:
If you want to do this in software, just set a variable in the ISR routine which indicates a event. Then, in your main loop (or in a function), do the debouncing of the relais (10 to 100ms should be enough).
if you want to do this in hardware, just add a R C combination as a filter to the pin (the c to ground and the r to the relais. Just try some combinations like 100k and 10µF.
My personal recommendation:
do both ;)
Annotations:
- using a 6 second delay in an interrupt routine is really a bad practice. Better: Use a Timer which is called every say 10 ms and increase a counter if the flag is set and the relais indicate power loss. If the relais has power, reset the flag and counter to 0. If the counter reaches 600, this means the power outage is longer than 6 seconds.
Have fun!

I had the problem myself, so I decided to answer it even though it is an old entry:
The reason why the interrupt fires twice is that the interrupt flag is not reset. This is a problem of some atmega types. You can solve the problem by one simple line:
Post this at the very end of you ISR interrupt function:
PCIFR |= (1<<PCIF2);
This will write a "1" into the interrupt-flag bit for the PCINT2 interrupt. If you are using other Interrupts, you have to set other flags to 1. Notice that the interrupt flag is inverted. So a 1 disables the interrupt, whereas a 0 triggers the interrupt.
Have a look into the datasheet:
http://www.atmel.com/images/doc2545.pdf
See Point 13.2.5:
Bit 2 - PCIF2: Pin change interrupt flag 2 -
When a logic change on any PCINT23..16 pin triggers an interrupt request, PCIF2 becomes set
(one). If the I-bit in
SREG and the PCIE2 bit in PCICR are
set (one), the MCU will jump to the
corresponding Interrupt Vector. The flag is cleared
when the interrupt routine is executed. Alternatively, the flag can be cleared by writing a logical one to it.
I hope this helped you and maybe some other people who have the same problem. Just search the datasheet for the name of the register and the bit corresponding to the interrupt flag and set it to 1.
Bye

Related

PIC18 Global Interrupt Enable bit toggling when keypad button pressed

Objective
I am trying to interface a 4x3 matrix keypad and 7 segment LED display to PIC18f4550 microcontroller. When I press buttons on keypad, I want the 7 segment display to show the number accordingly.
What I have done so far
Based on my research, I can either use scanning (continuously polling) or use interrupts to interface the keypad with the MCU. I decided to use interrupts as that way the microcontroller can be free up for other operations.
The following is the connection of keypad to MCU.
I am using RB0-2 as input to MCU and RB4-7 are set as output from MCU. RB4-7 are permanently set high so when the user press on the keypad button, it will trigger the RB0-2(INT0-INT2) interrupt to process.
For simplicity in this post, I will only discuss about the Column 1 of the keypad.
This is how I initialize and setup the registers.
void main(void)
{
OSCCON = 0x72;
TRISD = 0;
LATD = 0;
ADCON1 = 0x0F;
TRISB = 0x07;
LATB = 0xF0;// keep the RB4-7 high
INTCONbits.GIE = 1;
INTCONbits.INT0IF = 0;
INTCONbits.INT0IE = 1;
INTCON2bits.INTEDG0 = 1;
while (1);
}
My interrupt handling is as below:
if(INTCONbits.INT0IF == 1)
{
for(char scan=0x10; scan>=0x80; scan <<= 1) // send 1 to each row starting from RB4 till RB7
{
LATB = scan;
if (PORTBbits.RB0 = 1)
{
if(scan == 0x10)
{
display_number(1);
}
if(scan == 0x20)
{
display_number(4);
}
if(scan == 0x40)
{
display_number(7);
}
}
}
LATB = 0xF0;
INTCONbits.INT0IF == 0;
My problem
When I run the simulation, I noticed that the GIE bit keep toggling very fast between 0 and 1 as soon as I press on any button in Column 1 and no display of any number on 7segment as well. I added the watch window screenshot and highlighted the GIE bit that is toggling.
What have I done wrong? Is my logic of handling interrupt flawed?
UPDATE 1
As DavidHoadley suggested, I have changed to use use unsigned char instead of char.
I have also corrected the for loop condition.
What I observed was, If I keep the loop inside the interrupt routine, the loop will get stuck for some reason.
For now, I have given up trying to use loop inside the interrupt function and instead resort to have a function in main while loop to output high at each row sequentially forever and the interrupt function is only used to check the output using switch statement.
Your for loop end condition looks like the loop will immediately exit. The line:
for(char scan=0x10; scan>=0x80; scan <<= 1)
Should probably be:
for(char scan=0x10; scan<=0x80; scan <<= 1)
Can’t test this - hope it works
It is a normal behaviour for PIC micros. When PIC goes to the interrrupt vector the GIE bit reset by hardware and when it finishes servicing the interrupt, it returns with the RETFIE assembly instruction by setting the GIE bit which is not visible in C code. So there is no fault in your code for this matter, this is not even a matter.
I see in your code you are using only INT0 interrupt to detect presses and the rest of INTx pins are not activated. That's why the PIC micro will be able to detect the changes of the only first column. I suggest you to use interrupt on change (IOC) which features on RB<7:4> bits. This way you can free the INTx pins for other purposes. And you can move the 3 pins to a PORT other than PORTB. Here is the procedure or my suggestion if you interest:
Configure RB<7:4> pins as inputs and enable their IOC feature.
Configure any 3 pins as output of any port.
If you use the positive logic set the 3 pins high otherwise, low.
In your ISR poll the RBIF to know if there is a change on RB<7:4> pins.
If so make a button scan to detect the pressed key.
Unfortunately can't tell you anything for your display issue since you haven't shared the codes and the configuration of display.

AVR C Why wont this interrupt?

I was recently trying to make an interrupt on my atmega328p using atmelstudio to make a LED that is connected to digitalpin 13/PB5/PCINT5 blink four times as slow as normal when the button that is connected to a 5V output and digitalpin 2/PD0/PCINT18 is pressed down.
But whenever I run the code and press the button it will never(as far as i can tell) go true the interrupt code.
#include <avr/io.h>
#include <avr/interrupt.h>
volatile int t = 1;
int main(void)
{
init();
Serial.begin(9600);
DDRB = (1 << 5);
DDRD &= ~(1 << DDD2); // Clear the PD2 pin
// PD2 (PCINT0 pin) is now an input
PORTD |= (1 << PORTD2); // turn On the Pull-up
// PD2 is now an input with pull-up enabled
EICRA |= (1 << ISC00); // set INT0 to trigger on ANY logic change
EIMSK |= (1 << INT0); // Turns on INT0
sei(); // turn on interrupts
Serial.println("loop started");
while(1)
{
PORTB ^= (1 << 5);
delay(500*t);
}
}
ISR (INT0_vect)
{
Serial.println("interrupt");
if(t=1){
t=4;
}
else{
t=1;
}
}
I went through the datasheet multiple times and eventually stumbled onto this code online (yea yea i know i'm a real piece of work) and added my own pieces to it.
but it even this does not work, does anybody know why?
There are a few possible problems in your code:
The most important is the assignment in the if condition that was already mentioned in the comments.
clearly another one is the also mentioned serial.print stuff in the ISR.
ISRs should be as short and simple as possible.
Another is the hardware. If you press a button, they bounces and usually give multiple interrupts. so look for some de-bouncing code or have a look in the arduino library if there is something there. you may have to change the code, because usually the hardware logic itself is handled in interrupts, but the actual testing of the button states should belong to main code.
advanced stuff - if you currently reading tutorials and teach your self - ignore this, but may be keep in mind for actual projects
Another issue is the program design: your processor now cannot do anything else then toggling LEDs because his main program flow waits.
Normally you would want to use a hardware timer for this kind of tasks.
Either use it as a time base, that signals passed intervals to the main via a volatile flag variable. or directly use the PWM-Feature to directly interface the LED via one of the Output Compare Pins (OC[012][AB]).

PIC programing Interrupt malfunction.

I'm new to PIC programming and I'm using MPLAb. I have a question regarding interrupt..
so What I want to do, when I push a button then I want to turn on LED 0, and if I release the button then turn on LED 1. I thought the code I wrote making sense but it didn't work.
Here is what happens. Let say the initial state of interrupt pin is low (0), when a button is pushed. Then the LED 0 is on, and when I release the button then LED 1 is on. When I push the button again, I expect LED 0 is on, but LED 1 stays on, and never change the state.
I added last line to see the state of interrupt pin, and once the interrupt is high, it never change it to low.. Can you please advise me what is my misunderstanding?
Thanks in advance!
Here is my code:
void interrupt ISR(void)
{
if(INTCONbits.INTF)
{
nextLED = 1;
LATC = Output_Code_Buffer[nextLED];
__delay_ms(250);
}
else
{
nextLED = 0;
LATC = Output_Code_Buffer[nextLED];
__delay_ms(250);
}
nextLED = INTCONbits.INTF + 2;
LATC = Output_Code_Buffer[nextLED];
__delay_ms(250);
}
// Interrupt Enable settings
INTCONbits.INTE = 1;
INTCONbits.TMR0IE = 1; // Enable TMR0 interrupts
INTCONbits.TMR0IF = 0; // Clear TMR0 interrupt flag
INTCONbits.GIE = 1; // Enable global interrupts
You need to reset the interrupt flag in the ISR function or it will just keep triggering. Please read the datasheet, it should mention if this is necessary. So just add INTCONbits.INTF = 0; to the ISR and it should work as expected.
When setting up any peripheral or function of the mcu, you should go through the datasheet and use the description of the registers and what to set them. You'll also need to be careful with analogue ports, which often default to analogue instead of digital, causing interrupt not to fire as expected or causing unexpected interrupts. It's best to first setup the MCU config bits, set the TRIS and analogue selection registers (ANSELx or ANSELAx etc), then the registers for any peripheral you want to use. Then setup the interrupts, always reset all the interrupt flags you're going to use to start with a known state.
You also set TMR0IE = 1, which will do the same thing, trigger an interrupt. If you don't reset the TMR0 flag it will keep triggering, locking up your mcu or slow it down.

MSP430 UART TX Interrupt Enabling/Disabling

I have RX interrupts working just fine, but I wanted to add TX interrupts. I respond to long commands over the UART and don't want to waste cycles waiting for the TX to complete before sending along the next byte. I am trying to enable interrupts, transmit the data that needs to be transmitted, then disable the interrupts until the next TX packet comes along.
This works fine for the FIRST payload that I send out. I see it go out just fine. However as soon as I disable TX Interrupts once, I am never able to enter the ISR again. How on the MSP430 does one enable TX Interrupts on the UART and have it ever get into the ISR again?
Below where you see EUSCI_A_UART_enableInterrupt call, shouldn't this line trigger my ISR every time the interrupt gets enabled? If not, HOW DO I GET BACK INTO THE ISR AGAIN?
Here's the Transmit code:
void UartSendChar(uint8_t tx_char)
{
ring_buffer_put(_rbdTx, &tx_char);
EUSCI_A_UART_enableInterrupt(EUSCI_A1_BASE, EUSCI_A_UART_TRANSMIT_INTERRUPT); // Enable interrupt
}
Here's my ISR:
void EUSCI_A1_ISR(void)
{
int c=-1;
switch(__even_in_range(UCA1IV,USCI_UART_UCTXCPTIFG))
{
case USCI_NONE: break;
case USCI_UART_UCRXIFG:
...
case USCI_UART_UCTXIFG:
// If there's something in the Ring Buffer, transmit it
// If not, then disable TX interrupts until new data gets written.
if (ring_buffer_get(_rbdTx, &c) == 0)
{
EUSCI_A_UART_transmitData(EUSCI_A1_BASE, (uint8_t)c);
}
else
{
EUSCI_A_UART_disableInterrupt(EUSCI_A1_BASE, EUSCI_A_UART_TRANSMIT_INTERRUPT);
}
I figured out the problem. In this particular MSP430, when the interrupt vector is read and it shows a TX interrupt, the TXIFG bit automatically gets cleared by the micro. (How nice of it)
In order to get this ISR to fire again after re-enabling TX interrupts, the TXIFG bit must be manually set back to 1 again, which shows an interrupt pending. That way, when interrupts are enabled after data is shoved into the queue, the TXIFG bit is set, so the ISR executes and ships the data off.
Now my ISR looks like this:
void EUSCI_A1_ISR(void)
{
int c=-1;
switch(__even_in_range(UCA1IV,USCI_UART_UCTXCPTIFG))
{
case USCI_NONE: break;
case USCI_UART_UCRXIFG:
...
case USCI_UART_UCTXIFG:
// If there's something in the Ring Buffer, transmit it
// If not, then disable TX interrupts until new data gets written.
if (ring_buffer_get(_rbdTx, &c) == 0)
{
EUSCI_A_UART_transmitData(EUSCI_A1_BASE, (uint8_t)c);
}
else
{
EUSCI_A_UART_disableInterrupt(EUSCI_A1_BASE, EUSCI_A_UART_TRANSMIT_INTERRUPT);
// Set TXIFG manually back to 1 for next time.
HWREG16(EUSCI_A1_BASE + OFS_UCAxIFG) |= EUSCI_A_UART_TRANSMIT_INTERRUPT;
}

AVR C how to stop interrupt

I am programming a AVR MCU.
It has a POT that reads off an analogue pin. It seems that the interrupt is constantly called, and it must be called during a LCD_display method as it is messing with my LCD.
Is there a way to STOP the inturrupts until after the block is run?
int main(void)
{
/*Turn on ADC Interrupt */
ADCSRA |= (1 << ADIE);
/*Turn On GLobal Interrupts*/
sei();
/* Intalise LCD */
lcd_init(LCD_DISP_ON); /* initialize display, cursor off */
lcd_clrscr();
lcd_puts("READY");
DDRB &= ~(1 << PINB5); //set input direction
ADC_Init(128, 0); //initalize ADC
while (1)
{
if (!bit_is_clear(PINB, 5))
{
_delay_ms(500);
if (!pressed)
{
lcd_gotoxy(0,0);
lcd_clrscr();
lcd_puts("test"); //Doesnt work unless I dont comment out the last line of interrupt
pressed = 1;
}
}
/* INTERRUPTS */
//ADC INTERRUPT
ISR(ADC_vect)
{
char adcResult[4];
uint8_t theLowADC = ADCL;
uint16_t theTenBitResults = ADCH<<8 | theLowADC;
itoa(theTenBitResults, adcResult, 10);
ADCSRA |= (1 << ADSC); //next conversion *if i comment out this line it works*
}
If the interrupt handler behaves bad with your code, the reason could be you spend too much time in the interrupt handler. You should only do critical work in the interrupt handler and defer the less critical work in the application code; use a volatile flag shared between the handler and the application code to let the application code know if it has work to do. In your example, you should defer the itoa call in the application code.
Use cli(); to disable interrupts and sei(); to enable them again after you finished the display routine.
Which MCU are you using? You should propably use a timer instead of a delay of 500ms.
I believe, I am little late but still I had the same issue I solved it using the following method,
Interrupts are enabled using two flags
1.A global interrupt flag
2.A module related interrupt flag (in your case ADC)
You can have control over module related flag, in your case in the ADCSRA control register there is a flag named ADIE- ADC Interrupt Enable flag you can use that to control the Interrupts.
For example,
In main function you can enable the flag and in ISR disable the flag.
main()
{
//enable global flag and ADC flag
while(1)
{
//your logic
// enable ADC flag
}
}
ISR()
{
//disable the ADC flag
}
I hope this solves the issue you are having.

Resources