Skip to Content

Structuring PLC logic in Arduino C++: state machines instead of long loops

A single loop() that keeps growing with if statements becomes unreadable and hard to debug fast. In this course you will build one project from start to finish: the control of a pump station with a tank. In this first lesson you give it a solid structure with a state machine, the pattern most PLC logic in Arduino C++ eventually needs.

What you will learn

  • Replace nested ifs with named states and explicit transitions.
  • Write a three-state machine with a safe way out of a fault.
  • Decide when a new condition deserves a new state.

The project: a start button on I0_0, an emergency stop on I0_1, a pump contactor on Q0_0 and an alarm beacon on Q0_1. Later lessons add the tank level, timing, Modbus, memory, a watchdog and a dashboard.

Why a long loop() breaks down

As a sketch grows, one big loop() checking dozens of conditions becomes hard to read, hard to test and easy to break with an unrelated change. A state machine gives your program a small set of named states and clear rules for moving between them.

The pump station as a state machine

enum State { IDLE, RUNNING, FAULT };
State currentState = IDLE;

void setup() {
  pinMode(I0_0, INPUT);   // start button
  pinMode(I0_1, INPUT);   // emergency stop
  pinMode(Q0_0, OUTPUT);  // pump contactor
  pinMode(Q0_1, OUTPUT);  // alarm beacon
  digitalWrite(Q0_0, LOW);
  digitalWrite(Q0_1, LOW);
}

void loop() {
  switch (currentState) {
    case IDLE:
      digitalWrite(Q0_0, LOW);
      if (digitalRead(I0_0) == HIGH) {
        currentState = RUNNING;
      }
      break;

    case RUNNING:
      digitalWrite(Q0_0, HIGH);
      if (digitalRead(I0_1) == HIGH) {
        currentState = FAULT;
      }
      break;

    case FAULT:
      digitalWrite(Q0_0, LOW);
      digitalWrite(Q0_1, HIGH);
      if (digitalRead(I0_1) == LOW && digitalRead(I0_0) == HIGH) {
        digitalWrite(Q0_1, LOW);
        currentState = IDLE;
      }
      break;
  }
}

Each case is exactly one state, and a state only changes inside its own case, which makes it easy to trace how the machine went from IDLE to FAULT. Note the way out of FAULT: the machine only returns to IDLE when the emergency stop is released and the operator presses start again as an acknowledgement. A fault state with no exit is a common design mistake.

Expected result: pressing I0_0 turns on Q0_0, activating I0_1 turns it off and lights Q0_1, and the system is armed again only after releasing I0_1 and acknowledging with I0_0.

Visualizing the states and transitions

State machine diagram showing IDLE, RUNNING and FAULT states

When to add a new state instead of a new if

If you ask what mode the machine is in right now and the answer depends on three other variables, that combination is probably a missing state. A new condition is a signal for a new state, not another nested if inside an existing one.

States and the PLC scan cycle

Unlike a traditional PLC scan, an Arduino loop() runs as fast as the code allows, with no fixed scan time by default. A state machine does not change that, but it makes each pass predictable: read inputs, evaluate the current state, set outputs, move on.

Go deeper

Next lesson: timing without blocking, so the pump station can wait and still react. Take the quiz below first.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.