Trouble reading button press after waking up from LowerPower.powerDown

Hi everyone,

I am using a Moteino (basically an ATMega328P connected to an RFM69 radio) to create a two button remote. There is an “up” button and a “down” button.

I successfully attached an interrupt on D3 and used the enable interrupts library to enable another interrupt on D5 for each of the buttons. This worked reliably to trigger the Moteino to send out a message.

Then I introduced the “OneButton” library to detect single / double / long clicks to change the message sent out by the radio. This also worked reliably and the library appears to use some simple millis() calculations to “debounce” and determine what type of click.

Now, lastly, I am trying to use the LowerPower library to put the Moteino and radio to sleep. It should wake upon a button press and then detect single/double/long click and send the message as it was doing before. However, this is where I am running into issues.

In the code below I removed the functions for the double and long click just to simplify the troubleshooting process. It should now only look for single clicks.

The Moteino successfully wakes up upon pressing either button. I know this works because of the quick LED flash I added in after it wakes up. However, the only way I can get the single click functions to call is if I quickly release and press the button again during the time that the LED is flashed on/off. If I remove this LED flash code, then everything happens too fast and I can’t release and press again fast enough. Holding the button down will not trigger the single click to flash.

It is almost like I have to press the button once to trigger the interrupt, and then press it again quickly to get it to register the button press. This was not an issue before I tried putting the Moteino to sleep. When I was only using the interrupt to test the button click it worked perfectly.

I’m assuming this is some kind of timing issue or millis() problem after the controller wakes up? Any help would be appreciated! Thank you

#define EI_NOTEXTERNAL
#include <EnableInterrupt.h>
#include <LowPower.h>
#include <OneButton.h>
#include <RFM69.h>
#include <SPI.h>

// Setup a new OneButton on pin D5 (UP BUTTON) --> true parameter enables the pullup resistor on this pin
OneButton button1(5, true);

// Setup a new OneButton on pin D3 (DOWN BUTTON) --> true parameter enables the pullup resistor on this pin
OneButton button2(3, true);

#define UPPIN 5
#define DOWNPIN 3

// Addresses for this node. CHANGE THESE FOR EACH NODE!
#define NETWORKID     0   // Must be the same for all nodes
#define MYNODEID      2   // My node ID
#define TONODEID      1   // Destination node ID

// RFM69 frequency
#define FREQUENCY     RF69_915MHZ

// AES encryption (or not):
#define ENCRYPT       true // Set to "true" to use encryption
#define ENCRYPTKEY    "TOPSECRETPASSWRD" // Use the same 16-byte key on all nodes

// Use ACKnowledge when sending messages (or not):
#define USEACK        true // Request ACKs or not

// Packet sent/received indicator LED (optional):
#define LED           4 // LED positive pin
//#define GND           8 // LED ground pin

// Create a library object for our RFM69HCW module:
RFM69 radio;

int sendlength = 1;
char message[] = "1";

volatile bool newbutton = false;

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/


