Example 3: A SIMPLE state machine to show the traffic light sequence
A single traffic light is a good example of a system that can have clearly defined states; and the switch construct makes it easy to see how this can be implemented as a state machine.
Use the same arrangement of LEDs (see the fritzing) that you used in the first example.
This code also introduces the ENUM type declaration, and a very easy way to understand millis() timing.
At the start of each state we record the time "timeStart". Then the elapsed time "timeElapsed" in that state is the difference between the time now - milllis() - and the start time.
millis() - timeStart = timeElapsed
When the elapsed time exceeds the time required in that state timeStart is reset and the next state selected.

//*
This simple sketch is an example showing how the switch - case structure can be used to implementa state machine
LEDs are connected to pins 2,3,4 on the uno, with 470 ohm resistors to ground
J. Errington 29 October 2020
*/
// assign pin numbers for the leds
const byte led_G = 2;
const byte led_Y = 3;
const byte led_R = 4;
enum vtlStates {AMBER, RED, RA, GREEN}; // the states a single traffic light can be in
// the above line makes a variable data type "vtlStates" and assigns values 0, 1, 2, 3 to AMBER, RED, RA, GREEN
vtlStates tlState; //create a variable tlState
/*
*** if youre not happy with enum, this also works ***
const byte AMBER=0, RED=1, RA=2, GREEN=3;
byte tlState;
*/
const int timings[] = {3000, 10000, 2000, 10000}; //array to hold the time for each state AMBER, RED, RA, GREEN
unsigned long timeStart, timeElapsed ; //will hold the time the state started and the time elapsed in the current state
void setup() {
pinMode(led_G, OUTPUT);
pinMode(led_Y, OUTPUT);
pinMode(led_R, OUTPUT);
tlState = AMBER; //start with the amber on
timeStart = millis();
}
void loop() {
switch (tlState) {
case AMBER : {
//set the lights for this state
digitalWrite(led_R, LOW);
digitalWrite(led_Y, HIGH);
digitalWrite(led_G, LOW);
//is it time for the next state?
timeElapsed = millis() - timeStart;
if (timeElapsed > timings[AMBER]) {
//restart the timer and change the state
timeStart = millis();
tlState = RED;
}
}
break;
case RED: {
digitalWrite(led_R, HIGH);
digitalWrite(led_Y, LOW);
digitalWrite(led_G, LOW);
//is it time for the next state?
timeElapsed = millis() - timeStart;
if (timeElapsed > timings[RED]) {
//restart the timer and change the state
timeStart = millis();
tlState = RA;
}
}
break;
case RA: {
digitalWrite(led_R, HIGH);
digitalWrite(led_Y, HIGH);
digitalWrite(led_G, LOW);
//is it time for the next state?
timeElapsed = millis() - timeStart;
if (timeElapsed > timings[RA]) {
//restart the timer and change the state
timeStart = millis();
tlState = GREEN;
}
}
break;
case GREEN: {
digitalWrite(led_R, LOW);
digitalWrite(led_Y, LOW);
digitalWrite(led_G, HIGH);
//is it time for the next state?
timeElapsed = millis() - timeStart;
if (timeElapsed > timings[GREEN]) {
//restart the timer and change the state
timeStart = millis();
tlState = AMBER;
}
}
break;
}
}//loop
ENUM and timings[] array explained
This is all very simple. (ROFL)
enum vtlStates {AMBER, RED, RA, GREEN};
this makes a new type of variable. It can only take one of the listed values, and it assigns numbers to them in order, starting at zero.
tlState is a number that we will use to hold the current state; so we COULD declare it as
byte tlState; where byte is the type and tlState the name of the variable.
Instead we declare it as
vtlStates tlState; where vtlStates is the type, and tlState the name of the variable.
This allows us to use meaningful names (AMBER etc) for the system state.
const int timings[] = {3000, 10000, 2000, 10000};
timings[] is an array - a set of integer values (declared as constants) - so in memory it looks like this:
[3000] [10000] [2000] [10000]
The position of a value in array is called its index; starting at zero.
So timings[0] is 3000; timings[1] = 10000; timings[2]=2000 and timings[3] = 10000;
When using arrays you need to be careful not to exceed the region used (the “bounds” of the array); we dont know what we would find in timings[4]!
By using an enumerated type we can get values from the array as
timings[AMBER] - which is the same as timings[0];
it also protects from exceeding the array bounds because we can not have a value of 4 in “tlState”, the only values allowed are AMBER, RED, RA, GREEN corresponding to 0,1,2,3

switch_tl_state.ino (2.77 KB)