ESP32 Automated Bell System: Unexpected Triggering & Back EMF Issues

Project Overview:

I am building an automated bell system using an ESP32, an RTC DS1307, a MAX7219 display, and a solid-state relay (HW-883) to control a 220V Edison Bell. The system is powered by an LM2596 step-down module supplying 5V and 3.3V.

Circuit Setup:

  • ESP32 powered by 3.3V step-down module
  • MAX7219 dot matrix display connected to ESP32
  • RTC DS3231 for real-time clock functionality
  • Solid-state relay (HW-883) switching a 220V Edison Bell
  • LM2596 modules providing 5V for display & relay, 3.3V for ESP32

Issue Faced:

The bell system starts working fine, but after some time, it starts triggering
unexpectedly
. However, the relay is OFF when this happens, meaning the bell is activating without the relay being triggered by the ESP32.

What I Have Checked So Far:

:white_check_mark: The relay module is NOT turning ON when the bell triggers unexpectedly.
:white_check_mark: The ESP32 is still running and does not freeze or reboot when this happens.
:white_check_mark: The circuit works fine initially, but after some time, the bell starts activating on its own.

Possible Causes & Questions:

  1. Back EMF or Inductive Coupling?
  • Could the 220V bell be inducing voltage spikes that are somehow affecting the relay module or triggering the bell circuit?
  • Would adding a MOV (Metal Oxide Varistor) or RC snubber across the bell terminals help?
  1. Solid-State Relay (SSR) Leakage?
  • Some solid-state relays have leakage current that could be partially triggering the bell.
  • Should I test with a mechanical relay instead of SSR to rule this out?
  1. Power Supply Stability Issues?
  • Since I’m using multiple LM2596 step-down converters, could there be power fluctuations affecting the system?
  • Would adding a large capacitor (1000µF) near ESP32 VCC help stabilize power?

Questions:

  1. What could be causing the bell to trigger when the relay is OFF?
  2. How can I better protect the ESP32 and relay circuit from unwanted activation?
  3. Would adding an optocoupler or using a mechanical relay instead of SSR help?
