Low power consumption (0.1 uA) but wake up on keypad press

I'm reproducing your results, so I'm now working out why. I've added some debugging displays, which are interesting:

Awake!
Got key: 1
Awake!
Got key: 4
Awake!
Awake!
Got key: 4
Awake!
Awake!
Awake!
Got key: 4

First, we get an "awake" message (at the end of the sleep routine) which shows it wakes up but doesn't detect a valid press (for some reason).

Second, I can press key 4 and it doesn't always wake up. So, it seems there are two problems. :slight_smile:

just looked at the posting on your website
amazing piece of work
many thanks for taking the time to research and document
you, sir, are a gent, and no mistake!

@mmcp42: Thanks!

@Zigmund: After re-testing I found one major problem was simply that the way I had written it, it took a second per debugging blink. So if you press button 9, it spends 9 seconds blinking LED 13. So therefore if you quickly press that button again nothing happens.

Try taking out the slow blinking code, and replacing with serial debugging (of course, you need to do a Serial.begin in setup), like this:

void acknowledgeKeypress(byte key) 
  {
   Serial.print ("Got key: ");
   Serial.println ((char) key);
  }

Here's the exact code I tested with (it was a 4 x 3 keypad I had handy):

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

const byte ROWS = 4;
const byte COLS = 3;

char keys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'},
  
};  // end of keys

byte rowPins[ROWS] = { 5, 6, 7, 8 };
byte colPins[COLS] = { 9, 10, 11 }; 

// number of items in an array for pin re-configuration between sleep and keypad routines
#define NUMITEMS(arg) ((unsigned int) (sizeof (arg) / sizeof (arg [0])))

const byte ledPin = 13;

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// we don't want to do anything except wake
EMPTY_INTERRUPT (PCINT0_vect)
EMPTY_INTERRUPT (PCINT1_vect)
EMPTY_INTERRUPT (PCINT2_vect)

void setup()
   {
   pinMode (ledPin, OUTPUT);

   // pin change interrupt masks (see above list)
   PCMSK2 |= _BV (PCINT21);   // pin 5  - PORTD mask 0x20
   PCMSK2 |= _BV (PCINT22);   // pin 6  - PORTD mask 0x40
   PCMSK2 |= _BV (PCINT23);   // pin 7  - PORTD mask 0x80
   PCMSK0 |= _BV (PCINT0);    // pin 8  - PORTB mask 0x01
   
   Serial.begin (115200);
   }


void reconfigurePins ()
  {
  byte i;
  
  // go back to all pins as per the keypad library
  
  for (i = 0; i < NUMITEMS (colPins); i++)
    {
    pinMode (colPins [i], OUTPUT);
    digitalWrite (colPins [i], HIGH); 
    }  // end of for each column 

  for (i = 0; i < NUMITEMS (rowPins); i++)
    {
    pinMode (rowPins [i], INPUT);
    digitalWrite (rowPins [i], HIGH); 
    }   // end of for each row

  }  // end of reconfigurePins

