Relay spark and sticking contacts

hi. i made a controller board for my door. the door goes up and down using two swing Gate actuators on the sides. the actuators are 220v 350w.



for better security i used a relay to cut power and a relay for direction for each side.
when closing the door, the power relays activated but the direction relays are off and every thing is OK. however there is a problem, when opening the door the power relays and direction relays activated but there are big sparks every few seconds and so the contacts of direction relays are sticking. this is the relay:

i dont know whats the problem. maybe trashy relays or motor capacitor ends both going to a relay contacts and discharging there? when opening because there is more load the current is greater and about 1.1A but when closing about .7A
thanks for the help
EDIT:
before making this board i used two buttons and four relays with different setup, no power and direction relays but one for opening and one for closing of each actuators. and different relays much better and there was no problem.

That is an important clue.

Have you flyback diodes across the actuator coils (assuming these are DC and not reversible) ? Or, anyway, say exactly what the load is.

they where 24v 5A Phonix contacts something like this:

like i said before there are two swing gate actuators like this:


they are 220v AC 350w.

Use better quality relays, making sure that the contacts are rated for 220VAC @ 5A or higher.

before buying new relays i wanted to know if there is no problems with my circuit

If your circuit is working, then there is no problem with it. See if you can find relays rated for AC motor control. Relays with "wetted" silver contacts will be best. Otherwise look for tungsten metal contacts.

Google for "relay snubber circuit"

so it seems the problem is quality of relays and i must buy new ones.
what do you think about the setup of relays. one for connecting power and one for directions or one relay for each directions.

Seems you don't have snubber circuits across the motors.

The two high voltage traces of the two relays on the left on the circuit board are dangerously close to the low voltage pins. And, next time copy the power traces also on the other side of the board, for double the current handling capacity.
Leo..

thanks for the advise i redo the high voltage traces as you suggested. and my board is one layer. maybe i can make it 2 layer. if there is any other suggestion i appreciate it. and what do you think about the setup of the relays.

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ACS712.h>
#include <EEPROM.h>
#include "DisplayController.h"
// Pin definitions for sensors, relays, buttons and rf module
const int PIN_SENSOR_RIGHT = A1;
const int PIN_SENSOR_LEFT = A0;
const int PIN_BUZZER = 2;
const int PIN_RF_OPEN = 3;
const int PIN_RF_CLOSE = 4;
const int PIN_RF_STOP = 5;
const int PIN_BUTTON_UP = 6;
const int PIN_BUTTON_DOWN = 7;
const int PIN_BUTTON_SELECT = 8;
const int PIN_RELAY_RIGHT_DIRECTION = 9;
const int PIN_RELAY_RIGHT_POWER = 10;
const int PIN_RELAY_LEFT_POWER = 11;
const int PIN_RELAY_LEFT_DIRECTION = 12;

// Define EEPROM addresses for storing settings
const int STALL_TIME_ADDRESS = 0;
const int CURRENT_SENSOR_ON_ADDRESS = 2;
const int OPENING_R_THRESHOLD_ADDRESS = 3;
const int OPENING_L_THRESHOLD_ADDRESS = 7;
const int CLOSING_R_THRESHOLD_ADDRESS = 11;
const int CLOSING_L_THRESHOLD_ADDRESS = 15;
// Define menu items as strings stored in program memory
const char chrMainMenu[] PROGMEM = "Main Menu";
const char chrRightActuator[] PROGMEM = "Right Actuator";
const char chrLeftActuator[] PROGMEM = "Left Actuator";
const char chrStallTime[] PROGMEM = "Stall Time sec";
const char chrCurrentSensor[] PROGMEM = "Current Sensor";
const char chrExit[] PROGMEM = "Exit";
const char chrOThreshold[] PROGMEM = "O.Threshold";
const char chrCThreshold[] PROGMEM = "C.Threshold";
// Index definitions for menu items
const int STALL_TIME_INDEX = 2;
const int CURRENT_SENSOR_ON_INDEX = 3;
const int OPENING_R_THRESHOLD_INDEX = 5;
const int OPENING_L_THRESHOLD_INDEX = 8;
const int CLOSING_R_THRESHOLD_INDEX = 6;
const int CLOSING_L_THRESHOLD_INDEX = 9;

