4-channel relay sequentially on a single push button

Hello everyone,

I am looking for some help in designing a system for allowing power to LEGO transformers. The LEGO transformers requite AC and so, I choose to go with the 4 channel relay. I need the system to only have one push button and the sketch needs to sequentially activate each relay, but only after the button press. Then, power runs to the various LEGO models for the length of time we set the timer to.

This project is for a really cool display fundraiser. For the last few years our club has done fundraising for the Wake A Wish foundation. This year, our display is going to be an amusement park. There are going to be at least a dozen custom LEGO built amusement park rides. Kids will hit the push button to make the rides move. Normally, our timers that we already have would work fine. However, LEGO motors are pretty expensive and having them run for 5+ hours per day for 30 days completely wrecks them. By using this system, the motors only run for 1/3 of the time, and have a chance to cool down and not overheat.

For testing purposes I am just trying to get 1 relay to activate by button press, for a set time, then turn off. I am including the debounce I found.

Below is my first attempt. It doesn't work... My guess is the debounce is playing silly buggers with the delay.

Help would be appreciated :)

I am using a Arduino UNO. The relay board is: 4-channel relay


int relayPin4 = 9;
const int buttonPin = 8;

int relayState = HIGH;         // the current state of the output pin
int buttonState;               // the current reading from the input pin
int lastButtonState = LOW;     // the previous reading from the input pin

long lastDebounceTime = 0;     // the last time the output pin was toggled
long debounceDelay = 50;       // the debounce time; increase if the output flickers

void setup()
{
 pinMode(buttonPin, INPUT);
 pinMode(relayPin4, OUTPUT);                 // sets the digital pin as output
 digitalWrite(relayPin4, relayState);        // Prevents relays from starting up engaged
}

void loop()
{
 int reading = digitalRead(buttonPin);

 if (reading != lastButtonState)
 {
   lastDebounceTime = millis();
 }

 if ((millis() - lastDebounceTime) > debounceDelay)
 {
   if (reading != buttonState)
   {
     buttonState = reading;
     if (buttonState == HIGH)
     {
       relayState = !relayState;
     }
   }
 }

 digitalWrite(relayPin4, relayState);
 delay(1000);

 digitalWrite(relayPin4, !relayState);
 delay(1000);
 
lastButtonState = reading;
}

I hope the edits help.

Also, I HAVE spent about 12 hours searching and reading and testing and failing. I have learned a lot, however, I need advise.

Thanks in advance.

duddadu:
Also, I HAVE spent about 12 hours searching and reading and testing and failing. I have learned a lot, however, I need advise.

There is just one button, and if this button is pressed, the defined sequence starts playing?

No other devices to be handled “at the same time”?
The sequence doesn’t need to be interrupted while playing?

So what about a programming logic with three functions you have to write, like:

void setup()
{
  initRelays();
}

void loop()
{
  if (buttonPressed()) playSequence();
}

Writing which of those three functions do you need help with?

  • “void initRelays()”
  • “boolean buttonPressed()”
  • “void playSequence()”

Where is your problem?

For this project deboucing the button isn't needed.

just read the button and use it to call a function that runs the sequence, by the time the sequence ends the button will no longer be bouncing.

as long as you don't need anything else to happen this will work just fine.

like this:

void loop()
{
  if (digitalRead(buttonPin)) 
  {
    playSequence();
  }
}


void playSequence()  
{
  digitalWrite(relayPin4, HIGH);
  delay(1000);

  digitalWrite(relayPin4,LOW);
  delay(1000);
}

State machine.

You are expecting that pushing the button once will operate relay #1 and then pushing it again will operate relay #2? Pushing the button multiple times or holding it down while relay #1 is operating WON'T start the other relays?

Draw out the state diagram on a piece of paper. Start with a circle at the top and write "Idle" in it. Then, the first action that gets it out of idle state is the button going down. So draw an arrow to another state bubble and write next to the arrow "Run 1." So, in this state it turns on relay #1, which will run for 5 seconds or whatever.

So how does it leave state Run1? If the person releases the button within 5 seconds, then it stays in that state for the specified 5 seconds and then proceeds to "Idle 1". But what if they hold the button down? You need another state "wait 1" where you wait until the button is released. So there's two arrows leaving the Run1 state going to different state bubbles. They both wait for 5 seconds but depending on if the button is still held down you take a different path.

