Using timers and debounce code - issues?

Here are the details of my project, which PaulRB has been amazing helping me out with the hardware side of things, and some general coding advice.

http://forum.arduino.cc/index.php?topic=600984.0

I have a generalized coding question so I decided to ask that here.

To summarize the project:
Read two inputs from an alarm panel and write the states to the Blynk app virtual LED's
When Blynk app button is pressed, toggle the alarm 'SET' state.

I had the code running using interrupts and it works for a short time, but after a while the Wemos goes offline, i 'think' due to the server being spammed and rejecting the connection. So I decided to change the code to polling type, with debounce for the two inputs.

Getting to the question:
The two functions are called every 1000ms to check the inputs and update the Blynk app

The debounce code I used from the example is meant to be in void loop.

Is this going to cause me any issues?

My current code:

//v004 Garage alarm (change from interrupts to polling)
//
//
//Project requirements:
//The alarm panel has two outputs, Armed and Sounder
//Poll these two outputs every 1000ms and write the result to a virtual LED (Blynk app on phone)
//The alarm panel has one input SET which can arm or disarm the system
//When a virtual button is pressed (on phone Blynk app) the alarm is armed or unarmed.
//
//
//Issues:
//Previous code used interrupts, but was spamming the local server, causing it to go offline
//(maybe caused by inputs from the alarm giving square wave?)
//v004 changes code to polling with debounce for the two inputs
//
//To do:
//Complete function for debounce read of "Sounder" i.e sounder on or off
//Check - functions are called every 1000ms by timer, but code was written to be in void loop. Is this ok?
//


#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <SimpleTimer.h>


char auth[] = ""; //Enter the Auth code which was send by Blynk
char ssid[] = "";                           //Enter your WIFI Name
char pass[] = "";              //Enter your WIFI Password
char server[] = "192.168.1.90";                   //for local server only
int port = 8080;                                  //for local server only


//Setup constants for the sketch
const byte Armed = D5;    // INPUT - is the panel armed, or un-armed/alarmed? (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
const byte Sounder = D6;  // INPUT - is the sounder on or off? (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
const byte SET = D4;      // OUTPUT - set or unset the alarm (HIGH = unset the alarm,  LOW = set the alarm)

//Setup variables for Armed
int ArmedState = digitalRead(Armed); //reads armed state of the alarm (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
int lastArmedState = ArmedState; // the previous read from the input pin

//Setup variables for Sounder
int SounderState = digitalRead(Sounder); //reads state of sounder i.e on or off (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
int lastSounderState = SounderState; // the previous read from the input pin

//Setup variables for debouncing of inputs
unsigned long lastArmedDebounceTime = 0;    //setup debounce variable for checkArmed function
unsigned long lastSounderDebounceTime = 0;  // setup debounce variable for checkSounder function
unsigned long debounceDelay = 50;           // the global debounce time in ms, increase if debounce issues continue

//Setup variable for Blynk virtual pin
static unsigned long last_interrupt_time = 0;
bool LastVirtualButtonState = 0;  //"0","FALSE","LOW' means exactly the same

SimpleTimer timer; //setup simple timer to call functions on a timed basis from void loop



void setup()
{
  //Setup the previously assigned constants
  pinMode(Armed, INPUT);       //is the panel armed, or un-armed/alarmed? (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
  pinMode(Sounder, INPUT);     //is the sounder on or off? (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
  pinMode(SET, OUTPUT);        //set or unset the alarm (HIGH = unset the alarm,  LOW = set the alarm)
  digitalWrite(SET, LOW);      //ensures the alarm defaults to SET condition after power loss of Wemos

  Blynk.begin(auth, ssid, pass, server, port);  //connects to Wifi and LOCAL Blynk server (running on raspberry pi)

  //write the current states to the Blynk app
  Blynk.virtualWrite(V5, (ArmedState * 255));      // writes set or unset state of alarm to Blynk virtual LED pin V5
  Blynk.virtualWrite(V6, (!SounderState * 255));   //writes sounder on or off state to Blynk virtual LED pin V6 (inverted logic as sounder is on when at 0V

  timer.setInterval(1000L, checkArmed);  //Setup a function to be called every second
  timer.setInterval(1000L, checkSounder);  //Setup a function to be called every second
}




