6

Question (TL;DR): what is the optimal method to deep sleep (to run an ATtiny45 or ATmega on batteries for 1+ year) but still be able to detect a button press? Is it possible to deep sleep until a button is pressed?

I'm using a classic "debounce" method to detect a button press:

int buttonState;
int lastButtonState = LOW;
unsigned long lastDebounceTime = 0; 
unsigned long debounceDelay = 50;  

void setup() {
  pinMode(2, INPUT);
}

void loop() {
  int reading = digitalRead(2);
  if (reading != lastButtonState) { lastDebounceTime = millis(); }
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == HIGH) {
         // Button pressed, do something here! 
         // In my actual code, it sends a packet via RF 433 Mhz 
      }
    }
  }
  lastButtonState = reading;
  // snore(20);          // TinySnore here
}

My ATtiny45 should run during 1 year on 3 AA batteries, so I added a deep sleep using TinySnore. It works, and made the consumption go from more than 1mA to 0.2mA (@1 Mhz).

I know it's possible to go down to 5 µA if I snore(1000); but then obviously it won't be able to detect a button press.

I tried various sleep values: snore(10);, snore(20);, snore(50);, etc. but I didn't get consistent results.

Thus the question: how to deep sleep until a button is pressed?


I was thinking about using attachInterrupt() but then what kind of sleep should we use? snore()? delay()?

4
  • 1
    #include <avr/sleep.h> with sleep_cpu and friends, woken up by an interrupt. Nick Gammon has a pretty extensive forum post on this: gammon.com.au/forum/?id=11497 (github.com/connornishijima/TinySnore/blob/master/src/… uses the same functions but only seems to be able to do timer-wakeups) Nov 18, 2019 at 21:32
  • Thank you @MaximilianGerhardt. In this example: gammon.com.au/forum/?id=11497&reply=4#reply4, why is there no use of attachInterrupt? Are there different methods?
    – Basj
    Nov 18, 2019 at 21:43
  • 1
    Instead of attachInterrupt(), the interrupt service routine (ISR) for the necessary vectors are hooked directly, e.g. ISR (PCINT0_vect) { .. }. An example with attachInterrupt is in the first post with the code labeled Waking from sleep with a signal, which matches your button better. When an interrupt is configured for a pin, a change event will automatically wake the CPU from sleep. See datasheet (farnell.com/datasheets/1698186.pdf) section 7.1 "Sleep Modes", "INT0 and pin change" Nov 18, 2019 at 21:55
  • Just a caution that putting a standard ATMEGA. Board into sleep mode leaves the supporting circuitry (USB interface etc) drawing power so you would be better to go for the ATTINY.
    – kiwiron
    Feb 20 at 23:20

3 Answers 3

7

ATTINY's are designed to easily do this since they can wake from power down sleep mode by a pin change...

enter image description here

Basically you want to...

  1. Connect a normally-open push button between an IO pin and ground.
  2. Enable the pull-up on the IO.
  3. Enable the pin change interrupt on the IO pin.
  4. Enable interrupts.
  5. Enter "power down" sleep mode.

When the button is pushed, it will connect the IO pin to ground, which will cause a pin change interrupt, which will wake the chip from sleep.

Note that you do not need to do anything in the pin change ISR (you can use the EMPTY_INTERRUPT compiler macro). If the ISR is empty, execution will pick up on the line after the sleep upon waking.

While in deep sleep, the chip will use an astonishingly small amount of power. For example, an ATTINY25/45/85 running off of a 3V coin cell at room temp will use less than 0.2uA...

enter image description here

A typical CR2032 coin cell is rated for 235mAh to 2V, so at this rate we'd expect to be able to wait for a button press like this for 100+ years.

IRL the battery will likely self discharge and physically deteriorate long before then (rated at 1% per year), but still not too shabby.

enter image description here

1
2

First I was unable to make the intterupt work for multiple pins, but finally here is a working code:

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

ISR(PCINT0_vect) {
  if (digitalRead(0) == LOW)           // # PB0 = pin 5 pressed => LED on
    digitalWrite(4, HIGH);
  else if (digitalRead(1) == LOW)      // # PB1 = pin 6 pressed => LED off
    digitalWrite(4, LOW);
  else if (digitalRead(2) == LOW)      // # PB2 = pin 6 pressed => LED on
    digitalWrite(4, HIGH);
}

void setup() {  
  pinMode(4,OUTPUT); // LED
  pinMode(0,INPUT_PULLUP);
  pinMode(1,INPUT_PULLUP);
  pinMode(2,INPUT_PULLUP);
  ADCSRA = 0; // ADC disabled
  GIMSK = 0b00100000;  // General Interrupt Mask Register, / Bit 5 – PCIE: Pin Change Interrupt Enable / When the PCIE bit is set (one) and the I-bit in the Status Register (SREG) is set (one), pin change interrupt is enabled. Any change on any enabled PCINT[5:0] pin will cause an interrupt. The corresponding interrupt of Pin Change Interrupt Request is executed from the PCI Interrupt Vector. PCINT[5:0] pins are enabled individually by the PCMSK0 Register. / see https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf
  PCMSK = 0b00000111;  
} 

void loop() {
  sleep_enable();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  
  sleep_cpu(); 
}

Note: PCINTs are handled by a single PCINT_vect here.

PS: as mentioned by Maximilian in a comment, this post gives nice solutions: Detecting key presses on a keypad whilst asleep.

2

I would put the MCU in deep sleep and then use the reset button.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.