Disabling External Interrupt within its ISR

Go To Last Post
21 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi there,

 

I want to set up a code template which leaves the uC sleeping until I pull the External interrupt pin to LOW. Here is my code:

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>

#define F_CPU 8000000L
#include <util/delay.h>

void sleepNow();

// external interrupt pin
#define DDR_INT DDRB
#define PORT_INT PORTB
#define B_INT PINB2

// LED
#define DDR_LED DDRB
#define PORT_LED PORTB
#define B_LED PORTB3


int main(void)
{
        DDR_LED |= _BV(B_LED);		// set output    
    
        DDR_INT &= ~_BV(B_INT);		// set INT2 as input
        PORT_INT |= _BV(B_INT);       // enable pullup
    
	EICRA = _BV(ISC21); 	// falling edge of INT2 generates interrupt 

	while(1)
	{
    	        EIMSK = _BV(INT2);	// Enable interrupt on INT2 before sleeping
		PORT_LED &= ~_BV(B_LED); // turn off the light before sleeping
		sleepNow();

                // sleeping here
        
                EIMSK &= ~_BV(INT2);	// Disable interrupt on INT2
                PORT_LED |= _BV(B_LED); // turn on the light
                _delay_ms(1000);        
	}
}


ISR(INT2_vect){
	
}

void sleepNow(){
	// Choose our preferred sleep mode:
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	
	cli();
	
	// Set sleep enable (SE) bit:
	sleep_enable();
	
	sei();
	// Put the device to sleep:
	sleep_cpu();
	
}

I know this works fine, since my LED would turn on for a second and then off, everytime I pull the external interrupt low. However, I wanted to disable the interrupt immediately, so I moved the disable interrupt code into ISR(INT2_vect):

ISR(INT2_vect){
	EIMSK &= ~_BV(INT2); // Disable interrupt on INT2
}

Now it works for only the first instance of interrupt and does not respond to subsequent events. I initially considered the possibility of accidentally triggering ISR(INT2_vect) before going to sleep, but this behaviour is too consistent for that to be true.

 

Has anyone else encountered this unusual behaviour?

 

Edit: cleaned up messy code

This topic has a solution.

A newbie exploring the AVR land using Atmel Studio 6 (Version: 6.2.1548 - Service Pack 2)

Last Edited: Thu. May 21, 2015 - 07:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DO you mean your interrupt is triggered only once, never again?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Mandar,

 

That's correct. I attempted the same thing with pin change interrupt, with the same effect. I feel like I am on the verge of learning something new discovering a bug an undocumented feature, but not sure what that is. surprise

 

Are we not allowed to disable interrupts within ISR?

 

Here's the same code, replacing external interrupt with pin change interrupt:

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>

#define F_CPU 8000000L
#include <util/delay.h>

void sleepNow();

// pin change interrupt 
#define PORT_SW PORTB
#define PIN_SW PINB
#define B_SW PINB1
#define PCINT_SW PCINT9

// LED
#define DDR_LED DDRB
#define PORT_LED PORTB
#define B_LED PORTB3


int main(void)
{
    DDR_LED |= _BV(B_LED);		// set output    

	DDR_SW &= ~_BV(B_SW);		// set input
	PORT_SW |= _BV(B_SW);       // enable pullup
    
	// Set interrupt for pin change at PCINT1
    PCICR |= _BV(PCIE1); 	// Enable PCINT9 interrupt

	while(1)
	{
    	PCMSK1 |= _BV(PCINT_SW); 	// Enable PCINT9 interrupt before sleeping
		PORT_LED &= ~_BV(B_LED); // turn off the light before sleeping
		sleepNow();

                // sleeping here
        
        PCMSK1 &= ~_BV(PCINT_SW); 	// Disable PCINT9 interrupt
        PORT_LED |= _BV(B_LED); // turn on the light
        _delay_ms(1000);        
	}
}

ISR(PCINT1_vect){
	
}

void sleepNow(){
	// Choose our preferred sleep mode:
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	
	cli();
	
	// Set sleep enable (SE) bit:
	sleep_enable();
	
	sei();
	// Put the device to sleep:
	sleep_cpu();
	
}

Same thing happens when I move the disable interrupt code into ISR(PCINT1_vect).

A newbie exploring the AVR land using Atmel Studio 6 (Version: 6.2.1548 - Service Pack 2)

Last Edited: Thu. May 21, 2015 - 08:25 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Are we not allowed to disable interrupts within ISR?

But you aren't doing that? There is no code in INT2_vect? (actually you might want to explore EMPTY_INTERRUPT() )

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I'm thinking the interrupt flag is set so as soon as the interrupt is enabled, the isr gets called and turns it off again.
Solution: clear the interrupt flag before enabling it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank guys, it worked! Clearing just before enabling the interrupt again worked for me. Does this means that when the interrupt flag does not clear when RTI is executed if the interrupt itself is disabled? 

 