// Menu items definition
MenuItem Menu_Items[] = {
    /*Main menu page*/
    /*00*/{chrRightActuator, MenuItem::PAGE, {.intValue = 2}},
    /*01*/{chrLeftActuator, MenuItem::PAGE, {.intValue = 3}},
    /*02*/{chrStallTime, MenuItem::INT, {.intValue = 3}, {.intMin = 0}, {.intMax = 10}},
    /*03*/{chrCurrentSensor, MenuItem::BOOL, {.boolValue = false}},
    /*04*/{chrExit, MenuItem::PAGE, {.intValue = 0}},
    /*          Right Actuator menu page*/
    /*05*/{chrOThreshold, MenuItem::FLOAT, {.floatValue = 1.10}, {.floatMin = 0.500}, {.floatMax = 5.00}},
    /*06*/{chrCThreshold, MenuItem::FLOAT, {.floatValue = 1.10}, {.floatMin = 0.500}, {.floatMax = 5.00}},
    /*07*/{chrExit, MenuItem::PAGE, {.intValue = 1}},
    /*          Left Actuator menu page*/
    /*08*/{chrOThreshold, MenuItem::FLOAT, {.floatValue = 1.10}, {.floatMin = 0.500}, {.floatMax = 5.00}},
    /*09*/{chrCThreshold, MenuItem::FLOAT, {.floatValue = 1.10}, {.floatMin = 0.500}, {.floatMax = 5.00}},
    /*10*/{chrExit, MenuItem::PAGE, {.intValue = 1}},
};
// Menu index definitions
MenuIndex Menu_Index[] = {
    {chrMainMenu, 5, 0},
    {chrMainMenu, 5, 0},
    {chrRightActuator, 3, 5},
    {chrLeftActuator, 3, 8},
};
// Display controller initialization
DisplayController displayController;
// Current sensor initializations
ACS712 currentSensor_Right(ACS712_05B, PIN_SENSOR_RIGHT);
ACS712 currentSensor_Left(ACS712_05B, PIN_SENSOR_LEFT);
// Function prototypes
void readFromEEPROM();
void saveToEEPROM();
void rfStateUpdate();
bool debounce(unsigned long& lastDebounceTime);
void buttonHandler();
void currentCheck(float &r_Current, float &l_Current);
void setRelays();
void setBuzzer();

// Actuator states initialization
ActuatorState R_Actuator_State = STOPPED;
ActuatorState L_Actuator_State = STOPPED;
ActuatorState rf_State = STOPPED;
ActuatorState button_State = STOPPED;


const unsigned long DEBOUNCE_TIME = 200;  // Constant for debounce timing
unsigned long holdTime;
void setup() 
{
    Serial.begin(9600);                  // Start serial communication at 9600 baud
    displayController.begin();           // Initialize the display controller
    currentSensor_Right.calibrate();     // Calibrate the right current sensor
    currentSensor_Left.calibrate();      // Calibrate the left current sensor
    // Set pin modes for RF controls and buttons
    pinMode(PIN_RF_OPEN, INPUT);
    pinMode(PIN_RF_CLOSE, INPUT);
    pinMode(PIN_RF_STOP, INPUT);
    pinMode(PIN_BUTTON_UP, INPUT_PULLUP);
    pinMode(PIN_BUTTON_DOWN, INPUT_PULLUP);
    pinMode(PIN_BUTTON_SELECT, INPUT_PULLUP);
    // Set pin modes for relays controlling the actuators
    pinMode(PIN_BUZZER, OUTPUT);
    pinMode(PIN_RELAY_RIGHT_DIRECTION, OUTPUT);
    pinMode(PIN_RELAY_RIGHT_POWER, OUTPUT);
    pinMode(PIN_RELAY_LEFT_DIRECTION, OUTPUT);
    pinMode(PIN_RELAY_LEFT_POWER, OUTPUT);
    //
    pinMode(LED_BUILTIN, OUTPUT);
/*///////////////////////
    EEPROM.put(STALL_TIME_ADDRESS, 3);
    EEPROM.put(CURRENT_SENSOR_ON_ADDRESS, true);
    EEPROM.put(OPENING_R_THRESHOLD_ADDRESS, 1.20);
    EEPROM.put(OPENING_L_THRESHOLD_ADDRESS, 1.20);
    EEPROM.put(CLOSING_R_THRESHOLD_ADDRESS, 1.20);
    EEPROM.put(CLOSING_L_THRESHOLD_ADDRESS, 1.20);
*///////////////////////
    readFromEEPROM();                           // Read values from EEPROM

    displayController.menu = Menu_Items;        // Set the menu items for display
    displayController.menuIndex = Menu_Index;   // Set the menu index for navigation

    Serial.println("Start");

    displayController.displayLogo();
    int buzzer_State = LOW;
    for (int i = 0; i < 10; i++)
    {
        buzzer_State = !buzzer_State;
        digitalWrite(PIN_BUZZER ,buzzer_State);
        delay(100);
    }
    digitalWrite(PIN_BUZZER ,LOW);
    

}

