Interrupt INT0 is triggered multiple times on ATtiny10

I've been trying for dozens of hours to make a program for an ATtiny10 that does the following:

When the button is pressed for the first time, the program goes to sleep for a short time (707ms).
If during this short time the button is not pressed again then a diode will blink once and the "ON" mode will be activated: the diode will blink every 3 seconds (in sleep mode between flashes).

If the button is pressed a second time during the short time, a diode will blink once and the "OFF" mode will be activated (sleep for a super long time).

I don't understand why my "clickCount" variable always seems to be greater than 2, it should reset to zero but it doesn't.

This is not a debounce problem, I've set up a hardware debounce that I've tested with a more primitive program.

I've tested dozens of variants of this code, there must be something fundamental that I haven't understood, but I don't know what.

This is a simplified version of my code:

#define F_CPU 125000UL  // 125 KHz

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

const int shortTime = 707;

const long onTime = 3000;
const long offTime = 2147483645;

volatile long sleepCountdown = offTime;

volatile unsigned int clickCount = 0;

enum {
    ON,
    OFF
} currentMode = OFF;

void setup() {
    EICRA |= (1 << ISC01); // detect falling edge ext int on ISC0 0 and 1
    EIMSK |= (1 << INT0);  // turn on INT0 as interrupt vector
    // Set sleep mode
    SMCR |= (1 << SE);                             // enable sleep mode
    SMCR |= (0 << SM2) | (1 << SM1) | (0 << SM0);  // Power-down

    DDRB |= (1 << PB0); // put PB0 as output - it's a LED that blink
    DDRB |= (1 << PB1); // put PB1 as output - it's a debug LED that is on when clickCount == 1

    PUEB |= (1 << PUEB2); // enable pullup on PB2 - it's a button
}

void loop() {
    if (sleepCountdown > 0) {
        sleep();
    } else if (sleepCountdown == 0) {
        if (clickCount == 0) {
            buzz();
            if (currentMode == ON) {
                sleepCountdown = onTime;
            } else if (currentMode == OFF) {
                sleepCountdown = offTime;
            }
        } else if (clickCount > 0) {
            buzz();
            if (clickCount == 1) {
                currentMode = ON;
                sleepCountdown = onTime;
            } else  if (clickCount == 2) {
                currentMode = OFF;
                sleepCountdown = offTime;
            }
            clickCount = 0;
        }
    }
}

void oneBuzz() {
    PORTB |= (1 << PB0);        // PB0 ON
     _delay_ms(shortTime);
    PORTB &= ~(1 << PB0);       // PB0 OFF
}

void buzz() {
    oneBuzz();
     _delay_ms(shortTime);
    oneBuzz();
}

// WDTCSR values
const uint16_t wdtValues[] = {
    0b01100001,  // 8 s
    0b01100000,  // 4 s
    0b01000111,  // 2 s
    0b01000110,  // 1 s
    0b01000101,  // 512 ms
    0b01000100,  // 256 ms
    0b01000011,  // 128 ms
    0b01000010,  // 64 ms
    0b01000001,  // 32 ms
    0b01000000   // 16 ms
};

void sleep() {
    while (sleepCountdown > 0) {
        for (uint8_t i = 0; i < 10; i++) {
            WDTCSR = wdtValues[i];
            while (sleepCountdown > (1 << (13 - i))) {
                sleepCountdown -= (1 << (13 - i));
                power_off();
            }
        }
    }
}

void power_off() {
    cli(); // Disable interrupts
    sleep_enable();
    sei(); // Enable interrupts
    sleep_cpu();
    sleep_disable();
}

void onClick() {
    clickCount++;
    if (clickCount == 1) {
        PORTB |= (1 << PB1);        // PB1 ON
        sleepCountdown = shortTime;
    } else if (clickCount >= 2) {
        PORTB &= ~(1 << PB1);       // PB1 OFF
        sleepCountdown = 0;
    }
}

// Interrupt Watchdog Timer
ISR(WDT_vect) {}

// Interrupt Button
ISR(INT0_vect){
    onClick();
}

I think it is. Abandon interrupts for now and poll the button in loop().

what do you expect from this ?

can't you just go to sleep ?

I tried this code:

#define F_CPU 125000UL  // 125 KHz

const int buttonPin = PB2;
const int ledPin = PB1;

int buttonState = 0;
int lastButtonState = 0;
bool ledOn = false;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  buttonState = digitalRead(buttonPin);

  if (buttonState == LOW && lastButtonState == HIGH) {
    ledOn = !ledOn;
    digitalWrite(ledPin, ledOn ? HIGH : LOW);
  }

  lastButtonState = buttonState;
}

It toggle the led as expected (95% of times).

Is it what you mean as a test?

Sometimes I want my program to sleep for only for exemple 20 secondes.

The maximum sleep duration with the watchdogtimer before automatic wake up is 8192ms.
All sleep duration are multiple of 2: minimum: 16ms max: 8192ms. (16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192)

So for 20 seconds this code will make my program sleep for 8192ms, then 8192s again once it has woke up, then 2048ms, then 1024ms, then 512ms, then 32ms.

Total: 20000ms.

A more "human readable" code that apply the same logic can be found here:
https://github.com/connornishijima/TinySnore/blob/master/src/tinysnore.cpp

No.

I modified the code like this:

#define F_CPU 125000UL  // 125 KHz

#include <avr/io.h>
#include <util/delay.h>

const int shortTime = 707;

const long onTime = 3000;
const long offTime = 2147483645;

volatile long sleepCountdown = offTime;

volatile unsigned int clickCount = 0;
int previousButtonState = HIGH;

enum {
    ON,
    OFF
} currentMode = OFF;

void setup() {
    DDRB |= (1 << PB0); // put PB0 as output - it's a LED that blink
    DDRB |= (1 << PB1); // put PB1 as output - it's a debug LED that is on when clickCount == 1

    PUEB |= (1 << PUEB2); // enable pullup on PB2 - it's a button
}

void loop() {

    int buttonState = digitalRead(PB2);

    if (buttonState == LOW && previousButtonState == HIGH) {
        onClick();
    }

    previousButtonState = buttonState;

    if (sleepCountdown > 0) {
        //sleep();
    } else if (sleepCountdown == 0) {
        if (clickCount == 0) {
            buzz();
            if (currentMode == ON) {
                sleepCountdown = onTime;
            } else if (currentMode == OFF) {
                sleepCountdown = offTime;
            }
        } else if (clickCount > 0) {
            buzz();
            if (clickCount == 1) {
                currentMode = ON;
                sleepCountdown = onTime;
            } else  if (clickCount == 2) {
                currentMode = OFF;
                sleepCountdown = offTime;
            }
            clickCount = 0;
        }
    }
}

void oneBuzz() {
    PORTB |= (1 << PB0);  // PB0 ON
    _delay_ms(shortTime);
    PORTB &= ~(1 << PB0);  // PB0 OFF
}

void buzz() {
    oneBuzz();
    _delay_ms(shortTime);
    oneBuzz();
}

void onClick() {
    clickCount++;
    if (clickCount == 1) {
        PORTB |= (1 << PB1);  // PB1 ON
        sleepCountdown = shortTime;
    } else if (clickCount >= 2) {
        PORTB &= ~(1 << PB1);  // PB1 OFF
        sleepCountdown = 0;
    }
}

On the first click PB1 turn always ON, on the second click PB1 turn always OFF.

Is it the test you suggested?

Why do you want to put the device to sleep on the first button press ?
Why not simply record the time (millis()) when the button is pressed and, if there is no second press within 707ms the "ON" mode will be activated. If there is a second press, then the device goes into long sleep. Surely the first 707ms is not going to make a big difference to say current consumption

You may have another problem that edge triggered interrupts do not wake all AVR devices from sleep mode. It is then necessary to use pin change interrupts instead. The ATmega328P can be woken from sleep mode (power down) by an edge triggered interrupt. An ATTiny85 cannot. I don't know about the ATtiny10.

With complex sleep/wake behaviour, I've found it easier to use a state model. On waking it determines what the wake stimulus was and acts appropriately. An example is described in the documentation for this project: ATtiny1614 based Under Bed Light

It would make everything easier but because the ATtiny10 has a very small storage space (1024bytes) and the millis() use about 300bytes.
When my program (what I show here is a shortened simplified version) is complete, without millis() it use about 92% of the storage space.

There is "things" to interrupt sleep power down on Attiny's --> https://ww1.microchip.com/downloads/en/DeviceDoc/atmel-8127-avr-8-bit-microcontroller-attiny4-attiny5-attiny9-attiny10_datasheet.pdf (page 32, it's for ATtiny10)

I agree it makes everything easier, I have tried about a dozen versions of this code including some with several states and not just ON and OFF but I have always the same problem with my Interrupt triggered multiple times and I can't figure why.

Could the system be crashing at some point ? Can you blink a distinctive Led pattern in setup() so you can see if it does crash ?

