Maker.io main logo

How To Program an Arduino Finite State Machine

2023-08-14 | By Maker.io Staff

How To Program an Arduino Finite State Machine Image. Source: https://pixabay.com/photos/arduino-electronics-board-computer-631977/

A recent article investigated the theoretical concepts of finite state machines and how to model ‎them using simple directed graphs. While the theoretical aspects are essential for understanding ‎FSM concepts, you may be interested in how to incorporate this unique computational concept ‎into your projects. This article will explain how you can implement any finite state machine in a ‎variety of programs, such as an Arduino-powered project.

A State Machine Hello World Example

If you have ever written an Arduino sketch, you’re likely familiar with the simple blink example ‎sketch used to test whether a development board functions as expected with the environment ‎set up correctly. This first program similarly aims to explain FSM program concepts using as few ‎lines of code as possible by expanding upon the simple single-LED blink script.

This first state machine should periodically cycle through three LEDs, turning each on for two and a half seconds while the others remain off. The following diagram illustrates this process:

How To Program an Arduino Finite State Machine This graph represents a simple SFM for turning on one of three LEDs

Remember that the graph’s nodes represent the states in the program, and the arrows indicate ‎the possible transitions between states. Transitions may occur due to external events such as ‎user inputs or automatically — for instance, periodically or at the end of the current state. In this ‎example, all transitions occur periodically after about 2500 milliseconds.

Represent the FSM States in Arduino Programs

While there might be numerous methods to accomplish this task, enumerations (or enums, in short) are one fantastic way to represent the states of an FSM in programs. Enumerations offer excellent human-readable names to quickly identify states in the program code. The currently active state gets stored in a global variable. So, the state enum in the above hello-world example could look as follows:

Copy Code
enum States {
  RED,
  GREEN,
  BLUE
};

// Set the initial (i.e., starting) state
State state = States::RED;

You can switch between states by setting the state variable to a new value:

Copy Code
void nextState() {
  if (state == States::RED) state = States::GREEN;
  else if (state == States::GREEN) state = States::BLUE;
  else state = States::RED;
}

In this straightforward example, the transitions occur based on a simple set of rules modeled ‎using if-else blocks. When the machine is in the RED state, it transitions to GREEN. Similarly, ‎when it is in the GREEN state, it moves to the BLUE state. Finally, it cycles back to RED when it ‎finishes the BLUE state.

The program must then perform actions based on its current state. You can accomplish this in the sketch’s loop method, which may also contain code that’s similar for all states:

Copy Code
void loop() {
  // The following actions should always happen,
  // irrespective of the state
  digitalWrite(R_LED_PIN, LOW);
  digitalWrite(G_LED_PIN, LOW);
  digitalWrite(B_LED_PIN, LOW);

  // State-aware actions
  switch(state) {
    case States::RED:
      digitalWrite(R_LED_PIN, HIGH);
      break;
    case States::GREEN:
      digitalWrite(G_LED_PIN, HIGH);
      break;
    case States::BLUE:
      digitalWrite(B_LED_PIN, HIGH);
      break;
  }

  // Simulate some delay and then switch to the next state
  delay(2500);
  nextState();
}

The first three lines turn off all LEDs. These actions should take place in all states so they can ‎exist outside of the subsequent switch block, which checks the machine’s current state and then ‎turns on one of the three LEDs corresponding to the active state. The program then waits a short ‎time before switching to the next state.

Switching States Based on User Input

More realistic machines must react to user inputs — for example, to display different content on a screen when users interact with the device. You can accomplish this using states, as outlined in the previous article, where a vending machine would display info content when you pressed a button.

The Arduino Interrupt Service Routine (ISR) is perfect for switching states as a consequence of user inputs, as illustrated by the following two examples:

Copy Code
void returnButtonInterruptHandler() {
  // Only switch to the return state
  machineState = MachineStates::RETURN;
}

void infoButtonInterruptHandler() {
  // Switch to the info state
  machineState = MachineStates::INFO;
  // Set a variable to keep track of when the machine
  // started displaying the info screen
  infoStateStartMillis = millis();
}

The seconds ISR additionally stores the time you pressed a button, which later allows the machine to switch back to its IDLE state after a few seconds.

Similarly, a machine can transition between states based on your specific input sequences. Take ‎variable storage, for example. The program switches to an INFO or ERROR state whenever ‎variables have specific values. When the values are correct, it doesn’t transition to a different ‎state:

Copy Code
void dropSelectedItem(char a, char b) {
  if(a != '0' || a != '1' || a != '2') {
    machineState = MachineStates::INFO;
    infoStateStartMillis = millis();
  }
  else {
    if(a == '2' && (b == '6' || b == '7' || b == '8' || b == '9')) {
      machineState = MachineStates::INFO;
      infoStateStartMillis = millis();
    }
  }
}

A transition to a subsequent state can also happen automatically when the machine finishes its current task, such as boot-up sequence completion:

Copy Code
void displayBootScreen() {
  // Ignore this function in all states except for the bootup state
  if (!machineState == MachineStates::BOOTUP)
    return;

  /* Send the manufacturer's logo to an attached display and perform additional boot-up tasks */
  /* Register interrupts, pins, outputs, attached devices, etc. */

  // Once finished booting, the machine should go into its idle state
  machineState = MachineStates::IDLE;
}

Finally, the loop method only needs to check the current state and call functions or perform ad-hoc operations based on the state:

Copy Code
void loop() {
  // Perform actions based on the machine's state
  switch(machineState) {
    case MachineStates::BOOTUP:
      displayBootScreen();
      break;

    case MachineStates::IDLE:
      displayIdleScreen();
      break;

    case MachineStates::INFO:
      // Switch back to the idle state after five seconds
      if (infoStateStartMillis + 5000 > millis())
        machineState = MachineStates::IDLE;
      else
        displayInfoScreen(); // Until then, display an info screen
      break;

    case MachineStates::DROP:
      dropSelectedItem(selectedItem[0], selectedItem[1]);
      machineState = MachineStates::IDLE;
      break;

    case MachineStates::RETURN:
      returnMoney();
      machineState = MachineStates::IDLE;
      break;
  }
}

The examples above omitted a few methods and variable definitions to keep the article more ‎concise. You can download the complete code and the simple hello-world example program ‎here.

Summary

Enumerations are a perfect construct for representing the states of a simple finite state machine in an Arduino sketch. While there may be other alternatives, enums allow programmers to define understandable human-readable names for all states in the machine, and you can transition between states by changing a single variable’s value, making this approach perfect for use in interrupt handling routines.

Aside from the enumeration encoding the states and the transitions, the machine must also perform actions based on its current state. This typically occurs in the program’s main loop function, where a specific function can be called based on the current state. You may use if-else or switch blocks to make the program react to its current state.

制造商零件编号 A000066
ARDUINO UNO R3 ATMEGA328P BOARD
Arduino
¥190.97
Details
制造商零件编号 102010026
SEEEDUINO V4.3 ATMEGA328P DEV BD
Seeed Technology Co., Ltd
¥74.24
Details
制造商零件编号 DEV-13975
REDBOARD ATMEGA328 EVAL BRD
SparkFun Electronics
¥175.01
Details
制造商零件编号 NERO-LP1
NERO ARDUINO UNO W/ LONG PINS
Bridgetek Pte Ltd.
¥245.91
Details
制造商零件编号 DFR0216
DFRDUINO UNO V3.0 ATMEGA328P
DFRobot
¥105.01
Details
制造商零件编号 MAKER-UNO
MAKER UNO
Cytron Technologies Sdn Bhd
¥58.45
Details
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum