Debounce relay switched by serial stream

I can actually switch the relay on and off over serial. But I want the relay to hold it as long as the serial stream is getting received. It can get interrupted real quick from time to time thats why I want to debounce it. I don't want the relay to fall just for a few microseconds.

So how to add a fall delay in software?
I tried it with a hit and miss counter, but it is a mess.

Serial1 is just for debugging, also this will later run on a nano

#include <Thread.h>
#include <ThreadController.h>

int RELAY = 3;
int LED = 13;
int hit = 0;
int miss = 0;
uint32_t mytime = 0;
uint32_t mytime2 = 0;

String command = "0";

// ThreadController that will controll all threads
ThreadController controll = ThreadController();

//My Thread (as a pointer)
Thread* Thread1 = new Thread();
//His Thread (not pointer)
Thread Thread2 = Thread();

// callback for Thread1
void serialCallback(){
  if (Serial.available() > 0) {
    command = Serial.readString();
    command.trim();
    if (command == "X") {
      hit+10;
      } else {
      miss++;
      }
  } else {
      //hit = 0;
  }
  Serial1.println(String(hit) + " " + String(miss));
}

// callback for Thread2
void relayCallback() {
    mytime = millis();
    if (((millis() - mytime) > 100) && (miss < hit)) {
      digitalWrite(RELAY, HIGH);
      digitalWrite(LED, HIGH);
      Serial1.println("ON");
      }
    mytime2 = millis();
    if (((millis() - mytime2) > 100) && (miss > hit)) {
      digitalWrite(RELAY, LOW);
      digitalWrite(LED, LOW);
      Serial1.println("OFF");
      }
}

void setup(){
  Serial.begin(115200, SERIAL_8N1);
  Serial.setTimeout(3);
  Serial1.begin(9600, SERIAL_8N1); //DEBUG PORT
  Serial1.setTimeout(3);
  pinMode(LED, OUTPUT);
  pinMode(RELAY, OUTPUT);
  digitalWrite(RELAY, LOW);
  digitalWrite(LED, LOW);

	// Configure Thread1
	Thread1->onRun(serialCallback);
	Thread1->setInterval(1);

	// Configure Thread2
	Thread2.onRun(relayCallback);
	Thread2.setInterval(1);

	// Adds both threads to the controller
	controll.add(Thread1);
	controll.add(&Thread2); // & to pass the pointer to it
}

void loop(){
	controll.run();
}

python 3 send code for testing

import time
import serial

ser = serial.Serial('COM12', 115200, timeout = 0)

while True:
    ser.write("X\r\n".encode("ascii"))
    time.sleep(0.02) #random 0.01-0.05
1 Like

I suspect an XY problem. Please explain what you really want to achieve.

I'd define start and stop characters for controlling the relay.

Let's start with a hint:

  • You you use millis() in the routine relayCallback() but set mytime and mytime2 immediately before the if clause. This way (millis()-mytime <100) will never become true:
// callback for Thread2
void relayCallback() {
    mytime = millis();
    if (((millis() - mytime) > 100) && (miss < hit)) {
      digitalWrite(RELAY, HIGH);
      digitalWrite(LED, HIGH);
      Serial1.println("ON");
      }
    mytime2 = millis();
    if (((millis() - mytime2) > 100) && (miss > hit)) {
      digitalWrite(RELAY, LOW);
      digitalWrite(LED, LOW);
      Serial1.println("OFF");
      }
}

To achieve the effect you should set the global variables inside the if-clause like this

// callback for Thread2
void relayCallback() {
    if (((millis() - mytime) > 100) && (miss < hit)) {
      mytime = millis();
      digitalWrite(RELAY, HIGH);
      digitalWrite(LED, HIGH);
      Serial1.println("ON");
      }
    if (((millis() - mytime2) > 100) && (miss > hit)) {
       mytime2 = millis();
      digitalWrite(RELAY, LOW);
      digitalWrite(LED, LOW);
      Serial1.println("OFF");
      }
}

Another issue, the line

hit+10;

does probably not do what you want ... If you want hit to be increased by 10 it should read

hit += 10;

I made just the minimum changes to your sketch required to make it compile and work (please check the SoftSerial pins ... ):

#include <Thread.h>
#include <ThreadController.h>
#include "SoftwareSerial.h"

int RELAY = 3;
int LED = 13;
int hit = 0;
int miss = 0;
uint32_t mytime = 0;
uint32_t mytime2 = 0;

String command = "0";

SoftwareSerial Serial1(4,5);

