Relay Problem Help! Keg Washer Program

Help!

I cannot get the relays for an 8 Relay Module to end in an off position. Not sure how to accomplish this. I have worked and worked through this problem and am also complete. I just need to get an answer to the final step. I had a problem with getting the relays to start in the off position. I was able to solve them with the following commands.

digitalWrite(pin, HIGH);
pinMode(pin, OUTPUT);

Here is the code.

//keep I/O serial pin 1 in case of serial
#define startButton 12
#define compAirSlnd 2
#define drainSlnd 3
#define h2oSlnd 4
#define causticSlnd 5
#define causticRtrnSlnd 6
#define saniSlnd 7
#define saniRtrnSlnd 8
#define co2PurgeSlnd 9
#define co2FillSlnd 10
#define pumpSwitch 11

#define idle 0
#define airWaterAir1 1
#define airWaterAir2 2
#define caustic 3
#define sanitizer 4
#define co2Purge 5
#define co2Fill 6

//set initial state idle
//idle waits for start button
byte state = idle;

boolean stateChange = false;
byte buttonState = HIGH;
byte lastButtonState = HIGH;

unsigned long currentMillis = millis();
unsigned long stateStartMillis = millis();
unsigned long timeRemaining = 0;

//i2c lcd dislplay
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h> // include i/o class header

hd44780_I2Cexp lcd; // declare lcd object: auto locate & config display for hd44780 chip

void setup() {
pinMode(startButton, INPUT_PULLUP);
digitalWrite (compAirSlnd, HIGH);
pinMode (compAirSlnd, OUTPUT);
digitalWrite (co2FillSlnd, HIGH);
pinMode (co2FillSlnd, OUTPUT);
digitalWrite (co2PurgeSlnd, HIGH);
pinMode (co2PurgeSlnd, OUTPUT);
digitalWrite (h2oSlnd, HIGH);
pinMode (h2oSlnd, OUTPUT);
digitalWrite (pumpSwitch, HIGH);
pinMode (pumpSwitch, OUTPUT);
digitalWrite (causticSlnd, HIGH);
pinMode (causticSlnd, OUTPUT);
digitalWrite (causticRtrnSlnd, HIGH);
pinMode (causticRtrnSlnd, OUTPUT);
digitalWrite (saniSlnd, HIGH);
pinMode (saniSlnd, OUTPUT);
digitalWrite (saniRtrnSlnd, HIGH);
pinMode (saniRtrnSlnd, OUTPUT);
digitalWrite (drainSlnd, HIGH);
pinMode (drainSlnd, OUTPUT);

lcd.begin(16, 2);
lcd.print(“Keg Wash”);
delay(1000);
}
void loop() {
//use one value of time for all events in loop and functions
currentMillis = millis();

switch (state) {
case idle: {
buttonState = digitalRead(startButton);
//may need debouncing?
if (buttonState == LOW && lastButtonState == HIGH)
{
state = airWaterAir1;
}
//the next line may not be needed as will be leaving state on first press
//lastButtonState default HIGH from setup
lastButtonState = buttonState;
break;
}
case airWaterAir1: {
airWaterAir(15, 20, 15);
break;
}
case caustic: {
unsigned long causticTime = 90000UL;
if (stateChange == false)
{
stateStartMillis = millis();
stateChange = true;
digitalWrite(causticSlnd, HIGH);
digitalWrite(causticRtrnSlnd, HIGH);
digitalWrite(pumpSwitch, HIGH);
}
if (stateChange == true)
timeRemaining = stateStartMillis + causticTime - currentMillis;

if (currentMillis - stateStartMillis >= causticTime)
{
digitalWrite(causticSlnd, LOW);
digitalWrite(causticRtrnSlnd, LOW);
digitalWrite(pumpSwitch, LOW);
stateChange = false;
state = airWaterAir2;
}
break;
}
case airWaterAir2: {
airWaterAir(15, 15, 15);
break;
}
case sanitizer: {
unsigned long sanitizerTime = 60000UL;
if (stateChange == false)
{
stateStartMillis = millis();
stateChange = true;
digitalWrite(saniSlnd, HIGH);
digitalWrite(saniRtrnSlnd, HIGH);
digitalWrite(pumpSwitch, HIGH);
}

if (stateChange == true)
timeRemaining = stateStartMillis + sanitizerTime - currentMillis;

if (currentMillis - stateStartMillis >= sanitizerTime)
{
digitalWrite(saniSlnd, LOW);
digitalWrite(saniRtrnSlnd, LOW);
digitalWrite(pumpSwitch, LOW);
stateChange = false;
state = co2Purge;
}
break;
}
case co2Purge: {
unsigned long co2purgeTime = 30000UL;
if (stateChange == false)
{
stateStartMillis = millis();
stateChange = true;
digitalWrite(co2PurgeSlnd, HIGH);
digitalWrite(drainSlnd, HIGH);
}
if (stateChange == true)
timeRemaining = stateStartMillis + co2purgeTime - currentMillis;
if (currentMillis - stateStartMillis >= co2purgeTime)
{
digitalWrite(co2PurgeSlnd, LOW);
digitalWrite(drainSlnd, LOW);
stateChange = false;
state = co2Fill;
}
break;
}

case co2Fill: {
unsigned long co2fillTime = 10000UL;
if (stateChange == false)
{
stateStartMillis = millis();
stateChange = true;
digitalWrite(co2FillSlnd, HIGH);
}
if (stateChange == true)
timeRemaining = stateStartMillis + co2fillTime - currentMillis;
if (currentMillis - stateStartMillis >= co2fillTime)
{
digitalWrite(co2FillSlnd, LOW);
stateChange = false;
state = idle;
}
break;
}
}
lcdDisplay();
}