void loop() 
{
    float currentRight = currentSensor_Right.getCurrentAC(50);
    float currentLeft = currentSensor_Left.getCurrentAC(50);
    
    rfStateUpdate();
    buttonHandler();
    currentCheck(currentRight, currentLeft);

    if (button_State == CLOSING || rf_State == CLOSING)
    {
       //if (R_Actuator_State != CLOSING || L_Actuator_State != CLOSING)
        //{
            R_Actuator_State = CLOSING;
            L_Actuator_State = CLOSING;
            setRelays();
        //}
    } else if (button_State == OPENING || rf_State == OPENING) 
    {
        //if (R_Actuator_State != OPENING || L_Actuator_State != OPENING)
        //{
            R_Actuator_State = OPENING;
            L_Actuator_State = OPENING;
            setRelays();
        //}
    } else //if (button_State == STOPPED || rf_State == STOPPED)
    {
        R_Actuator_State = STOPPED;
        L_Actuator_State = STOPPED;
        setRelays();
    }
    //setBuzzer();
    /*
    Serial.print(F("button: "));
    Serial.print(button_State == STOPPED ? F("Stopped") : (button_State == CLOSING ? F("Closing") : F("Opening")));
    Serial.print(F("\t rf: "));
    Serial.print(rf_State == STOPPED ? F("Stopped") : (rf_State == CLOSING ? F("Closing") : F("Opening")));
    Serial.print(F("\t r_ac: "));
    Serial.print(R_Actuator_State == STOPPED ? F("Stopped") : (R_Actuator_State == CLOSING ? F("Closing") : F("Opening")));
    Serial.print(F("\t l_ac: "));
    Serial.println(L_Actuator_State == STOPPED ? F("Stopped") : (L_Actuator_State == CLOSING ? F("Closing") : F("Opening")));
    */
    if(displayController.Current_Page == MAIN_PAGE){
        displayController.displayMainPage(R_Actuator_State, L_Actuator_State, currentRight, currentLeft);
    }else {
        displayController.displayMenu();
    }
    
}

void rfStateUpdate()
{
    static bool prvRfOpenState;
    static bool prvRfCloseState;
    bool RfOpenState = digitalRead(PIN_RF_OPEN);
    bool RfCloseState = digitalRead(PIN_RF_CLOSE);
    bool RfStopState = digitalRead(PIN_RF_STOP);
    
    if (RfStopState)
    {
        rf_State = STOPPED;
    }else if(RfCloseState && !prvRfCloseState)
    {
        rf_State = CLOSING;
    }else if(RfOpenState && !prvRfOpenState)
    {
        rf_State = OPENING;
    }
    prvRfOpenState = RfOpenState;
    prvRfCloseState = RfCloseState;
    //Serial.print(rf_State == STOPPED ? F("Stopped") : (rf_State == CLOSING ? F("Closing") : F("Opening")));
}

bool debounce(unsigned long& lastDebounceTime) {
    unsigned long currentTime = millis();
    if (currentTime - lastDebounceTime > DEBOUNCE_TIME) {
        lastDebounceTime = currentTime;
        return true;
    }
    return false;
}

