Siemens TIA BLKMOV SFC20: Moving Date_And_Time to INOUT FB

David Krause9 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

Problem Overview

Copying a Date_And_Time (DTL/DT) variable from the STAT (static instance) area of a Siemens S7 Function Block into an INOUT parameter that is also typed Date_And_Time — particularly when the variables are members of a UDT (user-defined data type) and the INOUT sits inside a STRUCT inside an ARRAY — produces a syntax error in TIA Portal / STEP 7. The compiler refuses the call because INOUT parameters are passed by reference (pointer to a pointer), and BLKMOV / SFC20 requires a fully resolvable ANY source or destination.

This article documents the field-proven workaround: declaring a VARIANT or ANY in the INOUT interface, then staging the Date_And_Time through a typed VARIANT / ANY in TEMP before invoking BLKMOV.

Prerequisites

  • Siemens SIMATIC S7-300, S7-400, S7-1200, or S7-1500 CPU with firmware that supports SFC20 (BLKMOV) or the SCL MOVE_BLK instruction.
  • STEP 7 V5.5 / V5.6 or TIA Portal V13 through V18 (V19/V20 supported) with a valid license for the S7 function blocks you call.
  • A project in which the Date_And_Time (DT) or DTL member sits inside a UDT used as a STRUCT component of an ARRAY.
  • Read access to the Siemens Industry Online Support manual SIMATIC S7 Function Blocks for SFC20 specification and ANY pointer format.
Data-type note. On S7-300/400 the legacy 8-byte BCD DATE_AND_TIME is used. On S7-1200/1500 the 12-byte DTL structure is the modern equivalent. The 8-byte BCD length is critical: a BLKMOV with a wrong source length silently corrupts adjacent memory.

Why Direct BLKMOV Fails Against INOUT

SFC20 BLKMOV expects an ANY source and an ANY destination. The ANY pointer is a 10-byte structure (S7-300/400) or a 16-byte structure (S7-1200/1500 in classic ANY format) that fully describes the source area: byte length, repetition factor, DB number, area pointer, byte offset, and bit offset. When a parameter is declared VAR_IN_OUT in the FB interface, TIA Portal maps it internally to a typed pointer. The compiler will not auto-generate a flat ANY over the data behind the INOUT, so passing an INOUT directly to BLKMOV is rejected.

The same restriction applies to members of a UDT that are themselves nested inside an ARRAY OF STRUCT: the indexed element address MyUDT.Array[i].TimeStamp is a symbolic fully qualified name, not a literal ANY the compiler can bind. Attempting BLKMOV(SRCBLK := MyFB.Inst.MyUDT.Array[i].TimeStamp, DSTBLK => MyFB.InOut.TimeStamp) yields a syntax error "Formal parameter in-out does not allow expression".

ANY Pointer Structure (S7-300/400 — 10 Bytes)

Byte Content Meaning
0 0x10 SYNTAX ID — 0x10 = ANY (simple)
1 0xNN Data type code (0x07 = BYTE, 0x09 = WORD, 0x0B = DWORD, 0x0F = DATE_AND_TIME)
2,3 0x0008 Length in bytes — 0x0008 for DATE_AND_TIME
4,5 0x00DB DB number (e.g., 0x00DB = 219). 0x0000 = global / unknown.
6 0x84 / 0x81 Area code: 0x84 = DB, 0x81 = M, 0x82 = E (I), 0x83 = A (Q), 0x80 = P
7,8,9 Pointer Byte offset (24-bit, little-endian) of the start of the source/destination

On S7-1200/1500 the ANY format adds two extra bytes for the subarea type, but the staging pattern in SCL is identical: the compiler builds the ANY for you whenever you assign a typed tag of known length to an ANY-typed temporary.

