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();
}
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.
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.
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