void buttonHandler()
{
    static int prv_btnUp_State = HIGH;
    static int prv_btnDown_State = HIGH;
    int btnUp_State = digitalRead(PIN_BUTTON_UP);
    int btnDown_State = digitalRead(PIN_BUTTON_DOWN);
    int btnSelect_State = digitalRead(PIN_BUTTON_SELECT);
    
    if (displayController.Current_Page == MAIN_PAGE)
    {
        if (btnDown_State == LOW && (btnDown_State != prv_btnDown_State))
        {
            button_State = CLOSING;
            rf_State = STOPPED;
        }else if (btnUp_State == LOW && (btnUp_State != prv_btnUp_State))
        {
            button_State = OPENING;
            rf_State = STOPPED;

        }else if (btnUp_State && btnDown_State)
        {
            button_State = STOPPED;
        }

        prv_btnUp_State = btnUp_State;
        prv_btnDown_State = btnDown_State;
        //Serial.print("btnUp_State");
        //Serial.print(btnUp_State);
        //Serial.print("\t prv_btnUp_State");
        //Serial.println(prv_btnUp_State);
        //Serial.print(button_State == STOPPED ? F("Stopped") : (button_State == CLOSING ? F("Closing") : F("Opening")));
    } else
    {
        // handle Up button in menu
        if (btnUp_State == LOW) 
        {
            static unsigned long lastDebounceTimeUp = 0;
            if (debounce(lastDebounceTimeUp)) {displayController.menuItem_Inc();}
        }
        // handle Down button in menu
        if (btnDown_State == LOW) 
        {
            static unsigned long lastDebounceTimeDown = 0;
            if (debounce(lastDebounceTimeDown)) {displayController.menuItem_Dec();}
        }
    }

    // Handle Select button
    if (btnSelect_State == LOW) 
    {
            static unsigned long lastDebounceTimeSelect = 0;
            if (debounce(lastDebounceTimeSelect)) 
            {
                displayController.menuItem_Selc();
                if(displayController.ValueChanged())
                {
                    //Serial.println("value changed");
                    saveToEEPROM();
                }
            }
    }
}

void currentCheck(float &r_Current, float &l_Current)
{

    bool current_ON = Menu_Items[CURRENT_SENSOR_ON_INDEX].value.boolValue;  // Retrieve the boolean value for the current sensor from the menu items
    static unsigned long OverCurStartTime = 0;  // Track when the overcurrent condition started

    // Proceed only if the current sensor is enabled
    if (!current_ON) return;

    float r_threshold, l_threshold;
    // Check if either actuator is in the OPENING state
    if (R_Actuator_State == OPENING || L_Actuator_State == OPENING)
    {
        // Retrieve the overcurrent thresholds from the menu items
        r_threshold = Menu_Items[OPENING_R_THRESHOLD_INDEX].value.floatValue;  // Right actuator threshold
        l_threshold = Menu_Items[OPENING_L_THRESHOLD_INDEX].value.floatValue;  // Left actuator threshold
    } else
    {
        // Retrieve the overcurrent thresholds from the menu items
        r_threshold = Menu_Items[CLOSING_R_THRESHOLD_INDEX].value.floatValue;  // Right actuator threshold
        l_threshold = Menu_Items[CLOSING_L_THRESHOLD_INDEX].value.floatValue;  // Left actuator threshold
    }

    // Compare the current values against the thresholds
    if (r_Current > r_threshold || l_Current > l_threshold) //|| fabs(r_Current - l_Current) > 0.1)
    {
        digitalWrite(PIN_BUZZER ,HIGH);
        //if door is Opening imeditally stop the door after over current 
        if(R_Actuator_State == OPENING || L_Actuator_State == OPENING)
        {
            // Stop the actuator(s) if current exceeds the threshold
            button_State = STOPPED;
            rf_State = STOPPED;
            digitalWrite(PIN_BUZZER ,LOW);
            return;
        }

        unsigned long currentTime = millis();
        unsigned int  stallTime = Menu_Items[STALL_TIME_INDEX].value.intValue * 1000;
        // If this is the first overcurrent detection, set the start time
        if(OverCurStartTime == 0)
        {
            OverCurStartTime = currentTime;
        }
        
        if (currentTime - OverCurStartTime > stallTime) 
        {
            // Stop the actuator(s) if current exceeds the threshold
            button_State = STOPPED;
            rf_State = STOPPED;
            // After stopping, reset the OverCurStartTime to prevent repeated stops 
            OverCurStartTime = 0;  // Reset the timer after stopping
            digitalWrite(PIN_BUZZER ,LOW);
        }           
    }else
    {
        // If current is below the threshold, reset OverCurStartTime
        OverCurStartTime = 0; // Reset if the current is normal
    }
}

