5 CODESYS Function Block Examples Every PLC Programmer Needs
Function blocks are the building blocks of professional PLC code. A well-designed FB can be reused across dozens of projects, saving hours of development time.
Here are five function blocks that every CODESYS programmer ends up needing. All examples are written in IEC 61131-3 Structured Text and ready to use in any CODESYS V3 project.
1. Motor Control with Interlocks
The most common function block in industrial automation. This one handles start/stop, overcurrent protection, and runtime tracking.
FUNCTION_BLOCK FB_MotorControl
VAR_INPUT
bStart : BOOL; // Start command
bStop : BOOL; // Stop command
bEmergency : BOOL; // Emergency stop (NC)
bInterlock : BOOL; // External interlock (TRUE = OK)
rCurrent : REAL; // Measured motor current [A]
rCurrentMax : REAL := 20.0; // Overcurrent trip [A]
tStartDelay : TIME := T#500MS;
END_VAR
VAR_OUTPUT
bRunning : BOOL; // Motor is running
bFault : BOOL; // Fault active
nFaultCode : INT; // 0=OK, 1=Overcurrent, 2=Interlock, 3=E-Stop
tTotalRuntime : TIME;
END_VAR
VAR
bMotorCmd : BOOL;
rtrigStart : R_TRIG;
tonStart : TON;
tonRuntime : TON;
bLatched : BOOL;
END_VAR
// Edge detection on start
rtrigStart(CLK := bStart);
// Fault detection
IF NOT bEmergency THEN
bFault := TRUE;
nFaultCode := 3;
bLatched := FALSE;
ELSIF NOT bInterlock THEN
bFault := TRUE;
nFaultCode := 2;
bLatched := FALSE;
ELSIF bRunning AND rCurrent > rCurrentMax THEN
bFault := TRUE;
nFaultCode := 1;
bLatched := FALSE;
ELSE
nFaultCode := 0;
END_IF
// Start/Stop logic
IF bFault THEN
bLatched := FALSE;
ELSIF rtrigStart.Q AND NOT bFault THEN
bLatched := TRUE;
ELSIF bStop THEN
bLatched := FALSE;
END_IF
// Start delay
tonStart(IN := bLatched AND NOT bFault, PT := tStartDelay);
bMotorCmd := tonStart.Q;
bRunning := bMotorCmd;
// Runtime counter
tonRuntime(IN := bRunning, PT := T#24H);
tTotalRuntime := tonRuntime.ET;
// Reset fault when conditions clear and stop is pressed
IF bFault AND bStop AND bEmergency AND bInterlock AND (rCurrent <= rCurrentMax) THEN
bFault := FALSE;
END_IF
Usage:
fbMotor1(
bStart := bHMI_StartMotor1,
bStop := bHMI_StopMotor1,
bEmergency := bEmergencyCircuit,
bInterlock := bSafetyGatesClosed,
rCurrent := rMotor1Current
);
bDO_Motor1 := fbMotor1.bRunning;
2. Generic State Machine
A reusable state machine pattern with automatic timeout and error handling.
TYPE E_SeqState :
(
SEQ_IDLE := 0,
SEQ_INIT := 10,
SEQ_STEP_1 := 20,
SEQ_STEP_2 := 30,
SEQ_STEP_3 := 40,
SEQ_COMPLETE := 100,
SEQ_ERROR := 999
);
END_TYPE
FUNCTION_BLOCK FB_Sequence
VAR_INPUT
bExecute : BOOL; // Start sequence
bReset : BOOL; // Reset from error
bAbort : BOOL; // Abort sequence
tStepTimeout : TIME := T#30S; // Max time per step
END_VAR
VAR_OUTPUT
eState : E_SeqState;
bBusy : BOOL;
bDone : BOOL;
bError : BOOL;
nCurrentStep : INT;
END_VAR
VAR
rtrigExecute : R_TRIG;
tonTimeout : TON;
bStepActive : BOOL;
END_VAR
rtrigExecute(CLK := bExecute);
// Timeout monitoring
bStepActive := eState > E_SeqState.SEQ_IDLE
AND eState < E_SeqState.SEQ_COMPLETE;
tonTimeout(IN := bStepActive, PT := tStepTimeout);
IF tonTimeout.Q THEN
eState := E_SeqState.SEQ_ERROR;
END_IF
// Abort
IF bAbort AND bBusy THEN
eState := E_SeqState.SEQ_IDLE;
END_IF
CASE eState OF
E_SeqState.SEQ_IDLE:
bBusy := FALSE;
bDone := FALSE;
bError := FALSE;
IF rtrigExecute.Q THEN
eState := E_SeqState.SEQ_INIT;
bBusy := TRUE;
END_IF
E_SeqState.SEQ_INIT:
nCurrentStep := 1;
tonTimeout(IN := FALSE); // Reset timeout
// Add initialization logic here
eState := E_SeqState.SEQ_STEP_1;
E_SeqState.SEQ_STEP_1:
nCurrentStep := 1;
// Add step 1 logic here
// When done:
tonTimeout(IN := FALSE);
eState := E_SeqState.SEQ_STEP_2;
E_SeqState.SEQ_STEP_2:
nCurrentStep := 2;
// Add step 2 logic here
tonTimeout(IN := FALSE);
eState := E_SeqState.SEQ_STEP_3;
E_SeqState.SEQ_STEP_3:
nCurrentStep := 3;
// Add step 3 logic here
tonTimeout(IN := FALSE);
eState := E_SeqState.SEQ_COMPLETE;
E_SeqState.SEQ_COMPLETE:
bBusy := FALSE;
bDone := TRUE;
E_SeqState.SEQ_ERROR:
bBusy := FALSE;
bError := TRUE;
IF bReset THEN
eState := E_SeqState.SEQ_IDLE;
END_IF
END_CASE
3. Alarm Handler
Track alarms with timestamps, acknowledgment, and history.
FUNCTION_BLOCK FB_Alarm
VAR_INPUT
bCondition : BOOL; // Alarm condition (TRUE = alarm)
bAcknowledge : BOOL; // Operator acknowledges
tDebounce : TIME := T#1S; // Prevent flicker
sDescription : STRING(80);
END_VAR
VAR_OUTPUT
bActive : BOOL; // Alarm condition is TRUE now
bUnacknowledged : BOOL; // Needs operator attention
bLatched : BOOL; // Was active, may have cleared
nActivations : DINT; // Total times alarm triggered
END_VAR
VAR
tonDebounce : TON;
rtrigAlarm : R_TRIG;
rtrigAck : R_TRIG;
bPrevActive : BOOL;
END_VAR
// Debounced alarm condition
tonDebounce(IN := bCondition, PT := tDebounce);
bActive := tonDebounce.Q;
// Rising edge -- new alarm event
rtrigAlarm(CLK := bActive);
IF rtrigAlarm.Q THEN
bLatched := TRUE;
bUnacknowledged := TRUE;
nActivations := nActivations + 1;
END_IF
// Acknowledgment
rtrigAck(CLK := bAcknowledge);
IF rtrigAck.Q THEN
bUnacknowledged := FALSE;
// Clear latch if condition is gone
IF NOT bActive THEN
bLatched := FALSE;
END_IF
END_IF
// Auto-clear latch when acknowledged and condition clears
IF NOT bActive AND NOT bUnacknowledged THEN
bLatched := FALSE;
END_IF
Usage -- managing multiple alarms:
VAR
fbAlarmHighTemp : FB_Alarm;
fbAlarmLowPress : FB_Alarm;
fbAlarmOverspeed : FB_Alarm;
bAnyAlarmActive : BOOL;
bAnyUnacked : BOOL;
END_VAR
fbAlarmHighTemp(
bCondition := rTemperature > 85.0,
bAcknowledge := bHMI_AckAll,
sDescription := 'High temperature'
);
fbAlarmLowPress(
bCondition := rPressure < 2.0,
bAcknowledge := bHMI_AckAll,
sDescription := 'Low pressure'
);
bAnyAlarmActive := fbAlarmHighTemp.bActive OR fbAlarmLowPress.bActive;
bAnyUnacked := fbAlarmHighTemp.bUnacknowledged OR fbAlarmLowPress.bUnacknowledged;
4. Moving Average Filter
Essential for noisy analog signals from sensors.
FUNCTION_BLOCK FB_MovingAverage
VAR_INPUT
rInput : REAL; // Raw input value
bEnable : BOOL := TRUE;
bReset : BOOL;
END_VAR
VAR_OUTPUT
rFiltered : REAL; // Filtered output
bValid : BOOL; // TRUE when buffer is full
END_VAR
VAR CONSTANT
BUFFER_SIZE : INT := 20;
END_VAR
VAR
arBuffer : ARRAY[0..19] OF REAL;
nIndex : INT := 0;
nCount : INT := 0;
rSum : REAL := 0.0;
i : INT;
END_VAR
IF bReset THEN
FOR i := 0 TO BUFFER_SIZE - 1 DO
arBuffer[i] := 0.0;
END_FOR
nIndex := 0;
nCount := 0;
rSum := 0.0;
rFiltered := 0.0;
bValid := FALSE;
RETURN;
END_IF
IF NOT bEnable THEN
RETURN;
END_IF
// Subtract oldest value, add new value
rSum := rSum - arBuffer[nIndex] + rInput;
arBuffer[nIndex] := rInput;
// Advance index (circular buffer)
nIndex := nIndex + 1;
IF nIndex >= BUFFER_SIZE THEN
nIndex := 0;
END_IF
// Track fill level
IF nCount < BUFFER_SIZE THEN
nCount := nCount + 1;
END_IF
bValid := (nCount >= BUFFER_SIZE);
// Calculate average
IF nCount > 0 THEN
rFiltered := rSum / DINT_TO_REAL(nCount);
END_IF
Usage:
VAR
fbFilterTemp : FB_MovingAverage;
rRawTemp : REAL; // From analog input
rSmoothTemp : REAL;
END_VAR
fbFilterTemp(rInput := rRawTemp);
rSmoothTemp := fbFilterTemp.rFiltered;
5. Recipe Manager
Store and recall parameter sets for different products.
TYPE ST_Recipe :
STRUCT
sName : STRING(40);
rTemperature : REAL; // Setpoint [C]
rPressure : REAL; // Setpoint [bar]
rSpeed : REAL; // Setpoint [RPM]
tMixTime : TIME; // Process duration
nCycles : INT; // Repeat count
END_STRUCT
END_TYPE
FUNCTION_BLOCK FB_RecipeManager
VAR_INPUT
nLoadIndex : INT; // Recipe to load (0-based)
bLoad : BOOL; // Load command
bSave : BOOL; // Save current to index
END_VAR
VAR_OUTPUT
stActiveRecipe : ST_Recipe; // Currently active recipe
bLoaded : BOOL;
bError : BOOL;
END_VAR
VAR CONSTANT
MAX_RECIPES : INT := 10;
END_VAR
VAR PERSISTENT
arRecipes : ARRAY[0..9] OF ST_Recipe;
END_VAR
VAR
rtrigLoad : R_TRIG;
rtrigSave : R_TRIG;
END_VAR
rtrigLoad(CLK := bLoad);
rtrigSave(CLK := bSave);
bError := FALSE;
// Load recipe
IF rtrigLoad.Q THEN
IF nLoadIndex >= 0 AND nLoadIndex < MAX_RECIPES THEN
stActiveRecipe := arRecipes[nLoadIndex];
bLoaded := TRUE;
ELSE
bError := TRUE;
END_IF
END_IF
// Save current recipe to slot
IF rtrigSave.Q THEN
IF nLoadIndex >= 0 AND nLoadIndex < MAX_RECIPES THEN
arRecipes[nLoadIndex] := stActiveRecipe;
ELSE
bError := TRUE;
END_IF
END_IF
Tips for Writing Better Function Blocks
- Keep them focused -- one FB, one responsibility
- Use meaningful prefixes --
bfor BOOL,rfor REAL,nfor INT,tfor TIME,sfor STRING,fbfor function block instances - Document inputs and outputs with inline comments showing units
- Use edge detection (
R_TRIG,F_TRIG) for command inputs to prevent repeated execution - Default to safe states -- outputs should be safe (off, closed, stopped) when the FB initializes
Generate Function Blocks with AI
Writing these function blocks takes time. With PLC Assist, you can describe what you need in plain English and get complete, ready-to-use Structured Text code generated for your CODESYS project. The AI understands IEC 61131-3 patterns and generates code that follows best practices.
PLC Assist is an AI-powered engineering assistant for industrial automation. Start free -- 25 AI requests included, no credit card required.