void goToSleep ()
  {
  byte i;
   
  // set up to detect a keypress
  for (i = 0; i < NUMITEMS (colPins); i++)
    {
    pinMode (colPins [i], OUTPUT);
    digitalWrite (colPins [i], LOW);   // columns low
    }  // end of for each column

  for (i = 0; i < NUMITEMS (rowPins); i++)
    {
    pinMode (rowPins [i], INPUT_PULLUP);
    }  // end of for each row
    
   // now check no pins pressed (otherwise we wake on a key release)
   for (i = 0; i < NUMITEMS (rowPins); i++)
    {
    if (digitalRead (rowPins [i]) == LOW)
       {
       reconfigurePins ();
       return; 
       } // end of a pin pressed
    }  // end of for each row
  
  Serial.println ("Going to sleep ...");
  Serial.print ("PINB = ");
  Serial.println (PINB, HEX);
  Serial.print ("PIND = ");
  Serial.println (PIND, HEX);
  
  // overcome any debounce delays built into the keypad library
  delay (50);
  
  // at this point, pressing a key should connect the high in the row to the 
  // to the low in the column and trigger a pin change
  
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
  sleep_enable();

  byte old_ADCSRA = ADCSRA;
  // disable ADC to save power
  ADCSRA = 0;  

  PRR = 0xFF;  // turn off various modules
   
  PCIFR  |= _BV (PCIF0) | _BV (PCIF1) | _BV (PCIF2);   // clear any outstanding interrupts
  PCICR  |= _BV (PCIE0) | _BV (PCIE1) | _BV (PCIE2);   // enable pin change interrupts
   
  // turn off brown-out enable in software
  MCUCR = _BV (BODS) | _BV (BODSE);
  MCUCR = _BV (BODS); 
  sleep_cpu ();  
 
  // cancel sleep as a precaution
  sleep_disable();
  PCICR = 0;  // cancel pin change interrupts
  PRR = 0;    // enable modules again
  ADCSRA = old_ADCSRA; // re-enable ADC conversion

  Serial.println ("Awake!");
  Serial.print ("PINB = ");
  Serial.println (PINB, HEX);
  Serial.print ("PIND = ");
  Serial.println (PIND, HEX);
  
  // put keypad pins back how they are expected to be
  reconfigurePins ();
    
  }  // end of goToSleep

void loop()
{
   byte key = keypad.getKey();

   if (!key) 
      {
       goToSleep ();
       return;
      }

   acknowledgeKeypress(key);
}

void acknowledgeKeypress(byte key) 
  {
   Serial.print ("Got key: ");
   Serial.println ((char) key);
  }

In case it matters, here is a copy of the Keypad2 library I used (not sure where it came from):

http://gammon.com.au/Arduino/Keypad2.zip

That's similar to what I did for my 16-button remote control.
I built it around a 8MHz promini and 434 MHZ RF transmitter, with MAX1811 for LiPo battery charging.
Pressing a button pulls a row low, creates a hardware interrupt to wake up the processor, the keypad library figures out the key pressed, sends it out via virtualwire, and goes back into power down sleep mode. Found out I had to write the columns low when going into sleep mode, then back high on wake up, otherwise an interrupt was not created as the columns were left high by the keypad library.

Yep, you're using a "wired OR" to share one interrupt line. It certainly should be possible to make either method work.

Technically "wired AND", any input low makes the output low.
I did it that way because I was having trouble making PCINTs wake the part up from Power Down Sleep Mode.
Might have been a coding issue at the time, missing <interrupt.h> or something. Never got PCINTs to work until you & skyjumper helped me with them a year later for one of Aaron's cards.

It does not seem to be LED blinking issue (I am actually looking at the single byte sent to serial, communicating at 57600 baud)

It still does not work quite right for me, but there is quite a bit of difference between keypad and keypad2 libraries, keeping everything else in teh code the same. Using the Keypad, the first keypress always registers, but then 5-out-of-100 keypresses on the same key register; with Keypad2 library, after the first keypress that always seem to register, too, 85-out-of-100 keypresses on the same key register. With goToSleep() commented out, all the keypresses register, so it is not bad keyboard :slight_smile: The above numbers came out of 500 keypresses... Just wanted to share before I take a look at the two keypad library codes.

The code in my sketch which reconfigures the pins back to how they need to be for the keypad library, may need reworking if the other library does things differently. For the sleep, I set the columns low and the rows high, so we can detect a change (when a row connects to a column). Afterwards things have to go back to how the keypad library wants them. Notice how in keypad.cpp it initializes the columns differently:

void Keypad::initializePins() {
    // Configure column pin modes and states. Row pins get configured
    // in scanKeys(). See explanation there.
    // See http://arduino.cc/forum/index.php/topic,95027.0.html for an explanation
    // of why changing the column pins to INPUTs prevents inter-column shorts.
    for (byte C=0; C<sizeKpd.columns; C++) {
        pin_mode(columnPins[C],INPUT);
        pin_write(columnPins[C],HIGH);
    }
}

I was using output, he is using input.

I GOT IT!!!!

In the main loop I added a 10 ms delay after waking up, and no more missed keypresses when using your Keypad 2 library. I'm happy.

   if (!key) 
      {
       goToSleep ();
       delay(10);
       return;
      }