void setRelays()
{ 
    bool rA_O = R_Actuator_State == OPENING ? HIGH : LOW;
    digitalWrite(PIN_RELAY_RIGHT_DIRECTION, rA_O);  // Set right actuator direction to open
    bool lA_O = L_Actuator_State == OPENING ? HIGH : LOW;
    digitalWrite(PIN_RELAY_LEFT_DIRECTION, lA_O);   // Set left actuator direction to open
    delay(200);
    bool rA_S = R_Actuator_State != STOPPED ? HIGH : LOW;
    digitalWrite(PIN_RELAY_RIGHT_POWER, rA_S);      // Power on the right actuator
    bool lA_S = L_Actuator_State != STOPPED ? HIGH : LOW;
    digitalWrite(PIN_RELAY_LEFT_POWER, lA_S);       // Power on the left actuator
    /////////
    Serial.print("R_Dir; ");
    Serial.print(rA_O);
    Serial.print("\t L_Dir: ");
    Serial.print(lA_O);
    Serial.print("\t R_Power: ");
    Serial.print(rA_S);
    Serial.print("\t L_Power: ");
    Serial.println(lA_S);
}

void setBuzzer()
{
    static int buzzer_State = LOW;
    unsigned long currentTime = millis();
    static long prvTime = 0;
    unsigned long buzzerDellay = 100;
    if(R_Actuator_State == STOPPED && L_Actuator_State == STOPPED)
    {
        buzzer_State = LOW;
    }else
    {
        if ((currentTime - prvTime) > buzzerDellay )
        {
            buzzer_State = !buzzer_State;
        }
    }
    digitalWrite(PIN_BUZZER ,buzzer_State);
}

void readFromEEPROM()
{
    EEPROM.get(STALL_TIME_ADDRESS, Menu_Items[STALL_TIME_INDEX].value.intValue);
    EEPROM.get(CURRENT_SENSOR_ON_ADDRESS, Menu_Items[CURRENT_SENSOR_ON_INDEX].value.boolValue);
    EEPROM.get(OPENING_R_THRESHOLD_ADDRESS, Menu_Items[OPENING_R_THRESHOLD_INDEX].value.floatValue);
    EEPROM.get(OPENING_L_THRESHOLD_ADDRESS, Menu_Items[OPENING_L_THRESHOLD_INDEX].value.floatValue);
    EEPROM.get(CLOSING_R_THRESHOLD_ADDRESS, Menu_Items[CLOSING_R_THRESHOLD_INDEX].value.floatValue);
    EEPROM.get(CLOSING_L_THRESHOLD_ADDRESS, Menu_Items[CLOSING_L_THRESHOLD_INDEX].value.floatValue);
}

void saveToEEPROM()
{
    int index = displayController.Current_MenuItem;
    switch (index)
    {
    case STALL_TIME_INDEX:
        EEPROM.put(STALL_TIME_ADDRESS, Menu_Items[STALL_TIME_INDEX].value.intValue);
        break;
    case CURRENT_SENSOR_ON_INDEX:
        EEPROM.put(CURRENT_SENSOR_ON_ADDRESS, Menu_Items[CURRENT_SENSOR_ON_INDEX].value.boolValue);
        break;
    case OPENING_R_THRESHOLD_INDEX:
        EEPROM.put(OPENING_R_THRESHOLD_ADDRESS, Menu_Items[OPENING_R_THRESHOLD_INDEX].value.floatValue);
        break;
    case OPENING_L_THRESHOLD_INDEX:
        EEPROM.put(OPENING_L_THRESHOLD_ADDRESS, Menu_Items[OPENING_L_THRESHOLD_INDEX].value.floatValue);
        break;
    case CLOSING_R_THRESHOLD_INDEX:
        EEPROM.put(CLOSING_R_THRESHOLD_ADDRESS, Menu_Items[CLOSING_R_THRESHOLD_INDEX].value.floatValue);
        break;
    case CLOSING_L_THRESHOLD_INDEX:
        EEPROM.put(CLOSING_L_THRESHOLD_ADDRESS, Menu_Items[CLOSING_L_THRESHOLD_INDEX].value.floatValue);
        break;
    default:
        break;
    }   
}


and this is the code

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