I have been working on getting a state machine to work correctly but am having trouble with adding in the last bit of logic. What it needs to do is several things:
- Read two button inputs.
- Make sure they are pressed and held within a certain amount of time of each other as defined by "PushTimeWindow" and if not wait til both are released.
- Wait for a certain period as defined by "PushSafetyWindow".
- Turn on a relay for an amount of time as specified by "RelayOnTime".
- After the relay is off wait for both buttons to be released before they can be read again.
- If at any time one or both released turn off the relay and wait for both to be released to be pushed again.
- Count the number of successful relay periods and display them
Almost everything is doing what I had hoped it would do with the exception that I can't get the program to only allow the two switches to be pushed within a certain time frame as defined by the PushTimeWindow. Either button one needs to be pushed and held and then the second button pressed within the specified time. This window of time is intended to only allow the relay to be turned on if both buttons have been pushed within the set time. I have two states that I am trying to use to handle it but it seems to ignore them. Cases ONE_PUSHED and PUSHED_WINDOW are intended to handle this and seem to be ignored by the program. These two were added to handle that additional logic but otherwise the rest of the program works great and handles the other functions fine. I can't figure out what I am doing wrong with that logic. This is an added function to a previous question answered by user Jot here. I suspect that the two new cases might be redundant and true no matter what and that is why they seem to be ignored, or is it that they are never called on to be tested as true or not?
The sketch uses the Bounce2 library to debounce the noisy switches and each button has pull down resistors and otherwise works well.
Here is the sketch (Updated 10/17/17):
// A state machine is used, only to make it more clear what is going on.
//
// When a button is pressed, the input turns HIGH.
//
// Normally only one 'enum' is used for the state, that controls the code.
// This sketch has also a 'enum' for the buttons, which is called the input state.
// define the different states for this sketch.
// An 'enum' is like a number of #define with increasing numbers.
// include the library code:
#include <Bounce2.h>
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
enum
{
WAIT_FOR_BOTH_OFF,
WAIT_FOR_BOTH_ON,
ONE_PUSHED,
PUSHED_WINDOW,
SAFETY_WINDOW,
RELAY_ON,
} state; // an 'enum' is automatically an integer.
// define the different input states.
enum
{
BOTH_OFF, // no buttons are pressed.
BOTH_ON, // both buttons are pressed.
SOMETHING_ELSE, // probably one of the buttons is pressed.
};
const int ButtonPin1 = 2;
const int ButtonPin2 = 3;
const int RelayPin = 5;
// The previousMillis is used twice for two different things.
// That is okay in this state machine.
unsigned long previousMillis;
const unsigned long RelayOnTime = 1750; // Length of time for relay
const unsigned long PushTimeWindow = 300; // Length of time to allow two pushes
const unsigned long PushSafetyWindow = 50; // Safety Delay after push
// Counters for the number of failed relay triggers and successful cycles.
// They can probably be normal integers as well.
unsigned long failed = 0;
unsigned long success = 0;
Bounce debouncer1 = Bounce();
Bounce debouncer2 = Bounce();
void setup()
{
lcd.begin(16, 2);
lcd.print("Press Count");
Serial.begin(9600);
Serial.println("Unit Online");
// Set up first button
pinMode(ButtonPin1, INPUT);
debouncer1.attach(ButtonPin1);
debouncer1.interval(5); //Debounce time
// Set up second button
pinMode(ButtonPin2, INPUT);
debouncer2.attach(ButtonPin2);
debouncer2.interval(5); //Debounce time
pinMode(RelayPin, OUTPUT); // OUTPUT to Relay
Serial.println("Going to WAIT_FOR_BOTH_OFF state");
state = WAIT_FOR_BOTH_OFF;
}
void loop()
{
unsigned long currentMillis = millis();
// Update the bounce instances
debouncer1.update();
debouncer2.update();
int inputState = getInputState();
// Process the data in a state machine.
// The output part is also in the state machine.
switch(state)
{
case WAIT_FOR_BOTH_OFF:
// Both buttons must be released to be able to continue.
if(inputState == BOTH_OFF)
{
// Prepare to go to the next state.
Serial.println("Going to WAIT_FOR_BOTH_ON state");
state = WAIT_FOR_BOTH_ON;
}
break;
case ONE_PUSHED:
//One button is pushed
if(inputState == SOMETHING_ELSE)
{
//One button was pushed
Serial.println("One button Pushed");
previousMillis = currentMillis;
state = PUSHED_WINDOW;
}
break;
case WAIT_FOR_BOTH_ON:
// This is the state when everything is idle and no buttons are pressed.
// Normally both buttons are released,
// but when both are pressed, then go to the next state.
if(inputState == BOTH_ON)
{
// Prepare to go to the next state.
Serial.println("Going to SAFETY_WINDOW state");
previousMillis = currentMillis;
state = SAFETY_WINDOW;
}
break;
//Check to see if the other button is pushed, if not wait for both off
case PUSHED_WINDOW:
if(currentMillis - previousMillis >= PushTimeWindow)
{
Serial.println("Going to WAIT_FOR_BOTH_ON state 2");
state = WAIT_FOR_BOTH_OFF;
previousMillis = currentMillis;
}
//If window to push has expired go to WAIT_FOR_BOTH_OFF state
else if(inputState == BOTH_ON)
{
Serial.println("Going to SAFETY_WINDOW 2");
state = SAFETY_WINDOW;
}
case SAFETY_WINDOW:
if(inputState != BOTH_ON)
{
// During the safety time window, a button was released.
// Return to idle.
Serial.println("Going to WAIT_FOR_BOTH_OFF state");
state = WAIT_FOR_BOTH_OFF;
}
else if(currentMillis - previousMillis >= PushSafetyWindow)
{
// The buttons are still pressed.
// The time window has reached the end.
// Prepare to go to the next state.
Serial.println("Going to RELAY_ON state");
previousMillis = currentMillis;
digitalWrite(RelayPin, HIGH); // Turn ON RelayPin
state = RELAY_ON;
}
break;
case RELAY_ON:
if(inputState != BOTH_ON)
{
// One of the buttons was released.
// Stop the relay and return to idle.
// Although the relay was triggered, it was not succesful.
failed++;
Serial.print("Failed=");
Serial.println(failed);
Serial.println("Going to WAIT_FOR_BOTH_OFF state");
digitalWrite( RelayPin, LOW); // Turn Off RelayPin
state = WAIT_FOR_BOTH_OFF;
}
else if(currentMillis - previousMillis >= RelayOnTime)
{
// Everyting was okay.
// The buttons are still pressed.
// It was complete and succesful.
// It is time to turn off the relay and return to idle.
success++;
Serial.print("Success=");
Serial.println(success);
Serial.println("Going to WAIT_FOR_BOTH_OFF state");
lcd.setCursor(0,1);
lcd.print(success);
digitalWrite(RelayPin, LOW); // Turn Off RelayPin
state = WAIT_FOR_BOTH_OFF;
}
break;
default:
Serial.println("Error, unknown state");
break;
}
}
// This function returns the input state.
// There is no StateChangeDetection, because a state machine is used.
int getInputState()
{
int returnInputState;
// Read the debounce state and create a input state according to the 'enum' values.
int value1 = debouncer1.read();
int value2 = debouncer2.read();
if(value1 == LOW && value2 == LOW)
{
returnInputState = BOTH_OFF;
}
else if(value1 == HIGH && value2 == HIGH)
{
returnInputState = BOTH_ON;
}
else
{
// Not both on, not both off, so it must be something else.
returnInputState = SOMETHING_ELSE;
}
return(returnInputState);
}
The part of the sketch that doesn't seem to run are these two cases that should be called when one button is pressed:
case ONE_PUSHED:
//One button is pushed
if(inputState == SOMETHING_ELSE)
{
//One button was pushed
Serial.println("One button Pushed");
previousMillis = currentMillis;
state = PUSHED_WINDOW;
}
break;
The sketch should then run the case PUSHED_WINDOW to check if the inputState goes to BOTH_ON within the window defined in PushTimeWindow. If that time has been exceeded the sketch should then wait until both buttons are released again and then wait for another try.
case PUSHED_WINDOW:
if(currentMillis - previousMillis >= PushTimeWindow)
{
Serial.println("Going to WAIT_FOR_BOTH_ON state 2");
state = WAIT_FOR_BOTH_OFF;
previousMillis = currentMillis;
}
//If window to push has expired go to WAIT_FOR_BOTH_OFF state
else if(inputState == BOTH_ON)
{
Serial.println("Going to SAFETY_WINDOW 2");
state = SAFETY_WINDOW;
}
Unfortunately the sketch never seems to run either of the cases which is where I am having trouble debugging.
The sketch will run on an Arduino Nano with an ATmega328P programmed with Arduino 1.8.5. The two input buttons are Rees mushroom head momentary industrial switches that are software debounced and have smoothing capacitors to hardware debounce as well. Inputs have 1K pull down resistors and the system is powered by a 9V power supply. The output to the relay pin drives the base of a NPN TIP31C transistor that powers the input of a relay with 9V. The relay has a capacitor with a resistor across it on its input to smooth out back spikes that occur when the relay triggers. The hardware all seems to be working fine and as intended.
Edit 10/20/2017 The solution to the question is included in the below code. The problem was with the state machine seeming to skip the window required to press the button. Some of the enum names were also changed as per suggested but if possible will be renamed again if better names come to me to be more clear what they are to do.
Edit 10/23/2017 Tried to name everything as clearly as possible and include documentation as to source and function of sketch.
Here is the final sketch:
// This sketch reads inputs from two buttons to turn on a relay
// It requires that both buttons be pressed within a certain time of each other and held
// A relay pin is then turned on for a specified period of time
// Both buttons must then be released again for another cycle to begin
// A state machine is used, only to make it more clear what is going on.
// Used with an Arduino Nano board on Arduino 1.8.5
// Found on https://arduino.stackexchange.com/questions/45584/state-machine-logic-problem-with-added-states-monitoring-safety-switches/
// Uses the Bounce2 library: https://github.com/thomasfredericks/Bounce2
//
// When a button is pressed, the input turns HIGH.
//
// Normally only one 'enum' is used for the state, that controls the code.
// This sketch has also a 'enum' for the buttons, which is called the input state.
// define the different states for this sketch.
// An 'enum' is like a number of #define with increasing numbers.
// include the library code:
#include <Bounce2.h>
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
enum
{
WAIT_FOR_BOTH_LOW,
WAIT_FOR_BUTTON,
ONE_PUSHED_WAIT_FOR_OTHER_BUTTON,
SAFETY_WINDOW,
RELAY_ON,
} state; // an 'enum' is automatically an integer.
// define the different input states.
enum
{
BOTH_BUTTONS_LOW, // no buttons are pressed.
BOTH_BUTTONS_HIGH, // both buttons are pressed.
ONE_BUTTON_HIGH, // probably one of the buttons is pressed.
};
const int ButtonPin1 = 2;
const int ButtonPin2 = 3;
const int RelayPin = 5;
const int IndicatorPin = 4;
// The 'previousMillis' is exclusively for the state machine portion only.
// It is used for different software timers in different states.
// That is okay, because within the state machine the states are seperated from each other.
// Funtions outside the state machine would require a separate timer.
unsigned long previousMillis;
const unsigned long RelayOnTime = 1750; // Length of time for relay
const unsigned long TimeFrameForSecondButton = 300; // Length of time to allow two pushes
const unsigned long PushSafetyWindow = 50; // Safety Delay after push
// Counters for the number of failed relay triggers and successful cycles.
// They can probably be normal integers as well.
unsigned long failed = 0;
unsigned long success = 0;
// Instantiate Bounce objects
Bounce debouncer1 = Bounce();
Bounce debouncer2 = Bounce();
void setup()
{
lcd.begin(16, 2);
lcd.print("Press Count");
Serial.begin(9600);
Serial.println("Unit Online");
// Set up first button
pinMode(ButtonPin1, INPUT);
debouncer1.attach(ButtonPin1);
debouncer1.interval(5); //Debounce time
// Set up second button
pinMode(ButtonPin2, INPUT);
debouncer2.attach(ButtonPin2);
debouncer2.interval(15); //Debounce time
pinMode(RelayPin, OUTPUT); // OUTPUT to Relay
pinMode(IndicatorPin, OUTPUT); // OUTPUT to Indicator LED
Serial.println("Going to WAIT_FOR_BOTH_LOW state");
state = WAIT_FOR_BOTH_LOW;
}
void loop()
{
unsigned long currentMillis = millis();
// Update the Bounce instances
debouncer1.update();
debouncer2.update();
int inputState = getInputState();
// Process the data in a state machine.
// The output part is also in the state machine.
switch(state)
{
case WAIT_FOR_BOTH_LOW:
// Both buttons must be released to be able to continue.
if(inputState == BOTH_BUTTONS_LOW)
{
// Prepare to go to the next state.
Serial.println("Going to WAIT_FOR_BUTTON state");
state = WAIT_FOR_BUTTON;
}
break;
case WAIT_FOR_BUTTON:
// This is the state when everything is idle and no buttons are pressed.
// Normally both buttons are released.
// Waiting for the first button to be pressed.
// Both buttons could be pressed at the same time !
if(inputState == ONE_BUTTON_HIGH || inputState == BOTH_BUTTONS_HIGH)
{
// At least one button is pressed.
// Prepare to go to the next state.
// Start a timer, because there is a timeout.
Serial.println("Going to ONE_PUSHED_WAIT_FOR_OTHER_BUTTON state");
previousMillis = currentMillis;
state = ONE_PUSHED_WAIT_FOR_OTHER_BUTTON;
}
break;
case ONE_PUSHED_WAIT_FOR_OTHER_BUTTON:
if(inputState == BOTH_BUTTONS_LOW)
{
// The button was released. That's not what we want.
// Return to the state to wait for both off.
Serial.println("Going to WAIT_FOR_BOTH_LOW state");
state = WAIT_FOR_BOTH_LOW;
}
else if(currentMillis - previousMillis >= TimeFrameForSecondButton)
{
// The timeout has ended.
// The second button was not pushed after the first button
// within the timeout. That's too bad.
Serial.println("Too Long Going to WAIT_FOR_BOTH_LOW state");
state = WAIT_FOR_BOTH_LOW;
}
else if(inputState == BOTH_BUTTONS_HIGH)
{
// The timeout has not ended and is still running.
// And now both buttons are pressed.
// We can continue.
// Start the timer for the SAFETY_WINDOW state.
Serial.println("Going to SAFETY_WINDOW state");
previousMillis = currentMillis;
state = SAFETY_WINDOW;
}
break;
case SAFETY_WINDOW:
if(inputState != BOTH_BUTTONS_HIGH)
{
// During the safety time window, a button was released.
// Return to idle.
Serial.println("Going to WAIT_FOR_BOTH_LOW state");
state = WAIT_FOR_BOTH_LOW;
}
else if(currentMillis - previousMillis >= PushSafetyWindow)
{
// The buttons are still pressed.
// The time window has reached the end.
// Prepare to go to the next state.
Serial.println("Going to RELAY_ON state");
previousMillis = currentMillis;
digitalWrite(RelayPin, HIGH); // Turn ON RelayPin
digitalWrite(IndicatorPin, HIGH); // Turn ON IndicatorPin
state = RELAY_ON;
}
break;
case RELAY_ON:
if(inputState != BOTH_BUTTONS_HIGH)
{
// One of the buttons was released.
// Stop the relay and return to idle.
// Although the relay was triggered, it was not succesful.
failed++;
Serial.print("Failed=");
Serial.println(failed);
Serial.println("Going to WAIT_FOR_BOTH_LOW state");
digitalWrite( RelayPin, LOW); // Turn Off RelayPin
digitalWrite(IndicatorPin, LOW); // Turn Off IndicatorPin
state = WAIT_FOR_BOTH_LOW;
}
else if(currentMillis - previousMillis >= RelayOnTime)
{
// Everyting was okay.
// The buttons are still pressed.
// It was complete and succesful.
// It is time to turn off the relay and return to idle.
success++;
Serial.print("Success=");
Serial.println(success);
Serial.println("Going to WAIT_FOR_BOTH_LOW state");
lcd.setCursor(0,1);
lcd.print(success);
digitalWrite(RelayPin, LOW); // Turn Off RelayPin
digitalWrite(IndicatorPin, LOW); // Turn Off IndicatorPin
state = WAIT_FOR_BOTH_LOW;
}
break;
default:
Serial.println("Error, unknown state");
break;
}
}
// This function returns the input state.
// There is no StateChangeDetection, because a state machine is used.
int getInputState()
{
int returnInputState;
// Read the debounce state and create a input state according to the 'enum' values.
int value1 = debouncer1.read();
int value2 = debouncer2.read();
if(value1 == LOW && value2 == LOW)
{
returnInputState = BOTH_BUTTONS_LOW;
}
else if(value1 == HIGH && value2 == HIGH)
{
returnInputState = BOTH_BUTTONS_HIGH;
}
else
{
// Not both on, not both off, so it must be something else.
returnInputState = ONE_BUTTON_HIGH;
}
return(returnInputState);
}