State machines, a short tutorial

It’s not immediately apparent but, the sketch above has a fault, namely that if the switch is held closed the door will cycle continuously. The code checks to see if the switch is closed, not when the switch becomes closed. Real doors don’t do this so let’s fix it. The next sketch adds the Bounce2.h library to handle switch conditioning. Again, this is to avoid cluttering the listing with the switch debounce code. Anyway, now the state machine is triggered by the switchClosed transition, not the state of it being closed, so even if the switch is held closed the door will cycle only once. The rest of the code is unchanged.

// Switch/case tutorial part #2
// Using switch transitions to trigger the code
// Precludes continuous operation if a switch is
// held down.

#include <Bounce2.h>  // To handle switch closure

// Instantiate a Bounce object
//Bounce sitchInputdebouncer = Bounce();
Bounce debouncedSwitch = Bounce();

const unsigned char switchInput = 10;   //  Arduino pin 10 --| |--SW-- GND 
const unsigned char openContactor = 9;  //  +5--/\/\/- 330Ω -->|--DIO9
const unsigned char closeContactor = 7; //  +5--/\/\/- 330Ω -->|--DIO7

#define motorRun LOW
#define motorStop HIGH
#define accumulatedMillis millis() - timerMillis

const unsigned long motorTimerPreset = 2000;  // two seconds
unsigned long timerMillis;  // For counting time increments

// The door has four possible states it can be in
// Let's give the states descriptive names
enum {doorIsDown, doorIsUp, doorOpening, doorClosing};
unsigned char doorState = doorIsDown;  // What the door is doing at any given moment.

// New variables for switch actuation
bool switchClosed;  // On for one scan when switch is pressed.
bool switchClosedSetup;

void setup() {
  Serial.begin(115200);

  // After setting up the button, setup the Bounce instance :
  debouncedSwitch.attach(switchInput);
  debouncedSwitch.interval(5); // interval in ms

  pinMode(switchInput, INPUT_PULLUP);// +5V--| |--SW--DIO10
  pinMode(openContactor, OUTPUT);
  digitalWrite(openContactor, HIGH);
  pinMode(closeContactor, OUTPUT);
  digitalWrite(closeContactor, HIGH);
}

void loop() {

  // Update the Bounce instance :
  debouncedSwitch.update();
  // Get the updated value :
  unsigned char switchValue = debouncedSwitch.read();

  // switchClosed will be TRUE for one program scan when the
  // pushbutton is pressed and debounced.
  switchClosed = (!switchValue and switchClosedSetup); //
  switchClosedSetup = switchValue;

  switch (doorState) {

    case doorIsDown: // Nothing happening, waiting for switchInput
      Serial.println("door down");
      if (switchClosed) {
        timerMillis = millis(); // reset the timer
        doorState = doorOpening; // Advance to the next state
        break;
      }
      else {
        break; // Continue with rest of the program
      }

    case doorOpening:
      Serial.println("door opening");
      digitalWrite(openContactor, motorRun);
      //
      // Remember, accumulatedMillis is the same as millis() - timerMillis
      if (accumulatedMillis >= motorTimerPreset) { // Door up?
        digitalWrite( openContactor, motorStop); // Stop the motor
        doorState = doorIsUp;
        break;
      }
      else break;

    case doorIsUp:
      Serial.println("door up");
      if (switchClosed) { // User requests door close
        timerMillis = millis(); // reset the timer
        doorState = doorClosing; // Advance to the next state
        break;
      }
      else { // Continue with rest of program
        break;
      }

    case doorClosing:
      Serial.println("door closing");
      digitalWrite(closeContactor, motorRun); // Down contactor on
      if (accumulatedMillis >= motorTimerPreset) {
        digitalWrite(closeContactor, motorStop); // Stop the motor
        doorState = doorIsDown;  // Back to start point
        break;
      }
      else {
        break;
      }
    default:
      doorState = doorIsDown;
      break;
  }
}
2 Likes