FB vs MAIN Action — TwinCAT PLC architecture decision
Function Block
vs.
MAIN Action
Code organization choice for a Tray Inverter machine on TwinCAT 3. This article breaks down how the two styles differ in maintenance cost, debug observability, and community practice, then lands on a verdict for this specific machine.
Essential difference
Under IEC 61131-3 these are different levels of abstraction. A Function Block is a stateful, instantiable unit; an ACTION is just a named sub-snippet inside a program.
Function Block
- State
- Each instance owns its own VAR (FSM, MC_* instance)
- Parameters
- Explicit signature via VAR_INPUT / VAR_OUTPUT / VAR_IN_OUT
- Reuse
- The same FB instantiates N times, each running independently
- OOP
- METHOD / PROPERTY / INTERFACE / EXTENDS
- Online change
- Impact scoped to the FB
- Cross-reference
- Strong type / input naming makes Find References reliable
MAIN Action
- State
- Shares MAIN's VAR — a single global copy
- Parameters
- None; values flow through MAIN's global variables
- Reuse
- Not possible — only copy-paste or a giant CASE
- OOP
- None
- Online change
- Treated as a change to the entire MAIN program
- Cross-reference
- Can only search variable names — high noise
11 servos, 11 separate states
Each of the 11 servos on this machine needs its own MC_MoveAbsolute instance, its own eState, and its own xBusy / xDone. The diagram shows how an FB fans out into 11 independent instances, while ACTION forces every axis to share one state space.
FB instances
MAIN Action
In practice, the ACTION route has only two variants: (a) hand-copy 11 versions — change one place, change 11 places; (b) declare 11 axis-specific global variables plus a giant CASE — hand-rolling OOP in a procedural style and killing Cross-reference.
11 local axes + Master/Slave coupling
Below is the servo list for this machine. AX_Flip_RotMaster fans out to RotL / RotR through the NC MC_GearIn coupling — exactly where instance-level state management proves its worth.
Online view: visible vs invisible
Whether a running PLC is actually debuggable comes down to whether you can see every axis's state at the same moment. The mockup below shows TwinCAT online view under each architecture.
FB — per-instance watch
All 11 parallel tasks' real states are visible and comparable at the same instant. A breakpoint can pin to "the instance for axis 3" and hit precisely.
ACTION — shared state
// but nAxisId got overwritten the previous cycle
All variables share one copy in MAIN. A breakpoint can only attach to a code line; when 11 axes run through the same line, you can't tell them apart. Synchronization / fan-out scenarios become nearly undebuggable.
Change one place vs change 11 places
Maintenance cost isn't about how pretty today's code is — it's about how many places need to change when requirements shift three months out.
Function Block
- PROChange one FB and all 11 axes pick it up; regression scope is clear
- PROVAR_INPUT / VAR_OUTPUT acts as an interface contract — team handoffs and C# bridges read directly from the signature
- PROOnline change impact stays narrow, keeping hot-patch risk controllable
- PROUnit tests can isolate each FB individually
- PROFind All References has a high hit rate
- NEUFirst-time instance declaration costs an extra line
MAIN Action
- PROUseful for splitting a short linear sequence visually; up to ~5 lines it can improve readability
- PRONo instance to declare — saves a line on first write
- CONMAIN's variable space balloons into a "god program" as the machine grows
- CONCannot fan out — multi-axis scenarios force copy-paste
- CONNo input / output signature; the C# integration layer struggles to align
- CONOnline change scope equals the entire MAIN — risk amplified
Where the community lands
This one is lopsided. The chart below shows where major Beckhoff / IEC 61131-3 communities sit on "which to use at the architecture level". ACTION retains residual value only for "splitting a short sub-snippet inside an FB".
※ Percentages are qualitative estimates reflecting the mainstream recommendation ratio across these communities for "FB vs ACTION at the architecture level" — not rigorous survey data.
Verdict for this machine
Keep the current FB decomposition.
This machine's profile — 11 axes running in parallel plus Master/Slave coupling — makes per-instance observability a hard requirement for debugging. Building the architecture on ACTION would mean giving up instance-level state inspection and bounded Online change scope.
ACTION's reasonable role downgrades to a layout-splitting tool inside an FB — for example a 5-line "reset latch" sub-snippet inside MAIN_Alarm, or a named partition for each FSM state inside an FB.
Architecture-level concerns (task dispatch, interlock, motion task) stay in FB. The current FB_PlcTaskDispatcher / FB_TaskMoveAbs / FB_TaskHome / FB_Interlock decomposition is correctly oriented and does not need refactoring.