void airWaterAir(byte T1, byte T2, byte T3) {
unsigned long firstAirTime = T1 * 1000UL;
unsigned long h2oTime = T2 * 1000UL;
unsigned long secondAirTime = T3 * 1000UL;
unsigned long drainTime = (T1 + T2 + T3) * 1000UL;
static boolean firstAirComplete = false;

if (stateChange == false)
{
stateStartMillis = millis();
stateChange = true;
digitalWrite(compAirSlnd, HIGH);
digitalWrite(drainSlnd, HIGH);
}
if (stateChange == true)
timeRemaining = stateStartMillis + drainTime - currentMillis;

if (firstAirComplete == false && (currentMillis - stateStartMillis >= firstAirTime))
{
digitalWrite(compAirSlnd, LOW);
digitalWrite(h2oSlnd, HIGH);
digitalWrite(pumpSwitch, HIGH);
digitalWrite(drainSlnd,HIGH);
firstAirComplete = true;
}

if (currentMillis - stateStartMillis >= (firstAirTime + h2oTime))
{ digitalWrite(h2oSlnd, LOW);
digitalWrite (pumpSwitch, LOW);
digitalWrite(drainSlnd,HIGH);
digitalWrite (compAirSlnd, HIGH);
}
if (currentMillis - stateStartMillis >= drainTime)
{ digitalWrite(compAirSlnd, LOW);
digitalWrite(drainSlnd, LOW);
if (state == airWaterAir1)
state = caustic;
else
state = sanitizer;

stateChange = false;
firstAirComplete = false;
}
}

void lcdDisplay()
{
const char displayState[17] =
{
"idle ",
"airWaterAir1 ",
"airWaterAir2 ",
"caustic ",
"sanitizer ",
"co2Purge ",
"co2Fill ",
};
const int displayInterval = 1000;
static unsigned long lastDisplay = 0;
static byte lastState = 99;//forces initial display to idle

if (millis() - lastDisplay >= displayInterval)
{
lastDisplay += displayInterval;
if (state != lastState)
{
lcd.setCursor (0, 0);
lcd.print(displayState[state]);
}
lcd.setCursor(0, 1);
if (state == idle)
{
lcd.print(“XX”);
//timeRemaining = 0;
}
else if (timeRemaining / 1000 <= 10)
{
lcd.print(timeRemaining / 1000); //seconds
lcd.print(" ");//clear trailing 0
}
else
lcd.print(timeRemaining / 1000);

lastState = state;
}
}

