wake-from-sleep happens but ISR doesn't run how?

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

I think this program should only wake from sleep on pin change interrupt.

 

However, when pin changes are fast (bounce), it can happen that the

program gets more 'wakes' in main() than it does 'sets' in the ISR.

 

So the code below ends up in the bottom-most DBL_ASSERT_SHOW_POINT (0);

 

I don't understand why.  It seems like an ISR() for the pin change interrupt should always run after wake-up,

even if the pin change interrupt was blocked since before the sleep started.  I understand that you may not

get an ISR for every physical event because they can pile up and there's only one pin change interrupt flag,

but if the device wakes from sleep due to pin change interrupt, shouldn't you always get at least one ISR

after it wakes?

 

Clues?

#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

#include "debug_led.h"
#include "dio.h"

#define WUP DIO_PIN_PB2   // Wake-Up Pin

volatile uint8_t got_pbpci = 0;   // Got Port B Pin Change Interrupt

volatile uint8_t sets = 0;
volatile uint8_t wakes = 0;

ISR (PCINT0_vect)
{
  sets++;
  got_pbpci = 1;
}

int
main (void)
{
  DBL_INIT ();   // Initialize debug led

  DBL_CHKP ();   // Boot-up indication checkpoint

  DIO_INIT (WUP, DIO_INPUT, DIO_DISABLE_PULLUP, DIO_DONT_CARE);
  DIO_SET_PIN_CHANGE_INTERRUPT_MODE_ON (WUP);

  while ( 1 ) {
    set_sleep_mode (SLEEP_MODE_PWR_DOWN);
    sleep_enable ();
    sei ();
    sleep_cpu ();
    sleep_disable ();
    cli ();

    wakes++;

    if ( got_pbpci ) {
      got_pbpci = 0;
    }
    else {
      if ( DIO_PIN_CHANGE_INTERRUPT_FLAG_IS_SET (WUP) ) {
        DBL_ASSERT_SHOW_POINT (0);   // Shouldn't end up here
      }
      else {
        dbl_display_uint32 (sets);
        dbl_display_uint32 (wakes);
        _delay_ms (1442.0);   // So we can tell sets from wakes
        DBL_ASSERT_SHOW_POINT (0);   // Shouldn't end up here
      }
    }
  }
}

 

Last Edited: Fri. Nov 4, 2016 - 04:48 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

shouldn't you always get at least one ISR

after it wakes?

 

It is possible to wakeup from powerdown and not trigger the ISR.

I didn't see which AVR you are using, the quote below is from the tiny24A datasheet.

Note that if a level triggered interrupt is used for wake-up from Power-down, the required level
must be held long enough for the MCU to complete the wake-up to trigger the level interrupt. If
the level disappears before the end of the Start-up Time, the MCU will still wakeup, but no interrupt will be generated.

 

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

The key thing in that entire code is:

  DIO_SET_PIN_CHANGE_INTERRUPT_MODE_ON (WUP);

Yet you have not shown the code for that?

 

If you had told us which AVR it was then I could have a look at what the functionality of:

#define WUP DIO_PIN_PB2   // Wake-Up Pin

is - but I don't see it?

 

I can't help wondering if this is one of those occasions where you have confused the PCINT pin numbers and PCINT vector numbers? Pins PCINT0..POCINT7 trigger PCINT0_vect, PCINT8..PCINT15 trigger PCINT1_vect and so on.

 

I suspect you have set the PCINT1 pin to wake but are providing PCINT1_vect (which is wrong). If that is the case then it is the missing PCINT0_vect that triggers and because there is no ISR it will go to __bad_interrupt() and reset the AVR - which will look like a wake from sleep.

 

EDIT: I took a guess that it may be a 328P (because of Arduino - perhaps the most widely used mega AVR these days?) and in that PB2 = PCINT2 which is a PCINT0_vect interrupt source.

Last Edited: Fri. Nov 4, 2016 - 09:45 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bkerin wrote:
However, when pin changes are fast (bounce), it can happen that the program gets more 'wakes' in main() than it does 'sets' in the ISR.

It depends on the app, of course, but when I use pin-chage to awaken from deep sleep (e.g. powerdown), my ISR is almost always empty or nearly so.  (I might clear sleep-enable).

 