void loop()
{
  Blynk.run();  //This function should be called frequently to process incoming commands and perform housekeeping of Blynk connection.
  timer.run();  //Initiates SimpleTimer to runs timed functions
}




void checkArmed() //a function run every 1000ms to check if panel is armed or un-armed/alarmed? (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
{
  int readingArmed = digitalRead(Armed); // read the state of "Armed" into a local variable:

  if (readingArmed != lastArmedState)   //has the state changed?
  {
    lastArmedDebounceTime = millis();  // if yes(state has changed), reset the debouncing timer to the current millis
  }


  if ((millis() - lastArmedDebounceTime) > debounceDelay) // whatever readingArmed is at, it's been there for longer than the debounce delay, so take it as the actual current state
  {

    if (readingArmed != ArmedState) // has the armed state has changed?
    {
      ArmedState = readingArmed;  // if yes(state has changed) 

      {
        Blynk.virtualWrite(V5, (ArmedState) * 255); // writes ArmedState to Blnk V5 virtual LED names "Alarm armed?"
      }

    }
  }
  
  lastArmedState = readingArmed; // save the readingArmed. Next time through the function, it'll be the lastArmedState:
}







void checkSounder()  //a function run every 1000ms to check if the sounder is on or off? (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
{
  //To complete, essentialy the same as checkArmed
}




// BLYNK_WRITE is a function called every time the device gets an update of a Virtual Pin value from the server (e.g. Blynk app virtual button is pressed)
// contains "latching" code to stop long hold being registered as repeated presses.
BLYNK_WRITE(V3)
{
  int VirtualButtonState = param.asInt(); // assigning incoming value from pin V3 to a variable

  if ((VirtualButtonState) && (!LastVirtualButtonState)) // "VirtualButtonState" is the Blynk virtual button current state ||||||  this means same as "if ((VirtualButtonState == 1) && (LastVirtualButtonState == 0))"
    //if V3 virtual button is still being pressed, the LastVirtualState is set to 1, and !LastVirtualState will therefore be 0. Hence 1 && 0 condition == 0 and therefore function will not be called.

  {
    digitalWrite(SET, !digitalRead(SET));       //writes the inverse value to the pin  (booleon NOT operator )
    Blynk.virtualWrite(V0, digitalRead(SET) * 255);  // for  information only, writes the state of the keyswitch SET contacts to Blynk virtual LED at V0
  }

  LastVirtualButtonState = VirtualButtonState;  // sets LastVirtualButtonState to the same as pinValue, so if pinValue (V3 button) is high, LastVirtualPinState gets set to high
}

PCB layout for info:

There is no sense in looking for bounces if the input is only checked once a second. Contact bounce ends in a few milliseconds.

I'm confused as to why you check against two different "previous state" variables: lastArmedState and ArmedState

johnwasser:
There is no sense in looking for bounces if the input is only checked once a second. Contact bounce ends in a few milliseconds.

Thanks John, that quantifies what I was struggling with.

Yes in void loop the inputs are checked every few millis or so, but I can't put my code in the loop due to using Blynk.

With this project it's fine to just read the inputs every second, but what if the input bounces at the exact time it is read? (unlikely I suppose).

Or I could set the timers to a much lower value e.g. 50ms. and retain the debounce code

johnwasser:
I'm confused as to why you check against two different "previous state" variables: lastArmedState and ArmedState

Why I am checking two different "previous state" variables?
Well because I'm a noob :slight_smile: I need to re-read the code and see where I can trim it down, any advice is appreciated!

Being honest, I still find the debounce code a little confusing!

Many thanks

If you read once per second and the button is pushed at exactly the wrong moment so it is bouncing then you randomly get a HIGH or LOW. But those two answers are both correct.

Let's say it was HIGH (not pressed) and is going to be LOW (pressed.) If your random result is HIGH then it means the button was pushed "too late" and you will collect the final LOW value on the next read. If your random result is LOW then you respond just like a regular button-push event.

But once per second is too slow for regular humans pushing buttons. You should show feedback that the button pushing was detected much faster than that.

Thanks MorganS, I have reduced the timer intervals to 50ms so it polls much faster.

I should note : there are not buttons pressed by humans, it's the output from the alarm panel which are inputs to the Wemos.