Hi,
Learn how to post code so people can read it:

//keep I/O serial pin 1 in case of serial
#define startButton     12
#define compAirSlnd     2
#define drainSlnd       3
#define h2oSlnd         4
#define causticSlnd     5
#define causticRtrnSlnd 6
#define saniSlnd        7
#define saniRtrnSlnd    8
#define co2PurgeSlnd    9
#define co2FillSlnd    10
#define pumpSwitch     11

#define idle 0
#define airWaterAir1 1
#define airWaterAir2 2
#define caustic 3
#define sanitizer 4
#define co2Purge 5
#define co2Fill 6

//set initial state idle
//idle waits for start button
byte state = idle;

boolean stateChange = false;
byte buttonState = HIGH;
byte lastButtonState = HIGH;

unsigned long currentMillis = millis();
unsigned long stateStartMillis = millis();
unsigned long timeRemaining = 0;

//i2c lcd dislplay
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h> // include i/o class header

hd44780_I2Cexp lcd; // declare lcd object: auto locate & config display for hd44780 chip

void setup() {
  pinMode(startButton, INPUT_PULLUP);
  digitalWrite (compAirSlnd, HIGH);
  pinMode (compAirSlnd, OUTPUT);
  digitalWrite (co2FillSlnd, HIGH);
  pinMode (co2FillSlnd, OUTPUT);
  digitalWrite (co2PurgeSlnd, HIGH);
  pinMode (co2PurgeSlnd, OUTPUT);
  digitalWrite (h2oSlnd, HIGH);
  pinMode (h2oSlnd, OUTPUT);
  digitalWrite (pumpSwitch, HIGH);
  pinMode (pumpSwitch, OUTPUT);
  digitalWrite (causticSlnd, HIGH);
  pinMode (causticSlnd, OUTPUT);
  digitalWrite (causticRtrnSlnd, HIGH);
  pinMode (causticRtrnSlnd, OUTPUT);
  digitalWrite (saniSlnd, HIGH);
  pinMode (saniSlnd, OUTPUT);
  digitalWrite (saniRtrnSlnd, HIGH);
  pinMode (saniRtrnSlnd, OUTPUT);
  digitalWrite (drainSlnd, HIGH);
  pinMode (drainSlnd, OUTPUT);

  lcd.begin(16, 2);
  lcd.print("Keg Wash");
  delay(1000);
}