I should add that I initially tried clearing the flag WITHIN the ISR, but that didn't work. I wonder why.

 

 

Hi Clawson,

I did disable the interrupt within ISR(PCINT1_vect), but I didn't show it here since the code looks identical. :)

A newbie exploring the AVR land using Atmel Studio 6 (Version: 6.2.1548 - Service Pack 2)

Last Edited: Thu. May 21, 2015 - 08:51 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No need to wonder - if you get a falling edge on int2 whilst the 1 second delay is happening, then the int flag gets set.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't understand. Why would the flag be set if I have already disabled it upon waking up?

 

A newbie exploring the AVR land using Atmel Studio 6 (Version: 6.2.1548 - Service Pack 2)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I did disable the interrupt within ISR(PCINT1_vect), but I didn't show it here since the code looks identical. :)

A TRAGIC mistake - don't ever post different code to a forum from the code you are having problems with. Your own editing of "I don't need to show that bit" could easily have removed the very thing that was wrong! (not in this case - but it's a bad habit).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

I did disable the interrupt within ISR(PCINT1_vect), but I didn't show it here since the code looks identical. :)

A TRAGIC mistake - don't ever post different code to a forum from the code you are having problems with. Your own editing of "I don't need to show that bit" could easily have removed the very thing that was wrong! (not in this case - but it's a bad habit).

Noted with thanks.

A newbie exploring the AVR land using Atmel Studio 6 (Version: 6.2.1548 - Service Pack 2)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You only disabled the interrupt. Thr flag and edge detect logic keep working. It is not uncommon to use the edge detect logic without using interrupts - software can check the state.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Since a standard AVR can't do nested interrupts, I never have more than one ISR (timer), but often the HW to set the flag, that it would like to make an ISR, then you only have to check a bit to see if a thing has happend,

(and perhaps gone again), without wasting time in a ISR. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An AVR will nest interrupts if you want it to.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

yes but then you have to fiddle with then ISR flag your self. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

yes but then you have to fiddle with then ISR flag your self. 

???  I don't understand.  In its simplest form, you do an SEI inside your ISR and then any subsequent enabled interrupt source firing will "nest".

 

What do you mean by "standard AVR"?  [I'm guessing that sparrow2 is really referring to "some/most AVR8 C toolchains will by default generate a blocking ISR."]

 

x51 people like danni, and others, will decry the lack of nested interrupt >>priority<< mechanism in AVR8.  But that is slightly different.

 

Perhaps I just learned to live with it in many scores of production AVR8 apps over the past 15 years, but I've never really felt the need to nest interrupts.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What do you mean by "standard AVR"?

 

!!! All xmega's have it.

 

Ok I guess I should rephrase it to control of priority.  

and yes there I really like the 8051 way of doing it. (and for ISR the bank switch make it clean and simple.)

 

Just think if you could swap r24-r31 for a ISR. 

Last Edited: Thu. May 21, 2015 - 02:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As I mentioned, I've never felt constrained by the "standard AVR" (what I usually call AVR8 here) setup.  As long as I keep my ISRs short I've never seen a dire need.

 

Now, there are those that (especially if a sophisticated interrupt leveling system is available) might even writhe their apps "upside down", with the lowest level often engaged in doing length tasks such as display update and thus taking care of that during "idle time".

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

You only disabled the interrupt. Thr flag and edge detect logic keep working. It is not uncommon to use the edge detect logic without using interrupts - software can check the state.

 

Hi Kartman, 

 

I think you misunderstood me. Clearing the interrupt outside the ISR just before enabling the interrupt again worked for me.

However, when I try clearing the interrupt flag and disable the interrupt WITHIN the ISR, it didn't work. 

 

It seemed to me that clearing the flag within the ISR and outside the ISR made a difference.

 

Here is the code which didn't work:

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>

#define F_CPU 8000000L
#include <util/delay.h>

void sleepNow();

// external interrupt pin
#define DDR_INT DDRB
#define PORT_INT PORTB
#define B_INT PINB2

// LED
#define DDR_LED DDRB
#define PORT_LED PORTB
#define B_LED PORTB3


int main(void)
{
    DDR_LED |= _BV(B_LED);		// set output    

    DDR_INT &= ~_BV(B_INT);		// set INT2 as input
    PORT_INT |= _BV(B_INT);       // enable pullup
    
	EICRA = _BV(ISC21); 	// falling edge of INT2 generates interrupt 

	while(1)
	{
    	EIMSK = _BV(INT2);	// Enable interrupt on INT2 before sleeping
	PORT_LED &= ~_BV(B_LED); // turn off the light before sleeping
	sleepNow();

        // sleeping here

        EIMSK &= ~_BV(INT2);	// Disable interrupt on INT2 (Edit: this should not be here)
        PORT_LED |= _BV(B_LED); // turn on the light
        _delay_ms(1000);        
	}
}

ISR(INT2_vect){
    EIMSK &= ~_BV(INT2);	// Enable interrupt on INT2
    EIFR |= _BV(INTF2);   // Tried clearing flag here, but it doesn't work
}

void sleepNow(){
	// Choose our preferred sleep mode:
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	
	cli();
	
	// Set sleep enable (SE) bit:
	sleep_enable();
	
	sei();
	// Put the device to sleep:
	sleep_cpu();
	
}

 

 

A newbie exploring the AVR land using Atmel Studio 6 (Version: 6.2.1548 - Service Pack 2)

Last Edited: Fri. May 22, 2015 - 04:11 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

isoboy, I'd suggest you didn't understand me!

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>

#define F_CPU 8000000L
#include <util/delay.h>

void sleepNow();

// external interrupt pin
#define DDR_INT DDRB
#define PORT_INT PORTB
#define B_INT PINB2

// LED
#define DDR_LED DDRB
#define PORT_LED PORTB
#define B_LED PORTB3


int main(void)
{
    DDR_LED |= _BV(B_LED);		// set output    

    DDR_INT &= ~_BV(B_INT);		// set INT2 as input
    PORT_INT |= _BV(B_INT);       // enable pullup
    
	EICRA = _BV(ISC21); 	// falling edge of INT2 generates interrupt 

	while(1)
	{
	    EIFR = _BV(INTF2);  // don't do |= here, just a plain assignment
    	EIMSK |= _BV(INT2);	// Enable interrupt on INT2 before sleeping
		PORT_LED &= ~_BV(B_LED); // turn off the light before sleeping
		sleepNow();

        // sleeping here

        EIMSK &= ~_BV(INT2);	// Disable interrupt on INT2
        PORT_LED |= _BV(B_LED); // turn on the light
        _delay_ms(1000);        //what happens if there is a transition on INT2 during this time? Answer- INTF2 gets set.
                                //then we go up the top of the loop and enable the interrupt
	}                           // then the isr fires and disables the interrupt before you sleep.
}

ISR(INT2_vect){
    EIMSK &= ~_BV(INT2);	// disable interrupt on INT2
}

void sleepNow(){
	// Choose our preferred sleep mode:
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	
	cli();
	
	// Set sleep enable (SE) bit:
	sleep_enable();
	
	sei();
	// Put the device to sleep:
	sleep_cpu();
	
}

Alles klaar herr komisar?

Also consider if you have a pushbutton connected to INT2 it is going to bounce - that will cause the AVR to detect multiple edges over around 30ms. It only takes microseconds to detect the edge, the isr to fire and disable the int and exit the isr.

 

 

Last Edited: Fri. May 22, 2015 - 03:22 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Haha... I think I don't. I really appreciate your time to write back.

 

Let me try to summarise what I understand from your posts; (correct me if I am wrong) there are two places in which the code can go wrong:

  1. The ISR might fire more than one due to bounce
  2. If a transition is detected on INT2 during the 1 second delay, INTF2 gets set

 

In the first case:

After the first transition, the INTF2 is set and the MCU begins executing the ISR. Since the I-bit in the SREG is already cleared, further transitions caused by debouncing should not have any effect (INTF2 is still set.) When the interrupt is disabled, subsequent transitions should not do anything on INTF2. It is from here that the INTF2 (still set) is cleared, before RTI. 

 

 

In the second case:

Shouldn't the MCU ignore further transitions after RTI, since the interrupt was disabled in the ISR first? Are you saying that the MCU will continue setting INTF2 even after the interrupt is disabled?

 

A newbie exploring the AVR land using Atmel Studio 6 (Version: 6.2.1548 - Service Pack 2)

Last Edited: Fri. May 22, 2015 - 04:47 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Shouldn't the MCU ignore further transitions after RTI, since the interrupt was disabled in the ISR first?

No.

 

Are you saying that the MCU will continue setting INTF2 even after the interrupt is disabled?

Yes. Certainly.  [All?] IF flags will [always?] be set, regardless of whether it will trigger an ISR.  That allows polling of interrupt flags--in other word, it is a feature.

 

The ISR is entered within microseconds; switch bounce is [typically] milliseconds.  So, yes, there can well be multiple settings of the IF flag.  You can "disable" the entry into the ISR but the IF flag will still be set.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.