Relay Latching with Millis Timer

Apparently, I've stepped away from programming for too long... My intent is to have a light relay and a horn relay controlled by a single push button. The horn will follow the state of the push button (sounding when button is depressed, silent otherwise) and the light will be on for a period of time (say 3 seconds) from the first time the button is depressed. The light will stay on no matter the state of the push button during that aforementioned period of time allowing me to sound the horn repeatedly while not affecting the status of the light. After 3 seconds, the light turns off and is ready to relight upon next depression of the push button which, again, will also sound the horn.

With the code below, I have a couple issues:

  1. When the Uno is powered up initially, the horn relay is energized immediately until the first cycling of the push button, then it follows as described above correctly.
  2. The light relay never energizes no matter the state of the push button.

I believe the problem is in my application of the millis timing but I'm unsure where my logic is flawed.

Hardware
UNO R3
AOKIN 2-Relay Module
Generic Amazon pushbutton


```cpp

const int buttonPin = 5;  // the pin that the pushbutton is attached to
const int rlyHrn = 6;     // the pin that the horn relay is attached to
const int rlyLgt = 7;     // the pin that the light relay is attached to

// Variables will change:
int buttonState = 0;      // current state of the button
int lastButtonState = 0;  // previous state of the button
unsigned long previousMillis = 0;
unsigned long elapsedTime = 0;

void setup() {
  // initialize the button pin as a input:
  pinMode(buttonPin, INPUT);
  // initialize the horn relay as an output:
  pinMode(rlyHrn, OUTPUT);
  // initialize the light relay as an output:
  pinMode(rlyLgt, OUTPUT);
  // initialize serial communication:
  Serial.begin(9600);
}
void loop() {

  buttonState = digitalRead(buttonPin);  //read current state of button
  if (buttonState != lastButtonState) {  //compare current button state to last
    horn();                              //run horn subroutine
    lights();                            //run lighting subroutine
    lastButtonState = buttonState;       //set last button state to current status for future use
  }
}

void horn() {

  if (buttonState == LOW) {
    // if the current state is LOW then the button went from on to off:
    digitalWrite(rlyHrn, HIGH);
  } else {
    // if the current state is HIGH then the button went from off to onn:
    digitalWrite(rlyHrn, LOW);
  }
}

void lights() {

  previousMillis = millis();                // set present time into prevous millis for future math
  elapsedTime = millis() - previousMillis;  //establish elapsed time from line above and the *now* current time
  if (elapsedTime < 3000) {                 //if less than 3 seconds have elapsed since button has gone high, execute this logic
    digitalWrite(rlyLgt, LOW);              //turn lights on
  } else {
    digitalWrite(rlyLgt, HIGH);  // if more than 3s have elapsed, turn lights off
  }
}

Welcome to the forum

You only call lights() when the button state changes then in lights() you try to determine that 3 seconds has elapsed since the button press

To add to that problem you have this in lights()

    previousMillis = millis();                // set present time into prevous millis for future math
    elapsedTime = millis() - previousMillis;  //establish elapsed time from line above and the *now* current time

As previousMillis is equal to millis() what will the value of elapsedTime be ?

  • Use pull-ups on switches, INPUT_PULLUP.
  • Always add debounce on switches.

FYI

As @UKHeliBob points out, you are acting on both edges of the pushbutton press. To operate on just one, test for it:

  if (buttonState != lastButtonState) {  //compare current button state to last
    if (buttonState == HIGH) {
      horn();                              //run horn subroutine
      lights();                            //run lighting subroutine
   }
   lastButtonState = buttonState;       //set last button state to current status for future use
  }

But horn just follows the button:

  buttonState = digitalRead(buttonPin);  //read current state of button
  horn();

  if (buttonState != lastButtonState) {  //compare current button state to last
   // not here, why? horn();                              //run horn subroutine
    lightsON();                            // turn on the light
    lastButtonState = buttonState;       //set last button state to current status for future use
  }

  lightsOFF_maybe();

So the other problem is also easily solved. Separate turning on the LED from turning it off.

Turn the LED on when the button gets pressed, if it isn't already on. Set your timer to millis().

Elsewhere in the loop, check to see, if the LED is on, whether three seconds have elapsed. If so, turn it off.

HTH

a7

look this over

// horn follows state of button press
// light turned on for 3 secs whenever button pressed and light off

const int PinButton = 5;
const int PinHorn   = 6;
const int PinLight  = 7;

enum { LightOff    = LOW,  LightOn    = HIGH };
enum { HornOff     = LOW,  HornOn     = HIGH };
enum { ButReleased = HIGH, ButPressed = LOW };

unsigned long ThreeSec = 3000;
unsigned long msec0;

int lightState = LightOff;

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    // if light on, turn off after 3 sec
    if (LightOn == lightState && msec - msec0 >= ThreeSec) {
        lightState = LightOff;
        digitalWrite (PinLight, lightState);
    }

    // read button
    byte but = digitalRead (PinButton);

    // turn on horn when button pressed
    if (ButPressed == but)
        digitalWrite (PinHorn, HornOn);
    else
        digitalWrite (PinHorn, HornOff);

    // turn light on and capture timestamp if button pressed and light off
    if (ButPressed == but && lightState ==  LightOff)  {
        lightState = LightOn;
        digitalWrite (PinLight, lightState);
        msec0 = msec;
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    pinMode (PinButton, INPUT_PULLUP);

    pinMode (PinLight,  OUTPUT);
    pinMode (PinHorn,   OUTPUT);

    lightState = LightOff;
    digitalWrite (PinLight, lightState);


    digitalWrite (PinHorn,  HornOff);
}

Noted and fixed. Thank you!

This is excellent. I'll study this closely. Thank you very much!