Interrupt debouncing (or non-repeating function)

I recently started building a GPS tracker. My code is based off of some of the example code here.

I have stripped back the code and am just testing the sending of a Telegram message on a spare NodeMCU I had on hand. So far I have;

#include <Arduino.h>
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <UniversalTelegramBot.h>
#include <WiFiClientSecure.h>

//------- WiFi Settings -------
#define WIFI_SSID "my_ssid"
#define WIFI_PASSWORD "my_pass"

// ------- Telegram Config --------
#define BOT_TOKEN "_token_"  
#define CHAT_ID "_id_" 
X509List cert(TELEGRAM_CERTIFICATE_ROOT);
WiFiClientSecure secured_client;
UniversalTelegramBot bot(BOT_TOKEN, secured_client);

// ------- Pin Definitions --------
#define IgnitionPin D5
#define EnginePin D6
#define DoorsPin D7

// ------- Variables for Interrupts --------
volatile bool engineOnFlag = false;
volatile bool engineOffFlag = false;
volatile bool ignitionOnFlag = false;
volatile bool ignitionOffFlag = false;
volatile bool lockedFlag = false;
volatile bool unlockedFlag = false;

// ------- Status Functions --------
void IRAM_ATTR ignition() {
  if (digitalRead(IgnitionPin) == HIGH) {
    ignitionOnFlag = true;
   }
  else if (digitalRead(IgnitionPin) == LOW) {
    ignitionOffFlag = true;
  }
  return;
}
void IRAM_ATTR engine() {
  if (digitalRead(EnginePin) == HIGH) {
    engineOnFlag = true;
   }
  else if (digitalRead(EnginePin) == LOW) {
    engineOffFlag = true;
  }
  return;
}
void IRAM_ATTR locks() {
  if (digitalRead(DoorsPin) == HIGH) {
    lockedFlag = true;
  }
  else if (digitalRead(DoorsPin) == LOW) {
    unlockedFlag = true;
  }
  return;
}

// ------- Message Functions --------
void sendIgnitionOnMessage() {
  ignitionOnFlag = false;
  String message = "Ignition on";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  }
  return;
}
void sendIgnitionOffMessage() {
  ignitionOffFlag = false;
  String message = "Ignition off";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  }
  return;
}
void sendEngineOnMessage() {
  engineOnFlag = false;
  String message = "Engine on";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  }
  return;
}
void sendEngineOffMessage() {
  engineOffFlag = false;
  String message = "Engine off";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  }
  return;
}
void sendLockedMessage() {
  lockedFlag = false;
  String message = "Locked";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  }
  return;
}
void sendUnlockedMessage() {
  unlockedFlag = false;
  String message = "Unlocked";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  }
  return;
}

void setup() {
  // ------- Init Serial Port --------
  Serial.begin(115200);
  Serial.println();

  // ------- Pin Setup -------- 
  pinMode(IgnitionPin, INPUT_PULLUP);
  pinMode(EnginePin, INPUT_PULLUP);
  pinMode(DoorsPin, INPUT_PULLUP);

  // ------- Interrupt Setup --------
  // NOTE: It is important to use interrupts when making network calls in the sketch
  // if you just checked the status of the button in the loop you might miss the button press.
  //attachInterrupt(Doors, Doors(open), RISING); //this would be great but it isn't how interrupts work!
  //attachInterrupt(digitalPinToInterrupt(GPIO), ISR, mode); //mode can be rising, falling, change

  attachInterrupt(IgnitionPin, ignition, CHANGE);
  attachInterrupt(EnginePin, engine, CHANGE);
  attachInterrupt(DoorsPin, locks, CHANGE);

  // ------- Connect to WiFi network --------
  Serial.print("Connecting to WiFi SSID ");
  Serial.print(WIFI_SSID" ");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  secured_client.setTrustAnchors(&cert); // Add root certificate for api.telegram.org
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(500);
  }
  Serial.print("\nWiFi connected. IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Retrieving time: ");
  configTime(0, 0, "pool.ntp.org"); // get UTC time via NTP
  time_t now = time(nullptr);
  while (now < 24 * 3600)
  {
    Serial.print(".");
    delay(100);
    now = time(nullptr);
  }
  Serial.println(now);
  bot.sendMessage(CHAT_ID, "Tracker started up.", "");
}

void loop() {
  if (ignitionOnFlag) {
    sendIgnitionOnMessage();
  }
  if (ignitionOffFlag) {
    sendIgnitionOffMessage();
  }
  if (engineOnFlag) {
    sendEngineOnMessage();
  }
  if (engineOffFlag) {
    sendEngineOffMessage();
  }
  if (lockedFlag) {
    sendLockedMessage();
    }
  if (unlockedFlag) {
    sendUnlockedMessage();
  }
}