// ThreadController that will controll all threads
ThreadController controll = ThreadController();

//My Thread (as a pointer)
Thread* Thread1 = new Thread();
//His Thread (not pointer)
Thread Thread2 = Thread();

// callback for Thread1
void serialCallback(){
  if (Serial.available() > 0) {
    command = Serial.readString();
    command.trim();
    if (command == "X") {
      hit += 10;
      } else {
      miss++;
      }
    Serial1.println(String(hit) + " " + String(miss));    
  } else {
      //hit = 0;
  }
  
}

// callback for Thread2
void relayCallback() {
    if (((millis() - mytime) > 100) && (miss < hit)) {
      mytime = millis();
      digitalWrite(RELAY, HIGH);
      digitalWrite(LED, HIGH);
      Serial1.println("ON");
      }
    if (((millis() - mytime2) > 100) && (miss > hit)) {
      mytime2 = millis();
      digitalWrite(RELAY, LOW);
      digitalWrite(LED, LOW);
      Serial1.println("OFF");
      }
}

void setup(){
  Serial.begin(115200, SERIAL_8N1);
  Serial.setTimeout(3);
  Serial1.begin(9600); //DEBUG PORT
  Serial1.setTimeout(3);
  Serial.println("Start");
  pinMode(LED, OUTPUT);
  pinMode(RELAY, OUTPUT);
  digitalWrite(RELAY, LOW);
  digitalWrite(LED, LOW);

	// Configure Thread1
	Thread1->onRun(serialCallback);
	Thread1->setInterval(1);

	// Configure Thread2
	Thread2.onRun(relayCallback);
	Thread2.setInterval(1);

	// Adds both threads to the controller
	controll.add(Thread1);
	controll.add(&Thread2); // & to pass the pointer to it
}

void loop(){
	controll.run();
}

The tasks you have here could actually be done easily without threads ... If you like them you might make real use of their functionality. There is a working sketch on

https://wokwi.com/projects/331466515251462738

You can input the "X" in the serial command line after you started the sketch there.

And this makes use of the thread.setinterval() without millis() but using a boolean to avoid continous digitalWrite() ...

#include <Thread.h>
#include <ThreadController.h>
#include "SoftwareSerial.h"

int RELAY = 3;
int LED = 13;
int hit = 0;
int miss = 0;
boolean relaisOn = false;

String command = "0";

SoftwareSerial Serial1(4,5);

// ThreadController that will controll all threads
ThreadController controll = ThreadController();

//My Thread (as a pointer)
Thread* Thread1 = new Thread();
//His Thread (not pointer)
Thread Thread2 = Thread();

// callback for Thread1
void serialCallback(){
  if (Serial.available() > 0) {
    command = Serial.readString();
    command.trim();
    if (command == "X") {
      hit += 10;
      } else {
      miss++;
      }
    Serial1.println(String(hit) + " " + String(miss));    
  } else {
      //hit = 0;
  }
  
}

// callback for Thread2
void relayCallback() {
    if (miss < hit && !relaisOn) {
      digitalWrite(RELAY, HIGH);
      digitalWrite(LED, HIGH);
      Serial1.println("ON");
      relaisOn = true;
      }
    if (miss > hit && relaisOn) {
      digitalWrite(RELAY, LOW);
      digitalWrite(LED, LOW);
      Serial1.println("OFF");
      relaisOn = false;
      }
}

void setup(){
  Serial.begin(115200, SERIAL_8N1);
  Serial.setTimeout(3);
  Serial1.begin(9600); //DEBUG PORT
  Serial1.setTimeout(3);
  Serial.println("Start");
  pinMode(LED, OUTPUT);
  pinMode(RELAY, OUTPUT);
  digitalWrite(RELAY, LOW);
  digitalWrite(LED, LOW);

	// Configure Thread1
	Thread1->onRun(serialCallback);
	Thread1->setInterval(1);

	// Configure Thread2
	Thread2.onRun(relayCallback);
	Thread2.setInterval(100);

	// Adds both threads to the controller
	controll.add(Thread1);
	controll.add(&Thread2); // & to pass the pointer to it
}

void loop(){
	controll.run();
}

On Wokwi (with some Serial1 output directed to Serial):

https://wokwi.com/projects/331466752874512978

1 Like

hey thanks for the simulation.

it should work like this:

"X" gets sent and received -> switch relay on for let's say 50ms
if another "X" gets received in these 50ms it will extend to another 50ms (relay stays on)