In theory there should be no bouncing but I was told it might be the square wave generated from the alarm panel possibly.

Here is my updated code, it's working fine now....

but it feels like it's a bit messy, particulary the functions "checkArmed" and "checkSounder".
Seems like there's a lot of squiggly brackets :slight_smile:

Can anyone give some advice? As I think I have reached my current knowledge limit..

#define BLYNK_PRINT Serial
#include <BlynkSimpleEsp8266.h>
#include <SimpleTimer.h>

//Blynk credentials
char auth[] = ""; //Enter the Auth code which was send by Blink
//Wifi credentials
char ssid[] = "";  //Enter your WIFI Name
char pass[] = "";  //Enter your WIFI Password
//Server credentials
char server[] = "192.168.1.95"; //LOCAL SERVER ONLY
int port = 8080;  //LOCAL SERVER ONLY


//Setup constants for the sketch
const byte Armed = D5;    // INPUT - is the panel armed, or un-armed/alarmed? (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
const byte Sounder = D6;  // INPUT - is the sounder on or off? (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
const byte SET = D4;      // OUTPUT - set or unset the alarm (HIGH = unset the alarm,  LOW = set the alarm)

//Setup variables for Armed
int ArmedState = digitalRead(Armed); //reads armed state of the alarm (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
int lastArmedState = ArmedState; // the previous read from the input pin

//Setup variables for Sounder
int SounderState = digitalRead(Sounder); //reads state of sounder i.e on or off (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
int lastSounderState = SounderState; // the previous read from the input pin

//Setup variables for debouncing of inputs
unsigned long lastArmedDebounceTime = 0;    //setup debounce variable for checkArmed function
unsigned long lastSounderDebounceTime = 0;  // setup debounce variable for checkSounder function
unsigned long debounceDelay = 50;           // the global debounce time in ms, increase if debounce issues continue

//Setup variable for Blynk virtual pin
static unsigned long last_interrupt_time = 0;
bool LastVirtualButtonState = 0;  //"0","FALSE","LOW' means exactly the same

SimpleTimer timer; //setup simple timer to call functions on a timed basis from void loop



void setup()
{
  Serial.begin(115200);
  Blynk.begin(auth, ssid, pass, server, port);  //connects to Wifi and LOCAL Blynk server (running on raspberry pi)

  
  //Setup the previously assigned constants
  pinMode(Armed, INPUT);       //is the panel armed, or un-armed/alarmed? (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
  pinMode(Sounder, INPUT);     //is the sounder on or off? (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
  pinMode(SET, OUTPUT);        //set or unset the alarm (HIGH = unset the alarm,  LOW = set the alarm)
  digitalWrite(SET, LOW);      //ensures the alarm defaults to SET condition after power loss of Wemos

  //write the current states to the Blynk app
  Blynk.virtualWrite(V5, (ArmedState * 255));      // writes set or unset state of alarm to Blynk virtual LED pin V5
  Blynk.virtualWrite(V6, (!SounderState * 255));   //writes sounder on or off state to Blynk virtual LED pin V6 (inverted logic as sounder is on when at 0V

  timer.setInterval(100L, checkArmed);  //Setup a function to be called every second
  timer.setInterval(100L, checkSounder);  //Setup a function to be called every second
}




void loop()
{
  Blynk.run();  //This function should be called frequently to process incoming commands and perform housekeeping of Blynk connection.
  timer.run();  //Initiates SimpleTimer to runs timed functions
}




void checkArmed() //a function run every 1000ms to check if panel is armed or un-armed/alarmed? (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
{
  int readingArmed = digitalRead(Armed); // read the state of "Armed" into a local variable:

  if (readingArmed != lastArmedState)   //has the state changed?
  {
    lastArmedDebounceTime = millis();  // if yes(state has changed), reset the debouncing timer to the current millis
  }


  if ((millis() - lastArmedDebounceTime) > debounceDelay) // whatever readingArmed is at, it's been there for longer than the debounce delay, so take it as the actual current state
  {

    if (readingArmed != ArmedState) // has the armed state has changed?
    {
      ArmedState = readingArmed;  // if yes(state has changed) 

      {
        Blynk.virtualWrite(V5, (ArmedState) * 255); // writes ArmedState to Blnk V5 virtual LED names "Alarm armed?"
      }

    }
  }
  
  lastArmedState = readingArmed; // save the readingArmed. Next time through the function, it'll be the lastArmedState:
}




