Hello Arduino Community,
I have developed an ESP32-based automatic bell system for scheduled ringing using an Omron G3MB-202P solid-state relay (SSR) and an RTC module for precise timekeeping.
Project Overview:
- Microcontroller: ESP32
- Relay: Omron G3MB-202P SSR
- Power Supply: 220V AC to 5V DC conversion
- Snubber Circuit: 100Ω, 1W + 0.1µF capacitor
- Level Shifter: BC547 for logic conversion
- RTC Module: DS3231 for time-based triggering
Issue Faced:
- The bell sometimes triggers randomly even when the SSR relay is OFF.
- Also, noticed zero-crossing limitation of the SSR affecting precise control.
Solutions Implemented:
- Added an RC snubber circuit across the SSR output.
- Used a bleeder resistor (100KΩ - 470KΩ, 2W) to drain residual current.
- Improved grounding strategy to avoid floating ground issues.
I would appreciate any feedback on how to further optimize this system. Has anyone experienced SSR leakage issues with the Omron G3MB-202P before?
Looking forward to your insights!
HERE IS THE CODE:
#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 2
// 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, HIGH); // 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, LOW); // 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, LOW); // 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);
}
CIRCUIT:
CIRCUIT