← Back to Academy

Lesson 178: The Power of State Machines in Arduino

If you’ve been building Arduino projects, you’ve hit a wall: you want to blink an LED and read a button at the same time. But when you use the delay() function, the Arduino is frozen. It ignores everything. It is practically dead. The solution to this single-minded stubbornness is The Finite State Machine (FSM). It sounds scary, but it’s just a way to force your microcontroller to finally multitask.

What is a State Machine?

A Finite State Machine is a way of organizing your code based on “States”. At any given moment, your system is in exactly one state. It only changes to a different state when a specific event or condition happens (a “Transition”).

Think of a microwave oven:

  1. IDLE: Waiting for a button press.
  2. COOKING: Magnetron is on, timer is counting down.
  3. PAUSED: Door opened mid-cook.
  4. DONE: Timer hits 0, beeping.

The microwave can’t be COOKING and IDLE at the same time. It moves between these states based on triggers (like pressing “Start” or opening the door).

Building a State Machine: Enums and Switch/Case

To build a state machine in C++ (the language Arduino uses), we use two main tools: enum and switch...case.

1. Defining States with enum

An enum (enumeration) is just a way to give names to numbers. It makes our code incredibly easy to read.

enum SystemState {
  STATE_IDLE,
  STATE_BLINKING,
  STATE_ERROR
};

// Create a variable to hold our current state, starting at IDLE
SystemState currentState = STATE_IDLE;

2. The switch...case Statement

In our loop(), instead of running a long list of commands, we use a switch statement to check our currentState and only run the code for that specific state.

void loop() {
  switch (currentState) {
    case STATE_IDLE:
      // Code for IDLE state
      // If button pressed -> currentState = STATE_BLINKING;
      break;
      
    case STATE_BLINKING:
      // Code for BLINKING state (using millis() instead of delay!)
      // If error occurs -> currentState = STATE_ERROR;
      break;
      
    case STATE_ERROR:
      // Code for ERROR state
      // Flash red LED
      break;
  }
}

Why This is Powerful

By using a State Machine, your loop() runs thousands of times per second. It quickly jumps into the switch, checks the state, does a tiny piece of work, and immediately loops back around.

This means your Arduino can constantly check for button presses while simultaneously managing timers with millis(). You achieve true multitasking without ever needing to use delay().

Your Next Step

Take a previous project that uses delay()—like a traffic light sequence or a buzzer melody—and try rewriting it as a State Machine. Define your states, set up your switch...case, and watch how much more responsive your buttons become!