Problems with PCINT when waking from SLEEP mode

Hi,
I've been researching and struggling to get PCINT to wake ATmega328p from Sleep without resetting. Could really use some assistance in figuring this out. THANKS ahead of time!

Summary of Setup

  1. Two buttons (INT0, INT1)
  2. One Analog Input (In this example PCINT8)
    (Final project will use PCINT13 so could use help in understanding specific addressing.)

Summary of Process

  1. After powerup, a voltage is checked via Analog Input.
  2. When no voltage to input, Atmeg is put to sleep after short delay
  3. Atmeg to be woken up by either of the three occurrences
    -INT0 (Pin 2) WORKS!!
    -INT1 (Pin 3) WORKS!!
    -Voltage applied to Analog Input (PCINT) DOESN't WORK :frowning:
  4. When woken, want code to continue where left off WITHOUT resetting

ISSUE:
The PCINT8 (pin14) Resets whenever voltage is applied/removed regardless of Wake or Sleep status of ATmega

I know I'm messing this up, but just can't figure out where it's bugger'd.

Please, if anyone can help, sure would appreciate it!

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <RBD_Timer.h>
#include <RBD_Button.h>

RBD::Button LTsw(2);                  //Left Button
RBD::Button RTsw(3);                  //Right Button
const byte LED = 13;
static const int RTPos = 6;           //V
static const int LTPos = 7;           //PK
static const int IGN = A0;            //R/Y   (Analog input sensing when Ignition is on/off)
boolean IGNOFF = 0;                   //Ignition power status
boolean PwrDwn = 0;                   //Power Down pending status (0=Not powering down, 1=powering down)
boolean Status = 0;
unsigned long PWROFFtime;             //time to initiate Power Down Mode
unsigned long PWROFFint = 3000;       //Interval to stay awake 
 int INT0_interrupt_flag;
 int INT1_interrupt_flag;

// turn off interrupts until we are ready
ISR (PCINT8_vect)
  {
  PCICR = 0;                          // cancel pin change interrupts
  }

void setup () {
  Serial.begin(19200);   while (!Serial);
  pinMode(RTPos, OUTPUT);             //Right LED
  pinMode(LTPos, OUTPUT);             //Left LED
  RTsw.setDebounceTimeout(40);
  LTsw.setDebounceTimeout(40);
  PWROFFtime = millis() + PWROFFint;  //Set Power Down Time
  PCMSK1 |= bit (PCINT8);             // pin change interrupt mask  A0 (ProMini Pin 14)
  detachInterrupt (0);
  detachInterrupt (1);
  Startup ();                         //flashes LEDs
  }

void wake (){                   
  sleep_disable();                      // cancel sleep as a precaution
  
// Not sure which to use to disable ALL PCInt Interrupts
//  PCMSK = 0x00;                       // Is this how to Disable all PCInt Interrupts ???
//  PCMSK &= ~(1<<PCINT8);              // Is this how to Disable PCInt8 Interrupt ???
  detachInterrupt (0);
  detachInterrupt (1);
  power_all_enable();
  ADCSRA |= (1 << 7);                   // enable ADC 
  // precautionary while we do other stuff
  PCICR = 0;                            // cancel pin change interrupts
}

void sleep(){
  ADCSRA &= ~(1 << 7);                  // disable ADC                                                
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
  sleep_enable();
  noInterrupts ();                      // Do not interrupt before we go to sleep, or the
                                        // ISR will detach interrupts and we won't wake.  
  attachInterrupt (0, INT0_isr, FALLING);   //will be called when pin INT0 goes low - WORKS FINE
  attachInterrupt (1, INT1_isr, FALLING);   //will be called when pin INT0 goes low - WORKS FINE
  PCIFR  |= bit (PCIF1);                // clear any outstanding interrupts
  PCICR  |= bit (PCIE1);                // enable pin change interrupts
  EIFR = bit (INTF0);                   // clear flag for interrupt 0
  EIFR = bit (INTF1);                   // clear flag for interrupt 0
  MCUCR = bit (BODS) | bit (BODSE);     // turn off brown-out enable in software
  MCUCR = bit (BODS);                                       
  interrupts ();                            
  sleep_cpu ();
  }


