I want to change from PCINT to INT0 (external int) on ATTINY85

Hello, I have a program in which I’m not doing anything yet, but I have a wdt running and waking up my IC every 8 sec. I also want to be able to wake it up with an external interrupt, but I’m can’t make it work. Right now, my code works with a pin change interrupt, but any logic change activates the interrupt. That means I create 2 interrupts when I press the button and release it.

I /masked/ 3 lines of code in my Sleep() function that are supposed to make external interrupts on INT0 (pin 2) work, but I’m puzzled! I have been reading on every register in the datasheet but I can’t make it! thanks a lot

/**************************************************************************************************************************************************************/
////LIBRARIES
/**************************************************************************************************************************************************************/

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



/**************************************************************************************************************************************************************/
////PINS
/**************************************************************************************************************************************************************/


#define switchPin  2
#define ledPin  1
#define voltagePin  4


/**************************************************************************************************************************************************************/
////VARIABLES
/**************************************************************************************************************************************************************/

//WDT variables
volatile int watchDog_counter;

//External interrupt variables
volatile int buttonFlag;
unsigned long last_interrupt_time;
int voltage;

// values are set depending on resistor values. Common resistor is 10k
#define minNb  190 //175 to 185 based on a 4.7k resistor value for UP arrow button
#define maxNb  205
#define minSize  210 //189 to 220 based on a 1k resistor value for DOWN arrow button
#define maxSize  220
#define minReset  175 //120 to 166 based on a 10k resistor value for SELECT button
#define maxReset  189


//Food Data variables

//amount of meals per day
byte meal_nb;
#define max_meal_nb 3

//amount of cylinder spins (meal size)
byte meal_size;
#define max_meal_size 5

/**************************************************************************************************************************************************************/
////OBJECTS
/**************************************************************************************************************************************************************/

/**************************************************************************************************************************************************************/
////SETUP
/**************************************************************************************************************************************************************/

void setup() {

  watchDog_counter = 0;


  //pins setup
  DDRB &= ~(1 << switchPin);
  PORTB |= (1 << switchPin);
  DDRB |= (1 << ledPin);




  //Watchdog timer setup
  //This order of commands is important and cannot be combined
  MCUSR &= ~(1 << WDRF); //Clear the watch dog reset
  WDTCR |= (1 << WDCE) | (1 << WDE); //Set WD_change enable, set WD enable
  WDTCR = 0B100001; //Set prescaler to 8 sec (see p.46 of datasheet to change prescaler), and OVERWRITE WDTCR value ( dont just use |= )
  WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int

}



/**************************************************************************************************************************************************************/
////LOOP
/**************************************************************************************************************************************************************/


void loop() {
  sleep();
  PORTB |= (1 << ledPin);
  //digitalWrite(ledPin, HIGH);
  delay(100);
  PORTB ^= (1 << ledPin);
  //digitalWrite(ledPin, LOW);

  if (buttonFlag) {
    buttonFlag = 0;
  }
}


/**************************************************************************************************************************************************************/
////FUNCTIONS
/**************************************************************************************************************************************************************/

/*Sleep function, activates external interrupts*/
void sleep() {


    /*MCUCR |= 1<<ISC01; // Falling EDGE
    GIMSK |= 1<<INT0;  // Activate the INT0
    sei();             //Active General Interrupt*/


  GIMSK |= _BV(PCIE);   
  PCMSK |= _BV(PCINT2);                   // Use PB2 as interrupt pin (PCINT3 would be 3, etc.)
  ADCSRA &= ~_BV(ADEN);                   // ADC off
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement

  sleep_enable();                         // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
  sei();                                  // Enable interrupts
  sleep_cpu();                            // sleep

  cli();                                  // Disable interrupts
  PCMSK &= ~_BV(PCINT2);                  // Turn off PB2 as interrupt pin
  sleep_disable();                        // Clear SE bit
  ADCSRA |= _BV(ADEN);                    // ADC on

  sei();                                  // Enable interrupts
} // sleep



