Skip to main content

Most Siemens projects still use SCL as a slightly-fancier ladder language: a few IF statements, some FOR loops, the occasional CASE. That works for small machines. The moment your library grows past 30 Function Blocks and three engineers are touching it in parallel, you need object-oriented patterns — methods, interfaces, inheritance, and namespaces — to keep the codebase from collapsing into copy-paste chaos.

This guide covers the SCL OOP features in TIA Portal V21 (S7-1500 firmware V3.1+) and the patterns that actually make code reusable across projects.

Why OOP in PLC Code?

The honest reason: diff-friendly libraries. When ten machine variants share a Motor_Standard FB, OOP lets you ship one base class plus thin overrides instead of ten near-identical copies that drift apart over time.

Concrete benefits we see on real projects:

  • Smaller diffs in Git — a method override is 20 lines, not a forked 800-line FB
  • Faster onboarding — new engineers learn one base FB and N small extensions
  • Real unit tests — interfaces let you swap in test doubles for I/O blocks
  • Cleaner libraries — namespaces stop name collisions when you import third-party blocks

Feature Support Matrix

FeatureS7-1200S7-1200 G2S7-1500 (FW S7-1500 (FW ≥ V3.1)
FB MethodsNoLimitedYesYes
InterfacesNoNoYesYes
Inheritance (EXTENDS)NoNoYesYes
NamespacesNoNoNoYes
ANY_TYPE genericsNoNoLimitedYes

Bottom line: serious OOP needs S7-1500 with current firmware. S7-1200 G2 gets methods but skip the inheritance.

Pattern 1 — Methods Instead of “Mode” Parameters

The classic anti-pattern: one giant FB with a Mode input that switches behavior.

// BAD: god FB
FUNCTION_BLOCK "Motor_Anti"
VAR_INPUT
    Mode : INT;  // 1=Start, 2=Stop, 3=Reset, 4=Diagnose
END_VAR
BEGIN
    CASE Mode OF
        1: // ... start logic
        2: // ... stop logic
        3: // ... reset logic
        4: // ... diagnostic
    END_CASE;
END_FUNCTION_BLOCK

Refactored with methods — each operation is its own callable:

// GOOD: motor with methods
FUNCTION_BLOCK "Motor"
VAR_INPUT
    StartCmd, StopCmd, Reset : BOOL;
END_VAR
VAR_OUTPUT
    Run, Fault : BOOL;
END_VAR
VAR
    iState : INT;
END_VAR

METHOD Start : BOOL
BEGIN
    IF NOT #Fault THEN
        #Run := TRUE;
        #iState := 1;
        Start := TRUE;
    END_IF;
END_METHOD

METHOD Stop : VOID
BEGIN
    #Run := FALSE;
    #iState := 0;
END_METHOD

METHOD Diagnose : STRING
BEGIN
    Diagnose := CONCAT(IN1 := 'State=', IN2 := INT_TO_STRING(#iState));
END_METHOD

Caller:

"M1"(StartCmd := HMI.Start);    // main cyclic call
IF HMI.ResetBtn THEN
    "M1".Reset();                // method call
END_IF;

Methods are scoped, testable, and don’t pollute the FB’s input list.

Pattern 2 — Interfaces for Polymorphism

Define a contract every motor type must satisfy:

INTERFACE "IMotor"
    METHOD Start : BOOL;
    METHOD Stop : VOID;
    METHOD GetSpeed : REAL;
END_INTERFACE

Implement it in concrete blocks:

FUNCTION_BLOCK "Motor_DOL" IMPLEMENTS "IMotor"
    // direct on-line motor implementation
END_FUNCTION_BLOCK

FUNCTION_BLOCK "Motor_VFD" IMPLEMENTS "IMotor"
    // VFD-driven motor implementation
END_FUNCTION_BLOCK

Now a sequencer can drive any motor type without knowing which one:

VAR
    pMotor : "IMotor";   // interface reference
END_VAR

#pMotor := "Conv1_Motor";   // could be DOL or VFD
#pMotor.Start();
IF #pMotor.GetSpeed() > 0.0 THEN
    // ...
END_IF;

This is the same pattern as IDisposable in C# or duck typing in Python — code against the interface, not the implementation.

Pattern 3 — Inheritance for Variants

EXTENDS lets a child FB inherit all variables, methods, and the parent body:

FUNCTION_BLOCK "Motor_Base"
VAR
    Run : BOOL;
END_VAR

METHOD Start : BOOL
BEGIN
    #Run := TRUE;
    Start := TRUE;
END_METHOD
END_FUNCTION_BLOCK


FUNCTION_BLOCK "Motor_WithBrake" EXTENDS "Motor_Base"
VAR
    BrakeRelease : BOOL;
END_VAR

METHOD OVERRIDE Start : BOOL
BEGIN
    #BrakeRelease := TRUE;
    SUPER::Start();        // call parent
    Start := TRUE;
END_METHOD
END_FUNCTION_BLOCK

Use inheritance sparingly — favor composition (FB containing other FBs as IN_OUT) for relationships that aren’t “is-a”. Three levels of EXTENDS is the practical limit before debugging gets painful.

Pattern 4 — Namespaces to Avoid Library Collisions

TIA Portal V21 introduced namespaces for SCL libraries. If you import two third-party libraries that both define Motor, you previously had to rename one. Now:

USING "Vendor_A.Drives";
USING "Vendor_B.Motors" AS VBM;

VAR
    motor1 : Motor;            // resolves to Vendor_A
    motor2 : VBM.Motor;        // explicit Vendor_B
END_VAR

Standard practice: every reusable library gets its own namespace named . (e.g., Controlbyte.Safety, Controlbyte.Motion). Internal projects can stay namespace-less.

Pattern 5 — Generic Algorithms with ANY_TYPE

For utility functions that work on multiple data types (filters, scalers, statistics), use VARIANT and the new ANY_TYPE:

METHOD MovingAverage : REAL
VAR_INPUT
    NewValue : VARIANT;
END_VAR
VAR_TEMP
    rValue : REAL;
END_VAR
BEGIN
    CASE TypeOf(#NewValue) OF
        Type(REAL): #rValue := REAL#0.0;  // extract via VariantGet
        Type(INT):  #rValue := INT_TO_REAL(REF_TO INT(#NewValue)^);
    END_CASE;
    // ... circular buffer + average
END_METHOD

Less elegant than C++ templates, but it lets you write one filter that works for INT, REAL, and scaled values without three near-identical FBs.

Real Project Skeleton

A pattern that has worked on three different OEM machine families:

Library: Controlbyte.Equipment
├── Interfaces/
│   ├── IDevice         (Start, Stop, Reset, GetState)
│   ├── IMotor          (extends IDevice + GetSpeed)
│   └── IValve          (extends IDevice + GetPosition)
├── Base/
│   ├── Device_Base     (implements IDevice)
│   ├── Motor_Base      (extends Device_Base, implements IMotor)
│   └── Valve_Base      (extends Device_Base, implements IValve)
└── Concrete/
    ├── Motor_DOL
    ├── Motor_VFD
    ├── Valve_DigitalDO
    └── Valve_Analog

The sequencer references only IDevice[] arrays. New equipment = new concrete class implementing the interface. Zero changes to the sequencer.

Anti-Patterns to Avoid

A few things that look clever but cause pain:

  • Deep inheritance chains (4+ levels) — debugging method dispatch becomes a nightmare
  • Methods that modify global state — defeats the encapsulation point; pass state as parameters
  • Interfaces with 15+ methods — split into several smaller interfaces (Interface Segregation Principle)
  • Using OOP for one-off machine-specific logic — composition or plain SCL is fine for 80% of code

Migrating an Existing Library

A pragmatic conversion path for a 50-FB library:

  1. Inventory — group FBs by what they control (motors, valves, conveyors)
  2. Define one interface per group — start with the methods you actually use everywhere
  3. Create a Base FB per group with default implementations
  4. Refactor 1-2 concrete FBs to inherit from Base + implement Interface
  5. Run on a real machine before refactoring more
  6. Iterate — convert FBs as you touch them for other reasons; don’t big-bang the library

FAQ

Can I use OOP features on existing S7-1500 hardware?

Yes, if firmware is V3.1 or higher. Methods and basic inheritance work back to V2.5; namespaces and full ANY_TYPE need V3.1+. Check via Device Configuration → Properties → Firmware version, then upgrade via Online → Firmware Update if needed.

Do OOP features impact scan time?

Method dispatch is a few extra bytes of memory and ~5-10 ns per call. Inheritance and interfaces are resolved at compile time, so no runtime overhead. For typical OB1 cycles (10-50 ms) the impact is unmeasurable.

Is OOP code harder to commission on-site?

The opposite — when a sequencer fault traces back to “motor not running”, you can dig into one Motor_VFD FB instead of a 2000-line monolithic block. Watch tables work the same way.

Can I export an OOP library to a different TIA project?

Yes — Global Libraries (.al17 / .al18 format) preserve interfaces, inheritance, and namespaces. Compatible across V18+, with downgrade limitations for V21-only namespace features.

How does this compare to Codesys / Beckhoff TwinCAT?

Conceptually identical — Beckhoff TwinCAT 3 has had full OOP since 2014, Codesys since V3. Siemens caught up later, and the syntax is a strict subset (no abstract classes, no operator overloading). Code structure transfers between platforms with mechanical refactoring.

Want to write production-grade Siemens libraries? Our TIA Portal advanced courses at controlbyte.tech cover SCL OOP, library architecture, version control, and real OEM case studies. Hands-on with S7-1500 hardware.

Author

Simon Adamek

Author Simon Adamek

IT Engineer and PLC Specialist Manager at ControlByte "Guiding beginners into the world of PLCs and industrial innovation."

More posts by Simon Adamek