Wait1 will only have one exit - it goes to Idle1 when the button is released.

Idle1 looks like the original Idle state but of course, it goes to "Run 2" when the button is pressed and Run2 will operate relay 2.

Once you have the diagram then you can start writing the program. It can turn out to be surprisingly complex because of all the different states of "button (not) held down."

Thanks MorganS, I will try and draw it out.

To answer other questions and clarify things a bit:

When the button is pushed it activates a relay for a set amount of time. Whether it is held in or tapped repeatedly, the relay will be active for only that set of time.

Then, the next time the SAME button is pressed, a different relay is activated. And so on... forever.

I imagine that I will need an activate relay procedure ( ActivateRelay() ?), which would get called in the main loop. The "relayNumber" would be a global variable that would change on each loop.

Here is the kind of thing I'm thinking.

//Var
int relayNumber = 1;

void loop() {
  buttonPress stuff / check to see if the button is pressed

  if (button is pressed) {
      activateRelay();
      relayNumber++;
    }
  if (relayNumber > 4) {
    relayNumber = 1;
  }
}

int activateRelay(){
  make the relay active;
  for this long;
}

duddadu: TTo answer other questions and clarify things a bit:

When the button is pushed it activates a relay for a set amount of time. Whether it is held in or tapped repeatedly, the relay will be active for only that set of time.

Then, the next time the SAME button is pressed, a different relay is activated. And so on... forever.

Perhaps you can clearify it even more: is this for single-tasking or for multi-tasking?

Can several presses of the button activate more than just one relay for a given time at the same time, so that (maybe) 2, 3, 4 relays are to become active at the same time?

Or if a button is pressed and one relay is already active, the next button press will only start another relay, after the previous relay-task had been stopped?

That code will run continuously if the button is held down. Other projects usually require the button to be released and pressed again to start the next action but it looks like this is appropriate for the current project. It is detecting released-and-pressed that makes the state macine complex.

jurs: Perhaps you can clearify it even more: is this for single-tasking or for multi-tasking?

Can several presses of the button activate more than just one relay for a given time at the same time, so that (maybe) 2, 3, 4 relays are to become active at the same time?

Or if a button is pressed and one relay is already active, the next button press will only start another relay, after the previous relay-task had been stopped?

Single task. Press the button once, it activates the relay for a specific duration. THEN, the next time the button is presses, it triggers the next relay. No amount of pushing, holding, or tapping the button will change the sequence or timing while a relay is active. The program effectively ignores everything.

Only one relay is active at any given time. The button toggles sequentially from one relay to the next.

I'm sorry, I don't know how to be any more clear.

A switch (case) statement is one way to implement a state machine.

//Var
int relayNumber = 1;

void loop() {
  buttonPress stuff / check to see if the button is pressed

  if (button is pressed) {
      
      relayNumber++;

        switch(relayNumber)
    case 1:
      activateRelay1();
      break;
    case 2:
      activateRelay2();
      break;
    case 3:
      activateRelay3();
      break;
    case 4:
      activateRelay4();
      break;
    }
  if (relayNumber > 4) {
    relayNumber = 1;
  }

}

int activateRelay1(){   
  make the relay active;
  for this long;
}

@Hutkikz: your code looks fine to me. It should be noted that the activateRelayX() procedures should not return until the time has elapsed, i.e. using delay(active_timeX). Then the buttonPress stuff just reduces to pressed=digitalRead(buttonPin).

Thank you DrD.

Sometimes a simple task is best served with a simple solution. It's so easy to over complicate things (and yes they do tend to get that way all by themselves) that a simple and direct solution is often missed.

duddadu:
Single task. Press the button once, it activates the relay for a specific duration. THEN, the next time the button is presses, it triggers the next relay. No amount of pushing, holding, or tapping the button will change the sequence or timing while a relay is active. The program effectively ignores everything.

Here is some code for that:

// single-tasking sketch: switch relay ON for certain time
const byte NUMRELAYS = 4;
const byte RELAYACTIVE = LOW; // many mechanical relay boards are "active LOW" switching
const byte relayPins[] = {2, 3, 4, 5};
const byte buttonPin = 8; // connect button pins to D8 and GND
const long relayTimes[] = {10000L, 20000L, 30000L, 40000L}; // task duration in milliseconds (long)