type or paste code here
```#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Wire.h>
#include <RTClib.h>
#include "esp_task_wdt.h"

//-----------------------
// Hardware & Pin Setup
//-----------------------
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
// MAX7219 Display pins for ESP32
#define CLK_PIN   18    // Clock
#define DATA_PIN  23    // Data (MOSI)
#define CS_PIN    5     // Chip Select

// Relay control pin (inverted logic: LOW = ON, HIGH = OFF)
#define RELAY_PIN 4

// Watchdog Timer Timeout in seconds
#define WDT_TIMEOUT 10

//-----------------------
// Period Schedule (HH:MM)
//-----------------------
// For periods 1, 6, 7, and 10 the relay is ON for 10 sec; for others, 6 sec.
int periodStartTimes[10][2] = {
  {9, 0},    // Period 1
  {9, 15},   // Period 2
  {10, 15},  // Period 3
  {11, 15},  // Period 4
  {12, 15},  // Period 5
  {13, 15},  // Period 6
  {13, 45},  // Period 7
  {14, 35},  // Period 8
  {15, 25},  // Period 9
  {16, 15}   // Period 10
};

//-----------------------
// Global Objects & Variables
//-----------------------
MD_Parola display = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
RTC_DS3231 rtc;

bool blinkColon = true;
unsigned long prevMillis = 0;
const unsigned long blinkInterval = 1000;  // Blink colon every 1 second

// Relay timing variables
unsigned long relayStartTime = 0;
bool relayActive = false;
int relayDuration = 6 * 1000;  // Default 6 seconds

//-----------------------
// Function: setRTC()
// Allows manual RTC setting via Serial Monitor (format: YYYY MM DD HH MM SS)
void setRTC() {
  Serial.println("Enter Date & Time (YYYY MM DD HH MM SS):");

  int values[6] = {0}; // Array to store year, month, day, hour, minute, second
  int index = 0;       // Index for the values array
  String input = "";    // String to store serial input

  // Wait for input in a non-blocking way
  unsigned long startTime = millis();
  while (index < 6) {
    if (Serial.available() > 0) {
      char c = Serial.read();
      if (c == '\n' || c == ' ') {
        if (input.length() > 0) {
          values[index++] = input.toInt(); // Store the parsed integer
          input = "";                     // Reset the input string
        }
      } else {
        input += c; // Append the character to the input string
      }
    }

    // Feed the watchdog timer to prevent reset
    esp_task_wdt_reset();

    // Timeout after 30 seconds
    if (millis() - startTime > 30000) {
      Serial.println("Input timeout! Please try again.");
      return;
    }
  }

  // Validate input
  if (values[0] < 2000 || values[1] < 1 || values[1] > 12 || values[2] < 1 || values[2] > 31 ||
      values[3] < 0 || values[3] > 23 || values[4] < 0 || values[4] > 59 || values[5] < 0 || values[5] > 59) {
    Serial.println("Invalid input! Please try again.");
    return;
  }

  // Set the RTC
  rtc.adjust(DateTime(values[0], values[1], values[2], values[3], values[4], values[5]));
  Serial.println("RTC updated successfully!");
}

//-----------------------
// Function: triggerRelay()
// Activates the relay for the specified duration (in ms) and prints status.
void triggerRelay(int duration) {
  if (!relayActive) {
    digitalWrite(RELAY_PIN, LOW);  // Activate relay (inverted logic)
    relayStartTime = millis();
    relayDuration = duration;
    relayActive = true;
    Serial.println("Relay ON");
  }
}

//-----------------------
// Task: relayControlTask (Core 1)
// Monitors the RTC and controls relay activation.
void relayControlTask(void *parameter) {
  // Register this task with the watchdog timer.
  esp_task_wdt_add(NULL);
  static int lastPeriod = -1;
  for (;;) {
    DateTime now = rtc.now();
    // Check scheduled periods (prevent duplicate trigger within the same minute)
    for (int i = 0; i < 10; i++) {
      if (now.hour() == periodStartTimes[i][0] &&
          now.minute() == periodStartTimes[i][1] &&
          i != lastPeriod) {
        int duration = (i == 0 || i == 5 || i == 6 || i == 9) ? 10 * 1000 : 6 * 1000;
        triggerRelay(duration);
        lastPeriod = i;
        break;
      }
    }
    // Turn off relay after its duration
    if (relayActive && (millis() - relayStartTime >= relayDuration)) {
      digitalWrite(RELAY_PIN, HIGH); // Deactivate relay
      relayActive = false;
      Serial.println("Relay OFF");
    }
    esp_task_wdt_reset(); // Feed watchdog
    delay(10);
  }
}

//-----------------------
// Task: displayTask (Core 0)
// Handles display updates and RTC reading.
void displayTask(void *parameter) {
  // Register this task with the watchdog timer.
  esp_task_wdt_add(NULL);
  static char lastTimeBuffer[6] = "";
  for (;;) {
    DateTime now = rtc.now();
    // Convert to 12-hour format
    int displayHour = now.hour();
    if (displayHour == 0) displayHour = 12;
    else if (displayHour > 12) displayHour -= 12;

    // Blink colon every second
    unsigned long currentMillis = millis();
    if (currentMillis - prevMillis >= blinkInterval) {
      prevMillis = currentMillis;
      blinkColon = !blinkColon;
    }

    // Format time string with blinking colon
    char timeBuffer[6];
    if (blinkColon)
      snprintf(timeBuffer, sizeof(timeBuffer), "%2d:%02d", displayHour, now.minute());
    else
      snprintf(timeBuffer, sizeof(timeBuffer), "%2d %02d", displayHour, now.minute());

    // Update display only if time has changed
    if (strcmp(timeBuffer, lastTimeBuffer) != 0) {
      strncpy(lastTimeBuffer, timeBuffer, sizeof(lastTimeBuffer));
      display.displayReset();
      display.displayText(timeBuffer, PA_CENTER, 100, 0, PA_PRINT, PA_NO_EFFECT);
    } else {
      display.displayAnimate();
    }

    esp_task_wdt_reset(); // Feed watchdog
    delay(100);
  }
}

//-----------------------
// setup()
// Initialize Serial, peripherals, and create tasks.
void setup() {
  Serial.begin(115200);

  // Log that we're using the existing TWDT
  Serial.println("Using existing Task Watchdog Timer (TWDT).");

  // Register main loop task with watchdog.
  esp_task_wdt_add(NULL);

  // Initialize Display
  display.begin();
  display.setIntensity(2);
  display.displayClear();

  // Initialize RTC
  Wire.begin();
  if (!rtc.begin()) {
    Serial.println("RTC NOT FOUND!");
    while (1);
  }

  Serial.println("Enter 'SET' to update RTC manually.");

  // Set relay pin
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, HIGH);  // Ensure relay is off

  // Create tasks on separate cores
  xTaskCreatePinnedToCore(displayTask, "DisplayTask", 4096, NULL, 1, NULL, 0); // Core 0
  xTaskCreatePinnedToCore(relayControlTask, "RelayTask", 2048, NULL, 1, NULL, 1); // Core 1
}

//-----------------------
// loop()
// Main loop monitors Serial for RTC set command.
void loop() {
  if (Serial.available()) {
    String command = Serial.readString();
    command.trim();
    if (command == "SET") {
      setRTC();
      Serial.println("RTC updated. Enter 'RUN' to resume.");
    }
  }

  // Feed the watchdog timer in the main loop
  esp_task_wdt_reset();
  delay(10);
}

I would swap the SSR for an opto coupled relay.

It appears your grounds are not all connected together. Also be sure the ground goes through the converters. Some do not and control the negative line instead of the positive. your second clock chip is not connected. The power lines are not labeled as to voltage. Since I do not have that ESP device or one that looks like it I cannot tell if it is wired correctly. This and many more questions would be answered if you posted an annotated schematic in place of a frizzy.

I have connected all the grounds and made a common ground and tested every module using a multimeter, the issue still prceeds, i think the SSR is keeping the charge after the node mcu turns it off, I will add a Breeder Resistors or RC snubber and check it out

First, I have tried the Relay, it works but I had Back EMF from the bell so I used a Flyback dioed but it didn't fit well and the ESP32 would hang and did't function properly

I suggest you use a solid state zero-crossing relay. This does its switching when the AC load voltage is close to zero volts reducing EMI and RFI. Posting an annotated schematic would help.

Questions:

  1. What could be causing the bell to trigger when the relay is OFF?
  2. How can I better protect the ESP32 and relay circuit from unwanted activation?
  3. Would adding an optocoupler or using a mechanical relay instead of SSR help?

Your relay module uses 5V and you have it connected to a 3.3V microcontroller. In general it's not a good idea to connect any voltage to an ESP32 that is greater than 3.3V

The relay does not reliable turn off because you are not providing 5V to the input signal.

For reliable operation I suggest you use a simple transistor circuit to trigger the relay

I can't say for sure what might be causing the problems you described, but I have spotted some potential faults. Here are my comments on your circuit design:

  1. You are using a 5V 1A AC-DC power supply according to your diagram. You are using LM2596 modules to create 5V, but this won't work. You need at least about 6.5V input for these modules to output 5V. But why use them anyway? You need 5V for some components, and your power supply is 5V. What's the point of the 2x LM2596?
  2. You are using an LM2596 module to power only the solid state relay module, but this module will consume almost zero power from it. A mechanical relay would consume some power, but SSRs consume only a tiny amount of power.
  3. You are using an LM2596 module to power the ESP board, but the ESP board has a built-in 3.3V regulator and so can be powered from 5V. So that LM2596 is also not needed. But, in any case, you have the 3.3V input from the LM2596 connected to the Vin pin of the ESP board, which needs at least about 4.3V input to run the ESP board, so that won't work either. The Vin pin on the ESP board connects to the board's 3.3V regulator. If you want to supply 3.3V externally, you must connect it to the ESP board's 3.3V pin.
  4. Is there WiFi connectivity to the internet where this circuit will be installed? If so, there is no need for an RTC. The ESP's built-in RTC will keep more accurate time than DS1307/DS3231 by regularly synchronising with time servers on the internet. By default it does this every hour but that can be configured.

I have read the data sheet of the relay it said it considers 3.3v as high in pwm so i thought it won't be a issue

Thanks for the suggestions,
But I first tried it with 12v 2A power supply but the ESP32 didn't boot up, but when I switched it to 5v 1A it worked like a charm and the reason for using multiple LM2596 is my teachers said there might be AC fluctuations so I just used it before hand like a safety device

The bell is in a out range from the wifi router so I have to use the rtc rather than wifi

PWM?
Isn't it a low trigger relay?

Yes I have kept it high and turns off when the bell needs to be triggered

type or paste code here

As u can see here I have set the pin high and when the bell should turn on, it goes low

Is this the relay?

Yes it is

Then you need 5V to turn it OFF. 3.3V from the ESP32 may or may or may not turn it OFF

No, it does not work like a charm because if that were true you would not be asking for help on the forum. You may be thinking it works like a charm because of your inexperience, but your errors could have side-effects which are causing the problems you mentioned.

What is the point of using an ESP32? A simpler Arduino would be more than adequate, and if you choose a 5V Arduino your circuit will be simpler and perhaps more reliable.

So at the moment, you are giving the LM2596 a 5V input and 2 of them will not be able to give a 5V output. Have you measured their output?

The 3rd LM2596 should be able to give a 3.3V output but this is too low for the Vin pin if the ESP. Have you measured the voltage at the ESP's 3.3V pin or it's digital output pins when they are set to OUTPUT & HIGH?

i indeed used and arduino UNO ( it worked ) but the reason i switched to esp32 is the dual core CPU and the more advanced watchdog timer.

For an automated bell project... Right.

It's clear you are totally convinced you know best, so I'm no longer sure why you are asking for help on a forum and I won't waste any more time offering my unwanted advice.

i have tested it with a lamp load first it worked. but i think " Unlike mechanical relays, SSRs use semiconductors (triacs, thyristors, or MOSFETs) to switch loads. These components never fully isolate the circuit, which results in a small leakage current passing through even when the relay is off "