3 Button Problem: Edge Detection, Push Counter with Internal Loop

Hello,
I think I have a simple problem, but I'm struggling with knowing the right terminology.

The Background:
I have 3 buttons which will control a number of functions in a lighted suit (think Ironman):

Button 1: When pressed one time, this should close the helmet, illuminate the suit in blue, and allow for the use of Buttons 2 and 3 ("Suit Initialized"). When pressed 2 times it should open the helmet and turn off all the lights.

Button 2: When pressed one time, illuminate the suit in a pulsing red ("Attack Mode"). When pressed two times return from any previous mode to the Initialized Mode.

Button 3: When pressed by itself, will begin "Scanning" mode. When pressed in combination with Button 2, do an action based on the number of button 3 presses.

I have not yet coded the lighting and helmet servos below, nor initialized Button 3.

The problem:
I'd like the suit to remain in whichever mode it is in until another button is pressed, at which point it enters the appropriate mode. Currently it loops continuously through initialization over and over, then when I enter "Attack Mode" it alternates between the two. This is a problem as it means the servos and lights would be continuously off-and-on.

What is the appropriate way to do this? Is it a sub-loop which continuously checks the button inputs just like at the top and then exits the sub-loop?

const byte button1Pin = A0;  // pin for button 1
const byte button2Pin = A1;  // pin for button 2
const byte button3Pin = A2;  // pin for button 3

int button1State = 0;      // current state of the button
int lastButton1State = 0;  // previous state of the button
int button2State = 0;      // current state of the button
int lastButton2State = 0;  // previous state of the button
int button3State = 0;      // current state of the button
int lastButton3State = 0;  // previous state of the button

int button1PushCounter = 0;  // counter for the number of button presses
int button2PushCounter = 0;  // counter for the number of button presses
int button3PushCounter = 0;  // counter for the number of button presses

void setup() {
  Serial.begin(9600);
  pinMode(button1Pin, INPUT_PULLUP);
  pinMode(button2Pin, INPUT_PULLUP);
  pinMode(button3Pin, INPUT_PULLUP);
}


void loop() {
  // read the pushbutton input pin:
  button1State = digitalRead(button1Pin);

  // compare the buttonState to its previous state
  if (button1State != lastButton1State) {
    // if the state has changed, increment the counter
    if (button1State == HIGH) {
      // if the current state is HIGH then the button went from off to on:
      button1PushCounter++;
    }
    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  lastButton1State = button1State;

  // If the button 1 push counter is 1, check buttons 2 and 3
  if (button1PushCounter == 1) {
    Serial.println("Suit Initialized");
    button2State = digitalRead(button2Pin);
    if (button2State != lastButton2State) {
      // if the state has changed, increment the counter
      if (button2State == HIGH) {
        // if the current state is HIGH then the button went from off to on:
        button2PushCounter++;
        Serial.print("number of button 2 pushes: ");
        Serial.println(button2PushCounter);
      }
      // Delay a little bit to avoid bouncing
      delay(50);
    }
    // button3State = digitalRead(button3Pin);
    if (button3State != lastButton3State) {
      // if the state has changed, increment the counter
      if (button3State == HIGH) {
        // if the current state is HIGH then the button went from off to on:
        button3PushCounter++;
        Serial.print("number of button 3 pushes: ");
        Serial.println(button3PushCounter);
      }
      // Delay a little bit to avoid bouncing
      delay(50);
    }
    // Here we define what to do!
    if (button2PushCounter == 1) {
      Serial.println("Enter Attack Mode");
    }
    if (button3PushCounter == 1) {
      Serial.print("Enter Scanning Mode");
    }
    if (button2PushCounter == 2) {
      Serial.println("Exit Current Mode");
    }
    if (button2PushCounter == 1 & button3PushCounter == 1) {
      Serial.print("Enter Party Mode");
    }
    if (button2PushCounter == 1 & button3PushCounter == 2) {
      Serial.print("Enter X Mode");
    }
    if (button2PushCounter == 1 & button3PushCounter == 4) {
      Serial.print("Enter Y Mode");
    }
    // save the current state as the last state, for next time through the loop
    lastButton2State = button2State;
    lastButton3State = button3State;
  }
  if (button1PushCounter == 2) {
    Serial.print("Suit Shutdown");
    button2PushCounter = 0;
    button3PushCounter = 0;
    button1PushCounter = 0;
  }
}

What you have described is essentially a State Machine with four states

  • Startup
  • Initialized
  • Attack
  • Scanning

Forget code for the moment. Understand what a state machine is and how you want to switch between states. Once you have that mental model, coding it becomes much easier.

3 Likes

@cedarlakeinstruments was replying at the same time - First thing, define a STATE MACHINE to accomodate the states IDLE, INITIALISED, ATTACKING, SCANNING.

Then identify the conditions that jump between states,

  • Because you’re using ‘buttons’ as a trigger, you’ll need a stable and reliable method of detecting multiple button pushes.
    (Think millis - non blocking).

Get that working first, and Once you get the framework operating reliably you then add the internal code to complete each state.

3 Likes

Thanks you two!! I totally understand how this applies! I didn't know it was called a State Machine. Thanks for your help!

Hello tspaulding

Consider.

//https://forum.arduino.cc/t/3-button-problem-edge-detection-push-counter-with-internal-loop/1348576
//https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
#define ProjectName "3 Button Problem: Edge Detection, Push Counter with Internal Loop"
#define NotesOnRelease "Arduino DUE NANO UNO MEGA tested"
// -- some useful text replacements used because I'm lazy with typing --
#define equ ==
#define is ==
#define nequ !=
#define isNot !=

// make names
enum TimerEvent {NotExpired, Expired};
enum TimerControl {Halt, Run};
enum ButtonStates {Released, Pressed};
enum ButtonNames {ButtonOne, ButtonTwo,  ButtonThree, NoButton};
enum HelmetModes  {Suit, Attack, Scanning, Idle} helmetModes = Idle;

// make variables
uint32_t currentMillis = millis();
const String ButtonTxt[] {"ButtonOne", "ButtonTwo", "ButtonThree"};
constexpr uint8_t ButtonPins[] {A0, A1, A2};

// make structures
//-----------------------------------------
struct TIMER
{
  uint32_t interval;
  uint8_t control;
  uint32_t now;
  uint8_t expired(uint32_t currentMillis)
  {
    uint8_t timerEvent = currentMillis - now >= interval and control;
    if (timerEvent == Expired) now = currentMillis;
    return timerEvent;
  }
};
//-----------------------------------------
struct BUTTON
{
  uint8_t name;
  uint8_t pin;
  uint8_t stateOld;
  TIMER   debounce;
  void make()
  {
    pinMode(pin, INPUT_PULLUP);
    stateOld = digitalRead(pin) ? LOW : HIGH;
    debounce.interval = 20;
    debounce.control = Run;
    debounce.now = currentMillis;
  }
  uint8_t run()
  {
    uint8_t returnValue = NoButton;
    if (debounce.expired(currentMillis) == Expired)
    {
      uint8_t stateNew = digitalRead(pin) ? LOW : HIGH;
      if (stateOld != stateNew)
      {
        stateOld = stateNew;
        returnValue = name;
      }
    }
    return returnValue;
  }
};
//-----------------------------------------
// make objects
BUTTON buttons[]
{
  {ButtonOne, ButtonPins[ButtonOne], LOW, 20, Run, 0},
  {ButtonTwo, ButtonPins[ButtonTwo], LOW, 20, Run, 0},
  {ButtonThree, ButtonPins[ButtonThree], LOW, 20, Run, 0},
};
//-----------------------------------------
// make support
void heartBeat(const uint8_t LedPin, uint32_t currentMillis)
{
  static bool setUp = false;
  if (setUp == false) pinMode (LedPin, OUTPUT), setUp = true;
  digitalWrite(LedPin, (currentMillis / 500) % 2);
}
// make application
void setup()
{
  Serial.begin(115200);
  for (uint8_t n = 0; n < 32; n++) Serial.println("");
  Serial.print("Source: "), Serial.println(__FILE__);
  Serial.print(ProjectName), Serial.print(" - "), Serial.println(NotesOnRelease);

  for (auto &button : buttons) button.make();
  delay(2000);
  Serial.println(" =-> and off we go\n");
}
void loop()
{
  currentMillis = millis();
  heartBeat(LED_BUILTIN, currentMillis);
  for (auto &button : buttons)
  {
    if (button.run() != NoButton)
    {
      Serial.print(ButtonTxt[button.name]);
      Serial.println (button.stateOld equ Pressed ? " pressed" : " released");
      if (button.stateOld equ Pressed)  helmetModes = (HelmetModes) button.name;
    }
    if (helmetModes nequ Idle)
    {
      switch (helmetModes)
      {
        case Suit:
          Serial.println("Button 1: When pressed one time, this should close the helmet, illuminate the suit in blue, and allow for the use of Buttons 2 and 3 (Suit Initialized). When pressed 2 times it should open the helmet and turn off all the lights.");
          break;
        case Attack:
          Serial.println("Button 2: When pressed one time, illuminate the suit in a pulsing red (Attack Mode). When pressed two times return from any previous mode to the Initialized Mode.");
          break;
        case Scanning:
          Serial.println("Button 3: When pressed by itself, will begin Scanning mode. When pressed in combination with Button 2, do an action based on the number of button 3 presses.");
          break;
      }
      helmetModes = Idle;
    }
  }
  // ---
  // Here you can perform periodic tasks, such as handling the timer() function.
  // ---
}

looks like the code you posted is just dealing with buttons.

look this over.
it's a bit more generic and handles any # of buttons

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

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

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}

