Pin Change Interrupt works only once?

Hi,

I'm having a problem with SLEEP_MODE_PWR_DOWN and a pin change interrupt wake up.

I have a little egg timer project based on an attiny 4313. I am using CB's arduino-tiny core. My IDE is 1.6.5.

So the code I have now works like this. At the start the 4 digit display shows 0002 (short egg time for debugging.) I have three buttons (start, add minute, add second/clear display). I hit the start button and the display counts down as expected. 0001... 0000... and the piezo buzzer rings. Great so far. then there is a slight delay and the Attiny goes to sleep.

Now when I hit start the Attiny wakes up and the display starts at 0002 which seems reasonable. but I'm not sure what state the program is in now. If I hit start nothing happens. If I hit 'add minute' the minute appears on the display like 0102, but when I hit start the display goes back to 0002 and nothing happens.

I can get out of this state now by adding more than 4 seconds.... co if i hit the add second button 4 times and get to this: 0006, and then hit the start button the timer counts down 0005.... 0004.... 003... 0002... 0001... 0000... beeeep. Great. Until after a brief delay the Tiny goes back to sleep and now will not wake at all.

Can anyone tell me where I'm going wrong here. Is there a flag I have to clear after an INT to allow it to go off again?
I took most of the sleep code from Jack's MillionOhms project, and all the credit for the stuff that works goes to him. The mistakes I am sure are all mine.

Here is my code, the sleep stuff is in a function near the bottom and the ISR is the last function. The rest is a lot of bit twiddling to write to the display:

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


boolean button1state = 0;
boolean lastButton1state = 0;
boolean button2state = 0;
boolean lastButton2state = 0;
boolean button3state = 0;
boolean lastButton3state = 0;

boolean timeIsRunning = 0;

int minutes = 0;
int seconds = 2;
unsigned long oneSecond = 1000UL;
unsigned long startTime = 0;
unsigned long lastCheck = 0;

// Hardware Setup:
// A0 address select PA1
// A1 address select  PA0
// WR   PB0
// SW_1   PB1
// SW_2   PB2
// SW_3   PB3

void setup() {
  //seconds = seconds + 60 * minutes;   // 90 seconds
  setupPins();
  blankDisplay();
}

void setupPins() {
  seconds = seconds + 60 * minutes;
  DDRD = 0b01111111;
  PORTD = 0b00000000;   //PORTD is the ASCII data for the display, set output and start LOW
  DDRA = 0b00000011;    //PA0 & PA1 output
  DDRB = 0b00000001;    //PB0 output
  PORTB = 0b00001110;   //Pullups on switches
  lastCheck = millis();
}

void loop() {
  button1state = digitalRead(10);
  button2state = digitalRead(11);
  button3state = digitalRead(12);
  if (button1state != lastButton1state) {
    if (button1state == LOW) {
      addMinute();
    }
    lastButton1state = button1state;
  }
  if (timeIsRunning == 0) {
    if (button2state != lastButton2state) {
      if (button2state == LOW) {
        addSecond();
      }
      lastButton2state = button2state;
    }
  } else {
    if (button2state != lastButton2state) {
      if (button2state == LOW) {
        resetTimer();
      }
      lastButton2state = button2state;
    }
  }

  if (button3state != lastButton3state) {
    if (button3state == LOW) {
      startTimer();
    }
    delay(5);
    lastButton3state = button3state;
  }

  if (timeIsRunning == 1) {
    updateTime();
  } else {
    displayRemainingTime();
  }


}

void updateTime() {
  if (millis() - lastCheck >= oneSecond)
  {
    seconds--;
    lastCheck = millis();
    if (seconds < 0) {
      endTime();
      seconds = 5;
    }
    unsigned long timeMinutes = floor(seconds / 60);
    unsigned long timeSeconds = seconds - (timeMinutes * 60);
    int digit4 = timeSeconds % 10;
    int digit3 = (timeSeconds / 10) % 10 ;
    int digit2 = (timeMinutes) % 10;
    int digit1 = (timeMinutes / 10) % 10 ;

    PORTA = 0b00000011;
    PORTB &= ~0x01;        //take WR pin LOW
    PORTD = digit1 + '0';
    PORTB |= 0x01;        //take WR pin HIGH

    PORTA = 0b00000001;
    PORTB &= ~0x01;
    PORTD = digit2 + '0';
    PORTB |= 0x01;

    PORTA = 0b00000010;
    PORTB &= ~0x01;
    PORTD = digit3 + '0';
    PORTB |= 0x01;

    PORTA = 0b00000000;
    PORTB &= ~0x01;
    PORTD = digit4 + '0';
    PORTB |= 0x01;
  }
}