At this point it sends messages but it seems to have a real issue with sending multiple messages (both on and off, in no particular order) when triggering each input, and sometimes triggering one input causes messages regarding other inputs to be sent. I am putting this issue down to noise on the breadboard so really my issue is how to debounce the inputs in software (usually I would prefer top use hardware debouncing but as it this is for a vehicle inputs can be noisy so would rather clean them up in code to make sure we're good).

I did debounce the flags in the loop instead but this didn't seem to make much difference. I also moved the setting flags to false code to the start of the relevant functions as I wondered if the waiting for the success return from the Telegram message was causing an issue but seemingly not.

I would think that I want my code for debouncing would want to be in the main loop as I don't want to be waiting in functions.

For reference the code I had to debounce the flags was;

#include <Arduino.h>
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <UniversalTelegramBot.h>
#include <WiFiClientSecure.h>

//------- WiFi Settings -------
#define WIFI_SSID "my_ssid"
#define WIFI_PASSWORD "my_pass"

// ------- Telegram Config --------
#define BOT_TOKEN "_token_"  
#define CHAT_ID "_id_" 
X509List cert(TELEGRAM_CERTIFICATE_ROOT);
WiFiClientSecure secured_client;
UniversalTelegramBot bot(BOT_TOKEN, secured_client);

// ------- Pin Definitions --------
#define IgnitionPin D5
#define EnginePin D6
#define DoorsPin D7

// ------- Variables for Interrupts --------
volatile bool engineOnFlag = false;
volatile bool engineOffFlag = false;
volatile bool ignitionOnFlag = false;
volatile bool ignitionOffFlag = false;
volatile bool lockedFlag = false;
volatile bool unlockedFlag = false;

// ------- Status Functions --------
void IRAM_ATTR ignition() {
  ignitionInputTriggerTime = millis();
  if (digitalRead(IgnitionPin) == HIGH) {
    ignitionOnFlag = true;
   }
  else if (digitalRead(IgnitionPin) == LOW) {
    ignitionOffFlag = true;
  }
  return;
}
void IRAM_ATTR engine() {
  engineInputTriggerTime = millis();
  if (digitalRead(EnginePin) == HIGH) {
    engineOnFlag = true;
   }
  else if (digitalRead(EnginePin) == LOW) {
    engineOffFlag = true;
  }
  return;
}
void IRAM_ATTR locks() {
  lockInputTriggerTime = millis();
  if (digitalRead(DoorsPin) == HIGH) {
    lockedFlag = true;
  }
  else if (digitalRead(DoorsPin) == LOW) {
    unlockedFlag = true;
  }
  return;
}

// ------- Message Functions --------
void sendIgnitionOnMessage() {
  String message = "Ignition on";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  ignitionOnFlag = false;
  }
  return;
}
void sendIgnitionOffMessage() {
  String message = "Ignition off";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
ignitionOffFlag = false;
  }
  return;
}
void sendEngineOnMessage() {
  String message = "Engine on";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  engineOnFlag = false;
  }
  return;
}
void sendEngineOffMessage() {
  String message = "Engine off";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  engineOffFlag = false;
  }
  return;
}
void sendLockedMessage() {
  String message = "Locked";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  lockedFlag = false;
  }
  return;
}
void sendUnlockedMessage() {
  String message = "Unlocked";
  if(bot.sendMessage(CHAT_ID, message, "Markdown")) {
    Serial.println(message + " successfully sent");
  unlockedFlag = false;
  }
  return;
}

void setup() {
  // ------- Init Serial Port --------
  Serial.begin(115200);
  Serial.println();

  // ------- Pin Setup -------- 
  pinMode(IgnitionPin, INPUT_PULLUP);
  pinMode(EnginePin, INPUT_PULLUP);
  pinMode(DoorsPin, INPUT_PULLUP);

  // ------- Interrupt Setup --------
  // NOTE: It is important to use interrupts when making network calls in the sketch
  // if you just checked the status of the button in the loop you might miss the button press.
  //attachInterrupt(Doors, Doors(open), RISING); //this would be great but it isn't how interrupts work!
  //attachInterrupt(digitalPinToInterrupt(GPIO), ISR, mode); //mode can be rising, falling, change

  attachInterrupt(IgnitionPin, ignition, CHANGE);
  attachInterrupt(EnginePin, engine, CHANGE);
  attachInterrupt(DoorsPin, locks, CHANGE);

  // ------- Connect to WiFi network --------
  Serial.print("Connecting to WiFi SSID ");
  Serial.print(WIFI_SSID" ");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  secured_client.setTrustAnchors(&cert); // Add root certificate for api.telegram.org
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(500);
  }
  Serial.print("\nWiFi connected. IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Retrieving time: ");
  configTime(0, 0, "pool.ntp.org"); // get UTC time via NTP
  time_t now = time(nullptr);
  while (now < 24 * 3600)
  {
    Serial.print(".");
    delay(100);
    now = time(nullptr);
  }
  Serial.println(now);
  bot.sendMessage(CHAT_ID, "Tracker started up.", "");
}

void loop() {
  if ((ignitionOnFlag) && (millis() - ignitionInputTriggerTime >= debounceTime)) {
    sendIgnitionOnMessage();
  }
  if ((ignitionOffFlag) && (millis() - ignitionInputTriggerTime >= debounceTime)) {
    sendIgnitionOffMessage();
  }
  if ((engineOnFlag) && (millis() - engineInputTriggerTime >= debounceTime)) {
    sendEngineOnMessage();
  }
  if ((engineOffFlag) && (millis() - engineInputTriggerTime >= debounceTime)) {
    sendEngineOffMessage();
  }
  if ((lockedFlag) && (millis() - lockInputTriggerTime >= debounceTime)) {
    sendLockedMessage();
    }
  if ((unlockedFlag) && (millis() - lockInputTriggerTime >= debounceTime)) {
    sendUnlockedMessage();
  }
}```

As I said at the top of my post, the code was modified from elsewhere, that is what they thought. I did wonder if this was really necessary but it sounded like it could be an issue.

So you reckon my solution is just to get rid of the interrupts altogether and do everything in the main loop? Or just move all the button pressing logic into the main loop?

At some point I will also be adding the GPS functions and they will also send POST updates to my Traccar server. This shouldn't add any significant time to the program either. I was going to add other inputs also, these inputs used so far will obviously be on for a good period of time. I suppose the answer is to try it in the loop/not use interrupts here and see how it works.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.