i'm not sure having separate states for each mode is really what you want. I can imagine having different things active at the same time

1 Like

Thanks all! following @lastchancename and @cedarlakeinstruments suggestions, I've revised the code as follows. I haven't yet compiled and uploaded for testing but I think this works. I was wondering if I need a "default" "case" or if that's unnecessary. I do find that I have seven unique states currently, as needed for proper operation.

const byte button1Pin = A0;  // pin for button 1
const byte button2Pin = A1;  // pin for button 2
const byte button3Pin = A2;  // pin for button 3

enum {standby, initialized, idle, attack, scanning, party, shutDown};
unsigned character suitState;

void setup() {
  Serial.begin(9600);
  pinMode(button1Pin, INPUT_PULLUP);
  pinMode(button2Pin, INPUT_PULLUP);
  pinMode(button3Pin, INPUT_PULLUP);
  suitState = standby;
}


void loop() {
  switch (suitState) {

    case standby: // Suit is standby, nothing happening, waiting for input
      Serial.println("Suit in Standby");
      if (digitalRead(button1Pin) == LOW) {
        doorState = initialized; //Initialize the suit
        break;
      }
      else { // No buttons pressed
        break; // State remains the same, continue
      }

    case initialized: // Suit turns on and helmet closes
      Serial.println("Suit Initialized! Powering up!");
      // insert what to do here for initialization
      suitState = idle; // The suit is now idle
      break;
    
    case idle: // Suit lights are on, awaiting further presses
      Serial.println("Suit idle, waiting for command");
      // insert what to do here for idle state
      if (digitalRead(button1Pin) == LOW){ // button 1 pressed from idle
        suitState = shutDown; // The suit is now shutting down
        break;
      }
      if (digitalRead(button2Pin) == LOW){ // button 2 pressed from idle
        suitState = attack; // The suite is now in attack mode
        break;
      }
      if (digitalRead(button3Pin) == LOW){ // button 3 pressed from idle
        suitState = scanning; // The suit is now in attack mode
        break;
      }
      else {
        break; // if no buttons pressed, remain idle
      }
    
    case attack: // Suit lights change to red
      Serial.println("Danger! Entering Attack Mode");
      // insert what to do here for attack mode
      if (digitalRead(button2Pin) == LOW){ // button 2 pressed from attack
        suitState = idle; // The suit goes back to idle
        break;
      }
      if (digitalRead(button3Pin) == LOW){ // button 3 pressed from attack
        suitState = party; // The suit goes into party mode
        break;
      }
      else {
        break;
      }
    
    case scanning
      Serial.println("Scanning for Targets");
      // insert what to do here for scanning mode
      if (digitalRead(button2Pin) == LOW){ // button 2 pressed from scanning
        suitState = idle; // The suit goes back to idle
        break;
      }
      else {
        break;
      }

    case party
      Serial.println("All threats neutralized, time to party!");
      // insert what to do here for party mode
      if (digitalRead(button2Pin) == LOW){ // button 2 pressed from party mode
        suitState = idle; // The suit goes back to idle
        break;
      }
      else {
        break;
      }

    case shutDown
      Serial.println("Suit Shutting Down! Goodbye!");
      // insert what to do here while shutting down
      suitState = standby; // The suit turns standby
      break;     
  }
}

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