//DELETE
//const byte Sounder = D6;  // INPUT - is the sounder on or off? (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
//Setup variables for Sounder
//int SounderState = digitalRead(Sounder); //reads state of sounder i.e on or off (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
//int lastSounderState = SounderState; // the previous read from the input pin


void checkSounder()  //a function run every 1000ms to check if the sounder is on or off? (Sounder on = LOW/0.33V and Sounder off = HIGH/3.3V)
{
 int readingSounder = digitalRead(Sounder); // read the state of "Armed" into a local variable:

  if (readingSounder != lastSounderState)   //has the state changed?
  {
    lastSounderDebounceTime = millis();  // if yes(state has changed), reset the debouncing timer to the current millis
  }


  if ((millis() - lastSounderDebounceTime) > debounceDelay) // whatever readingSounder is at, it's been there for longer than the debounce delay, so take it as the actual current state
  {

    if (readingSounder != SounderState) // has the sounder state has changed?
    {
      SounderState = readingSounder;  // if yes(state has changed) 

      {
        Blynk.virtualWrite(V6, (!SounderState) * 255); // writes SounderState to Blnk V6 virtual LED named "Sounder on?"


        if (SounderState == LOW)
        {
          Blynk.notify("Garage alarm is sounding!");  //only send Blynk app notification when then sounder is ON
        }

        
      }

    }
  }
  
  lastSounderState = readingSounder; // save the readingSounder. Next time through the function, it'll be the lastSounderState:
}





// BLYNK_WRITE is a function called every time the device gets an update of a Virtual Pin value from the server (e.g. Blynk app virtual button is pressed)
// contains "latching" code to stop long hold being registered as repeated presses.
BLYNK_WRITE(V3)
{
  int VirtualButtonState = param.asInt(); // assigning incoming value from pin V3 to a variable

  if ((VirtualButtonState) && (!LastVirtualButtonState)) // "VirtualButtonState" is the Blynk virtual button current state ||||||  this means same as "if ((VirtualButtonState == 1) && (LastVirtualButtonState == 0))"
    //if V3 virtual button is still being pressed, the LastVirtualState is set to 1, and !LastVirtualState will therefore be 0. Hence 1 && 0 condition == 0 and therefore function will not be called.

  {
    digitalWrite(SET, !digitalRead(SET));       //writes the inverse value to the pin  (booleon NOT operator )
    Blynk.virtualWrite(V0, digitalRead(SET) * 255);  // for  information only, writes the state of the keyswitch SET contacts to Blynk virtual LED at V0
  }

  LastVirtualButtonState = VirtualButtonState;  // sets LastVirtualButtonState to the same as pinValue, so if pinValue (V3 button) is high, LastVirtualPinState gets set to high
}

I only count two unecessary pairs of braces.

But the last if() has a lot of space between it and the opening brace. Consider putting those long comments inside the braces so the opening brace directly follows the if().

Can your checkArmed() code not simply be:

void checkArmed() //a function run every 1000ms to check if panel is armed or un-armed/alarmed? (armed = HIGH/3.3V and Unarmed/alarmed = LOW/0V)
{
    int readingArmed = digitalRead(Armed); // read the state of "Armed" into a local variable:

    if (readingArmed != lastArmedState)   //has the state changed?
    {
        lastArmedState = readingArmed;
        ArmedState = readingArmed;
        Blynk.virtualWrite(V5, (ArmedState) * 255); // writes ArmedState to Blnk V5 virtual LED names "Alarm armed?"
    }
    
}

Are you sure the panel output needs to be debounced? Is it solid-state or a relay?

@MorganS I can’t see which two braces are the excessive ones?

@Blackfin it’s a solid state output so I wouldn’t have thought debouncing would even be needed. But the code was behaving spuriously and causing the server to reject the connection.

I will try your code though and report back, thanks.

      {

Blynk.virtualWrite(V5, (ArmedState) * 255); // writes ArmedState to Blnk V5 virtual LED names "Alarm armed?"
      }

Thanks :slight_smile: