Table of contents
Open Table of contents
Introduction
In the previous post, I introduced my goal of building a prototype driveway gate controller using an STM32 microcontroller, a pair of servos to simulate gate actuators, and a collection of sensors and buttons to represent real-world inputs. While the project will eventually involve motors, limit switches, and safety controls, the first challenge is creating an environment that allows the system to evolve in a structured way.
Rather than relying on a vendor-specific IDE, I chose to build the project using LLVM, OpenOCD, and a simple Makefile-based workflow. This approach provides complete visibility into the build process while keeping the project independent of any particular development environment. Source files are compiled with Clang, linked using LLD, and programmed onto the target hardware through OpenOCD and an ST-Link debugger.
One of the advantages of this setup is that each stage of the development process becomes visible. The source code is compiled into object files, linked into an executable image, converted into a format suitable for flash memory, and finally programmed onto the microcontroller. Understanding this pipeline helps demystify the development process and makes troubleshooting significantly easier when problems arise.
With the development environment in place, the next challenge is deciding how the system should behave.
When people first approach automation projects, it is common to think in terms of inputs and outputs. A button is pressed, a motor starts moving. A sensor activates, a light turns on. While this works for simple systems, it quickly becomes difficult to manage as additional features are added.
Consider a driveway gate. What happens if the gate is opening and an emergency stop is pressed? What if a close command is received while the gate is already opening? What if a sensor detects an obstruction? These situations introduce rules, and rules introduce state.
A state machine provides a structured way to model those rules. Instead of allowing hardware inputs to directly control outputs, inputs generate events and the state machine determines how the system should respond.
Before writing any implementation code, it is useful to identify the major states that can exist within the system. For a gate controller, examples might include idle, opening, open, closing, stopped, and fault conditions. Defining these states early creates a shared vocabulary for the project and makes future design decisions easier.
Once the states have been identified, I prefer to place the state definitions in a dedicated header file. Centralizing state definitions provides a single source of truth for the rest of the application and makes it easier to understand the behavior of the system at a glance. As the project grows, additional files can focus on specific responsibilities such as handling inputs, controlling outputs, or processing state transitions.
This separation of responsibilities is one of the reasons state machines scale so effectively. Hardware details remain isolated within their respective modules while the overall behavior of the system is managed by a centralized state model.
In the next post, I will begin defining the states and events for the gate controller and explore how a state transition model can be used to describe system behavior before any hardware-specific logic is implemented.
If you’d like to follow along, all development is being done on a low-cost STM32F103 development board using open tools and standard C. The goal is not only to build a functioning controller, but also to demonstrate how software engineering principles can be applied to real-world automation projects.
States
For my driveway gate controller project, I defined three high-levels states: system, event, and gate.
The system states define the controller’s overall behaviour. The most common state will be idle, but other valid states include: opening, open, closing, closed, stopped, and fault.
The events states are a combination of commands, user intiated commands (event open command, event stop command) and system detected events (event timeout, event fault detected).
The gate state represent the physical state of the gates.
State-Transition Table
There are different types of state-transition tables, but their purpose is to describe it state and how a state evolve to another state via an event. In my case, the table is used to describe a Finite State Machine (FSM) and the events that trigger state shift.
| Current State | Event | Next State |
|---|---|---|
| IDLE | OPEN_COMMAND | OPENING |
| OPENING | GATE_OPENED | OPEN |
| OPEN | CLOSE_COMMAND | CLOSING |
| CLOSING | GATE_CLOSED | IDLE |
| ANY | ESTOP_PRESSED | STOPPED |
| STOPPED | RESET_COMMAND | IDLE |
| ANY | FAULT_DETECTED | FAULT |