void loop() {
  //use one value of time for all events in loop and functions
  currentMillis = millis();

  switch (state) {
    case idle: {
        buttonState = digitalRead(startButton);
        //may need debouncing?
        if (buttonState == LOW && lastButtonState == HIGH)
        {
          state = airWaterAir1;
        }
        //the next line may not be needed as will be leaving state on first press
        //lastButtonState default HIGH from setup
        lastButtonState = buttonState;
        break;
      }
    case airWaterAir1: {
        airWaterAir(15, 20, 15);
        break;
      }
    case caustic: {
        unsigned long causticTime = 90000UL;
        if (stateChange == false)
        {
          stateStartMillis = millis();
          stateChange = true;
          digitalWrite(causticSlnd, HIGH);
          digitalWrite(causticRtrnSlnd, HIGH);
          digitalWrite(pumpSwitch, HIGH);
        }
        if (stateChange == true)
          timeRemaining = stateStartMillis + causticTime - currentMillis;

        if (currentMillis - stateStartMillis >= causticTime)
        {
          digitalWrite(causticSlnd, LOW);
          digitalWrite(causticRtrnSlnd, LOW);
          digitalWrite(pumpSwitch, LOW);
          stateChange =  false;
          state = airWaterAir2;
        }
        break;
      }
    case airWaterAir2: {
        airWaterAir(15, 15, 15);
        break;
      }
    case sanitizer: {
        unsigned long sanitizerTime = 60000UL;
        if (stateChange == false)
        {
          stateStartMillis = millis();
          stateChange = true;
          digitalWrite(saniSlnd, HIGH);
          digitalWrite(saniRtrnSlnd, HIGH);
          digitalWrite(pumpSwitch, HIGH);
        }

        if (stateChange == true)
          timeRemaining = stateStartMillis + sanitizerTime - currentMillis;

        if (currentMillis - stateStartMillis >= sanitizerTime)
        {
          digitalWrite(saniSlnd, LOW);
          digitalWrite(saniRtrnSlnd, LOW);
          digitalWrite(pumpSwitch, LOW);
          stateChange = false;
          state = co2Purge;
        }
        break;
      }
    case co2Purge: {
        unsigned long co2purgeTime = 30000UL;
        if (stateChange == false)
        {
          stateStartMillis = millis();
          stateChange = true;
          digitalWrite(co2PurgeSlnd, HIGH);
          digitalWrite(drainSlnd, HIGH);
        }
        if (stateChange == true)
          timeRemaining = stateStartMillis + co2purgeTime - currentMillis;
        if (currentMillis - stateStartMillis >= co2purgeTime)
        {
          digitalWrite(co2PurgeSlnd, LOW);
          digitalWrite(drainSlnd, LOW);
          stateChange = false;
          state = co2Fill;
        }
        break;
      }

    case co2Fill: {
        unsigned long co2fillTime = 10000UL;
        if (stateChange == false)
        {
          stateStartMillis = millis();
          stateChange = true;
          digitalWrite(co2FillSlnd, HIGH);
        }
        if (stateChange == true)
          timeRemaining = stateStartMillis + co2fillTime - currentMillis;
        if (currentMillis - stateStartMillis >= co2fillTime)
        {
          digitalWrite(co2FillSlnd, LOW);
          stateChange = false;
          state = idle;
        }
        break;
      }
  }
  lcdDisplay();
}

void airWaterAir(byte T1, byte T2, byte T3) {
  unsigned long firstAirTime = T1 * 1000UL;
  unsigned long h2oTime = T2 * 1000UL;
  unsigned long secondAirTime = T3 * 1000UL;
  unsigned long drainTime = (T1 + T2 + T3) * 1000UL;
  static boolean firstAirComplete = false;

  if (stateChange == false)
  {
    stateStartMillis = millis();
    stateChange = true;
    digitalWrite(compAirSlnd, HIGH);
    digitalWrite(drainSlnd, HIGH);
  }
  if (stateChange == true)
    timeRemaining = stateStartMillis + drainTime - currentMillis;

  if (firstAirComplete == false && (currentMillis - stateStartMillis >= firstAirTime))
  {
    digitalWrite(compAirSlnd, LOW);
    digitalWrite(h2oSlnd, HIGH);
    digitalWrite(pumpSwitch, HIGH);
    digitalWrite(drainSlnd, HIGH);
    firstAirComplete = true;
  }

  if (currentMillis - stateStartMillis >= (firstAirTime + h2oTime))
  { digitalWrite(h2oSlnd, LOW);
    digitalWrite (pumpSwitch, LOW);
    digitalWrite(drainSlnd, HIGH);
    digitalWrite (compAirSlnd, HIGH);
  }
  if (currentMillis - stateStartMillis >= drainTime)
  { digitalWrite(compAirSlnd, LOW);
    digitalWrite(drainSlnd, LOW);
    if (state == airWaterAir1)
      state = caustic;
    else
      state = sanitizer;

    stateChange = false;
    firstAirComplete = false;
  }
}

