Passing DB Name as FC Input Parameter in SCL (TIA Portal)

David Krause14 min read
SiemensTIA PortalTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Passing DB Name as FC Input Parameter in SCL (TIA Portal)

Reusable Function (FC) blocks that read or write to multiple instance data blocks are a recurring need on S7-1200 and S7-1500 controllers. A common symptom: code compiles when the FC hard-codes the target DB name, but the FC cannot be retargeted at runtime without recompiling or duplicating logic. In SCL on TIA Portal there are two production-ready solutions: a typed IN_OUT parameter backed by a PLC Data Type (UDT), and the DB_ANY data type combined with the OPN / LAR1 register operations. Both are valid; each carries trade-offs in performance, type safety, and maintainability.

Scope. This article targets SCL on STEP 7 V15.1 through V18 (TIA Portal) for S7-1200 (firmware ≥ 4.2) and S7-1500 (firmware ≥ 1.8). Behavior is consistent across these versions; DB_ANY support has been present since V13. STL-only constructs such as UC/CC calls are referenced only for context.

1. Problem Definition

You maintain an FC (for example, FC2_SequenceStep) that processes one step of a machine sequence. The sequence data lives in a global DB (for example, SEQUENCE_1) and includes fields such as StepNumber, StepTime, NextStep, FaultCode, and Outputs.

Compiling the FC with a hard-coded DB reference works:

SCL
// Hard-coded reference - inflexible
IF "SEQUENCE_1".StepNumber = 10 THEN
    "SEQUENCE_1".StepTime := "SEQUENCE_1".StepTime + 1;
END_IF;

The problem appears when a second DB (SEQUENCE_2) must drive the same logic. Three paths are typically considered:

  1. Duplicate the FC for every DB and call the right copy. This scales linearly with the number of sequences and forces copy-paste maintenance.
  2. Pass an IN/IN_OUT parameter that carries the DB identity. This is the goal — but the parameter type matters.
  3. Use indirect addressing through pointer registers. This was the standard STL pattern (OPN DB, LAR1) but is awkward in SCL.

2. Root Cause: Why the DB Symbol Cannot Be an FC Parameter Directly

A global DB reference in SCL resolves to an absolute address at compile time. The FC parameter interface accepts typed values (INT, BOOL, ARRAY, UDT, etc.) or pointer-like values, but it cannot accept a DB symbol as a parameter slot. When the SCL compiler encounters "SEQUENCE_1".StepNumber, it emits a direct DB-DB area-internal address with a fully-resolved offset. The DB is part of the generated code, not a runtime variable.

To make the DB a runtime decision, the FC must either:

  • Receive a copy of the DB's payload (or a reference to it) as a typed parameter — the UDT/IN_OUT approach.
  • Receive a numeric or symbolic handle to the DB and dereference it with register indirection — the DB_ANY approach.

3. Solution A: UDT + IN_OUT Parameter (Recommended Default)

This is the cleanest, type-safe pattern and should be your default unless you have a documented reason to use indirection. The DB becomes a typed container; the FC receives the data (or a reference to the data) through its interface.

3.1 Create the PLC Data Type (UDT)

In the project tree, expand PLC data types and add a new type named Sequence:

SCL
// PLC data type: "Sequence" (UDT)
TYPE "Sequence"
    STRUCT
        StepNumber : INT;            // current step index
        StepTime   : TIME;           // dwell in current step
        NextStep   : INT;            // next step index on success
        FaultCode  : INT;            // 0 = OK, <>0 = error code
        Outputs    : WORD;           // output bit image
        Flags      : ARRAY[0..15] OF BOOL;
    END_STRUCT;
END_TYPE

3.2 Create a Global DB Containing One or More Sequence Instances

Rather than building one DB per sequence with the UDT fields at the top level, place named Sequence instances inside a single DB. This gives the compiler a stable data layout and avoids DB sprawl.

SCL
DATA_BLOCK "myDB"
    STRUCT
        Sequence1 : "Sequence";      // machine line 1
        Sequence2 : "Sequence";      // machine line 2
        Sequence3 : "Sequence";      // machine line 3
        ActiveIdx : INT;             // which sequence the FC should use
    END_STRUCT;
BEGIN
END_DATA_BLOCK

3.3 Declare the FC with an IN_OUT Parameter of the UDT

SCL
FUNCTION "FC2_SequenceStep" : VOID
VAR_INPUT
    TickMs : INT;                   // cycle tick
END_VAR
VAR_IN_OUT
    Seq    : "Sequence";            // typed reference into myDB.SequenceN
END_VAR
VAR_TEMP
    i : INT;
END_VAR
BEGIN
    // The Seq parameter is the live alias of myDB.SequenceN
    Seq.StepTime := Seq.StepTime + INT_TO_TIME(TickMs);

    IF Seq.StepTime >= T#5s THEN
        Seq.StepNumber := Seq.NextStep;
        Seq.StepTime := T#0s;
    END_IF;

    // Array scan uses typed bounds
    FOR i := 0 TO 15 DO
        Seq.Flags[i] := (Seq.Outputs.%X{i}) OR Seq.Flags[i];
    END_FOR;
END_FUNCTION
Why IN_OUT instead of INPUT? The data lives inside a global DB. IN (call-by-value) would copy the entire UDT onto the stack on every call, which is wasteful and loses the link to the source. IN_OUT passes a pointer to the original storage, so writes inside the FC are visible to the rest of the program immediately. See the Siemens guidance on passing parameters to blocks.

3.4 Call the FC with the Correct Instance

SCL
CASE "myDB".ActiveIdx OF
    1: "FC2_SequenceStep"(TickMs := 100, Seq := "myDB".Sequence1);
    2: "FC2_SequenceStep"(TickMs := 100, Seq := "myDB".Sequence2);
    3: "FC2_SequenceStep"(TickMs := 100, Seq := "myDB".Sequence3);
END_CASE;

The compiler validates the type match between myDB.Sequence1 and the Seq : "Sequence" interface. Mismatches are caught at compile time, not in the field.

3.5 Inline SCL View of the Generated Code

For diagnostic purposes you can monitor the UDT instance live. Online > Monitor/Modify gives a structured view; the slice syntax "myDB".Sequence1.StepTime is valid in watch tables, HMI tags, and OPC UA exposed symbols.

4. Solution B: DB_ANY + OPN / LAR1 Indirection

DB_ANY was introduced for S7-1200/1500 to replace the legacy WORD-based DB-pointer pattern (DBNO/DBLW). It is a 32-bit identifier that points to a data block without exposing its content. To use the data inside, you still need to load the start address into AR1 and address it through P# / % accessors, or wrap it with an AT overlay.

4.1 Declare the FC with a DB_ANY Input

SCL
FUNCTION "FC2_SequenceStep_Any" : VOID
VAR_INPUT
    TickMs : INT;
    pSeq   : DB_ANY;                // symbolic handle to a DB
END_VAR
VAR_TEMP
    pData AT pSeq : ARRAY[*] OF BYTE;  // SCL 0-byte AT overlay forbidden; use ST-equivalent
END_VAR
BEGIN
END_FUNCTION
Important SCL limitation. A DB_ANY parameter cannot be combined with an AT overlay in the FC's VAR_TEMP section — the compiler rejects it. The AT-overlay trick is permitted only inside FB static sections (multi-instance background). For FC usage you must drop down to register-level addressing.

4.2 The Working Pattern: OPN + LAR1 + Symbolic Field Access

The SCL compiler will accept OPN and LAR1 instructions when embedded inline. You can then dereference the area using pointer offsets. The cleanest form on S7-1500 is to call an FB inside which AT-overlaid static variables exist; the FC opens the DB, the FB addresses the overlay.

SCL
FUNCTION_BLOCK "FB_SequenceStep"
VAR
    // AT overlay lives here, not in VAR_TEMP
    StepNumber AT %MD10 : INT;
    StepTime   AT %MD14 : TIME;
    NextStep   AT %MD22 : INT;
    FaultCode  AT %MD26 : INT;
    Outputs    AT %MW30 : WORD;
END_VAR
BEGIN
    // Body uses StepNumber, StepTime, ... directly
    StepTime := StepTime + INT_TO_TIME(100);
END_FUNCTION_BLOCK
SCL
FUNCTION "FC2_SequenceStep_Caller" : VOID
VAR_INPUT
    TickMs : INT;
    pSeq   : DB_ANY;
END_VAR
VAR_TEMP
    dbNum  : DINT;
END_VAR
BEGIN
    // Convert the DB_ANY handle to a DB number
    dbNum := DB_ANY_TO_DINT(pSeq);
    IF dbNum = 0 THEN RETURN; END_IF;

    // Open the DB and load its start address into AR1
    OPN DB[dbNum];
    LAR1 ;

    // The FC must invoke an FB whose AT overlay now points into the opened DB
    "InstSequenceStep"(StepNumber := ??? );
END_FUNCTION

This pattern requires that the FB instance's data block (or the target DB) has identical layout. It is fragile and harder to maintain; it is also the only way to handle truly heterogeneous DBs (where each DB has a different structure that the FC must adapt to at runtime).

4.3 Converting DB_ANY to a Usable Pointer

Siemens exposes several conversion blocks in the standard library:

Block Purpose Notes
DB_ANY_TO_DINT DB number as DINT 0 means invalid/null
DB_ANY_TO_UINT DB number as UINT Preferred for OPN DB[n]
WORD_TO_BLOCK_DB Legacy DB pointer cast Compatibility only

These functions are documented in the TIA Portal help under Instructions > Basic instructions > Addressing > DB_ANY. Once you have the number, OPN DB[ UINT_VARIABLE ] opens the block, and LAR1 with no operand loads the start of the opened area into AR1.

4.4 Why the DB_ANY Compiler Error Appears

A frequent error during this exercise is:

Compiling SCL: "DB_ANY" cannot be combined with "AT" overlay in VAR_TEMP.

Root cause: AT overlays in VAR_TEMP need a fixed local stack address, but DB_ANY is resolved at runtime. The compiler cannot bind a static offset to a dynamic area. Move the AT overlay into an FB's static area, or drop the AT overlay and use direct pointer arithmetic with P#.

5. Side-by-Side Comparison

Criterion UDT + IN_OUT DB_ANY + OPN/LAR1
Type safety Compile-time check of every field access No check — runtime offsets are author responsibility
Performance One pointer pass; native field access Multiple instructions per field; pointer math per call
Memory copy None (IN_OUT passes pointer) None, but area-register swap costs cycles
HMI / OPC UA mapping Direct symbol access Indirect via DB index
Maintenance Add a field to UDT → propagates Renumber offsets manually
Refactoring risk Low — compiler enforces High — silent misreads
DB layout freedom All instances share one UDT shape Any DB layout, any size
Best fit Repeated, identical structures Truly dynamic, heterogeneous targets

6. Choosing the Right Pattern

  • Use UDT + IN_OUT when: all target DBs share the same record shape (steps, alarms, parameters). This covers ~95% of machine sequence code, recipe data, axis parameter sets, and report records.
  • Use DB_ANY when: the FC must read or write DBs whose layout is not known at compile time — for example, library FBs that operate on a customer DB without recompiling the library, or generic diagnostic utilities that walk arbitrary structures.
  • Hybrid pattern: put the UDT in a single DB with an array of instances, and let the FC index into the array. You keep UDT type safety and gain dynamic instance selection without DB_ANY.
SCL
DATA_BLOCK "myDB"
    STRUCT
        Sequences : ARRAY[1..16] OF "Sequence";
        ActiveIdx : INT;
    END_STRUCT;