Double check:
4 "X"s are received every 20ms. So it's on for 110ms.

debounce

I don't think threading is needed to be honest.

I changed the Wokwi accordingly:

https://wokwi.com/projects/331487846106923603

Here is the sketch:

#include <Thread.h>
#include <ThreadController.h>
#include "SoftwareSerial.h"

int RELAY = 3;
int LED = 13;
int hit = 0;
int miss = 0;
boolean relaisOn = false;
boolean keepRelaisOn = false;

unsigned long lastX = 0;

String command = "0";

SoftwareSerial Serial1(4,5);

// ThreadController that will controll all threads
ThreadController controll = ThreadController();

//My Thread (as a pointer)
Thread* Thread1 = new Thread();
//His Thread (not pointer)
Thread Thread2 = Thread();

// callback for Thread1
void serialCallback(){
  if (Serial.available() > 0) {
    command = Serial.readString();
    command.trim();
    if (command == "X") { 
      lastX = millis();
      keepRelaisOn = true;
    }
  }
}

// callback for Thread2
void relayCallback() {
    if (keepRelaisOn && !relaisOn && millis()-lastX > 10) {  // Delay to switch on = 10 msec
      digitalWrite(RELAY, HIGH);
      digitalWrite(LED, HIGH);
      Serial.println("ON");
      relaisOn = true;
      keepRelaisOn = false;
      }
    if (relaisOn && millis()-lastX > 1000) {  // Delay to switch off = 1000 msec (for manual use ;-) )
      digitalWrite(RELAY, LOW);
      digitalWrite(LED, LOW);
      Serial.println("OFF");
      relaisOn = false;
      keepRelaisOn = false;
      }
}

void setup(){
  Serial.begin(115200, SERIAL_8N1);
  Serial.setTimeout(3);
  Serial1.begin(9600); //DEBUG PORT
  Serial1.setTimeout(3);
  Serial.println("Start");
  pinMode(LED, OUTPUT);
  pinMode(RELAY, OUTPUT);
  digitalWrite(RELAY, LOW);
  digitalWrite(LED, LOW);

	// Configure Thread1
	Thread1->onRun(serialCallback);
	Thread1->setInterval(1);

	// Configure Thread2
	Thread2.onRun(relayCallback);
	Thread2.setInterval(100);

	// Adds both threads to the controller
  lastX = millis();
	controll.add(Thread1);
	controll.add(&Thread2); // & to pass the pointer to it
}

void loop(){
	controll.run();
}

It takes 1000 msec in Wokwi (to allow for manual testing :wink: ) just change this line

if (relaisOn && millis()-lastX > 1000) { // Delay to switch off = 1000 msec (for manual use :wink: )

Hope it works ... :wink:

P S. You should in any case change the Serial input as the thread only received one char each msec... That applies to about 9600 Baud...

1 Like

thanks! I removed the switch on delay, I don't think its needed.

If you want to be safe you should set the value from 1000 to 50 msec. That will work with higher Baud rates also.

But to avoid an overrun of the Serial buffer you should read from Serial as quick as possible... Means: Call Serial routine directly in loop() and not in a thread.

ok, so this is the final code

#include <Thread.h>
#include <ThreadController.h>

int RELAY = 9;
int LED = 13;

boolean relaisOn = false;
boolean keepRelaisOn = false;
unsigned long lastX = 0;
String command = "0";

ThreadController controll = ThreadController();

Thread Thread2 = Thread();

void relayCallback() {
  if (keepRelaisOn && !relaisOn) {
    digitalWrite(RELAY, HIGH);
    digitalWrite(LED, HIGH);
    relaisOn = true;
    keepRelaisOn = false;
  }
  if (relaisOn && millis() - lastX > 50) {
    digitalWrite(RELAY, LOW);
    digitalWrite(LED, LOW);
    relaisOn = false;
    keepRelaisOn = false;
  }
}

void setup() {
  Serial.begin(115200, SERIAL_8N1);
  Serial.setTimeout(1);
  pinMode(LED, OUTPUT);
  pinMode(RELAY, OUTPUT);
  digitalWrite(RELAY, LOW);
  digitalWrite(LED, LOW);
  Thread2.onRun(relayCallback);
  Thread2.setInterval(100);
  lastX = millis();
  controll.add(&Thread2);
}

void loop() {
  if (Serial.available() > 0) {
    command = Serial.readString();
    command.trim();
    if (command == "X") {
      lastX = millis();
      keepRelaisOn = true;
    }
  }
  controll.run();
}

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