Interrupt recursion

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

Hi,

 

I'm using an Xmega-E5 Xplained board and i'm trying to handle two momentary buttons:  one is an on/off, and the other invokes a function ("foo").

 

Both buttons will be on PORTA, pins 0 and 1.  Right now, i've just got the on/off button on pin 0 connected so as to simplify debugging.  Both momentary buttons are push-to-make, so i've got both pins pulled low.  To determine if either has been hit, I check the corresponding bits in PORTA_INTFLAGS.

 

I've "offloaded" the "heavy lifting" code from the ISR into the main() area.  Each time a button is pressed, I turn off the interrupt mask, wait 10 milliseconds, then reset the interrup mask.

 

I set a breakpoint on the first statement of the interrupt; i.e.:

PORTA_INTMASK = 0b00000000;

When I run the code the board goes to sleep, and then I press the on/off switch.  The breakpoint gets hit, and when I continue the execution, the breakpoint gets hit again - even though I only pressed the on/off switch once.

 

Here's the relevant code:

 

volatile uint8_t  hitOnOff   = 0x00;								//	ISR:main comms
volatile uint8_t  requestFoo = 0x00;								//	only request foo if switch hit

ISR(PORTA_INT_vect) {
    PORTA_INTMASK = 0b00000000;								//	prevent debounce interrupt recursion
    if (PORTA_INTFLAGS & 0b00000001) {							//	was on/off button pushed?
        hitOnOff   = 0x01;								//	let main know on/off button was pushed
    } else {										//	otherwise ...
        requestFoo = 0x01;								//	let main know user requested foo
    }											//	gotta be one or 'tother
    reti();											//	clear and return
}

void deBounce(void) {
    _delay_ms(10);										//	wait 10 milli's which will hopefully debounce the switches
    PORTA_INTMASK = 0b00000011;								//	set interrupt mask for both on/off and foo buttons
}

void goToSleep(void) {										//    Enable interrupts on Port A Pin 0 - our "On/Off" Pushbutton
    PORTA_INTMASK  = 0b00000001;								//	ignore foo button whilst sleeping
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);							//	select sleep mode type
    sleep_enable();										//	enable sleep mode
    sei();											//	enable interrupts
    areWeRunning = 0x00;									//	ind sleeping for PORTA_INT_vect
    sleep_cpu();										//	go to sleep
    sleep_disable();									//	and we're back!
    areWeRunning = 0x01;									//	ind we're running
    PORTA_INTMASK = 0b00000011;								//	set interrupt mask for both on/off and foo buttons

    //	setup clock - 32MHz internal
    OSC.CTRL|=OSC_RC32MEN_bm;
    while (!(OSC.STATUS & OSC_RC32MRDY_bm));
    CCP=CCP_IOREG_gc;
    CLK.CTRL=CLK_SCLKSEL_RC32M_gc;
    OSC.CTRL&=(~OSC_RC2MEN_bm);
}

int main(void) {

//	first part of setup - stuff that doesn't change whilst sleeping or doing foo
    PMIC_CTRL      = 0b00000111;								//	enable high, medium, and low level interrupts
    PORTA_INTCTRL  = 0b00000001;								//	interrupt level 1
    PORTA_PIN0CTRL = PORT_OPC_PULLDOWN_gc;							//	on/off button pin is a pin wid a pullDOWN default value
    PORTA_PIN1CTRL = PORT_OPC_PULLDOWN_gc;							//	foo  button pin is a pin wid a pullDOWN default value

//	as soon as we power-on, go to sleep.
    goToSleep();

    .
    .
    .

    while(1) {										//	i.e., forevah
        if (hitOnOff) {									//	user hit on/off button?
            hitOnOff = 0x00;							//	yes, reset this immediately
            deBounce();								//	yes, debounce here
            if (areWeRunning) {							//	are we running?
                goToSleep();							//	yes, time to call it a day
            }									//	if not, we're awake, that's all we needed
        }
        if (requestFoo) {								//	user hit foo button?
            deBounce();								//	yes, debounce here
            doFoo();								//	do function foo
        }
    }

 

I'm sure it's something I have done, but I can't seem to find it.  If anyone has any ideas, or if I've done something glaringly wrong, please advise.  Thanks in advance!

 

Last Edited: Wed. Nov 16, 2016 - 09:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Avoid using pushbuttons that fire interrupts! In your case you want the interrupt to wake the micro - that's ok, but disable the interrupt and enable a timer tick interrupt to sample the pushbutton input and debounce properly.
Note recursion is when another interrupt from the same source interrupts the isr. Having separate invocations of the same isr is not recursion.

Last Edited: Wed. Nov 16, 2016 - 09:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok thanks for that, Kartman!

disable the interrupt and enable a timer tick interrupt to sample the pushbutton input and debounce properly.

I've googled around and searched the freak's forums for "how to determine if a button has been pressed whilst on low power", and haven't found much (tho I did find a very interesting post on haggis).  Thus, I have more questions.

 

I'm thinking that the code should wakeup every 5 (?) ms and look at the pins tied to the buttons.  Every 5ms allows for two inspections per "typical debounce cycle" of 10ms.  Won't this significantly affect the power utilization?  Should it wake up more often?  5ms is 200x per second, the device runs on batteries and it could be in sleep mode for 8 hours at a time.  That's a lot of wakeups!  Would it be better to wakeup every, say, 100ms (for quick button response) and if a button has been pressed then start the debounce processing?  That would save a bunch of battery power.

 

Since the pushbuttons must be debounced, doesn't this leave the watchdog timer out as it's minimum interval is 8ms?  I'm already using timer4, so I guess this means timer5 or the RTC, yes?  I read in "AVR1010: Minimizing the power consumption of Atmel AVR XMEGA devices" where RTC interrupts will wakeup the chip but I can't seem to find where timer5 interrupts will do the same.

 

Is there a TUT on this anywhere?  Or am I over-complicating the matter?

 

Sorry for so many questions but I want to do the right thing here in my code.

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

Maybe I am missing something here, but if the MCU was awakened out of its sleep mode, doesn't that (by default) mean that a button MUST have been pressed?  Or do you have other interrupt sources active?  I don't see why you would need to scan anything, unless you wanted to run a debounce cycle after the switch was pressed and the MCU awake . . .

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

Exactly so.  The interrupt means the button is pressed.  A timer needs to be used to re-enable the interrupt after the contact stops bouncing.  I have no idea how long they bounce.  If you say 10 ms, I can't argue.

 

If you have a periodic timer interrupt that runs continuously, I've seen people, including the smartest people on earth, if you ask them, screw it up.  In this case, you need a countdown count of at least 2.  For instance, if a 10 ms delay is enough, and you have a continuous timer running with a period of 10 ms, you must set a countdown count of 2 or more.  If you use a count of 1, the delay will be between 0 and 10 ms.  A count of 2 gives a delay of 10 to 20 ms, etc.

 

You could have a delay of 100 ms and the user would never know.  A delay much slower than that would be noticed.  

Last Edited: Thu. Nov 17, 2016 - 09:39 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The comments I made were based on Kartman's statment:

disable the interrupt and enable a timer tick interrupt to sample the pushbutton input and debounce properly.

I'm going to play it by ear for now and see what works best.  And re-read all the AppNotes on timers.

 

Thanks for your comments!

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

My point was is you still use the external interrupt to wake the cpu, but as soon as it has woken up:

disable the external interrupt

enable the timer

debounce the switch via the timer. ie: count the number of timer ticks the switch is active. if the timer is 10ms, the switch should be active for, say, three ticks (30ms) to be tagged as active

decide if the switch is active

if a transient woke you up, enable external ints and go back to sleep.

In a perfect world, we wouldn't have transients caused by mobile phones,lightning, static electricity etc and mechanical switches wouldn't bounce. In the real world we have these things, so we need to mitigate the effects. We can use hardware to do part of the work - in fact we need a capacitor to take care of EMC and ESD just to protect the micro, let alone cope with the secondary effects. Our software cleans up what's left. Net result is we have a robust unit.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
My point was is you still use the external interrupt to wake the cpu

*Thank You* for letting me know that!  I rewrote the external interrupt as an event capture using the T5 timer but after reviewing the code I'm not really sure about it, probably because I've never written event capture code before.  I'll rewrite the interrupt and debounce code and see how that goes.

 

After this I will try an event capture just for education purposes - while they look interesting, I believe I've spent enough time going up the learning curve without trying to scale K2.

 

Thanks again

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

Do you have an oscilloscope to view the contact bounce time?  Does the switch in question have a maximum bounce time specification?

 

When I need to debounce switches, I save the image of the switch inputs and start the debounce timer, and then compare it against the switch status every millisecond (or so).  If the switches are different from the previously saved image, I save the new image and restart the debounce timer.  Once the debounce timer has reached some terminal value, it means that the switches have been stable for that amount of time.  For the small 'tact' type SPST switches, I have found that a 20ms debounce timer is more than generous.

 

You can adjust the timings mentioned here to meet your requirements.

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

I see no need to test the switch.  Just give it plenty of time.  I see no benefit of re-arming the switch quicker than 100 ms, but as you said, 20 ms should be enough. 

 

If waiting 100 ms before re-arming causes the user a problem, that means the user is trying to push the button more than 10 times a second.  That's almost impossible to do.

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

World record for button pushing rate is 16Hz :-)

 

In any case, I normally don't bother polling at more than 5Hz. That's enough to give an "instant" response feeling and excellent debouncing.

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

I've gotten addicted to using a logic analyser to track software execution.

I use a little debug lib with a few macro's which shifts some data out of a pin but it's even easier to use hardware spi / uart /  to track your software execution.

In the LA you can easily follow the program flow, timing, interrupts of the firmware. Impact on timing is also almost always negligible because writing a byte to SPI is just a single opcode.

 

And for the USD <10 these critters cost you have almost no excuse to not get some for yourself.

I use a saleae clone from Ali with the sigrok software.

http://www.sigrok.org/wiki/Suppo...

Any board with a CY7C68013-56PVC will do.

The saleae clones have 8 input bits with input protection and the general development boards can have 16 input channels in Sigrok, but they usually have no input protection.

Firmware for the cypress is also easy. There is no flash in the chip. It's uploaded via DFU when it is connected to the USB port.

 

On my linux box I haven't even tried the atmel software suite, just gcc & avrdude. Some days I'm trying to concentrate on doing more with stm32f103 with ST-Link, supported by gnu debugger.

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com