Using the Keypad 3.0 (and Nick, yes, thank you for the pointer above, I did miss the different cols setting, but fixing it did not change the code behavior), with 10 ms delay, it misses much fewer keypresses on wakeup, but it still misses quite a few, about 3-out-of-4.

So you are saying it is totally fixed? Provided you use the Keypad2 library?

Well that explains why it worked for me. (It's not my library I don't think, I can't remember where it came from).

Hi Nick & Everyone,

I've been following this post with great interest. Of course I'm finding the low power stuff to be very interesting but equally as important I was discovering what impact my changes have made between the two versions of the library.

Nick: You are using version 1.8 of the library (Keypad2) which had the original problem with shorted pins when pressing multiple keys. Since then I've implemented your suggestions so the latest version, 3.0, includes those fixes.

Zigmund: Along the way I figured out that I could drastically improve the number of times the loop runs per second by restricting the number of calls to the library to once every 10 milliseconds (mS) by default. The default can be changed by setDebounceTime(1) which sets the debounce time to 1mS which is the minimum. By reducing the debounce time it allows getKey() to be called more frequently. I would love to hear how it affects the responsiveness when using version 3.0 of the library. I may need to change my method of limiting.

You should be able to keep the keypad processing low by doing what the low-power stuff does. Set all columns low and all rows high-pullup. Then a quick test confirms whether or not a key is pressed. Then a longer loop could work out exactly which key.

Sounds like the sleep code should be incorporated into a Keypad library. A variant of keypad.getkey() could be used to sleep until a key is pressed. It would probably be necessary to allow the depth of sleep to be adjusted.

The danger with that idea (nice though it is) is that to use the pin-change interrupts you need the interrupt handler. Then it could clash with things like software serial. Although in my code I used an "empty" ISR. I'm not sure what would happen if I omitted it entirely. The interrupt vector must go somewhere.

Judging by the generated code, if you don't put an interrupt handler in it generates a jump to 'bad_interrupt', which then jumps back to 0 (the reset interrupt vector). However in my testing, I don't actually seem to get a reset as a print in setup does not display again.

mstanley:
I could drastically improve the number of times the loop runs per second by restricting the number of calls to the library to once every 10 milliseconds (mS) by default. The default can be changed by setDebounceTime(1) which sets the debounce time to 1mS which is the minimum. By reducing the debounce time it allows getKey() to be called more frequently. I would love to hear how it affects the responsiveness when using version 3.0 of the library.

Mark: with the debounce time set to 50 ms in 3.0 library, only 13 out of 100 keypresses get registered. Reducing the debounce time to 10 ms gets 56 of 100 keypresses, 5 ms - 96 of 100, and 1 ms - 96 out of 100 as well.

Hey Nick,

After running your sleep code successfully on Pro Mini and Nano boards, I endeavored into ATtiny-based solution (4313 in particular). The code won't compile, complaining about WDTCSR, PCIFR, PCIF0, ..., BODS, BODSE not declared. Do you know where are these declared, so I can try updating for ATtiny 4313?

AVR registers are defined in hardware/tools/avr/avr/include/avr/io.h

It has:

#elif defined (__AVR_ATtiny2313__)
#  include <avr/iotn2313.h>

I don't see anything specifically for the ATtiny4313.

Thanks John!

The ATtiny4313 seems to be implemented in the most recent WinAVR (2010-01-10) - not the one that comes with Arduino, but the latest in SourceForge. io.h does contain

#elif defined (__AVR_ATtiny4313__)
#  include <avr/iotn4313.h>

and there is iotn3413.h The registers seem there, just some (vaguely straightforward) footwork needed to modify Nick's code and reference chip-appropriate registers.

Atmel has published app notes on migration from 2313 to 4313 here: http://www.atmel.com/Images/doc8283.pdf. From the first brief reading it seems straightforward, but there are a few things I don't completely understand, like brown-out register bits 0 and 1: the Atmel papers refer to them as BODSE and BODS, and AVR io file has them as BPDS and BPDSE. Typo or my superficial understanding?