void lcdDisplay()
{
  const char displayState[][17] =
  {
    "idle            ",
    "airWaterAir1    ",
    "airWaterAir2    ",
    "caustic         ",
    "sanitizer       ",
    "co2Purge        ",
    "co2Fill         ",
  };
  const int displayInterval = 1000;
  static unsigned long lastDisplay = 0;
  static byte lastState = 99;//forces initial display to idle

  if (millis() - lastDisplay >= displayInterval)
  {
    lastDisplay += displayInterval;
    if (state != lastState)
    {
      lcd.setCursor (0, 0);
      lcd.print(displayState[state]);
    }
    lcd.setCursor(0, 1);
    if (state == idle)
    {
      lcd.print("XX");
      //timeRemaining = 0;
    }
    else if (timeRemaining / 1000 <= 10)
    {
      lcd.print(timeRemaining / 1000); //seconds
      lcd.print(" ");//clear trailing 0
    }
    else
      lcd.print(timeRemaining / 1000);

    lastState = state;
  }
}

Please tell us more about how this is supposed to work, and exactly what your problem is.

Here is an example of handling those "Active Low" relays. See the definitions of RELAY_ON and RELAY_OFF.

 /* YourDuino Example: Relay Control 1.10
  Handles "Relay is active-low" to assure
  no relay activation from reset until
  application is ready.
   terry@yourduino.com */

/*-----( Import needed libraries )-----*/
/*-----( Declare Constants )-----*/
#define RELAY_ON 0
#define RELAY_OFF 1
/*-----( Declare objects )-----*/
/*-----( Declare Variables )-----*/
#define Relay_1  2  // Arduino Digital I/O pin number
#define Relay_2  3
#define Relay_3  4
#define Relay_4  5

void setup()   /****** SETUP: RUNS ONCE ******/
{
//-------( Initialize Pins so relays are inactive at reset)----
  digitalWrite(Relay_1, RELAY_OFF);
  digitalWrite(Relay_2, RELAY_OFF);
  digitalWrite(Relay_3, RELAY_OFF);
  digitalWrite(Relay_4, RELAY_OFF);

//---( THEN set pins as outputs )----  
  pinMode(Relay_1, OUTPUT);
  pinMode(Relay_2, OUTPUT);
  pinMode(Relay_3, OUTPUT);
  pinMode(Relay_4, OUTPUT);
  delay(4000); //Check that all relays are inactive at Reset

}//--(end setup )---


void loop()   /****** LOOP: RUNS CONSTANTLY ******/
{
//---( Turn all 4 relays ON in sequence)---
  digitalWrite(Relay_1, RELAY_ON);// set the Relay ON
  delay(1000);              // wait for a second
  digitalWrite(Relay_2, RELAY_ON);// set the Relay ON
  delay(1000);              // wait for a second  
  digitalWrite(Relay_3, RELAY_ON);// set the Relay ON
  delay(1000);              // wait for a second
  digitalWrite(Relay_4, RELAY_ON);// set the Relay ON
  delay(4000);              // wait see all relays ON

//---( Turn all 4 relays OFF in sequence)---  
  digitalWrite(Relay_1, RELAY_OFF);// set the Relay OFF
  delay(1000);              // wait for a second
  digitalWrite(Relay_2, RELAY_OFF);// set the Relay OFF
  delay(1000);              // wait for a second  
  digitalWrite(Relay_3, RELAY_OFF);// set the Relay OFF
  delay(1000);              // wait for a second
  digitalWrite(Relay_4, RELAY_OFF);// set the Relay OFF
  delay(4000);              // wait see all relays OFF  


}//--(end main loop )---



//*********( THE END )***********

Which way do you set the output pin to energize the relay, HIGH or LOW?
Are the loads connected through the NO or NC contacts?


Sorry 8)