void setup() {
  for (int i = 0; i < NUMRELAYS; i++)
  {
    digitalWrite(relayPins[i], !RELAYACTIVE);
    pinMode(relayPins[i], OUTPUT);
  }
  pinMode(buttonPin, INPUT_PULLUP); // connect button pins to D8 and GND
}

boolean buttonPressed() // reading button presses of a button with
{
  static unsigned long lastTime;
  static byte lastState = digitalRead(buttonPin);
  if (millis() - lastTime < 10) return false; // debounce 10 milliseconds
  byte currentState = digitalRead(buttonPin);
  if (currentState == lastState) return false;
  lastState=currentState;
  if (currentState==LOW) return true;
  else return false;
}

byte currentRelay = 0;
void loop() {
  if (buttonPressed())
  {
    digitalWrite(relayPins[currentRelay], RELAYACTIVE);
    delay(relayTimes[currentRelay]);
    digitalWrite(relayPins[currentRelay], !RELAYACTIVE);
    currentRelay++;
    if (currentRelay >= NUMRELAYS) currentRelay = 0;
  }
}

Please adjust as needed:

const byte RELAYACTIVE = LOW; // correct setting for most "ready made" mechanical relay boards
or
const byte RELAYACTIVE = HIGH; // correct setting for SSR relay boards

The button logic is for a button connected to GND and D8, activating the internal pullup resistor of the Atmega (INPUT_PULLUP), so your circuit needs no external pulldown resistor.

Have fun!

To jurs,

Thank You! Thank You! Thank You! Thank You! Thank You! Thank You!

Sincerly, duddadu

duddadu: To jurs,

Thank You! Thank You! Thank You! Thank You! Thank You! Thank You!

Sincerly, duddadu

No problem.

A word about switching AC transformers on/off using mechanical relays: Transformers typically are "inductive loads" and switching inductive loads may create more or less sparkling at the relay contacts when switching.

So perhaps you might want to use a "snubber circuit" to avoid RF communications disturbance and maximize the lifespan of your relays. Simple RC-snubber circuits consist of a suitable resistor and a capacitor combined. Better snubber circuits also use a varistor.

Link for further technical information: http://cp.literature.agilent.com/litweb/pdf/5988-6917EN.pdf

Thanks, I’ll check it out.

One interesting thing; sometimes, the relays self activate. I had the circuit running for a couple hours to test stability and every now and the “click” a relay would activate. Why would that happen? I was running the Arduino and relays without any loads…

duddadu: One interesting thing; sometimes, the relays self activate. I had the circuit running for a couple hours to test stability and every now and the "click" a relay would activate. Why would that happen?

How long is the cable from the Arduino board to the button?

What type of cable is it? Is it a shielded cable and is the cable shielding at one of the ends connected to something?

The wire running to the button is about 4 feet long. However, this was happening even when the button was not connected.

duddadu: The wire running to the button is about 4 feet long. However, this was happening even when the button was not connected.

So: - the wire is 4 feet long - the wire is unshielded - the wire had been connected to the Arduino - but the button had not been connected to the wire - then the relay is sometimes switching as if the button is connected and pressed That way?

A long and unshielded wire is always acting like an "antenna": Your wire-antenna tries to receive electromagnetic signals, and if the wire-antenna is connected to an Arduino input, the level of the input may change, even for short times.

Two possibilities for corrective actions: 1) hardware 2) software

To 1) In hardware you are currently using the "internal pull-up resistor" of the Atmega. This is a relatively "weak pull-up". This is typically enough for short wires withing an Arduino case (4 to 10 inches). With a longer wire you could add an "external pull-up resistor" as a stronger pull-up additionally: Connect a resistor of value 3.3K to 10K (kilo Ohm), possibly 4.7K between 5V and button pin.

Does that help to avoid ghost switching?

To 2) The other possibility would be a "low pass filtering" of the input signal with a software low-pass signal filter. So that level changes of only a few microseconds will be ignored and not considered as "button pressed". This would require the sketch to be changed a bit.

Wow... Thanks, I'll try the resistor. I happen to have a 10K resistor right in front of me.