ISR(PCINT0_vect) {

  unsigned long interrupt_time = millis(); //saves time at which this interrupt starts
  if (interrupt_time - last_interrupt_time > 200)  //compares last time interrupt was finished to now. We give 200 millis to ignore rebounds
  {

    voltage = analogRead(voltagePin);
    buttonFlag = 1; //tell the arduino a button was pressed



    /**************************************************************************************************************************************************************/
    //NB OF MEALS BUTTON IS PRESSED
    if (minNb <= voltage && voltage <= maxNb) {
      if (meal_nb < max_meal_nb) meal_nb++;
      else meal_nb = 1;
    }


    /**************************************************************************************************************************************************************/
    //SIZE OF MEALS BUTTON IS PRESSED
    if (minSize <= voltage && voltage <= maxSize) {
      if (meal_size < max_meal_size) meal_size++;
      else meal_size = 1;
    }



    /**************************************************************************************************************************************************************/
    //RESET BUTTON IS PRESSED
    if (minReset <= voltage && voltage <= maxReset) {

    }

    //SAVE WHEN THE INTERRUPT OCCURED TO PREVENT BOUNCES FROM TRIGGERING MULTIPLE INTERRUPTS
    last_interrupt_time = interrupt_time;
  }
}



ISR(WDT_vect) {
  watchDog_counter++;
}

I'm pretty sure the answer to this is on page 46-52 of the datasheet, but I can't solve it!

datasheet: http://ww1.microchip.com/downloads/en/devicedoc/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf

OK I got the external interrupt working! So nevermind the rest of this post. I think it was late and my code had a couple mistakes. I decided to modify all my registers in the SETUP and not in a separate function; it seems to do the trick. I am sharing my new functioning code here.

I still have a problem I dont understand however: I can only trigger my INT0 with low lvl interrupts??? If I setup a rising or falling edge, the interrupt doesnt trigger at all (I comment that problem in the code as you see). Anyone knows what could be causing this? I tried changing isc01 and isc00 pins at the same time, but it doesnt change the results.

/**************************************************************************************************************************************************************/
////LIBRARIES
/**************************************************************************************************************************************************************/

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



/**************************************************************************************************************************************************************/
////PINS
/**************************************************************************************************************************************************************/


#define switchPin  2
#define ledPin  1
#define voltagePin  4


/**************************************************************************************************************************************************************/
////VARIABLES
/**************************************************************************************************************************************************************/

//WDT variables
volatile int watchDog_counter;

//External interrupt variables
volatile int buttonFlag;
unsigned long last_interrupt_time;
int voltage;

// values are set depending on resistor values. Common resistor is 10k
#define minNb  190 //175 to 185 based on a 4.7k resistor value for UP arrow button
#define maxNb  205
#define minSize  210 //189 to 220 based on a 1k resistor value for DOWN arrow button
#define maxSize  220
#define minReset  175 //120 to 166 based on a 10k resistor value for SELECT button
#define maxReset  189


//Food Data variables

//amount of meals per day
byte meal_nb;
#define max_meal_nb 3

//amount of cylinder spins (meal size)
byte meal_size;
#define max_meal_size 5

/**************************************************************************************************************************************************************/
////OBJECTS
/**************************************************************************************************************************************************************/

/**************************************************************************************************************************************************************/
////SETUP
/**************************************************************************************************************************************************************/

void setup() {

  watchDog_counter = 0;


  //pins setup
  DDRB &= ~(1 << switchPin);
  PORTB |= (1 << switchPin);
  DDRB |= (1 << ledPin);




  //Watchdog timer setup
  //This order of commands is important and cannot be combined
  MCUSR &= ~(1 << WDRF); //Clear the watch dog reset
  WDTCR |= (1 << WDCE) | (1 << WDE); //Set WD_change enable, set WD enable
  WDTCR = 0B100001; //Set prescaler to 8 sec (see p.46 of datasheet to change prescaler), and OVERWRITE WDTCR value ( dont just use |= )
  WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int



  //External interrupt setup
  //Enable interrupts
  GIMSK |= (1 << INT0);
  sei();
  //set low lvl trigger (keeps triggering while button not released)
  MCUCR &= ~(1 << ISC01);
  //TRYING TO PUT THIS INSTEAD!! (FOR FALLING EDGE):  MCUCR |= (1 << ISC01);
  MCUCR &= ~(1 << ISC00);

  ADCSRA &= ~_BV(ADEN);      //Turn ADC off, saves ~230uA           
  sleep_enable();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}



/**************************************************************************************************************************************************************/
////LOOP
/**************************************************************************************************************************************************************/