void loop (){
  if (analogRead(IGN) <= 400){              //Check if Ignition voltage is off
    IGNOFF = 1;                             //Set Ignition Status OFF
    if (Status == 0){                       //Check if Routine is running
      if (PwrDwn == 0){                     //Check if Powering Down Status already set
        PWROFFtime = millis() + PWROFFint;  //Set Power Down Time
        PwrDwn = 1;                         //Set Powering Down Status YES
      } else { 
 SerPrint();
        digitalWrite (LED, HIGH);  delay (400);
        digitalWrite (LED, LOW);   delay (100);
          if (millis() >= PWROFFtime){      //Check Power Down Time
            sleep();                        //Go to sleep!
            wake ();                        //Waking up!
            Serial.println("I'M AWAKE");
            IGNOFF = 0;                     //Set Ignition Status ON    
            PwrDwn = 0;                     //Powering Down Status NO
 SerPrint();
          } //End PWROFFtime
      } //end of PwrDwn check
    } else {                                //Routine is ON, don't sleep, keep flashing with IGN off
      digitalWrite (LED, HIGH); delay (100);
      digitalWrite (LED, LOW);  delay (50);
    } //end of Status check
  } else {    
 SerPrint();
    IGNOFF = 0;                             //Set Ignition Status ON   
    PwrDwn = 0;                             //Powering Down Status NO
    digitalWrite (LED, HIGH); delay (100);  //Slower Flashing to indicate Powering Down
    digitalWrite (LED, LOW);  delay (50);
  }  //end IGN check

    switch (Status) {
      case 0:
        break;
      case 1:                           //LED Flashing Routine
          digitalWrite (RTPos, HIGH); delay (100);
          digitalWrite (RTPos, LOW);  delay (50); 
          digitalWrite (RTPos, HIGH); delay (100);
          digitalWrite (RTPos, LOW);  delay (50);
    }
 SerPrint();
  // Button Selection Routines 
  if (LTsw.onPressed()) {
    Status = 1;                         //Turns LED Routine on
    digitalWrite (RTPos, HIGH);  delay (100);
    digitalWrite (RTPos, LOW);   delay (50); 
  }
  if (RTsw.onPressed()) {
    Status = 0;                         //Turns LED Routine off
    digitalWrite (LTPos, HIGH);  delay (100);
    digitalWrite (LTPos, LOW);   delay (50);;
  }
}  //end of main loop


void INT0_isr(){
  detachInterrupt(0);
 INT0_interrupt_flag = 1;
}
void INT1_isr(){
  detachInterrupt(0);
 INT1_interrupt_flag = 1;
}

void Startup (){      //LED Flashing sequence at Startup
  digitalWrite (RTPos, HIGH); digitalWrite (LTPos, HIGH); delay (50);
  digitalWrite (RTPos, LOW);  digitalWrite (LTPos, LOW);  delay (50); 
  digitalWrite (RTPos, HIGH); digitalWrite (LTPos, HIGH); delay (50);
  digitalWrite (RTPos, LOW);  digitalWrite (LTPos, LOW);  delay (200);
  digitalWrite (RTPos, HIGH); digitalWrite (LTPos, HIGH); delay (50);
  digitalWrite (RTPos, LOW);  digitalWrite (LTPos, LOW);  delay (50); 
  digitalWrite (RTPos, HIGH); digitalWrite (LTPos, HIGH); delay (50);
  digitalWrite (RTPos, LOW);  digitalWrite (LTPos, LOW);  delay (50);
}
  
void SerPrint() 
{
  Serial.print(Status);  Serial.print('\t');
  Serial.print(IGNOFF);  Serial.print('\t');
  Serial.print(PwrDwn);  Serial.print('\t');
  Serial.print(analogRead(IGN));  Serial.println('\r');
} //End of SerPrint
ISR (PCINT8_vect)
{
  PCICR = 0;                          // cancel pin change interrupts
}

PCINT8_vect doesn't exist, so you have enabled interrupts but not supplied an appropriate handler - default action: reset.

Forgot to mention: check out Nick Gammon's tips on interrupts.

PCINT8, which is pin 14, which is pin A0 is actually pin C0. That is to say bit zero on PORTC. For a pin change interrupt to work you need to do a few things in advance. First and foremost, read the datasheet. All the answers are there. For the sake of brevity (as the mapping I think tends to get quite convoluted) PORTC is handled by the PCINT1 ISR, which needs to be activated by setting the pin change enable bit for INT1 in the pin change interrupt control register. Got that?

So PCICR |= 1 << PCIE1 will enable pin change interrupts for PORTC.

Next you need to specify which of the pins will actually cause the interrupt to fire. Do this by setting the corresponding bits in the mask register. Here at least is some consistency as the pin change mask register is also 1.

So PCMSK1 |= 1 << PCINT8 (which is actually bit zero so you could say =0B00000001) will enable interrupts for activity on this pin.

Your service routine is then handled by

ISR(PCINT1_vect)

where, as you've only activated the single pin you don't need to test to see who blew the alarm. There are other settings which you can make to specify whether the trigger is caused by rising, falling or levels, but as most hook these up in an open drain manner with input pullup, the default is falling. A simple de-bounce check when you enter the ISR which can take 8-12 ticks is to read the value of the pin. If its back to logic 1 then chances are it was a spurious hit on the line.

