Example of a simple state machine, that moves from state to state (indicated by a pair of LEDs) via the pressing of a button (note - the following example has been checked for compilation - but not for operation):
// NOTE: Debounce code is based on the code found at:
// http://www.arduino.cc/en/Tutorial/Debounce
int ledPin1 = 13; // LED connected to digital pin 13
int ledPin2 = 14; // LED connected to digital pin 14
int buttonPin = 7; // pushbutton connected to digital pin 7
int buttonState; // the state of the pushbutton
int lastReading = LOW; // initialise the previous reading from the pushbutton
// the following variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 50; // the debounce time; increase if the output flickers
int machineState; // the state of the state machine
void setup() {
machineState = 1; // initialize state machine state
}
void loop() {
int getButtonState = readButton();
switch(machineState) {
case 1:
// turn off both LEDs
digitalWrite(ledPin1, LOW);
digitalWrite(ledPin2, LOW);
if (getButtonState == HIGH) {
digitalWrite(ledPin1, HIGH); // turn on first LED
machineState = 2;
}
break;
case 2:
if (getButtonState == HIGH) {
digitalWrite(ledPin2, HIGH); // turn on second LED
machineState = 3;
}
break;
case 3:
if (getButtonState == HIGH) {
digitalWrite(ledPin1, LOW); // turn off first LED
machineState = 4;
}
break;
case 4:
if (getButtonState == HIGH) {
digitalWrite(ledPin2, LOW); // turn off first LED
machineState = 5;
}
break;
default:
// since there is no "machineState = 5" - the switch-case
// will default and change the machineState to "1"
machineState = 1;
}
}
int readButton() {
// the following debounce code is based on the code found at:
// http://www.arduino.cc/en/Tutorial/Debounce
// read the current state of the button
int readButton = digitalRead(buttonPin);
// check to see if you just pressed the button
// (i.e. the input went from LOW to HIGH), and you've waited
// long enough since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (readButton != lastReading) {
// reset the debouncing timer
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current state:
// if the button state has changed:
if (readButton != buttonState) {
buttonState = readButton;
}
}
// save the reading. Next time through the loop,
// it'll be the lastButtonState:
lastReading = readButton;
return buttonState; // return the state of the button
}
In the above example, the state of the machine is represented by an integer value; in a real state machine, you would want to declare a set of named constants to indicate the state, then use those constant names in your state machine transition checking logic - as doing so is easier to understand and debug than a number. However, I was trying to keep this example as simple as possible, while still being operational (hence the debounce code - I hope that doesn't complicate things too much).
Note also that "loop" keeps looping - regardless of the state of the machine; you could easily set things up so that within one of the state check you called a function, and within that function use code similar to the "blink-without-delay" example to cause an action to occur repeatedly - or to check for other stuff happening (like incrementing a value based on the reading of another sensor, perhaps). Basically - while not necessary for a state machine to function - implementing non-blocking code allows you to expand and utilize the processor to the fullest extent (blocking code merely wastes cycles which could be better spent doing something - also, you might miss certain things, like sensor readings, with blocking code).
While the above example shows the state machine transitioning states in an orderly and serial fashion, nothing says this has to be the case. You could jump to any state you want from any other state, based on whatever criteria, logic, or readings you take while in the current state.
Finally - state machines don't have to be implemented as a switch-case. You could, for example, do it all with functions (again - I note that this compiles, but I didn't check the operation - but it should function similarly to the earlier state machine example):
// NOTE: Debounce code is based on the code found at:
// http://www.arduino.cc/en/Tutorial/Debounce
int ledPin1 = 13; // LED connected to digital pin 13
int ledPin2 = 14; // LED connected to digital pin 14
int buttonPin = 7; // pushbutton connected to digital pin 7
int buttonState; // the state of the pushbutton
int lastReading = LOW; // initialise the previous reading from the pushbutton
// the following variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 50; // the debounce time; increase if the output flickers
void setup() {
machineState1(); // go to first machine state
}
void loop() {
// nothing to do in here!
}
void machineState1() {
while (true) {
// turn off both LEDs
digitalWrite(ledPin1, LOW);
digitalWrite(ledPin2, LOW);
if (readButton() == HIGH) {
digitalWrite(ledPin1, HIGH); // turn on first LED
machineState2(); // jump to the next state!
}
}
}
void machineState2() {
while (true) {
if (readButton() == HIGH) {
digitalWrite(ledPin2, HIGH); // turn on second LED
machineState3(); // jump to the next state!
}
}
}
void machineState3() {
while (true) {
if (readButton() == HIGH) {
digitalWrite(ledPin1, LOW); // turn off first LED
machineState4(); // jump to the next state!
}
}
}
void machineState4() {
while (true) {
if (readButton() == HIGH) {
digitalWrite(ledPin2, LOW); // turn off first LED
machineState1(); // jump to the next state!
}
}
}
int readButton() {
// the following debounce code is based on the code found at:
// http://www.arduino.cc/en/Tutorial/Debounce
// read the current state of the button
int readButton = digitalRead(buttonPin);
// check to see if you just pressed the button
// (i.e. the input went from LOW to HIGH), and you've waited
// long enough since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (readButton != lastReading) {
// reset the debouncing timer
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current state:
// if the button state has changed:
if (readButton != buttonState) {
buttonState = readButton;
}
}
// save the reading. Next time through the loop,
// it'll be the lastButtonState:
lastReading = readButton;
return buttonState; // return the state of the button
}
There's ways of doing it in other fashions as well (using if-then-else logic, using pointer transitions, function array lookup tables, and more).
Don't get hung up on what the "right way" of implementing a state machine is - but rather understand what a state machine is, how it works, what it is for, and what it's advantages and disadvantages are compared to other potential solutions to a particular problem. Then, implement the state machine in the manner that best suits the need and solution (and your understanding and style).