void loop() {
  sleep_cpu();
  //ADCSRA |= _BV(ADEN);        //will ADC need to be turned on after sleep?
  
  PORTB |= (1 << ledPin);
  delay(100);
  PORTB ^= (1 << ledPin);
  delay(500);

  if (buttonFlag) {
    buttonFlag = 0;
  }
}


/**************************************************************************************************************************************************************/
////FUNCTIONS
/**************************************************************************************************************************************************************/




ISR(INT0_vect) { //NOT PCINT0_vect!

  unsigned long interrupt_time = millis(); //saves time at which this interrupt starts
  if (interrupt_time - last_interrupt_time > 200)  //compares last time interrupt was finished to now. We give 200 millis to ignore rebounds
  {

    voltage = analogRead(voltagePin);
    buttonFlag = 1; //tell the arduino a button was pressed



    /**************************************************************************************************************************************************************/
    //NB OF MEALS BUTTON IS PRESSED
    if (minNb <= voltage && voltage <= maxNb) {
      if (meal_nb < max_meal_nb) meal_nb++;
      else meal_nb = 1;
    }


    /**************************************************************************************************************************************************************/
    //SIZE OF MEALS BUTTON IS PRESSED
    if (minSize <= voltage && voltage <= maxSize) {
      if (meal_size < max_meal_size) meal_size++;
      else meal_size = 1;
    }



    /**************************************************************************************************************************************************************/
    //RESET BUTTON IS PRESSED
    if (minReset <= voltage && voltage <= maxReset) {

    }

    //SAVE WHEN THE INTERRUPT OCCURED TO PREVENT BOUNCES FROM TRIGGERING MULTIPLE INTERRUPTS
    last_interrupt_time = interrupt_time;
  }
}



ISR(WDT_vect) {
  watchDog_counter++;
}

JCSB:
I still have a problem I dont understand however: I can only trigger my INT0 with low lvl interrupts???

9.2
See “Code Examples” on page 6.
External Interrupts
The INT0 interrupts can be triggered by a falling or rising edge or a low level. This is set up as indicated in the
specification for the MCU Control Register – MCUCR. When the INT0 interrupt is enabled and is configured as
level triggered, the interrupt will trigger as long as the pin is held low. Note that recognition of falling or rising edge
interrupts on INT0 requires the presence of an I/O clock, described in “Clock Systems and their Distribution” on
page 23.

7.1.3 Power-down Mode
When the SM[1:0] bits are written to 10, the SLEEP instruction makes the MCU enter Power-down mode. In this
mode, the Oscillator is stopped, while the external interrupts, the USI start condition detection and the Watchdog
continue operating (if enabled). Only an External Reset, a Watchdog Reset, a Brown-out Reset, USI start condition
interupt, an external level interrupt on INT0 or a pin change interrupt can wake up the MCU. This sleep mode halts
all generated clocks, allowing operation of asynchronous modules only.

When the SM[1:0] bits are written to 10, the SLEEP instruction makes the MCU enter Power-down mode. In this
mode, the Oscillator is stopped, while the external interrupts, the USI start condition detection and the Watchdog
continue operating (if enabled). Only an External Reset, a Watchdog Reset, a Brown-out Reset, USI start condition
interupt, an external level interrupt on INT0 or a pin change interrupt can wake up the MCU. This sleep mode halts
all generated clocks, allowing operation of asynchronous modules only.

Thanks for the info! So if I want to keep power_down, I will have to find a way around multiple interrupts per button press.. I was thinking maybe going with a pin change interrupt instead, and then setting a flag so that always 1/2 of interrupts (so 1 per button press) are actually triggering what the button is supposed to do. Does that sound like something you would do?

Note that recognition of falling or rising edge interrupts on INT0 requires the presence of an I/O clock, described in "Clock Systems and their Distribution" on page 23.

That has been determined to be incorrect and confirmed by Atmel. I believe Jack Christensen (@JChristensen) discovered the discrepancy and was able to get confirmation from Atmel.

What I cannot remember is if the discrepancy applies to the t85 processor. Knowing Jack's interests I suspect it does.

The discussion took place in the Microcontrollers section.

An edge trigger works with an atmega328 but not with an attiny85 . I have also been caught by this.

Jack's thread for the curious...

Jack's thread for the curious...
http://forum.arduino.cc/index.php?topic=179873.0