void displayRemainingTime() {
  unsigned long timeMinutes = floor(seconds / 60);
  unsigned long timeSeconds = seconds - (timeMinutes * 60);
  int digit4 = timeSeconds % 10;
  int digit3 = (timeSeconds / 10) % 10 ;
  int digit2 = (timeMinutes) % 10;
  int digit1 = (timeMinutes / 10) % 10 ;
  PORTA = 0b00000011;
  PORTB &= ~0x01;
  PORTD = digit1 + '0';
  PORTB |= 0x01;

  PORTA = 0b00000001;
  PORTB &= ~0x01;
  PORTD = digit2 + '0';
  PORTB |= 0x01;

  PORTA = 0b00000010;
  PORTB &= ~0x01;
  PORTD = digit3 + '0';
  PORTB |= 0x01;

  PORTA = 0b00000000;
  PORTB &= ~0x01;
  PORTD = digit4 + '0';
  PORTB |= 0x01;
  delay(50);
}

void addMinute() {
  seconds +=  60;

}

void resetTimer() {
  timeIsRunning = 0;
  seconds = 0;
}

void addSecond() {
  seconds += 1;
}

void startTimer() {
  timeIsRunning = !timeIsRunning;
}

void endTime() {
  timeIsRunning = 0;
  seconds = 0;
  tone(13, 2800, 400);
  delay(1000);
  goToSleep();
}

void blankDisplay() {
  //digit3
  PORTA = 0b00000011;
  PORTB &= ~0x01;
  PORTD = ' ';
  PORTB |= 0x01;
  //digit2
  PORTA = 0b00000001;
  PORTB &= ~0x01;
  PORTD = ' ';
  PORTB |= 0x01;
  //digit1
  PORTA = 0b00000010;
  PORTB &= ~0x01;
  PORTD = ' ';
  PORTB |= 0x01;
  //digit0
  PORTA = 0b00000000;
  PORTB &= ~0x01;
  PORTD = ' ';
  PORTB |= 0x01;
}



void displayChar(const char myChar, int myPos) {
  switch (myPos) {
    case 0:
      PORTA = 0b00000011;
      break;
    case 1:
      PORTA = 0b00000001;
      break;
    case 2:
      PORTA = 0b00000010;
      break;
    case 3:
      PORTA = 0b00000000;
      break;
  }
  PORTB &= ~0x01;
  PORTD = myChar;
  PORTB |= 0x01;
}

void goToSleep() {
  blankDisplay();
  DDRD = 0b00000000;
  DDRA = 0b00000000;
  DDRB = 0b00000000;
  PORTB = 0b00001000;   //leave pullup on PCINT3.


  GIMSK |=  (1 << INT0);                     //enable INT0
  GIMSK |=  (1 << PCIE0);                    // enabel pin Change 0
  MCUCR |=  (1 << ISC00) | (1 << ISC01);    // any logic change on INT 0
  PCMSK |=  (1 << PCINT3);

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  cli();

  sei();                         //ensure interrupts enabled so we can wake up again
  sleep_cpu();
  //----zzzz----zzzz----zzzz----zzzz
  cli();                         //wake up here, disable interrupts
  GIMSK = 0x00;                  //disable INT0
  sleep_disable();
  sei();
  setupPins();
  
}


ISR(INT0_vect)
{
  GIMSK = 0x00;                 //disable INT0
}

Thanks,

Jimmy

ISR(INT0_vect)

I believe Pin Change Interrupt Vector names start with "PC".

I took most of the sleep code from Jack's MillionOhms project

I believe Jack typically uses reset to wake from sleep. There are a few reasons that method works better than trying to wake from a pin-change interrupt. There is, however, a risk for your project with that method.

Have you considered doing something similar?

Thanks Coding Badly. I have done projects in the past where I use PWR_DOWN for sleep and reset to start over. I should have done this here, but the boards are made and I didn't put a switch on Reset.

I wonder why the INT0_vect seems to reset the display. I'll try PCINT0 and see if it behaves differently.

Thanks for taking a look!

Jimmy

Adding this:

ISR(PCINT0_vect)
{
  GIMSK = 0x00;                 //disable INT0
}

Wakes it up and sets the time to 0005, which is progress, and what I expect, but after the second sleep, it won't wake again. So it only wakes once.

Thanks,

Jimmy

Well, here's an update on this one just for completeness. I still don't understand what is going on, but I have come up with a working(?) solution.

I took Coding Badly's advice and added a vector for PCINT

ISR(PCINT0_vect)
{
}

ISR(INT0_vect)
{
}

I don't know why, but it works if I have both interrupt vectors. I'll need to try to figure out which one is being called, or in what order.

The key factor I think was in not trying to manipulate the Interrupt Registers from within the ISR. Once I removed

GIMSK = 0x00;

from within the interrupt, everything started working as I expected. (except for the fact that I have both INT0 and PCINT3 vectors.)

Thanks Coding Badly for helping,

Jimmy