Author Topic: Trouble reading button press after waking up from LowerPower.powerDown [SOLVED]  (Read 2266 times)

doublec4

  • Jr. Member
  • **
  • Posts: 53
Hi everyone,

I am using a regular Moteino 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 remote. This also worked reliably and it 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 Moteino wakes up? Any help would be appreciated! Thank you

Code: [Select]
#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

« Last Edit: April 13, 2018, 05:18:55 PM by doublec4 »

doublec4

  • Jr. Member
  • **
  • Posts: 53
Hi Luka,

I don't have the PULLUP line in there because I believe it is redundant. When I checked out the source code in the "OneButton" library, when I say:

Code: [Select]
OneButton button1(5, true);

The true parameter activates the pull up on that pin.

Also, I read somewhere that the flow after the ISR (on a wake up) resumes right after the powerDown command. I'll try and find where I read that...

EDIT:

https://www.thearduinomakerman.info/blog/2018/1/24/guide-to-arduino-sleep-mode
« Last Edit: April 13, 2018, 12:48:15 AM by doublec4 »

LukaQ

  • Sr. Member
  • ****
  • Posts: 302
  • Country: si
Hi Luka,

I don't have the PULLUP line in there because I believe it is redundant. When I checked out the source code in the "OneButton" library, when I say:

Code: [Select]
OneButton button1(5, true);

The true parameter activates the pull up on that pin.

Also, I read somewhere that the flow after the ISR (on a wake up) resumes right after the powerDown command. I'll try and find where I read that...

EDIT:

https://www.thearduinomakerman.info/blog/2018/1/24/guide-to-arduino-sleep-mode
It does resume from where it exits the interrupt yes
What about pullup on d3?

If you have to press twice, maybe do check if it is pressed for a second time with digital read?
« Last Edit: April 13, 2018, 01:11:56 AM by LukaQ »

doublec4

  • Jr. Member
  • **
  • Posts: 53
Hi Luka,

pullup on D3 is handled by the next line of code:

Code: [Select]
OneButton button2(3, true);

I can use digitalread to check for the second time, but I suppose it defeats the purpose if I'm having to press the button twice.

It just seems odd that everything worked normally until the powerDown is introduced.

doublec4

  • Jr. Member
  • **
  • Posts: 53
I'm going to play around with these settings in the "OneButton" library:


Code: [Select]
 _debounceTicks = 50;      // number of millisec that have to pass by before a click is assumed as safe.
  _clickTicks = 600;        // number of millisec that have to pass by before a click is detected.
  _pressTicks = 1000;       // number of millisec that have to pass by before a long button press is detected.

Maybe the wake up process slows things down a little and it is not properly registering a click...

doublec4

  • Jr. Member
  • **
  • Posts: 53
Okay, so without touching those settings, I'm looking at the OneButton library closer... it appears that the .tick() function needs to be looped to work properly... so by only having it run once and then putting the device back to sleep things aren't going to happen as expected...

I will have to try this later!

LukaQ

  • Sr. Member
  • ****
  • Posts: 302
  • Country: si
try moving it into ISR routine, maybe make loop there

doublec4

  • Jr. Member
  • **
  • Posts: 53
I don't think moving it into the ISR routine is a good idea. From my (limited) understanding, ISR routine should be very short and not use delay or millis

Anyways, I did get it working by changing if(newbutton) to while(newbutton) and getting rid of the LED test flash.

Originally I was under the impression that the looping was done inside the .tick() library function. However, this is not the case and it needs to be looped in my code.

Solved!

doublec4

  • Jr. Member
  • **
  • Posts: 53
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 Moteino to go back to sleep without getting caught in the loop and wasting batteries while waiting for the next button press. Makes the sketch more low power friendly.

Code: [Select]
#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
           
   
}



Hope this helps someone in the future!