An edge trigger works with an atmega328 but not with an attiny85 . I have also been caught by this.
External interrupts on SLEEP_MODE_PWR_DOWN - Project Guidance - Arduino Forum
ATTINY85 / external inerrupt INT0 / RISING - Programming Questions - Arduino Forum

Got it, thanks for the clarification. Will be useful for my next atmega328 project. For this tiny85 project, however, I need to find an efficient way for the user to trigger the button's effect only once per pressing. How would you guys do it? I had this idea:

So if I want to keep power_down, I will have to find a way around multiple interrupts per button press.. I was thinking maybe going with a pin change interrupt instead, and then setting a flag so that always 1/2 of interrupts (so 1 per button press) are actually triggering what the button is supposed to do. Does that sound like something you would do?

JCSB:
That means I create 2 interrupts when I press the button and release it.

Why would that matter?

What about setting the flag 'buttonFlag' in your ISR only if, after reading the port, that you determine the button has actually been pressed and not simply released. Anyway, move most of that logic (apart from the flag setting) out of the ISR and put it in the loop() where you can also reject unwanted button presses based on time or whatever criterion.
Or simply extend the timer that you are using to debounce the button to cover the whole period between pressing and a releasing the button.

last_interrupt_time should incidentally be declared a volatile.

6v6gt:
What about setting the flag 'buttonFlag' in your ISR only...

Why do anything in the pin change interrupt service routine?

If the processor is awake there are three possibilities: 8 seconds elapsed, button down, button up. Just check for the second two in loop when control returns from sleeping.

Or simply extend the timer that you are using to debounce the button to cover the whole period between pressing and a releasing the button.

I thought about this but I want the user to be able to press the button multiple times in a row, quite fast. So your 1st idea seems very good:

What about setting the flag ‘buttonFlag’ in your ISR only if, after reading the port, that you determine the button has actually been pressed and not simply released.