Have fun.

dkw

DKWatson:
PCINT8, which is pin 14, which is pin A0 is actually pin C0. That is to say bit zero on PORTC. For a pin change interrupt to work you need to do a few things in advance. First and foremost, read the datasheet. All the answers are there. For the sake of brevity (as the mapping I think tends to get quite convoluted) PORTC is handled by the PCINT1 ISR, which needs to be activated by setting the pin change enable bit for INT1 in the pin change interrupt control register. Got that?

So PCICR |= 1 << PCIE1 will enable pin change interrupts for PORTC.

Next you need to specify which of the pins will actually cause the interrupt to fire. Do this by setting the corresponding bits in the mask register. Here at least is some consistency as the pin change mask register is also 1.

So PCMSK1 |= 1 << PCINT8 (which is actually bit zero so you could say =0B00000001) will enable interrupts for activity on this pin.

Your service routine is then handled by

ISR(PCINT1_vect)

where, as you've only activated the single pin you don't need to test to see who blew the alarm. There are other settings which you can make to specify whether the trigger is caused by rising, falling or levels, but as most hook these up in an open drain manner with input pullup, the default is falling. A simple de-bounce check when you enter the ISR which can take 8-12 ticks is to read the value of the pin. If its back to logic 1 then chances are it was a spurious hit on the line.

Have fun.

dkw

I really appreciate the detailed response and taking the time. But to be honest, my head is swimming. I did read the datasheet (many times) and that seemed to make it worst. I've been experimenting for days, trying different things. Nothing is working.

At this point am lost on exactly what code to put where to: create an Interrupt on A0 that wakes it up when a voltage is applied. (RISING Signal)

I know what do with the INT0 and INT1... (Attach/Detach) I just can't understand how to do it with the PCINT. The different terms don't make sense to me... and I REALLY want to understand this for when I do future coding changes to other inputs.

From what you wrote, I THINKyou're saying I need to do the following:

  1. ENABLE Pinchangeinterrupts on the appropriate PORT (for A0 that's pin14 on the PRO MINI)

  2. ENABLE Interrupt Activity on the specific pin within that PORT

  3. Somethiing about a Service Routine...

Sorry, but I just really need a specific fix to this so I can think through how it works and why.

Bwanna:
From what you wrote, I THINKyou're saying I need to do the following:

  1. ENABLE Pinchangeinterrupts on the appropriate PORT (for A0 that's pin14 on the PRO MINI)

  2. ENABLE Interrupt Activity on the specific pin within that PORT

  3. Somethiing about a Service Routine...

I haven't checked DKWatson's "calculations", but basically yes. The point about point 3 is that the pin change interrupts are not individual interrupts - there is not a 1:1 mapping between pins and interrupts. I think for your device you have PCINT0_vect, PCINT1_vect and PCINT2_vect interrupt handlers. Each one is triggered from a separate group of pins. You need to configure the appropriate handler that is used for the pin you want to monitor - which one of the three is documented in the datasheet.

Now, if multiple pins are configured to generate PC interrupts, it is possible that one and the same interrupt handler may be called for a number of different pins. In that case, you need to do a digitalRead() to determine which of the pins has actually changed.

Since you said that you want to interrupt on RISING, you will have to detect the rising manually; so you will need to store the status of the pin, then when an interrupt occurs you will need to check to see if the new state has indicated a transition from LOW to HIGH, and in that case do whatever it is you want to do on RISING.

Did you read my link to Nick Gammon's tips? There is a short section on PC interrupt handling.

arduarn:
I haven't checked DKWatson's "calculations", but basically yes. The point about point 3 is that the pin change interrupts are not individual interrupts - there is not a 1:1 mapping between pins and interrupts. I think for your device you have PCINT0_vect, PCINT1_vect and PCINT2_vect interrupt handlers. Each one is triggered from a separate group of pins. You need to configure the appropriate handler that is used for the pin you want to monitor - which one of the three is documented in the datasheet.

Now, if multiple pins are configured to generate PC interrupts, it is possible that one and the same interrupt handler may be called for a number of different pins. In that case, you need to do a digitalRead() to determine which of the pins has actually changed.

Since you said that you want to interrupt on RISING, you will have to detect the rising manually; so you will need to store the status of the pin, then when an interrupt occurs you will need to check to see if the new state has indicated a transition from LOW to HIGH, and in that case do whatever it is you want to do on RISING.

Did you read my link to Nick Gammon's tips? There is a short section on PC interrupt handling.

Thank you. That helps. I've read Nick's stuff alot and tried many different approaches. While I prefer to code directly what I need, out of frustration last night I tried NicoHood's PinChangeInterrupt.h as was able to get it working.

Despite this, I'm going to continue late on when when I have time to try and code directly what I need.

Really appreciate the explanation. The Port groupings make sense and understanding there's a tiered approach. For this particular project the Port level interrupt is sufficient as both my breadboard and final setups use pins within the same Port.

I'll walk you through this if you want, others may be interested.

Decided not to wait for your reply. Here's a bit more discussion.

Before we start, understand that we are talking about the atmega328p microcontroller which is used in the Uno, Nano, Micro and a host of other boards. As long as you're using AVR-GCC as the compiler and include avr/io.h with the defined processor being AVR_ATmega328P the translations will work. If you're usimg the Arduino IDE or ATStudio you do not really need to know this as it is done 'under the hood' for you. As this is kind of a tutorial though, I'll mention it for completeness.

Next, to make sure we're on the same page, see that you have the datasheet handy. I'll use what I believe to be the latest version dated 11/2016. If you need it, the following link will deliver it to you.

328p datasheet 11/2016

INT0 and INT1 are the external interrupts attached to Arduino pins 2 and 3 respectively. We will not be discussing them here.

The sections of the datasheet that mean the most for this discussion begin on page 92, section 17.2.4, the Pin Change Interrupt Control Register.

Before that however, I think it useful to have a table that shows the mapping of the pins. Referring to the diagram on page 14,

PCINT Port Pin Arduino Uno

PCINT0 PB0 8
PCINT1 PB1 9
PCINT2 PB2 10
PCINT3 PB3 11
PCINT4 PB4 12
PCINT5 PB5 13
(PB6 and 7 are used for the crystal)

PCINT8 PC0 14/A0
PCINT9 PC1 15/A1
PCINT10 PC2 16/A2
PCINT11 PC3 17/A3
PCINT12 PC4 18/A4
PCINT13 PC5 19/A5
(PC6 and 7 are not avaiable on the Uno)
PCINT14 reset
PCINT15 doesn't exist

PCINT16 PD0 0
PCINT17 PD1 1
PCINT18 PD2 2
PCINT19 PD3 3
PCINT20 PD4 4
PCINT21 PD5 5
PCINT22 PD6 6
PCINT23 PD7 7

Ok so far? Go back to page 92 and read from the bottom up.

PCIE0: Pin Change Interrupt Enable 0
Handles PCINT[7:0], that's Atmel nomenclature for PCINT7, PCINT6, ... , PCINT0 which correspond the the pins on PORTB.

Similarly, PCIE1 handles PCINT[14:8] aka PORTC leaving PCIE2 for PORTD and PCINT[23:16].

Now look at the top of the page where you see the Pin Change Interrupt Control Register and the lower three bits reflect the PCIE we just talked about. Writing a 1 to any of these bit positions enables the interrupts for that port. In our case we want A0 - PCINT8 which is part of the group under PCIE1.

So you can:

PCICR |= 1 << PCIE1; or
bitSet(PCICR,PCIE1); or
bitSet(PCICR,1); or
PCICR |= 0B00000010; or
bitWrite(PCICR,1,1); or
bitWrite(PCICR,PCIE,HIGH); or
on and on ...

What you have just done is enable interrupts for the entire group (port) but as yet no interrupts are active. To activate interrupts on a specific pin in that group, you need to address the mask for that particular grouping. The masks are detailed on pages 94-96 with the mask port PCIE1 on page 95. Again skip to the bottom and read first confirming that PCMSK1 indeed enables PCINT8 thru PCINT14 and we see now from the register diagram that PCINT8 maps to bit position 0. As before we can:

PCMSK |= 1 << PCINT8; or any one of the other methods that meets your fancy.

Having done that now any level change on pin C0/A0/14/PCINT8 will fire the interrupt that deals with that group. Not here that there is one interrupt for each of the groups, not the individual pins. Any pin activated by writing a 1 to the corresponding bit position in its respective PCIE will trigger the interrupt. It is up to your interrupt service routine to determine which is the culprit. This is often done by reading the entire port and then comparing that reading with a previous value and noting the change(s?). As in this particular case we are only dealing with a single pin we know that any interrupt is a result of it changing. Turning to page 82 we see that the interrupt is at address 0x0008 which we know will jmp us to the routine named ISR(PCINT1_vect) so in your code,

ISR(PCINT1_vect)
{
// your interrupt service routine goes here
}

That just about does it. All of this presupposes that global interrupts are turned on. sei() will take care of that as will SREG |= 0B10000000;.

A couple of little extras, perhaps for convenience, switching the interrupt off/on, which you may choose to do while servicing it so as not to be sidelined;

#define enableA0 PCMSK1 |= 0B00000001
#define disableA0 PCMSK1 &= 0B11111110

Of course you can rename enableA0 and disableA0 to anything you like.

Good luck. Let me know if you're still confused. I've keyed this in as slowly as I could.

dkw