BEGIN
END_DATA_BLOCK
SCL
// Indexed instance selection — still strongly typed
"FC2_SequenceStep"(TickMs := 100,
                   Seq    := "myDB".Sequences["myDB".ActiveIdx]);
Performance ceiling. The indexed form forces a runtime bound check on ActiveIdx. STEP 7 does not eliminate this even in optimized blocks. If ActiveIdx can be operator-set, add explicit validation before the call to avoid PLC stop on out-of-range index.

7. SCL Expression Rules That Affect Both Patterns

When calling an FC/FB inside SCL, the call is an expression statement. Parameter passing follows the rules in the SCL expressions and operations reference:

  • Simple data types (INT, DINT, REAL, BOOL, etc.) are passed call-by-value; the FC receives a copy on the stack.
  • Complex data types (STRUCT, ARRAY, STRING, UDT) are passed call-by-reference when the parameter is IN_OUT, by-pointer at the language level. The SCL keyword IN_OUT is the only way to receive a writable typed reference.
  • Tags of type DB_ANY cannot be combined with slice syntax (.field) directly. They must first be converted into a usable handle.

8. Common Pitfalls and Field-Tested Fixes

Symptom Likely Cause Fix
Compiler: "DB_ANY cannot be combined with AT" AT overlay declared in VAR_TEMP Move AT into FB static section, or remove AT and use P# pointer arithmetic
FC writes have no effect Parameter declared INPUT instead of IN_OUT Switch interface section to VAR_IN_OUT
Compiler: "Type mismatch" at FC call Passed symbol is not of the UDT Confirm caller passes myDB.Sequence1 and not myDB.Sequence1.StepNumber
PLC goes STOP with SF LED Index out of range on array parameter Bound-check before indexed access; use WITH guard or pre-validate
AR1 corrupted after FC returns FC opened DB but caller expected previous DB open Use OPN DI for instance DBs and OPN DB for globals; save/restore if multiple are interleaved
HMI cannot reach field Field accessed via DB_ANY indirection Expose a typed HMI-visible DB; avoid indirect symbols on HMI tags
Optimized block access warning Symbolic access disabled on DB Enable "Optimized block access" on DB properties

9. Verification Procedure

After implementing either pattern, run through this checklist before commissioning:

  1. Compile clean. Project > Compile > Software (rebuild all). No warnings on DB/FC access.
  2. Download to PLC. Use "Download to device" with "consistent download" enabled for the affected blocks.
  3. Monitor online. Right-click the FC in the program tree > Monitor/Modify. Confirm Seq.StepNumber (or the DB_ANY-routed equivalent) shows the expected instance.
  4. Force a value change. In a watch table, write a new StepNumber into myDB.Sequence1 and confirm the FC reflects it on the next scan.
  5. Swap the instance. Change ActiveIdx from 1 to 2; confirm the FC now operates on myDB.Sequence2 without recompile.
  6. HMI smoke test. Display myDB.Sequence1.StepTime on an HMI screen; toggle the active instance and confirm the value stream changes.
  7. Cycle-time budget. Open the online & diagnostics > Cycle time view. Confirm the FC adds no more than the documented per-call cost.
  8. Stop-recovery. Pull the SD card (S7-1500) or trigger a STOP-RUN cycle; confirm the DB data survives and the FC re-initializes correctly.

10. Extended Example: Parameterized Recipe Bank

A practical use of the UDT pattern is a recipe bank where each recipe has identical structure but different values:

SCL
// PLC data type: Recipe
TYPE "Recipe"
    STRUCT
        Name        : STRING[32];
        Setpoint    : REAL;
        Tolerance   : REAL;
        RampTime    : TIME;
        HoldTime    : TIME;
        Enabled     : BOOL;
    END_STRUCT;
END_TYPE
SCL
DATA_BLOCK "Recipes"
    STRUCT
        Items : ARRAY[1..50] OF "Recipe";
        Count : INT;
    END_STRUCT;
