Go Down

Topic: Low power consumption (0.1 uA) but wake up on keypad press (Read 7510 times) previous topic - next topic

Nick Gammon

I've been wondering how things like remote controls, gate combination locks, calculators, etc. work. They seem to use low amounts of power, but wake up when any key is pressed.

The sketch below reproduces that behaviour. With a 16-key keypad connected (4 columns, 4 rows) it uses only 100 nA of power when asleep. However by using pin change interrupts, any keypress will wake it. It then uses the Keypad library to find which key that was, and flash LED 13 x times (where x is the key).

This low power consumption was measured on a "bare bones" board, no voltage regulator, USB interface, or anything like that.

Code: [Select]

// Wake from deep sleep with a keypress demonstration

// Author: Nick Gammon
// Date: 18th November 2012

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

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

char keys[ROWS][COLS] =
  {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'},
  };

byte rowPins[ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {2, 3, 4, 5}; //connect to the column pinouts of the keypad
 
// number of items in an array
#define NUMITEMS(arg) ((unsigned int) (sizeof (arg) / sizeof (arg [0])))

const byte ledPin = 13;

  // Create the Keypad
Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

/*

Pin change interrupts.

Pin                  Mask / Flag / Enable

D0   PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1   PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2   PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3   PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4   PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5   PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6   PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7   PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8   PCINT0 (PCMSK0 / PCIF0 / PCIE0)
D9   PCINT1 (PCMSK0 / PCIF0 / PCIE0)
D10   PCINT2 (PCMSK0 / PCIF0 / PCIE0)
D11   PCINT3 (PCMSK0 / PCIF0 / PCIE0)
D12   PCINT4 (PCMSK0 / PCIF0 / PCIE0)
D13   PCINT5 (PCMSK0 / PCIF0 / PCIE0)
A0   PCINT8 (PCMSK1 / PCIF1 / PCIE1)
A1   PCINT9 (PCMSK1 / PCIF1 / PCIE1)
A2   PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3   PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4   PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5   PCINT13 (PCMSK1 / PCIF1 / PCIE1)

*/

// 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 (PCINT22);   // pin 6
  PCMSK2 |= _BV (PCINT23);   // pin 7
  PCMSK0 |= _BV (PCINT0);    // pin 8
  PCMSK0 |= _BV (PCINT1);    // pin 9

  }  // end of setup

// set pins as keypad library expects them
// or call: kpd.initializePins ();
//    however in the library I have that is a private method

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 =  kpd.getKey();
   if (!key)
     {
     // no key pressed? go to sleep
     goToSleep ();
     return;
     }

  // confirmation we woke - flash LED number of times
  // for the appropriate pin (eg. pin 1: one time)
  for (byte i = 0; i < (key - '0'); i++)
    {
    digitalWrite (ledPin, HIGH);
    delay (500);
    digitalWrite (ledPin, LOW);
    delay (500);
    }  // end of for loop
   
  } // end of loop


A copy of this is now on my "power saving" page:

http://www.gammon.com.au/power
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

cyclegadget



Thanks Nick! You are always coming up good stuff!
Good links: Eagle tutorial= http://www.youtube.com/playlist?list=PLDE1858BD83D19C70
General Arduion tutorials = http://tronixstuff.wordpress.com
http://www.gammon.com.au/forum/bbshowpost.php?bbtopic_id=123

johnwasser

I'm having trouble finding the Keypad2 library.  Tried Google but all I found were a few places that USED it, not where is was defined.

It worked with the latest Keypad library from http://www.arduino.cc/playground/code/Keypad after changing "Keypad2.h" to "Keypad.h" but only with Board set to "Arduino UNO", not the Mega 2560
Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

robtillaart

Well done Nick!
The keypad is of course just one way to wake up the thing. Tripwire would also work.

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


Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Nick Gammon


I'm having trouble finding the Keypad2 library. 


I might have modified Keypad and called it Keypad2. I think it was because of the issue that the original one would have problems if you pushed multiple buttons at once.

As for the Mega, don't know about that unless it does something strange with ports.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Nick Gammon


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.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Zoran Djurisic

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()?

Nick Gammon

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.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Zoran Djurisic

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...

Code: [Select]

#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).

Nick Gammon

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

Code: [Select]

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. :)
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

mmcp42

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!
there are only 10 types of people
them that understands binary
and them that doesn't

Nick Gammon

@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:

Code: [Select]

void acknowledgeKeypress(byte key)
  {
   Serial.print ("Got key: ");
   Serial.println ((char) key);
  }
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Nick Gammon

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

Code: [Select]

#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
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

CrossRoads

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.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Nick Gammon

Yep, you're using a "wired OR" to share one interrupt line. It certainly should be possible to make either method work.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Go Up