Here’s what I’ve done. Looks pretty cool and works so far (I switched to PCINT, moved everything from ISR and made sure I read PINB2 to check its low before triggering buttons effects, so nothing happens when I release, even though my “fake” button action happens when I first press (quickly flashes 5 times)

/**************************************************************************************************************************************************************/
////LIBRARIES
/**************************************************************************************************************************************************************/

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



/**************************************************************************************************************************************************************/
////PINS
/**************************************************************************************************************************************************************/


#define switchPin  2
#define ledPin  1
#define voltagePin  4


/**************************************************************************************************************************************************************/
////VARIABLES
/**************************************************************************************************************************************************************/

//WDT variables
volatile int watchDog_counter;

//External interrupt variables
volatile int buttonFlag;
unsigned long last_interrupt_time;
int voltage;

// values are set depending on resistor values. Common resistor is 10k
#define minNb  190 //175 to 185 based on a 4.7k resistor value for UP arrow button
#define maxNb  205
#define minSize  210 //189 to 220 based on a 1k resistor value for DOWN arrow button
#define maxSize  220
#define minReset  175 //120 to 166 based on a 10k resistor value for SELECT button
#define maxReset  189


//Food Data variables

//amount of meals per day
byte meal_nb;
#define max_meal_nb 3

//amount of cylinder spins (meal size)
byte meal_size;
#define max_meal_size 5

/**************************************************************************************************************************************************************/
////OBJECTS
/**************************************************************************************************************************************************************/

/**************************************************************************************************************************************************************/
////SETUP
/**************************************************************************************************************************************************************/

void setup() {

  watchDog_counter = 0;


  //pins setup
  DDRB &= ~(1 << switchPin);
  PORTB |= (1 << switchPin);
  DDRB |= (1 << ledPin);




  //Watchdog timer setup
  //This order of commands is important and cannot be combined
  MCUSR &= ~(1 << WDRF); //Clear the watch dog reset
  WDTCR |= (1 << WDCE) | (1 << WDE); //Set WD_change enable, set WD enable
  WDTCR = 0B100001; //Set prescaler to 8 sec (see p.46 of datasheet to change prescaler), and OVERWRITE WDTCR value ( dont just use |= )
  WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int



  //Pin Change interrupt setup
  //Enable interrupts
  GIMSK |= (1 << PCIE);
  PCMSK |= _BV(PCINT2);
  sei();

  //EXT INTERRUPT STUFF
  //IMSK |= (1 << PCIE);
  //set low lvl trigger (keeps triggering while button not released)
  //MCUCR &= ~(1 << ISC01);
  //TRYING TO PUT THIS INSTEAD!! (FOR FALLING EDGE):  MCUCR |= (1 << ISC01);
  //MCUCR |= (1 << ISC00);


  ADCSRA &= ~_BV(ADEN);      //Turn ADC off, saves ~230uA
  sleep_enable();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}



/**************************************************************************************************************************************************************/
////LOOP
/**************************************************************************************************************************************************************/


void loop() {
  sleep_cpu();
  //ADCSRA |= _BV(ADEN);        //will ADC need to be turned on after sleep?

  PORTB |= (1 << ledPin);
  delay(100);
  PORTB ^= (1 << ledPin);

  //  delay(500);

  if (buttonFlag) {
    buttonsAction();
    buttonFlag = 0;
  }
}


/**************************************************************************************************************************************************************/
////FUNCTIONS
/**************************************************************************************************************************************************************/




void buttonsAction() {
  unsigned long interrupt_time = millis(); //saves time at which this interrupt starts
  if (interrupt_time - last_interrupt_time > 200 && !(PINB & (1 << PB2)))  //compares last time interrupt was finished to now. We give 200 millis to ignore rebounds
  {

    voltage = analogRead(voltagePin);


    for (int i = 0; i < 10; i++) {
      PORTB ^= (1 << ledPin);
      delay(100);
    }

    /**************************************************************************************************************************************************************/
    //NB OF MEALS BUTTON IS PRESSED
    if (minNb <= voltage && voltage <= maxNb) {
      if (meal_nb < max_meal_nb) meal_nb++;
      else meal_nb = 1;
    }


    /**************************************************************************************************************************************************************/
    //SIZE OF MEALS BUTTON IS PRESSED
    if (minSize <= voltage && voltage <= maxSize) {
      if (meal_size < max_meal_size) meal_size++;
      else meal_size = 1;
    }



    /**************************************************************************************************************************************************************/
    //RESET BUTTON IS PRESSED
    if (minReset <= voltage && voltage <= maxReset) {

    }

    //SAVE WHEN THE INTERRUPT OCCURED TO PREVENT BOUNCES FROM TRIGGERING MULTIPLE INTERRUPTS
    last_interrupt_time = interrupt_time;

  }
}







ISR(PCINT0_vect) { //NOT PCINT0_vect!
  buttonFlag = 1; //tell the arduino a button was pressed
}



ISR(WDT_vect) {
  watchDog_counter++;
}
  //Watchdog timer setup
  //This order of commands is important and cannot be combined

...and the commands need to be protected by disabling interrupts.

Really?? Can you explain please? It does work for now. Do you think it is bound to fail on the long run? And how can I keep interrupts down while sleeping and the wdt or the interrupt are supposed to wake it up? Thanks!

  if (buttonFlag) {
    buttonsAction();
    buttonFlag = 0;
  }

…has a race condition. The correct sequence is…

• Disable interrupts
• Save the value of buttonFlag to a local variable
• Clear buttonFlag
• Restore interrupts
• Use the local variable for the if-statement

Hi, sorry for the late responce. Why is this a race condition? And what does that imply? Does it mean it will work most but all the time?? And this means all my modified registers lines must be placed in loop instead of setup, because I will constantly turn them on and off? Thanks!

I can't think of a way to make that race condition produce bad behavior here...

The concern is that you test buttonFlag... but between then and when you set it back to zero, the interrupt could fire and change that variable. I don't see how that can break the simple case of a button, though.

But it's something you need to be keenly aware of when you are working with interrupts. Always think about "Would my code break if an interrupt fired between two points at which I interact with this variable and changed it" - if the answer is yes, you need to disable interrupts while you copy it to a local variable.

Note that at least in AVR-land, interrupts are disabled during an ISR unless you explicitly enable them.

DrAzzy:
I can't think of a way to make that race condition produce bad behavior here...

A call to buttonsAction is skipped. The critter getting fed won't be happy. Or the person poking the button. Or both.

I'll write up details later...

I’m going with Q & A instead…

JCSB:
Why is this a race condition?

  if (buttonFlag) {
    buttonsAction();

    // <----- Button is pressed at this moment ----<

    buttonFlag = 0;
  }

What happens?