The main code then only knows that it passed the sleep.  It doesn't care whether it was sleeping for microseconds or days.  It doesn't care if the button was pressed the last cycle before going to sleep.

 

Anyway, the main code yawns and stretches and looks around for something to do.  If nothing to do, it lays back down for more sleep.

 

The "looking around" is often doing normal wake handling of button press -- a few periodic polls and values entered into the debounce mechanism.

 

 

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

mrono wrote:

shouldn't you always get at least one ISR

after it wakes?

 

It is possible to wakeup from powerdown and not trigger the ISR.

I didn't see which AVR you are using, the quote below is from the tiny24A datasheet.

Note that if a level triggered interrupt is used for wake-up from Power-down, the required level
must be held long enough for the MCU to complete the wake-up to trigger the level interrupt. If
the level disappears before the end of the Start-up Time, the MCU will still wakeup, but no interrupt will be generated.

 

 

Yes, that also appears in ATMega328P datasheet.  However, my understanding is that pin change interrupts are not level-triggered.  They're one-shot events which set a flag, unlike level-change interrupts which keep on firing as long as the level is held.  The power management section says:

If an enabled interrupt occurs while the MCU is in a sleep mode, the MCU wakes up. The MCU is then
halted for four cycles in addition to the start-up time, executes the interrupt routine, and resumes
execution from the instruction following SLEEP.

So it sure looks we're supposed to always get a ISR after a wake for pin change interrupts.  Unless there's a documented exception hiding somewhere else.

It seems like in practice there's some difference in the wake-from-sleep detection hardware and whatever it is that decides if a pin change has happened.

 

IIRC I've been surprised in the past to find that a value read in a pin change ISR seems to *always* get the new value for the pin, no matter how bouncy

the transitions in question.  That suggest that there's some sort of virtualization going on, which might be screwing things up in this case.

 

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

Cliff told you what may well be happening.  Show all the macro definitions.  Or get rid of them for a test program.  There is no "virtualization" going on.

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

clawson wrote:

The key thing in that entire code is:

  DIO_SET_PIN_CHANGE_INTERRUPT_MODE_ON (WUP);

Yet you have not shown the code for that?

 

If you had told us which AVR it was then I could have a look at what the functionality of:

#define WUP DIO_PIN_PB2   // Wake-Up Pin

is - but I don't see it?

 

I can't help wondering if this is one of those occasions where you have confused the PCINT pin numbers and PCINT vector numbers? Pins PCINT0..POCINT7 trigger PCINT0_vect, PCINT8..PCINT15 trigger PCINT1_vect and so on.

 

I suspect you have set the PCINT1 pin to wake but are providing PCINT1_vect (which is wrong). If that is the case then it is the missing PCINT0_vect that triggers and because there is no ISR it will go to __bad_interrupt() and reset the AVR - which will look like a wake from sleep.

 

EDIT: I took a guess that it may be a 328P (because of Arduino - perhaps the most widely used mega AVR these days?) and in that PB2 = PCINT2 which is a PCINT0_vect interrupt source.

 

Sorry, those are macros from my own interface to the IO pins.  I've got correct

interrupt vector for my pin, but otherwise you have guessed correctly on all points.

The macros work in many other apps and are causing ISRs here as well, just not as expected.

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

theusch wrote:

Cliff told you what may well be happening.  Show all the macro definitions.  Or get rid of them for a test program.  There is no "virtualization" going on.

 

Ok, below is a version in terms of only AVR libc.  I'll throw it on an Arduino

and add flattened debug LED output back in next in case that makes it

easier for people to verify

 

#include <assert.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

// The wake-up pin is PB2

volatile uint8_t got_pbpci = 0;   // Got Port B Pin Change Interrupt

volatile uint8_t sets = 0;
volatile uint8_t wakes = 0;

ISR (PCINT0_vect)
{
  sets++;
  got_pbpci = 1;
}

int
main (void)
{
  // Initialize wake-up pin as an input, with no internal pull-up
  DDRB &= ~(_BV (DDB2));
  PORTB &= ~(_BV (PORTB2));

  // Enable pin change interrupts on wake-up pin
  PCICR |= _BV (PCIE0);
  PCMSK0 |= _BV (PCINT2);

  while ( 1 ) {
    set_sleep_mode (SLEEP_MODE_IDLE);
    sleep_enable ();
    sei ();
    sleep_cpu ();
    sleep_disable ();
    cli ();

    wakes++;
 
    if ( got_pbpci ) {
      got_pbpci = 0;
    }
    else {
      if ( ((PCIFR & _BV (PCIF0)) >> PCIF0) ) {
        assert (0);   // Shouldn't be here
      }
      else {
        // wakes is usually but not always greater that sets when we end
        // up here
        assert (0);   // Shouldn't be here
      }
    }
  }
}

 

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