This from the data sheet makes me believe that you will have to use a pin change interrupt to wake the device on pressing the button. You are using an edge triggered interrupt (falling). You cannot use the level triggered interrupt (LOW) because that generates multiple interrupts when the pin is held low (which is exactly what you don't appear to want).

8.2.3. Power-Down Mode
When the SMCR.SM is written to '0x010', the SLEEP instruction makes the MCU enter Power-Down
mode. In this mode, the external Oscillator is stopped, while the external interrupts and the Watchdog
continue operating (if enabled).
Only an these events can wake up the MCU:
• Watchdog System Reset
External level interrupt on INT0
• Pin change interrupt
This sleep mode basically halts all generated clocks, allowing operation of asynchronous modules only

My code was not working because of a stackoverflow.

There is only 32 bytes of SRAM in an ATtiny10, it was not enough for my code to work.

This is my code that now works, in case someone want to use parts of it for his own program:

#define F_CPU 125000UL  // 125 KHz

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

volatile unsigned long sleepCountdown;
volatile unsigned char WDTCSRValue;
volatile unsigned int sleepTime;
volatile unsigned char clickCounter;
volatile unsigned char isBuzzing;

enum {
	MEMO,
	WORK,
	BREAK,
	PRESSED_ON,    // Button has just been pressed, we stop current setSleepTime() and increment clickCounter
	PRESSED_AFTER  // Just after the actions of PRESSED_ON
} timerMode = MEMO;

void sleepWDT() {
	cli(); // Disable interrupts
	// ### Set WDT
	CCP = 0xD8;
	WDTCSR = WDTCSRValue;
	sleepCountdown -= sleepTime;
	SMCR = 0x07;  // Enable sleep mode (SLEEP_MODE_PWR_DOWN)
	sei(); // Enable interrupts
	sleep_cpu();

	cli(); // Disable interrupts
	SMCR |= (0 << SE);  // Disable sleep mode
	// ### Disable WDT
	CCP = 0xD8;
	WDTCSR = 0x00;
	sei(); // Enable interrupts
}

void turnOff() {
	cli(); // Disable interrupts
	SMCR = 0x07;  // Enable sleep mode (SLEEP_MODE_PWR_DOWN)
	sei(); // Enable interrupts
	sleep_cpu();

	cli(); // Disable interrupts
	SMCR |= (0 << SE);  // Disable sleep mode
	sei(); // Enable interrupts
}

/*
	### WDTCSR values
		0x61  // 8192 ms
		0x60  // 4096 ms
		0x47  // 2048 ms
		0x46  // 1024 ms
		0x45  // 512 ms
		0x44  // 256 ms
		0x43  // 128 ms
		0x42  // 64 ms
		0x41  // 32 ms
		0x40  // 16 ms
*/

void setSleepTime() {
	while (sleepCountdown > 16 && timerMode != PRESSED_ON) {
		if (sleepCountdown >= 8192) {
			WDTCSRValue = 0x61;
			sleepTime = 8192;
		} else if (sleepCountdown >= 4096) {
			WDTCSRValue = 0x60;
			sleepTime = 4096;
		} else { // if (sleepCountdown < 4096) {
			WDTCSRValue = 0x47; // 2048 ms
			sleepTime = 2048;
			while (sleepTime >= sleepCountdown) {
				WDTCSRValue--;
				sleepTime /= 2;
			}
		}
		sleepWDT();
	}
}

void buzz(char buzzCount) {
	isBuzzing = 1;
	for (char i = 0; i < buzzCount; i++) {
		PORTB |= (1 << PB0);   // PB0 ON
		sleepCountdown = 650;
		setSleepTime();
		PORTB &= ~(1 << PB0);  // PB0 OFF 
		sleepCountdown = 325;
		setSleepTime();
	}
	isBuzzing = 0;
}

ISR(WDT_vect) {}

ISR(INT0_vect) {
	if (isBuzzing == 0) {
		timerMode = PRESSED_ON;
	}
}

int main() {
	// ### setup() ###
	// # IN / OUT
	DDRB |= (1 << PB0);                // Set led on PB0
	PUEB |= (1 << PUEB2);              // Set button on PB2
	// # Set interrupt on button press
	EIMSK |= (1 << INT0);              // Enable INT0 as interrupt vector
	EICRA |= (1 << ISC01);             // Falling edge of INT0 generates an interrupt request
	// # Save power
	ADCSRA = 0;                        // disable ADC
	PORTB |= (1 << PB1) | (1 << PB3);  // enable pull-up resistor on PB1 and PB3 (unused)

	// ### loop() ###
	while (1) {
		switch (timerMode) {
			case PRESSED_ON:
				clickCounter++;
				sleepCountdown = 432;
				timerMode = PRESSED_AFTER;
				break;
			// After the button is pressed, and the delay between clicks is over
			case PRESSED_AFTER:
				if (clickCounter == 1) {
					clickCounter = 0;
					buzz(1);
					timerMode = MEMO;
					sleepCountdown = 969569;
				} else if (clickCounter == 2) {
					clickCounter = 0;
					buzz(2);
					timerMode = WORK;
					sleepCountdown = 1319999;
				} else { // if (clickCounter > 2) {
					clickCounter = 0;
					buzz(3);
					turnOff();
				}
				break;
			// After a timerMode is finished without button interruption
			case WORK:
				buzz(2);
				timerMode = BREAK;
				sleepCountdown = 328569;
				break;
			case BREAK:
				buzz(2);
				timerMode = WORK;
				sleepCountdown = 1319999;
				break;
			default: //case MEMO:
				buzz(1);
				sleepCountdown = 969569;
				break;
		}
		setSleepTime();
	}

	return 0;
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.