Changing modes using push button

Hello, I'm new to arduino and programming in general. I'm working on building a simple traffic light controller for a "four way" fixture.

I've been able to figure out on my own how to run a simple program that cycles through the normal signal phases but I'd like to learn how to add additional operating modes.

Specifically I'd like to add a push button that when actuated triggers a "test" mode that illuminates all lights, and when pushed a second time, triggers a flashing red light.

I have spent some time searching around but am having trouble finding tutorials or code examples demonstrating this. I've come across examples showing button presses changing variables but not selecting different kinds of "loops" as I need.

I'm also having trouble finding information or discernible examples for the millis function. I understand using delay is undesirable but can't figure out how to implement millis.

I have spent some time searching around but am having trouble finding tutorials or code examples demonstrating this.

Have you encountered state machines? That’s what you need. Each time the switch becomes pressed (see the state change detection example), you change state. What happens in each state, and what causes a transition from one state to another, and what needs to be done when a transition happens, is all up to you.

Thank you, I have not encountered that. I will read up and explore the examples.

To add to that, every time you detect a change in the state of the button (e.g. from low to high) you can increment a variable from e.g. 0 to 1 to 2 and next set it back to zero. If you name that variable 'mode' and use it in the state machine, it will probably be clear what to do in the state machine.

What sterretje wrote is true but the button must be debounced. Otherwise you may get multiple changes in the state of the button for a single press.

A newbie could use a byte variable named 'mode' to increment from 0 to 1 to 2 to 0. A more advanced programmer might be inclined to use an enum named 'mode' and give the values names such as NORMAL, TEST_ALL_LIGHTS, and TEST_FLASHING_RED.

If I recall correctly, the state change example does handle debouncing by means of delay().

The other code that the OP can look at is the debounce example that comes with the IDE and uses millis() based delay.

Thank you all so much for the responses. I'm pretty confident I have the state machine thing down. I can now (somewhat) toggle between the three desired operating modes using a push button. I say 'somewhat' because I still cannot figure out how to avoid using delay. So the button pushes only increment at the end of a cycle.

I have studied endless examples online of using millis or similar, but it seems damn near every one is an example of blinking or toggling an output. This doesn't work for me because I'm not toggling but running through six discreet signal phases. I think I can hack something together by nesting each phase but that doesn't seem correct?

I still cannot figure out how to avoid using delay.

Imagine that delay() simply did not exist.

On any given pass through loop(), it is, or is not, time to do something. The blink without delay example shows how to implement that decision.