Thanks. The code Terry posted definitely controls the relays exactly the way I want. What I am trying to do is build a keg washer. To do this I need to control relays to turn off and on in order to open and close solenoid valves. The code I posted earlier that Terry helped me to post properly outlines the sequence and timing of the process. That program works, but at the end of the program all of the relays stay on. I’m sure there are many ways to accomplish this, some more elegant than others, but I am just trying to find an answer that works. So, I need the code to do the following.

  1. Stay idle/off when power is applied.
  2. Start when a button is pushed without bouncing. Ideally pin12
  3. Stop, reset, and go to idle if another button is pushed. Ideally pin0
  4. Turn off and on the relays in the sequence, timing combination shown in the first post.
  5. Stop at the end.
  6. Restart if the start button is pressed again.

I feel more than a little guilty getting help here, but am extremely grateful for the help I have already received. I would of course be super pleased if someone would just write the code I need and post it, but I think that is a cop out. So, in the interest of learning by doing, my question is should I try to move forward using the RELAY_ON RELAY_OFF commands or try to add code to stop/reset the first code at the end.

Also, I searched for the definition of RELAY_ON RELAY_OFF and didn’t find a definitive answer. Where is the best place to look?

@septillion, why don't you like #define statements?

francotira:
Also, I searched for the definition of RELAY_ON RELAY_OFF and didn't find a definitive answer. Where is the best place to look?

#define RELAY_ON 0
#define RELAY_OFF 1

try to add code to stop/reset the first code at the end.

The posted code returns to idle at the end of the co2fill.

Indeed, it meets all your 6 listed things except #3.

  1. Stop, reset, and go to idle if another button is pushed. Ideally pin0

If you want to interrupt the running code, reset and return to idle, you will need to pay close attention to what condition you want all the relays, solenoid valves and pumps to be in. It's not clear that the default IO status on start up is where you want to be if you interrupt a cycle.

It does return to idle and I can restart the cycle by pushing the start button again. But the relays end in the open position.

I want it to return to the “beginning” with all the relays off (idle) if I push the “reset button” (pin0, I think?). I thought that was why the “#define idle 0” command was used.

But the relays end in the open position.

You need to think beyond the relays. They control power to solenoid valves, and you will need some detailed review of the code to know what is normally open and what is normally closed before being actuated.

I want it to return to the "beginning" with all the relays off (idle) if I push the "reset button" (pin0, I think?). I thought that was why the "#define idle 0" command was used.

No. The arduino reset button is connected to a separate pin called "reset" which will reset the program when switched to ground. #define idle 0 defines a program "state" and is unrelated to any pins.

Power_Broker:
@septillion, why don't you like #define statements?

This is what the inventor of C++ has to say about macros.

Also it can lead to completely nightmarish error messages. There is really no reason not to use consts for pin definitions and get proper error messages in case of mistakes.

There is one thing where I have not found a good replacement, and that is string concatenation. When I have config head, where I e.g. want to set a base path for MQTT topics and want to use it as a prefix in the main code, then I have not found a good replacement for

BaseConfig.h

#define MQTT_BASE "mybase/thisdevice/"

main.cpp

...
const char* mqtt_temp = MQTT_BASE "tmp";
...

@ElCaron THANKS for that.. I will try to remember it. ALWAYS something to learn here.

Power_Broker:
@septillion, why don't you like #define statements?

Exactly what ElCaron said! It's 2019, not 1999.

@ElCaron, I don't know an alternative but the fact you have to do it is already a pity. Because it will just cause large strings to be in memory. If you could pass the base and the suffix separate you don't end up with the base being in memory multiple times.

But yeah, there is some trickery you can only do with a macro but if you can avoid it, please do.

septillion:
@ElCaron, I don't know an alternative but the fact you have to do it is already a pity. Because it will just cause large strings to be in memory. If you could pass the base and the suffix separate you don't end up with the base being in memory multiple times.

Well, at some point, PubSubClient will require the whole string ...
On the other hand, I am not sure what the compiler will do with this. Since I use this mainly on ESPs, we are not talking about AVR anyway, and there is more RAM available.

ElCaron:
[…] and there is more RAM available.

True :slight_smile: But still a pity.