theusch wrote:

bkerin wrote:
However, when pin changes are fast (bounce), it can happen that the program gets more 'wakes' in main() than it does 'sets' in the ISR.

It depends on the app, of course, but when I use pin-chage to awaken from deep sleep (e.g. powerdown), my ISR is almost always empty or nearly so.  (I might clear sleep-enable).

 

The main code then only knows that it passed the sleep.  It doesn't care whether it was sleeping for microseconds or days.  It doesn't care if the button was pressed the last cycle before going to sleep.

 

Anyway, the main code yawns and stretches and looks around for something to do.  If nothing to do, it lays back down for more sleep.

 

 

Agreed I hate ISR() and always do least possible or better just don't use at all.

 

However in this case I want to be able to distinguish between a WDT-induced wake-up, and a wake-up due to *new* activity on the wake-up pin.  So non-empty ISR is needed, because WDT hardware clears WDIF for us when the handler runs so can't reconstruct what happened without work in ISR.  I don't understand why I can't have wake-up on PCIF/WDIF *without* an ISR, but that's no how things work apparently.

Last Edited: Fri. Nov 4, 2016 - 08:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When I wrote my reply previously your code had PCINT1_vect?

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

clawson wrote:

When I wrote my reply previously your code had PCINT1_vect?

 

No, I'm 99.99% sure.  PCINT1_vect doesn't even show up in my git.  I've known about the sharing of vectors between pins and confusing numbers for years and the behavior of the test isn't at all consistent with what would happen if that was the issue.  In any case it's definitely correct as of now.

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

Here's a version that goes on a vanilla Arduino UNO Rev 3 and shows almost exactly the same behavior.

The only diff is PCIF0 seems to always end up set (though the ISR is still not running).  On my other board

(a 4 MHz atmega328P) it only sometimes was.  I switched to using internal pull-up so all that's needed to

reproduce this is an Uno Rev3 with USB cable and wires plugged into GND and Digital 10 and touch them

together a few times.

 

The sleep ends but the ISR doesn't always run after.

 


#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

// The wake-up pin is PB2

// For showing when we hit a trap:
#define BLINK_PB5_LED_FOREVER(period_ms) \
  do {                                   \
    for ( ; ; ) {                        \
      PORTB |= _BV (PORTB5);             \
      _delay_ms (period_ms / 2.0);       \
      PORTB &= ~(_BV (PORTB5));          \
      _delay_ms (period_ms / 2.0);       \
    }                                    \
  } while ( 0 );


volatile uint8_t got_pbpci = 0;   // Got Port B Pin Change Interrupt

volatile uint8_t sets = 0;
volatile uint8_t wakes = 0;

ISR (PCINT0_vect)
{
  sets++;
  got_pbpci = 1;
}

int
main (void)
{
  // Initialize PB5 for output, with low as initial value
  PORTB &= ~(_BV (PORTB5));
  DDRB |= _BV (DDB5);

  // Initialize wake-up pin as an input, with internal pull-up
  DDRB &= ~(_BV (DDB2));
  PORTB |= _BV (PORTB2);

  // Enable pin change interrupts on wake-up pin
  PCICR |= _BV (PCIE0);
  PCMSK0 |= _BV (PCINT2);
    
  while ( 1 ) {
    set_sleep_mode (SLEEP_MODE_IDLE);
    sleep_enable ();
    sei ();
    sleep_cpu ();
    sleep_disable ();
    cli ();
        
    wakes++;
 
    if ( got_pbpci ) {
      got_pbpci = 0;
    }
    else {
      if ( ((PCIFR & _BV (PCIF0)) >> PCIF0) ) {
        BLINK_PB5_LED_FOREVER (100.42);
      }
      else {
        // wakes is usually but not always greater that sets when we end
        // up here
        BLINK_PB5_LED_FOREVER (200.42);
      }
    }
  }
}