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 SCLMOVE_BLKinstruction. - 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) orDTLmember sits inside a UDT used as aSTRUCTcomponent of anARRAY. - Read access to the Siemens Industry Online Support manual SIMATIC S7 Function Blocks for
SFC20specification andANYpointer format.
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
-
Declare the INOUT as
VARIANTorANY. Change the interface declaration fromDAT : Date_And_TimetoDAT : Variant(S7-1500) orDAT : Any(S7-300/400). This makes the INOUT a generic pointer the compiler can pass toBLKMOV. -
Provide a typed local mirror in
VAR. DeclareDATStat : Date_And_Timeso the source has a known, absolute area pointer. Initialize it (e.g.,DT#2014-05-01-00:00:00) to avoid undefined memory. -
Stage a typed
ANYinVAR_TEMP. DeclareDATPtr : Any. The compiler will populate it when you assign the source tag. -
Assign source to staging ANY, then call BLKMOV.
DATPtr := DATStatwrites the absolute pointer into the staging variable;BLKMOV(SRCBLK := DATPtr, DSTBLK => DAT)then copies 8 bytes into the INOUT. -
Evaluate
RET_VAL.BLKMOVreturns 0 on success, 8091 (no DB) or 80B1 (length zero) on failure. Capture it in aVAR_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
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 : Anywithout a referenced variable leaves the length field 0; the firstBLKMOVreturns 80B1 and writes nothing. Always wire the INOUT in the call site. -
IEC timer / counter overlap. Avoid placing a
Date_And_Timetag 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
- Compile and download the FB to the target CPU. Resolve any SCL compile warnings about implicit conversions.
- Online → Monitor & Force → set a breakpoint at
#Retval := BLKMOV(...). - Force the caller's INOUT to a known address (e.g.,
DB24.DBX0.0 BYTE 8) andDATStattoDT#2024-11-15-14:30:00. - Run the FB in single step. Confirm
Retval = 0and the destination bytes decode back to2024-11-15 14:30:00. - Force
DATStat := DT#0001-01-01-00:00:00to test the BCD encoding edge case (year 1 is valid, year 0 is not). - Force a non-existent DB on the INOUT; verify
Retval = 8091and a diagnostic buffer entry is created.
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.