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

robtillaart:
But what about "complex" sensors like a Hall sensor - I imagine a RPM counter that sleeps most of the time ...possible???)
Or a PIR??

Any sensor that would cause a digital pin change should work (that excludes ADC conversions of course).

Bear in mind the time the processor takes to wake. I think fuse settings can alter that, but with some of them you are looking at a millisecond or more to wake. So that would be OK for a human pressing a key, not so good for a tachometer or something.

Looking at the datasheet, if you use the internal oscillator the wake-up time might only be 6 or so clock cycles, so that isn't too bad.

I am getting strange behavior with this code: on the first press on a key (any key), the led blinks as it was supposed to; on the subsequent clicks of the same key, the led does not blink... To get the led to blink, I'd have to press some other key, and then any-but-this-"other" key, to which led blinks OK. If I comment out the goToSleep() call, add else Serial.write(key), and remove blinking, every keypress returns the result fine, so hardware-wise everything seems fine.

I am using Pro Mini, the code above, 2x2 keyboard with rows on 6,7, cols at 8,9, and the latest Keypad library (3.0). As far as I can see the only difference is the Keypad2 instead of Keypad library I have.

Does anyone else have the result like mine? It is driving me nuts the whole afternoon and evening. Can it be the non-blocking nature of getKey()?

My initial reaction is that this is something to do with the keypad library not returning the same key pressed twice.

Can you post the exact code you are using please? If you change the rows and columns you also need to change the pin change interrupt masks.

Thanks for the reply! Here is the actual code I am using. Pressing different keys makes LED blink on every other keypress (On occasion, repeted keypress on the same key does make the led blink). Comment out line 136 (goToSleep), and the LED blinks on every key press...

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

const byte ROWS = 3;
const byte COLS = 2;

char keys[ROWS][COLS] = {
  {'6','3'},
  {'5','2'},
  {'4','1'}
};

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

// 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
   PCMSK2 |= _BV (PCINT22);   // pin 6
   PCMSK2 |= _BV (PCINT23);   // pin 7
   }



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);
    digitalWrite (rowPins [i], HIGH);  // rows high (pull-up)
    }  // 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
  
  // 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
  
  // put keypad pins back how they are expected to be
  reconfigurePins ();
    
  }  // end of goToSleep


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

   if (key) 
      {
         // acknowledgeKeypress(key);
      }
   else                        // no key pressed? go to sleep
      {
         goToSleep ();
         return;
      }

   acknowledgeKeypress(key);
}



void acknowledgeKeypress(byte key) {
   for (byte i = 0; i < (key - '0'); i++) {
      digitalWrite (ledPin, HIGH);
      delay (500); 
      digitalWrite (ledPin, LOW);
      delay (500); 
   }
}

For the record, I very much like this approach, as it reduces the power consumption of the Arduino Pro Mini 5V/16MHz right away by the factor of 5 - to some 4.5 mA with 9 V battery, with two leds ON, and JY-MCU HC-05 bluetooth client paired; without powerdown, the current form the battery is 19.5 mA (for the purpose of testing the above code, the bluetooth module was not connected).

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.