06.02 Arduino State Machine
What is a State Machine?
A state machine is a series of “states” that a program can be in. Each of the states may have tasks and then wait for inputs or reasons to move to another state. It is like a report on the status of a system as it waits for a command to change or transition to other state. There can be Deterministic Finite State machines, where each state can only move to one other state, and Non-Deterministic Finite State Machines, where states can move to more than one state from a specific state.1Beyond simple applications, state machines can become unwieldy as the number os states increases. A Unified Modeling Language (UML) State Machine allows for more complexity than a finite state machine without adding more states.2 A state machine is an ideal way to control and schedule an electronics project on an Arduino or other micro-controller.
Sleep / Awake State Machine Example
A simple human sleeping vs being awake state machine might have two states, sleeping and awake. The inputs to change states could be hearing an alarm and getting tired.
Awake State - Am I tired? Yes, then go to sleep. No, then stay awake. Sleep State - Did the Alarm go off? Yes, then wake up. No, then stay asleep.
Traffic Light State Machine Example
Another example of a state machine could be a traffic light. If the state is traffic traveling on the north/south road with a green light, then you definitely want the east/west road to have a red light. The lights can’t work on individual timers since that would allow them to sometimes both be green and then, crash. You could think about this traffic state machine as having two states, either traffic is going north and south or traffic is going east and west. But what about the transition between directions? Should there be a state for yellow lights? Should there be a state with all lights red for a short period before switching directions? As a sequence is analyzed more states and transitions become apparent.
Then we would have the following states:
- North/South Green and East/West Red State
- North/South Yellow and East/West Red State
- North/South Red and East/West Red Switch to East/West State
- North/South Red and East/West Green State
- North/South Red and East/West Yellow State
- North/South Red and East/West Red Switch to North/South State
This is only controlling the lights with timers. It is not taking into account turn lanes, or sensors that detect the presence of cars. That would create more states and more inputs. Using a state machine helps control a complex sequence by defining what happens during a state and the conditions for that state to change to another state. This can help with testing code since you can work on the code for one state at a time.
Traffic Light Example State Machine Code
// Traffic Light State Machine
// Traffic Light LED Pins
const unsigned char northSouthRed = 12;
const unsigned char northSouthYellow = 11;
const unsigned char northSouthGreen = 10;
const unsigned char eastWestRed = 9;
const unsigned char eastWestYellow = 8;
const unsigned char eastWestGreen = 7;
enum states {
Start_All_Red, // start with all red
NS_Green_EW_Red, // North South Green and East West Red
NS_Yellow_EW_Red, // North South Yellow and East West Red
NS_to_EW_Transition, // Everything Red during transition
NS_Red_EW_Green, // North South Red and East West Green
NS_Red_EW_Yellow, // North South Red and East West Yellow
EW_to_NS_Transition, // Everything Red during transition
};
enum states trafficState;
unsigned long currentMillis; // keeps track of current time in Milliseconds
unsigned long startTimer; // tracks last time startTimer expired
unsigned long trafficTimer; // tracks last time trafficTimer
const unsigned char LEDPinArray[] = { northSouthRed,
northSouthYellow,
northSouthGreen,
eastWestRed,
eastWestYellow,
eastWestGreen };
const unsigned char LED_NUMBER = sizeof(LEDPinArray); // get the size of the array and store as variable
void setup() {
// use a for loop to iterate through the array of LED pins
for (int i = 0; i < LED_NUMBER; i++) {
pinMode(LEDPinArray[i], OUTPUT);
}
startTimer = millis(); // sets the startup timer to the current time
}
void loop() {
currentMillis = millis(); // set to current time in milliseconds
switch (trafficState) {
case Start_All_Red:
changeTrafficLightLEDs(1, 0, 0, 1, 0, 0);
if (checkTime(startTimer, 500UL)) {
trafficTimer = currentMillis; // start the trafficTimer
trafficState = NS_Green_EW_Red; // new state
}
break;
case NS_Green_EW_Red:
changeTrafficLightLEDs(0, 0, 1, 1, 0, 0);
if (checkTime(trafficTimer, 5000UL)) {
trafficState = NS_Yellow_EW_Red; // new state
}
break;
case NS_Yellow_EW_Red:
changeTrafficLightLEDs(0, 1, 0, 1, 0, 0);
if (checkTime(trafficTimer, 2000UL)) {
trafficState = NS_to_EW_Transition; // new state
}
break;
case NS_to_EW_Transition:
changeTrafficLightLEDs(1, 0, 0, 1, 0, 0);
if (checkTime(trafficTimer, 1000UL)) {
trafficState = NS_Red_EW_Green; // new state
}
break;
case NS_Red_EW_Green:
changeTrafficLightLEDs(1, 0, 0, 0, 0, 1);
if (checkTime(trafficTimer, 5000UL)) {
trafficState = NS_Red_EW_Yellow; // new state
}
break;
case NS_Red_EW_Yellow:
changeTrafficLightLEDs(1, 0, 0, 0, 1, 0);
if (checkTime(trafficTimer, 2000UL)) {
trafficState = EW_to_NS_Transition; // new state
}
break;
case EW_to_NS_Transition:
changeTrafficLightLEDs(1, 0, 0, 1, 0, 0);
if (checkTime(trafficTimer, 500UL)) {
trafficState = NS_Green_EW_Red; // new state
}
break;
} // end of switch
} // end of loop
// BEGIN CheckTime()
boolean checkTime(unsigned long &lastTimerExpiredTime, unsigned long timerLength) {
// is the time up for this task?
if (currentMillis - lastTimerExpiredTime >= timerLength) {
lastTimerExpiredTime += timerLength; //get ready for the next iteration
return true;
}
return false;
}
//END CheckTime()
void changeTrafficLightLEDs(unsigned char NSR, unsigned char NSY, unsigned char NSG, unsigned char EWR, unsigned char EWY, unsigned char EWG) {
digitalWrite(northSouthRed, NSR);
digitalWrite(northSouthYellow, NSY);
digitalWrite(northSouthGreen, NSG);
digitalWrite(eastWestRed, EWR);
digitalWrite(eastWestYellow, EWY);
digitalWrite(eastWestGreen, EWG);
}
Example Traffic Light State Machine on TinkerCad
This is the above traffic light state machine using 6 LEDs on TinkerCad. Click the start simulation button to see the traffic lights turn on and off in the proper sequence based on the states in the state machine.
New Code Constructs
Up until now we have used int
for number variables and long
for big number variables. Additional variable types include, char
for characters in ASCII, byte
for small numbers, float
for 32 bit decimal numbers. 3
enum
The state machine code above uses an enum
that allows the declaration of a new type of variable. Once the new type is declared then you can “enumerate” the values that are allowed in this type. Other values will not be allowed. You could create an enum
of fruit
and give is permissible values of bannana, apple, grape, strawberry
. That just creates a new type of variable fruit
that is like int
. Then you could create a variable of the fruit
type such as enum fruit todaysFruit = grape;
. That creates a new variable todaysFruit
with the value of grape
.
Arduino Multitasking Resources
Simple Multitasking Arduino by Matthew Ford
How to code Timers and Delays in Arduino by Matthew Ford
State Machine Resources
State Machine with Structure Timer by Larry D
Fruit Basket State Machine by Larry D