Step-by-Step Solution in SCL

  1. Declare the INOUT as VARIANT or ANY. Change the interface declaration from DAT : Date_And_Time to DAT : Variant (S7-1500) or DAT : Any (S7-300/400). This makes the INOUT a generic pointer the compiler can pass to BLKMOV.
  2. Provide a typed local mirror in VAR. Declare DATStat : Date_And_Time so the source has a known, absolute area pointer. Initialize it (e.g., DT#2014-05-01-00:00:00) to avoid undefined memory.
  3. Stage a typed ANY in VAR_TEMP. Declare DATPtr : Any. The compiler will populate it when you assign the source tag.
  4. Assign source to staging ANY, then call BLKMOV. DATPtr := DATStat writes the absolute pointer into the staging variable; BLKMOV(SRCBLK := DATPtr, DSTBLK => DAT) then copies 8 bytes into the INOUT.
  5. Evaluate RET_VAL. BLKMOV returns 0 on success, 8091 (no DB) or 80B1 (length zero) on failure. Capture it in a VAR_TEMP Retval : Int.

Working SCL Code (STEP 7 / TIA V13+)

FUNCTION_BLOCK "FB_Test"
{ S7_Optimized_Access := 'FALSE' }
VAR_IN_OUT
    "DAT" : Any;
END_VAR
VAR
    "DATStat" : Date_And_Time := DT#2014-05-01-00:00:00;
END_VAR
VAR_TEMP
    "DATPtr" : Any;
    "Retval" : Int;
END_VAR
BEGIN
    #DATPtr := #DATStat;                // stage: build flat ANY from typed source
    #Retval := BLKMOV(SRCBLK := #DATPtr, DSTBLK => #DAT);
END_FUNCTION_BLOCK

Call the FB and wire the INOUT to a fully qualified address such as "MyDB"."MyStruct"."TimeStamp or a literal P#DB24.DBX0.0 BYTE 8 in the call. The 8 bytes land at byte 0..7 of the destination DB.

Step-by-Step Solution in STL (LAD/FBD)

If you must stay in LAD/FBD without an SCL block, the same principle applies — you build the ANY by hand using P# pointer literals:

// Network 1 — source ANY in TEMP
// DATStat is a Date_And_Time in STAT. Its absolute pointer is known to the compiler.
// In STL you can pass the symbolic source directly to BLKMOV if the source is
// fully qualified, but the destination MUST be ANY. Wire the INOUT as
// P#<DB>.DBX<offset>.BYTE 8 in the call.

CALL BLKMOV
    SRCBLK   := P#DB25.DBX0.0 BYTE 8     // any 8-byte source area
    RET_VAL  := MW100                    // 0 = OK, 80B1 = length error
    DSTBLK   := P#DB26.DBX0.0 BYTE 8;    // INOUT wired here

For the nested ARRAY OF STRUCT case, pre-compute the offset Offset = i * sizeof(UDT_element) + offsetof(UDT_element, TimeStamp) and pass the result as P#DBxx.DBXoffset.0 BYTE 8 to BLKMOV. The two-stage ANY pattern only saves code; the runtime result is identical.

Block Diagram — State Machine of the Copy

STAT: Date_And_Time TEMP: Any (staging) DATPtr := DATStat INOUT: Date_And_Time assign BLKMOV RET_VAL = 0 ? 0 = OK, 80B1 = length, 8091 = no DB On RET_VAL ≠ 0: raise diagnostic alarm (SFCT 39), skip consumer logic

Alternative Approaches

Method Controller Pros Cons
Direct := assignment of two typed Date_And_Time tags S7-1200/1500 No ANY, no SFC, fully symbolic Not available on SFC20-only path; INOUT must stay typed
SCL MOVE_BLK (count = 1) S7-1500 Optimized, no SFC, type-checked Same INOUT type issue as SFC20
PEEK / POKE (S7-1500) on byte offset S7-1500 Indexable, no SFC Manual byte-level math, no safety
Two-stage Any in VAR_TEMP + BLKMOV All S7 Universal, works for any length Requires ANY or VARIANT INOUT
Wrap in UDT + IEC SCAN / SLICE S7-1500 Type-safe, slice-aware Slice of DTL/DT not directly assignable in pre-V16

S7-1500 Variant (DTL 12 Bytes)

On S7-1500 the 12-byte DTL structure (years, months, days, hours, minutes, seconds, nanoseconds — each as UINT or DWORD) is copied by the SCL compiler with a single := if both tags are statically typed DTL. The ANY workaround is only required when the destination is a generic pointer (INOUT declared VARIANT or ANY) or the source is buried in a UDT inside an array and the compiler cannot build a flat ANY. Use RET_VAL := BLKMOV(SRCBLK := DATPtr, DSTBLK => DAT); with DATPtr : Variant and pass DAT as the INOUT.

Troubleshooting Matrix

Symptom Likely Cause Fix
Compiler: "Formal parameter in-out does not allow expression" INOUT declared typed and passed into BLKMOV Declare INOUT as Variant or Any
Compiler: "Type conflict in parameter SRCBLK" Source length and INOUT length differ (8 vs 12) Match bytes — 8 for DT, 12 for DTL
RET_VAL = 80B1 Length field in source ANY is zero Ensure the source tag is initialised, not symbolic empty
RET_VAL = 8091 Destination ANY references a non-existent DB Verify DB number, download DB, check optimized access flags
RET_VAL = 80B0 / 80B2 DB not loaded / area not available Check CPU STOP, DB integrity in online view
Date values look offset (e.g., year + 2000) Symbolic tag has 16-byte buffer, BLKMOV only wrote 8 Hardcode length to 8 for DT, 12 for DTL
Online watch shows constant 1970-01-01 Source INOUT not initialised in caller Pre-assign the caller's INOUT before FB run

Edge Cases and Field Notes

  • Optimized access. With S7-1200/1500 "optimized block access" the symbolic offset is hidden; SCL still generates a correct ANY because the compiler resolves the address internally. STL/FBD require symbolic-any (s7-1500) or the old P# notation.
  • Multi-instance depth. When the FB itself sits inside another FB as a multi-instance, add the full multi-instance prefix: CallerFB.InnerFB.DATPtr := CallerFB.InnerFB.DATStat. TIA Portal will resolve the offset.
  • Length of zero. Declaring DAT : Any without a referenced variable leaves the length field 0; the first BLKMOV returns 80B1 and writes nothing. Always wire the INOUT in the call site.
  • IEC timer / counter overlap. Avoid placing a Date_And_Time tag in the same DB byte range as a TP / TON instance — both have 8-byte initialisation patterns that can mask each other during cold restart.
  • Cross-DB copy. BLKMOV is the only IEC-standard way to copy across DB boundaries in S7-300/400 without manual load/store. On S7-1500 the SCL := does the same with type checking.

Verification Procedure

  1. Compile and download the FB to the target CPU. Resolve any SCL compile warnings about implicit conversions.
  2. Online → Monitor & Force → set a breakpoint at #Retval := BLKMOV(...).
  3. Force the caller's INOUT to a known address (e.g., DB24.DBX0.0 BYTE 8) and DATStat to DT#2024-11-15-14:30:00.
  4. Run the FB in single step. Confirm Retval = 0 and the destination bytes decode back to 2024-11-15 14:30:00.
  5. Force DATStat := DT#0001-01-01-00:00:00 to test the BCD encoding edge case (year 1 is valid, year 0 is not).
  6. Force a non-existent DB on the INOUT; verify Retval = 8091 and a diagnostic buffer entry is created.
BCD caveat. Date_And_Time on S7-300/400 stores years 1990–2089 cleanly. Years outside that window require masking the high nibble of the year byte before BLKMOV; the standard BLKMOV does not normalize the BCD range. Pre-validate the source value with a comparison against DT#1990-01-01-00:00:00 and DT#2089-12-31-23:59:59.

FAQ

Why does BLKMOV reject a typed Date_And_Time INOUT?

BLKMOV (SFC20) accepts only ANY parameters. A typed INOUT is internally a pointer-to-pointer, not a flat ANY. Declare the INOUT as Variant or Any and pass it directly, or stage a typed source through a VAR_TEMP of type ANY.

How many bytes does a Date_And_Time tag occupy in S7-300/400?

8 bytes. Year and month are BCD-encoded in the high nibble of the first two bytes. On S7-1500, the DTL structure is 12 bytes (year, month, day, weekday, hour, minute, second, nanoseconds).

Can I use the same pattern for ARRAY OF UDT elements?

Yes. Pre-compute the byte offset as i * sizeof(UDT) + offsetof(member, TimeStamp) and pass the result as P#DBxx.DBXoffset.0 BYTE 8 to BLKMOV, or use the SCL staging pattern with an indexed symbolic source.

What does RET_VAL 80B1 mean?

Length field in the ANY pointer is zero. Ensure the source tag is fully declared, has a non-zero initial value, and the staging variable is not in optimized-access-only memory without a length entry.

Does this work on S7-1200/1500 with optimized access?

Yes. TIA Portal generates a correct flat ANY for typed SCL tags regardless of optimized access. For pure STL/FBD code you must use P# pointer literals because the symbolic offset is hidden.

Is there a fully symbolic alternative without ANY?

On S7-1500 you can assign two DTL tags directly with := if both are statically typed. The ANY workaround is only required when the destination INOUT is declared Variant or Any, or when calling the legacy SFC20 path.

Back to blog