void setup() {

  pinMode(LED,OUTPUT);
  digitalWrite(LED,LOW);
  
  // link the button 1 functions.
  button1.attachClick(click1); //single click


  // link the button 2 functions.
  button2.attachClick(click2); //single click


  //Initialize the RFM69HCW:
  radio.initialize(FREQUENCY, MYNODEID, NETWORKID);
  
  // Turn on encryption if set to true above
  if (ENCRYPT)
    radio.encrypt(ENCRYPTKEY);

} 

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void upinterrupt(){
  newbutton = true;
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void downinterrupt(){
  newbutton = true;
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void loop() 
{
   digitalWrite(LED,LOW); //make sure the LED is off before going to sleep indefinitely.
  
//These interrupts will wake the module when it is powered down to save battery
  enableInterrupt(UPPIN, upinterrupt, FALLING);      //enables interrupt on UPPIN and calls upinterrupt
  attachInterrupt(digitalPinToInterrupt(DOWNPIN), downinterrupt, FALLING);   //enables interrupt on DOWNPIN and calls downinterrupt

  radio.sleep();                                          //Put the RFM69 to sleep
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);    //Put Moteino to power down sleep. Will wake on interrupt and run the next line of code after ISR

//First line to run after the ISR that wakes the module:

  disableInterrupt(UPPIN);
  detachInterrupt(DOWNPIN);                               //Prevents interrupts being called again until everything is complete

digitalWrite(LED,HIGH);
delay(100);
digitalWrite(LED,LOW);
delay(250);
  
  if(newbutton){                                          //ISR that wakes up module will set this to true
  button1.tick(); //check button1 (UP) for click, double click, hold
  button2.tick(); //check button1 (UP) for click, double click, hold      //checking to see what button was pressed and if click/doubleclick/hold
  }
  

  
} // loop

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

//UP BUTTON PRESSED

void click1() {

    
  message[0] = '1';
  if (radio.sendWithRetry(TONODEID, message, sendlength)){
     digitalWrite(LED,LOW);
          delay(150);
          digitalWrite(LED,HIGH);   //ack received, quick confirm flash
          delay(250);
          digitalWrite(LED,LOW);
          delay(150);
          digitalWrite(LED,HIGH);
          delay(250);
          digitalWrite(LED,LOW);
          }
        else{                         //no ack received, assume receiver did not get message. Slow flash.
          digitalWrite(LED,HIGH);
          delay(1000);
          digitalWrite(LED,LOW);
          }
   newbutton = false;
                   
   
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

// DOWN BUTTON PRESSED

void click2() {

  
  message[0] = '3';
  if (radio.sendWithRetry(TONODEID, message, sendlength)){
     digitalWrite(LED,LOW);
          delay(150);
          digitalWrite(LED,HIGH);   //ack received, quick confirm flash
          delay(250);
          digitalWrite(LED,LOW);
          delay(150);
          digitalWrite(LED,HIGH);
          delay(250);
          digitalWrite(LED,LOW);
          }
        else{                         //no ack received, assume receiver did not get message. Slow flash.
          digitalWrite(LED,HIGH);
          delay(1000);
          digitalWrite(LED,LOW);
          }
   newbutton = false;
}
 // click2

You're going to need to post a link to the OneButton library you are using, so we can see what it does/needs to keep track of while you are snoozing.

For a general pin, using pin change interrupts, CHANGE is the only interrupt option. Could you be getting multiple interrupts (due to button bounce)?

Much better to disable that particular interrupt than to use detach:

  detachInterrupt(DOWNPIN);                               //Prevents interrupts being called again until everything is complete

Thanks for the replies guys. I went through the OneButton library closer:

https://github.com/mathertel/OneButton/blob/master/src/OneButton.cpp

and realized that the .tick() function needs to be looped.

In my code I am only allowing it to run once before going back to sleep.

rather than the:

if(newbutton)

I need to use:

while(newbutton)

Then it can properly check the conditions for a click/doubleclick/etc until the appropriate function is called and I reset newbutton to false, allowing the loop to end and the module to return to sleep.

I will try this tonight!

For those who run into the same issue and want to power down between button clicks and use this handy “Onebutton” library, here is a general sketch that I got to work.

I had to edit the library, see attached, to create a new function that gets called when a debounce error is detected. This allows the while loop to exit and the uC to go back to sleep without getting caught in the loop. Makes the sketch more low power friendly.

#define EI_NOTEXTERNAL
#include <EnableInterrupt.h>
#include <LowPower.h>
#include <OneButton.h>
#include <RFM69.h>
#include <SPI.h>

// Setup a new OneButton on pin D5 (UP BUTTON) --> true parameter enables the pullup resistor on this pin
OneButton button1(5, true);

// Setup a new OneButton on pin D3 (DOWN BUTTON) --> true parameter enables the pullup resistor on this pin
OneButton button2(3, true);

#define UPPIN 5
#define DOWNPIN 3

// Addresses for this node. CHANGE THESE FOR EACH NODE!
#define NETWORKID     0   // Must be the same for all nodes
#define MYNODEID      2   // My node ID
#define TONODEID      1   // Destination node ID

// RFM69 frequency
#define FREQUENCY     RF69_915MHZ

// AES encryption (or not):
#define ENCRYPT       true // Set to "true" to use encryption
#define ENCRYPTKEY    "TOPSECRETPASSWRD" // Use the same 16-byte key on all nodes

// Use ACKnowledge when sending messages (or not):
#define USEACK        true // Request ACKs or not

// Packet sent/received indicator LED (optional):
#define LED           9 // LED positive pin
//#define GND           8 // LED ground pin

// Create a library object for our RFM69HCW module:
RFM69 radio;


volatile bool newbutton = false;

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/


void setup() {

  pinMode(LED,OUTPUT);
  digitalWrite(LED,LOW);
  
  // link the button 1 functions.
  button1.attachClick(click1); //single click
  button1.attachNull(nullclick1); //debounce error
  //button1.attachDoubleClick(doubleclick1);
  //button1.attachLongPressStop(longPressStop1);   

  // link the button 2 functions.
  button2.attachClick(click2); //single click
  button2.attachNull(nullclick2); //debounce error
  //button2.attachDoubleClick(doubleclick2);
  //button2.attachLongPressStop(longPressStop2);

  //Initialize the RFM69HCW:
  radio.initialize(FREQUENCY, MYNODEID, NETWORKID);
  
  // Turn on encryption if set to true above
  if (ENCRYPT)
    radio.encrypt(ENCRYPTKEY);

} 

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void upinterrupt(){
  newbutton = true; //this variable will be used to enter loop to start checking for button clicks
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void downinterrupt(){
  newbutton = true; //this variable will be used to enter loop to start checking for button clicks
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

void loop() 
{
   digitalWrite(LED,LOW); //make sure the LED is off before going to sleep indefinitely.
  
//These interrupts will wake the module when it is powered down to save battery
  enableInterrupt(UPPIN, upinterrupt, FALLING);      //enables interrupt on UPPIN and calls upinterrupt. This is using the enable interrupt library for a pin change interrupt
  attachInterrupt(digitalPinToInterrupt(DOWNPIN), downinterrupt, FALLING);   //enables interrupt on DOWNPIN and calls downinterrupt. This is the standard external interrupt pin

  radio.sleep();                                          //Put the RFM69 to sleep
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);    //Put Moteino to power down sleep. Will wake on interrupt and run the next line of code after ISR

//First line to run after the ISR that wakes the module:

  disableInterrupt(UPPIN);
  detachInterrupt(DOWNPIN);                               //Prevents interrupts being called again until everything is complete


  
  while(newbutton){                                          //ISR that wakes up module will set this to true and we can keep checking for click/doubleclick/hold
  button1.tick(); //check button1 (UP) for click, double click, hold
  button2.tick(); //check button1 (UP) for click, double click, hold      
  }
  
 
} // loop

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

//UP BUTTON PRESSED

void click1() {

    
 //do something here on a single click event. Send a message with the radio or check for messages etc.
 
   newbutton = false; //after the event is complete it allows us to exit the loop checking for button presses
                   
   
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

// DOWN BUTTON PRESSED

void click2() {

 //do something here on a single click event. Send a message with the radio or check for messages etc.
 
   newbutton = false; //after the event is complete it allows us to exit the loop checking for button presses
   
}
 // click2

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

//Debounce Error on first button

void nullclick1() {

   newbutton = false; //in the event of a debounce error we still need to set this variable to false, otherwise while loop will keep moteino awake until the next button press essentially wasting battery 
            
   
}

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------*/

//Debounce Error on second button

void nullclick2() {

   newbutton = false; //in the event of a debounce error we still need to set this variable to false, otherwise while loop will keep moteino awake until the next button press essentially wasting battery
            
   
}

Also note that the RFM69 and SPI libraries are not necessary if you do not have the RFM69 module in your project. I left them in there along with the radio initialization code but removed my code that was sending messages through the radio. Cut things out as required for your project.

Hope this helps someone in the future!

OneButtonEdited.zip (12.3 KB)