If it IS time to do something, the mode (based on how many times you've pressed the switch) defines what to do, which can also affect when it will be time to do the next thing.

but it seems damn near every one is an example of blinking or toggling an output.

That the action is trivial is completely beside the point. You know what you want to do the next time it is time to do something. That something may be dependent on the ith element of an array or it might be trivial.

If your goal is to interrupt, you may want to look at interrupts.

PaulS: On any given pass through loop(), it is, or is not, time to do something. The blink without delay example shows how to implement that decision.

This is what I'm struggling to understand. The example shows how to toggle a state. It doesn't show how to make the program pause before executing the next line of code. I need to basically:

Turn on outputs 1 & 2

Wait 1 second

Turn off output 2

Turn on output 3

Wait 8 seconds

Turn off output 3

Turn on output 4

Wait 2 seconds

etc....

All while listening for an input that changes operating mode to do something entirely different.

Here is my extremely crude newbie program that "basically" does what I need it to do: (I realize this is messy and inefficient, I'm eager to learn so I'm open to any and all feedback / suggestions)

int redLED = 11;
int redLED2 = 8;
int yellowLED = 12;
int yellowLED2 = 9;
int greenLED = 7;
int greenLED2 = 10;
int modeSelect = 2;
int counter = 1;
int buttonState = 1;
int lastButtonState = 0;


void setup() {
  pinMode (redLED, OUTPUT);
  pinMode (yellowLED, OUTPUT);
  pinMode (greenLED, OUTPUT);
  pinMode (redLED2, OUTPUT);
  pinMode (yellowLED2, OUTPUT);
  pinMode (greenLED2, OUTPUT);
  pinMode (modeSelect, INPUT);
  Serial.begin(9600);
}

void LEDoff () {
  digitalWrite(redLED, LOW);
  digitalWrite(yellowLED, LOW);
  digitalWrite(greenLED, LOW);
  digitalWrite(redLED2, LOW);
  digitalWrite(yellowLED2, LOW);
  digitalWrite(greenLED2, LOW);
}

void normalOps() {
  
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    delay(1000);

    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(greenLED2, HIGH);
    delay(8000);

    LEDoff ();
     digitalWrite(redLED, HIGH);
     digitalWrite(yellowLED2, HIGH);
     delay(2000);

    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    delay(1000);

    LEDoff ();
    digitalWrite(redLED2, HIGH);
    digitalWrite(greenLED, HIGH);
    delay(8000);

    LEDoff ();
    digitalWrite(redLED2, HIGH);
    digitalWrite(yellowLED, HIGH);
    delay(2000);
}

void flashMode() {
     LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    delay(1000);

    LEDoff ();
    delay(1000);
}

// the loop function runs over and over again forever
void loop() {
  
  buttonState = digitalRead(modeSelect);
  
  if (buttonState != lastButtonState) {

    if (buttonState == HIGH) {
      counter++;
      if (counter == 4) {
      counter = 1;
    }
      Serial.println("on");
      Serial.print("number of button pushes:  ");
      Serial.println(counter);
    } else {
      Serial.println("off");
    }
    delay(50);
  }

  lastButtonState = buttonState;

  switch (counter) {
  case 1: 
    normalOps ();
    break;
    
  case 2:
    flashMode ();
    break;
    
  case 3:
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    digitalWrite(yellowLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    digitalWrite(greenLED, HIGH);
    digitalWrite(greenLED2, HIGH);
    break;
  
}

}

And here is my attempt at using millis() examples that doesn't work. I tried using it to replace the delay for phase 1 in normalOps. Clearly there's much I'm failing to comprehend from the tutorials and examples.

int redLED = 11;
int redLED2 = 8;
int yellowLED = 12;
int yellowLED2 = 9;
int greenLED = 7;
int greenLED2 = 10;
int modeSelect = 2;
int counter = 1;
int buttonState = 1;
int lastButtonState = 0;

unsigned long previousMillis = 0;
unsigned long greenTime = 8000;
unsigned long yellowTime = 2000;
unsigned long redTime = 1000;


void setup() {
  pinMode (redLED, OUTPUT);
  pinMode (yellowLED, OUTPUT);
  pinMode (greenLED, OUTPUT);
  pinMode (redLED2, OUTPUT);
  pinMode (yellowLED2, OUTPUT);
  pinMode (greenLED2, OUTPUT);
  pinMode (modeSelect, INPUT);
  Serial.begin(9600);
}

void LEDoff () {
  digitalWrite(redLED, LOW);
  digitalWrite(yellowLED, LOW);
  digitalWrite(greenLED, LOW);
  digitalWrite(redLED2, LOW);
  digitalWrite(yellowLED2, LOW);
  digitalWrite(greenLED2, LOW);
}

void normalOps() {
   
  unsigned long currentMillis = millis();
  
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    //delay(1000);
    if ((unsigned long)(currentMillis - previousMillis) >= redTime) {
      LEDoff ();
      previousMillis = millis();
    }

    digitalWrite(redLED, HIGH);
    digitalWrite(greenLED2, HIGH);
    delay(8000);

    LEDoff ();
     digitalWrite(redLED, HIGH);
     digitalWrite(yellowLED2, HIGH);
     delay(2000);

    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    delay(1000);

    LEDoff ();
    digitalWrite(redLED2, HIGH);
    digitalWrite(greenLED, HIGH);
    delay(8000);

    LEDoff ();
    digitalWrite(redLED2, HIGH);
    digitalWrite(yellowLED, HIGH);
    delay(2000);
}

void flashMode() {
     LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    delay(1000);

    LEDoff ();
    delay(1000);
}

//the loop function runs over and over again forever
void loop() {

  buttonState = digitalRead(modeSelect);
  
  if (buttonState != lastButtonState) {

    if (buttonState == HIGH) {
      counter++;
      if (counter == 4) {
      counter = 1;
    }
      Serial.println("on");
      Serial.print("number of button pushes:  ");
      Serial.println(counter);
    } else {
      Serial.println("off");
    }
    delay(50);
  }

  lastButtonState = buttonState;

  switch (counter) {
  case 1: 
    normalOps ();
    break;
    
  case 2:
    flashMode ();
    break;
    
  case 3:
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    digitalWrite(yellowLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    digitalWrite(greenLED, HIGH);
    digitalWrite(greenLED2, HIGH);
    break;
  
}

}

Thanks again for all responses.

Ok, I’ve found what seems to be a simple solution to insert a delay. But I’m still running in to the issue of the button input not being read until the cycle is complete. I took the button detection routine directly from the State Change tutorial (https://www.arduino.cc/en/Tutorial/StateChangeDetection?from=Tutorial.ButtonStateChange) but it isn’t working as expected for me:

int redLED = 11;
int redLED2 = 8;
int yellowLED = 12;
int yellowLED2 = 9;
int greenLED = 7;
int greenLED2 = 10;
int modeSelect = 2;
int counter = 1;
int buttonState = 1;
int lastButtonState = 0;

unsigned long greenTime = 8000;
unsigned long yellowTime = 2000;
unsigned long redTime = 1000;

void retarder(long howLong) {
  long now = millis();
  long end = now + howLong;
  while(millis() < end) {
    
  }
}

void setup() {
  pinMode (redLED, OUTPUT);
  pinMode (yellowLED, OUTPUT);
  pinMode (greenLED, OUTPUT);
  pinMode (redLED2, OUTPUT);
  pinMode (yellowLED2, OUTPUT);
  pinMode (greenLED2, OUTPUT);
  pinMode (modeSelect, INPUT);
  Serial.begin(9600);
}

void LEDoff () {
  digitalWrite(redLED, LOW);
  digitalWrite(yellowLED, LOW);
  digitalWrite(greenLED, LOW);
  digitalWrite(redLED2, LOW);
  digitalWrite(yellowLED2, LOW);
  digitalWrite(greenLED2, LOW);
}

void normalOpsPhase1() {
  
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    //delay(1000);
    retarder(redTime);
}

void normalOps() {

    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(greenLED2, HIGH);
    //delay(8000);
    retarder(greenTime);
 
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    //delay(2000);
    retarder(yellowTime);

    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    //delay(1000);
    retarder(redTime);

    LEDoff ();
    digitalWrite(redLED2, HIGH);
    digitalWrite(greenLED, HIGH);
    //delay(8000);
    retarder(greenTime);

    LEDoff ();
    digitalWrite(redLED2, HIGH);
    digitalWrite(yellowLED, HIGH);
    //delay(2000);
    retarder(yellowTime);
}

void flashMode() {
     LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    //delay(1000);
    retarder(redTime);

    LEDoff ();
    //delay(1000);
    retarder(redTime);
}

//the loop function runs over and over again forever
void loop() {

  buttonState = digitalRead(modeSelect);
  
  if (buttonState != lastButtonState) {

    if (buttonState == HIGH) {
      counter++;
      if (counter == 4) {
      counter = 1;
    } 
      Serial.println("on");
      Serial.print("number of button pushes:  ");
      Serial.println(counter); 
    } else {
      Serial.println("off"); 
    }
    delay(50);
  }

  lastButtonState = buttonState;

  switch (counter) {
  case 1: 
    normalOps ();
    break;
    
  case 2:
    flashMode ();
    break;
    
  case 3:
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    digitalWrite(yellowLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    digitalWrite(greenLED, HIGH);
    digitalWrite(greenLED2, HIGH);
    break;
  
}

}

Your retarder function still blocks due to the while loop.

Maybe the below function is more explaining than the blink-without-delay.

/*
  non blocking delay
  in:
    delay duration
  returns:
    false if delay in progress, true if delay is finished
*/
bool retarder(unsigned long howLong)
{
  unsigned long currentTime = millis();
  static unsigned long startTime= 0;

  // if delay not started
  if (startTime == 0)
    // set the start time
    startTime = currentTime;
  }

  // if delay has lapsed
  if ( currentTime - startTime >= howLong)
  {
    // reset start time for next time that we want to do a delay
    startTime= 0;
    // indicate that delay has lapsed
    return true;
  }

  // indicate delay not finished yet
  return false;
}

void loop()
{
  // read button
  ...
  ...

  // delay, do something if it's time
  if (retarder(1000) == true)
  {
    //it's time to do something
   ...
   ...
  }
}

This is based on your approach; other approaches might better suited. There is a thread called ‘demonstration of several things at the same time’ that also might give you some ideas.

PS code not compiled nor tested.

It doesn't show how to make the program pause before executing the next line of code. I need to basically:

On any given pass through loop, that is NOT what you want to do. There may be a list of things that need to be done, and there may be different times between the things to do.

Create a function to perform the first task (turn on outputs 1 and 2) and set the interval until the next task needs to be done.

Do the same for the other tasks.

In setup(), set a variable called taskNum to 0.

In loop(), see if it is time to perform the task whose number is in taskNum. If it is, call the appropriate function AND increment taskNum.

A switch statement is relatively easy, to cause the appropriate task function to be executed, based on taskNum. An array of function pointers makes the task selection even easier, but requires a lot more understanding to get set up.

Well, here it what I came up with. It’s definitely very amateur and probably quite inefficient, but mission accomplished! Thank you to the community members for their guidance and to the other coders whose snippets I borrowed from.

int redLED = 11;
int redLED2 = 8;
int yellowLED = 12;
int yellowLED2 = 9;
int greenLED = 7;
int greenLED2 = 10;
int modeSelect = 2;
int counter = 1;
int buttonState = 0;
int lastButtonState = 0;
int phase = 0;

unsigned long timeGreen = 8000;
unsigned long timeYellow = 2000;
unsigned long timeRed = 1000;

unsigned long previousMillis = 0;
unsigned long previousMillis1 = 0;

void retarder(long howLong) {
  long now = millis();
  long end = now + howLong;
  while(millis() < end) {
    
  }
}

void setup() {
  pinMode (redLED, OUTPUT);
  pinMode (yellowLED, OUTPUT);
  pinMode (greenLED, OUTPUT);
  pinMode (redLED2, OUTPUT);
  pinMode (yellowLED2, OUTPUT);
  pinMode (greenLED2, OUTPUT);
  pinMode (modeSelect, INPUT);
}

void LEDoff () {
  digitalWrite(redLED, LOW);
  digitalWrite(yellowLED, LOW);
  digitalWrite(greenLED, LOW);
  digitalWrite(redLED2, LOW);
  digitalWrite(yellowLED2, LOW);
  digitalWrite(greenLED2, LOW);
}

void opsPhase1() {
  digitalWrite(redLED, HIGH);
  digitalWrite(redLED2, HIGH);
  phase = 1;
}

void opsPhase2() {
  digitalWrite(redLED, HIGH);
  digitalWrite(greenLED2, HIGH);
  phase = 2;
}

void opsPhase3() {
  digitalWrite(redLED, HIGH);
  digitalWrite(yellowLED2, HIGH);
  phase = 3;
}

void opsPhase4() {
  digitalWrite(redLED, HIGH);
  digitalWrite(redLED2, HIGH);
  phase = 4;
}

void opsPhase5() {
  digitalWrite(redLED2, HIGH);
  digitalWrite(greenLED, HIGH);
  phase = 5;
}

void opsPhase6() {
  digitalWrite(redLED2, HIGH);
  digitalWrite(yellowLED, HIGH);
  phase = 6;
}

void flashPhase1() {
  digitalWrite(redLED, HIGH);
  digitalWrite(yellowLED2, HIGH);
  phase = 7;
}

void flashPhase2() {
  LEDoff ();
  phase = 8;
}

void flashMode() {

  unsigned long currentMillis1 = millis();

  if (phase == 9) {
    LEDoff ();
    flashPhase1();
  }
  
  if (phase == 7) {
    if (currentMillis1 - previousMillis1 >= timeRed) {
      flashPhase2();
      previousMillis1 = currentMillis1;
    }
  }

  if (phase == 8) {
    if (currentMillis1 - previousMillis1 >= timeRed) {
      flashPhase1();
      previousMillis1 = currentMillis1;
    }
  }
}

void normalOps() {
  unsigned long currentMillis = millis();

  if (phase == 0) {
    LEDoff();
    opsPhase1();
  }

  if (phase == 1) {
    if (currentMillis - previousMillis >= timeRed) {
      LEDoff();
      opsPhase2();
      previousMillis = currentMillis;
    }
  }

  if (phase == 2) {
    if (currentMillis - previousMillis >= timeGreen) {
      LEDoff();
      opsPhase3();
      previousMillis = currentMillis;
    }
  }

  if (phase == 3) {
    if (currentMillis - previousMillis >= timeYellow) {
      LEDoff();
      opsPhase4();
      previousMillis = currentMillis;
    }
  }

  if (phase == 4) {
    if (currentMillis - previousMillis >= timeRed) {
      LEDoff();
      opsPhase5();
      previousMillis = currentMillis;
    }
  }

  if (phase == 5) {
    if (currentMillis - previousMillis >= timeGreen) {
      LEDoff();
      opsPhase6();
      previousMillis = currentMillis;
    }
  }

  if (phase == 6) {
    if (currentMillis - previousMillis >= timeYellow) {
      LEDoff();
      opsPhase1();
      previousMillis = currentMillis;
    }
  }
}

void readButton() {
  
  buttonState = digitalRead(modeSelect);
  
  if (buttonState != lastButtonState) {

    if (buttonState == HIGH) {
      counter++;
      if (counter == 4) {
      counter = 1;
      } 
    } 
    delay(30);

  lastButtonState = buttonState;
  }

}

//the loop function runs over and over again forever
void loop() {

  readButton ();

  switch (counter) {
  case 1: 
    if (phase > 6) {
      phase = 0;
    }
    normalOps ();
    break;
    
  case 2:
    if (phase < 6) {
      phase = 9;
    }
    flashMode ();
    break;
    
  case 3:
    LEDoff ();
    digitalWrite(redLED, HIGH);
    digitalWrite(redLED2, HIGH);
    digitalWrite(yellowLED, HIGH);
    digitalWrite(yellowLED2, HIGH);
    digitalWrite(greenLED, HIGH);
    digitalWrite(greenLED2, HIGH);
    break;
  
}

}

I’m really looking forward to getting better at this and it’s nice to know this resource is here to guide me along.