BEGIN
END_DATA_BLOCK
SCL
FUNCTION "FC_ApplyRecipe" : VOID
VAR_IN_OUT
    R : "Recipe";
END_VAR
VAR_TEMP
    i : INT;
END_VAR
BEGIN
    IF NOT R.Enabled THEN RETURN; END_IF;
    // Apply setpoint with ramp
    // (assume an analog-output FC elsewhere)
    "FC_SetAnalog"(Setpoint := R.Setpoint, Ramp := R.RampTime);
    "FC_StartTimer"(Duration := R.HoldTime);
END_FUNCTION
SCL
// OB1 cycle
FOR i := 1 TO "Recipes".Count DO
    "FC_ApplyRecipe"(R := "Recipes".Items[i]);
END_FOR;

The same FC handles every recipe without recompilation; the array index decides which instance is active.

11. Migration Notes from Legacy STL Patterns

Code carried over from STEP 7 Classic often uses OPN DB[MW10], LAR1 P#DBX 0.0, and indirect field reads like L DBW [AR1,P#2.0]. Modernizing this to TIA Portal SCL:

  1. Replace OPN DB[MW10] with a typed DB_ANY input and an internal OPN DB[UINT_VARIABLE].
  2. Replace LAR1 P#DBX 0.0 with a P# pointer constructed from the opened area, or eliminate entirely by using an AT overlay inside an FB.
  3. Replace L DBW [AR1,P#2.0] with SCL slice notation: myField.%W0 or a UDT-typed reference.
Backwards compatibility. STL statements OPN, LAR1, TAR1, LAR2 still compile inside SCL source files when marked as inline statements — but mixing pointer manipulation with high-level SCL control flow is the most common source of subtle bugs. Prefer the UDT/IN_OUT form unless you have a measured need for DB_ANY.

12. FAQ

Can I pass a DB name as an FC parameter directly in SCL?

No. A DB symbol is resolved to an absolute address at compile time and cannot be bound to a parameter slot. Use a UDT-typed IN_OUT parameter (preferred) or a DB_ANY input combined with OPN/LAR1 indirection to achieve a runtime-retargetable FC.

Why does DB_ANY combined with AT in VAR_TEMP fail to compile?

An AT overlay in VAR_TEMP requires a fixed local stack offset, but DB_ANY resolves to the target DB only at runtime. The SCL compiler cannot bind a static offset to a dynamic area. Move the AT overlay into an FB's static section, or remove the AT and address the area with P# pointer arithmetic.

Is IN_OUT or INPUT correct for passing a structured parameter to an FC?

Use VAR_IN_OUT for complex types (STRUCT, ARRAY, UDT, STRING). Simple data types can use VAR_INPUT, but for UDTs you want IN_OUT because it passes a pointer to the original storage rather than copying the entire structure onto the stack, and it allows the FC to write back to the caller's variable.

When is DB_ANY truly required instead of a UDT?

Use DB_ANY when the FC must operate on a DB whose layout is unknown at compile time — for example, a library block that processes customer-specific DBs without recompilation, or a generic diagnostic tool that walks heterogeneous structures. For any pattern where all targets share one shape, the UDT/IN_OUT form is simpler, faster, and type-safe.

How do I convert a DB_ANY handle into an open DB?

Use the standard conversion block (DB_ANY_TO_DINT or DB_ANY_TO_UINT) to obtain the DB number, then issue OPN DB[ UINT_VARIABLE ] in the same SCL source file. The compiler accepts OPN as an inline instruction; pair it with LAR1 ; to load the area start address into AR1 for indirect access, or call an FB whose AT overlay already points into the opened DB.

Does indexed access on a UDT array stay type-safe?

Yes — the compiler still resolves Recipes.Items[i].Setpoint against the UDT definition. The variable i is runtime, but each field access is fully checked. Add explicit bounds validation on i before the access to prevent PLC stop on out-of-range indices.

Back to blog