Overview
Siemens STEP 7 and TIA Portal expose two parameter types that are essential when writing generic, reusable function blocks: BLOCK_DB and ANY. They look similar on the surface but serve very different purposes, and the interaction between them is the single most common stumbling block for engineers writing library FBs that need to operate on data residing in caller-selected data blocks.
A BLOCK_DB parameter is essentially a typed handle to a data block — it carries the DB number and a reference into the block descriptor. It can only point to a DB; it cannot be used as a memory address for byte-level read/write operations. An ANY parameter is a 10-byte area pointer describing a typed memory region (data type code, length, DB number, byte/bit offset). It is the only pointer type accepted by SFC20 BLKMOV, SFC21 FILL, SFC75 SET, and a long list of standard library FBs that perform bulk operations.
The real-world problem appears whenever a function block must accept a destination DB that is only known at call time, and then route data into that DB through an ANY-based system function. The classic editor does not allow you to pass a BLOCK_DB parameter where an ANY is expected, and it will not automatically synthesise the ANY structure for you. The solution is to construct the ANY pointer in a temporary variable at runtime and pass that constructed ANY into the downstream call.
BLOCK_FB, BLOCK_FC, BLOCK_DB, and BLOCK_SDB. Each accepts a block address as the actual parameter (e.g., DB3 for BLOCK_DB, FB16 for BLOCK_FB). They are not memory addresses and cannot be dereferenced like POINTER or ANY values.How the Editor Constructs ANY Pointers Behind the Scenes
When you assign a symbolic variable (static, temp, or hard-coded address) to an ANY input of an FC, the STEP 7 / TIA editor silently emits code that builds the 10-byte ANY structure in the local stack. The 10 bytes are laid out as follows:
| Offset | Width | Field | Notes |
|---|---|---|---|
| +0 | WORD | Syntax ID / Data type code | 0x10 = BYTE, 0x1002 = BYTE array, 0x1005 = INT, 0x1008 = REAL, 0x100B = BOOL, 0x100C = WORD, 0x100D = DWORD |
| +2 | WORD | Number of elements (length) | Bytes for byte-type, count for non-byte types |
| +4 | WORD | DB number | 0 for non-DB areas |
| +6 | DWORD | Area pointer (byte.bit) | Bit 24..31 = area ID; 0x81 = DB, 0x82 = DI (instance DB), 0x83 = local, 0x84 = M, 0x85 = I, 0x86 = Q, 0x87 = L |
Four canonical examples illustrate the emitted code paths. Each is followed by the editor-generated CALL expansion in STL/MC7 so the runtime construction is visible.
Example 1 — ANY from FB static variable
The source call is:
CALL FC 2
pAny := #StaticVarReal;
NOP 0;
Behind the scenes, the editor writes the ANY to LW 21 and passes P#L 21.0 to the FC. The instance DB number is taken from DINO, and the static offset is added to AR2 to form the absolute area pointer.
CALL
BLD 1
= L 20.0
TAR2 LD 16 //save AR2 in local data
L W#16#1008 //type 8 = REAL
T LW 21
L 1 //count = 1
T LW 23
L DINO //current instance DB
T LW 25
+AR2 P#2.0 //add offset of StaticVarReal
TAR2 //absolute area pointer
LAR2 LD 16 //restore AR2
T LD 27 //ptr address into ANY area
UC FC 2
P#L 21.0 //any pointer at L21.0
LAR2 LD 16
BLD 2
END_CALL
NOP 0;
Example 2 — ANY from FB temp variable
Same skeleton, but DB number = 0 because temp data lives in the L stack.
CALL
BLD 1
= L 20.0
TAR2 LD 16
L W#16#1008
T LW 21
L 1
T LW 23
L 0 //DB number = 0 for local
T LW 25
L P#L 2.0 //area pointer to TempVarReal
T LD 27
UC FC 2
P#L 21.0
LAR2 LD 16
BLD 2
END_CALL
NOP 0;
Example 3 — ANY from hard-coded DB address
CALL FC 2
pAny := P#DB99.DBX 0.0 INT 33;
NOP 0;
The editor emits a length of 33 (INT count) and DB number 99 with area ID 0x81.
CALL
BLD 1
= L 20.0
TAR2 LD 16
L W#16#1005 //type 5 = INT
T LW 21
L 33 //count = 33
T LW 23
L 99 //DB99
T LW 25
L P#DBX 0.0 //area pointer
T LD 27
UC FC 2
P#L 21.0
LAR2 LD 16
BLD 2
END_CALL
NOP 0;
Example 4 — ANY from a temp variable of type ANY
This is the critical case for our use. When the actual parameter is a temp ANY variable, the editor emits no construction code. It assumes you have already written the 10 bytes into that temp location before the call.
CALL
BLD 1
= L 20.0
TAR2 LD 16
UC FC 2
P#L 6.0 //editor trusts you filled L6.0..L15.7
LAR2 LD 16
BLD 2
END_CALL
NOP 0;
This is the door the runtime construction pattern walks through: declare a temp ANY, fill it yourself with LAR1 P##var and a few loads/stores, and pass it as the actual to the downstream block call.
Constructing an ANY Pointer at Runtime from a BLOCK_DB
The pattern requires five ingredients:
- A
BLOCK_DBinput parameter carrying the destination block from the caller. - An
INTinput for the byte count (or element count, depending on the chosen syntax ID). - A
TEMPvariable of typeANY(this is the variable the editor will pass through unchanged). - An
AR1setup pointing at the start of the temp ANY. - Open the destination DB with
OPN #DestDBbefore the SFC/FB call so the runtime resolves the area correctly.
The reference FB from the source — used by the engineer to test SFC21 FILL through a runtime-built ANY — is reproduced here with the field-tested comments restored.
FUNCTION_BLOCK FB 2
TITLE =
VERSION : 0.1
VAR_INPUT
DestDB : BLOCK_DB;
iNumberOfBytes : INT;
END_VAR
VAR
StaticData : BOOL;
END_VAR
VAR_TEMP
TempData : BYTE;
RunTimeCreatedAny : ANY;
END_VAR
BEGIN
NETWORK
TITLE = Create a runtime ANY pointer
LAR1 P##RunTimeCreatedAny; //AR1 -> ANY structure
L W#16#1002; //syntax ID: BYTE (array of bytes)
T W [AR1,P#0.0];
L #iNumberOfBytes; //length in bytes
T W [AR1,P#2.0];
OPN #DestDB; //open the destination DB
L DBNO; //read active DB number
T W [AR1,P#4.0]; //store as DB number field
L P#DBX 0.0; //area pointer, byte 0 bit 0
T D [AR1,P#6.0];
NETWORK
TITLE = Example - SFC21 FILL via the constructed ANY
L 99;
T #TempData;
CALL "FILL" (
BVAL := #TempData,
RET_VAL := MW 0,
BLK := #RunTimeCreatedAny);
END_FUNCTION_BLOCK
Key behavioural points:
-
P##RunTimeCreatedAnyresolves at runtime to the absolute local-stack address of the temp ANY, including the0x83area identifier for L-stack data. The compiler will not let you use this on a non-temp variable. -
OPN #DestDBopens the destination DB so subsequentL DBNOreads the correct number. Without this, the ANY will still hold DB 0 (or whatever was previously open) and SFC21 will returnW#16#8091. -
P#DBX 0.0places the area ID0x81in the high byte of the area pointer DWORD, which is what SFC20/SFC21 expect for a DB area. - You may freely change the syntax ID. Use
W#16#1002for byte arrays,W#16#1005for INT,W#16#1008for REAL,W#16#100Bfor BOOL, etc. The length field then becomes element count, not byte count, except for type 0x10 (BYTE) and 0x1002 where it is byte count.
Step-by-Step Implementation of CopyByte
The original question asks for an FB that copies a configurable number of bytes from an ANY source to a destination whose DB number is only available as a BLOCK_DB. The implementation below uses SFC20 BLKMOV rather than the byte-by-byte loop, which is the practical way to copy more than a few bytes at a time.
FUNCTION_BLOCK FB 100
TITLE = 'CopyByte - generic copy via runtime ANY'
VERSION : 1.0
VAR_INPUT
Source : ANY; //caller supplies source ANY
DestDB : BLOCK_DB; //caller supplies destination DB
NumberOfBytes : INT; //byte count, 1..N
Error : INT; //SFC20 RET_VAL echoed here
END_VAR
VAR_TEMP
SourceAnyCopy : ANY; //mirror of input for static usage
DestAny : ANY; //constructed destination ANY
END_VAR
BEGIN
NETWORK 1
TITLE = Mirror source ANY into temp (input ANYs are read-only on first scan in some FBs)
LAR1 P##SourceAnyCopy;
L P##Source;
LAR2 ;
L W [AR2,P#0.0]; T W [AR1,P#0.0];
L W [AR2,P#2.0]; T W [AR1,P#2.0];
L W [AR2,P#4.0]; T W [AR1,P#4.0];
L D [AR2,P#6.0]; T D [AR1,P#6.0];
NETWORK 2
TITLE = Build destination ANY from DestDB input
LAR1 P##DestAny;
L W#16#1002; //BYTE
T W [AR1,P#0.0];
L #NumberOfBytes;
T W [AR1,P#2.0];
OPN #DestDB;
L DBNO;
T W [AR1,P#4.0];
L P#DBX 0.0;
T D [AR1,P#6.0];
NETWORK 3
TITLE = Execute SFC20 BLKMOV
CALL "BLKMOV" (
SRCBLK := #SourceAnyCopy,
RET_VAL := #Error,
DSTBLK := #DestAny);
END_FUNCTION_BLOCK
Calling the FB looks like this in a higher-level OB or FB:
CALL FB 100, DB 200
Source := P#DB10.DBX 0.0 BYTE 16,
DestDB := DB 60,
NumberOfBytes := 16,
Error := MW 100;
Sixteen bytes are copied from DB10 into DB60. Replace DB60 with DB61, DB62, or DB63 depending on the Cognex station that triggered the call.
Routing Data into the Correct Station DB (Cognex Case Study)
The follow-up question describes fetching supplementary data from a Cognex camera on one of four stations, and storing the result in DB60, DB61, DB62, or DB63 depending on which station the camera is mounted on. The pattern is a textbook runtime-ANY application.
- Determine the destination station with a small piece of logic that maps a station number (1..4) to a DB number (60..63). Avoid
B#16#0for the DB number field at any time —SFC20rejects DB 0 withW#16#8090. - Pass the computed DB number into the
DestDBinput of the CopyByte FB as aBLOCK_DBactual parameter, e.g.,DestDB := DB [StationNumber + 59]. The square-bracket indexed access is supported on BLOCK_DB parameters in SCL and STL. - Re-open the destination DB inside the FB with
OPN #DestDBbefore readingDBNO. This is required even if the caller opened it; the FB cannot assume the DI/DB registers survived the call. - Set the ANY syntax ID and length to match the data being moved. For a 32-byte Cognex status record the values are
W#16#1002and32; for a structure of five REALs useW#16#1008and5. - Evaluate the
Erroroutput of the FB and propagate to the caller's error word. The SFC20RET_VALvalues most often seen in this scenario are listed in the troubleshooting matrix below.
DB [n] in STL or DB_NUMBER in SCL resolves to a BLOCK_DB value at runtime and is the only way to forward a variable DB number into a BLOCK_DB formal parameter in classic STEP 7. In TIA Portal V15 and later, you can also use "DB_name" with array-of-DB techniques, but the runtime OPN + DBNO pattern still applies.Restrictions on Passing BLOCK_DB Parameters
Siemens KB article 8686787 — "Passing on parameters of the BLOCK_DB type" — is the authoritative reference for the rules and edge cases. The rules that affect the CopyByte pattern are:
| Call topology | BLOCK_DB pass-through | Allowed? |
|---|---|---|
| OB → FB1 → FC (BLOCK_DB formal → actual) | Direct | Yes |
| FB1 → FB2 (both have BLOCK_DB formal) | Direct pass-through | Yes |
| FB1 multi-instance → nested FB (different instance) | Direct | Yes (since S7-400 and S7-300 v3.x) |
| FC1 → FB2 (FC has BLOCK_DB formal) | Direct | Yes (FC formally) |
| Any block → SFC/SFB with BLOCK_DB formal | Direct | Generally yes, but check the SFC signature |
Indirect DB access using DB [n]
|
Passes runtime value to ANY construction | Yes (recommended pattern) |
The classic failure is the S7-300 v2.x restriction: passing a BLOCK_DB parameter into another block's BLOCK_DB formal was not supported on S7-300 CPUs below firmware V3.0. The pattern is fully supported on every S7-300/400, S7-1500, and WinAC controller that STEP 7 V5.x and TIA Portal V13+ target. On ET200S IM151 CPUs, consult the CPU-specific manual because the smallest variants sometimes block the OPN DI variant.
The TIA V14 BLOCK_DB data type thread is also worth scanning for a real-world failure mode: the OPN instruction on an in-out BLOCK_DB formal sometimes compiles cleanly in TIA V14 but generates the wrong AR/DB combination when the calling FB is a multi-instance, because AR2 is already pointing at the parent instance. Always save and restore AR2 around the OPN if the calling context is a multi-instance, and prefer LAR1 P##DestAny over the implicit-pointer path.
Syntax ID and Area ID Reference
Pick the syntax ID and area ID that match the SFC/FB you are calling. Mismatches yield W#16#8092 (length error) or W#16#8094 (alignment error) from SFC20.
| Data type | Syntax ID | Length field means |
|---|---|---|
| BOOL | W#16#0001 (bit) or W#16#1001 (array) | bit count or element count |
| BYTE | W#16#0002 (bit) or W#16#1002 (array) | byte count |
| CHAR | W#16#1003 | element count (1 char = 1 byte) |
| WORD | W#16#1004 | element count (1 word = 2 bytes) |
| INT | W#16#1005 | element count |
| DWORD | W#16#1006 | element count |
| DINT | W#16#1007 | element count |
| REAL | W#16#1008 | element count |
| TIME / DATE / TOD / S5TIME | W#16#1009 .. W#16#100F | element count |
Area ID byte in the high byte of the area pointer DWORD:
| Area | Area ID | Use with |
|---|---|---|
| DB | 0x81 | P#DBX x.y |
| DI (instance DB) | 0x82 | Multi-instance context |
| L (local / temp) | 0x83 | Temp variables of containing block |
| M (merker) | 0x84 | Bit memories |
| I (process input image) | 0x85 | Inputs |
| Q (process output image) | 0x86 | Outputs |
Verification and Commissioning
-
Online ANY inspection. Open the FB in online mode, place the cursor on the temp
RunTimeCreatedAny, and use Monitor/Modify on the L stack. The 10 bytes must read1002 0010 003C 81 00 00 00for a 16-byte copy into DB60 starting at byte 0. (Hex:1002= type byte-array,0010= 16 bytes,003C= DB60,81 00 00 00= area DB, offset 0.0.) -
SFC20 RET_VAL check. Force the destination DB to be smaller than
NumberOfBytes. The expectedW#16#8091confirms the runtime construction placed the correct DB number in the ANY. -
Step-by-step cycle. Set a breakpoint after
OPN #DestDBand verifyDBNOmatches the caller-supplied DB. InconsistentDBNOindicates a missingOPNor an instance-DB confusion in a multi-instance context. -
Length cross-check. Add an Error = 0 branch that sets a flag if
NumberOfBytes > 8192(the SFC20 length cap for non-byte types) or ifNumberOfBytes < 1. The SFC will accept length 0 with no error but the copy will be a no-op — easy to mistake for a logic bug. -
Station routing test. Cycle through stations 1..4 and confirm the destination DB increments
DBNObetween calls. Use VAT or HMI tag to readDBNOdirectly after theOPN.
Troubleshooting Matrix
| Symptom | Probable cause | SFC20 RET_VAL | Fix |
|---|---|---|---|
| RET_VAL = W#16#8090 | ANY references DB0 or area mismatch | W#16#8090 | Ensure OPN #DestDB ran before L DBNO; the high byte of area pointer must be 0x81 for DB area. |
| RET_VAL = W#16#8091 | Length exceeds destination DB | W#16#8091 | Cap NumberOfBytes at the destination DB length; use DB_LENGTH intrinsic if available in TIA V16+. |
| RET_VAL = W#16#8092 | Source and destination areas overlap illegally | W#16#8092 | Use two non-overlapping ranges; for in-place ops, split the move. |
| RET_VAL = W#16#8093 | Alignment error (e.g., WORD at odd byte offset) | W#16#8093 | Use syntax ID 0x1002 (BYTE) for byte-granular moves; switch to 0x1004 (WORD) only on word-aligned offsets. |
| RET_VAL = W#16#80B1 | Source DB does not exist | W#16#80B1 | Confirm the source ANY carries the correct DB number; often caused by reading a stale DI in a multi-instance FB. |
| Data appears scrambled in destination | Wrong syntax ID (e.g., REAL declared as INT) | n/a | Match syntax ID to the actual data layout: 0x1008 for REAL, 0x1005 for INT, 0x1002 for BYTE. |
| No error but zero bytes copied | Length field 0 or 1 with bit syntax ID | n/a | Use byte-array syntax ID 0x1002 with explicit byte count. |
| SF LED on CPU after first call | Stack overflow from too many temp ANYs | n/a | Consolidate to one temp ANY per FB; declare locally, not as STATIC, to release L-stack on FB exit. |
TIA V14 compiles but OPN jumps to wrong DB at runtime |
Multi-instance AR2 pointer conflict | n/a | Save/restore AR2 around the OPN; avoid in-out BLOCK_DB in multi-instance FBs in TIA V14 SP1. |
Edge Cases and Field-Proven Caveats
- Optimised block access. In TIA Portal with "optimised block access" on the destination DB, the SFC still works because the ANY uses DB number + byte offset. The optimisation is internal to the compiler; it does not change the runtime area ID.
-
Type ANY on a function (FC) input. When the ANY is on the input of a downstream FC, the editor will again synthesise code. Pass the temp
RunTimeCreatedAnyas the actual parameter and let the editor walk through Example 1's expansion at the new call site. -
Calling a multi-instance FB. In a multi-instance context, the instance DB register (DI) is the multi-instance parent, not the one you
OPN'd. If you are copying into a different DB inside a multi-instance FB, you must re-OPN to the destination DB before any pointer arithmetic that usesDBNO. TheAR2register holds the instance base, and operations like+AR2 P#x.yresolve against that instance — do not mix this with the destination DB offset. -
Length overflow with REAL syntax ID. Using
0x1008and length 5 means five REALs = 20 bytes. The SFC uses the syntax ID to scale, not to validate. If you supply length 5 and source only has one REAL, the read will bleed past the DB boundary and the CPU will enter STOP with a DB length error on the source side. -
Symbolic ANY vs. absolute ANY. TIA Portal SCL accepts
P#"MyDB".StaticStructas an ANY literal. At compile time the symbolic reference is resolved to a byte offset; at runtime the resolved value is the offset. You cannot use a symbolic reference inside a runtime-constructed ANY — you must compute the byte offset symbolically (e.g.,DB_OFFSET_OF("MyStruct")in SCL) and use that as the area pointer. -
32-bit count for large moves. If the move is more than 32 KB, replace
INTwithDINTfor the byte count and load the DINT into the W field with a manual split. SFC20 supports up to the per-call maximum of the CPU (e.g., 64 KB on S7-1500, 16 KB on S7-315, 32 KB on S7-416).
Alternative Implementations in SCL
The same logic in SCL is significantly shorter and easier to maintain. The runtime-construction of the ANY is hidden behind the MOVE_BLK / FILL_BLK builtin if the destination is reachable symbolically, but for variable DB targets the OPN / L DBNO idiom is still required.
FUNCTION_BLOCK "CopyByteSCL"
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : AUTHTECH
FAMILY : LIB
VAR_INPUT
Source : VARIANT; //or ANY, but VARIANT is TIA V14+
DestDB : BLOCK_DB;
NumberOfBytes : INT;
END_VAR
VAR_OUTPUT
Error : INT;
END_VAR
VAR_TEMP
info : VARIANT_INFO; //TIA V15.1+ for length introspection
destAny : ANY;
END_VAR
BEGIN
// Build the destination ANY
destAny := DWORD_TO_ANY(IN := DW#16#1002_0010_0081_0000);
// Note: the DWORD layout is bytes 6..9 (area pointer) in the low DWORD of the
// 10-byte structure, with DB number fixed at runtime by OVERWRITE below.
// For TIA V16+ a cleaner alternative is to use the *AnyBuilder helpers in
// the LGF library, e.g. "LGF_AnyToDBDestination".
OPN #DestDB;
destAny.dbNumber := DBNO;
destAny.len := UINT(#NumberOfBytes);
Error := BLKMOV(
SRCBLK := Source,
DSTBLK := destAny,
RET_VAL => Error);
END_FUNCTION_BLOCK
If Source is a VARIANT, the BLKMOV builtin (SFC20) accepts it directly. If Source is a typed ANY, the conversion is implicit. The SCL version is functionally equivalent to the STL version above and is the recommended style for new TIA Portal projects.
FAQ
Why does the STEP 7 editor reject passing a BLOCK_DB directly into an ANY input?
BLOCK_DB and ANY are separate data type families. BLOCK_DB is a typed handle to a data block (4 bytes: DB number + flags), while ANY is a 10-byte area pointer including syntax ID, length, DB number, and byte.bit offset. The editor cannot auto-coerce one into the other; you must build the ANY in a temp variable at runtime with the OPN #DestDB / L DBNO idiom.
Which syntax ID should I use for a byte-by-byte copy of N bytes?
Use W#16#1002 (BYTE array). The length field is the byte count, and the area pointer uses area ID 0x81 for DB. For INT or REAL arrays, switch to 0x1005 or 0x1008 and supply the element count, not the byte count, in the length field.
Can I pass the BLOCK_DB from one FB to another FB without rebuilding the ANY?
Yes, as long as both blocks use BLOCK_DB as the formal parameter type. The runtime-construction step only happens at the call into SFC20/SFC21 or any other block that requires an ANY. The Siemens KB article 8686787 documents the supported pass-through topologies and the S7-300 V2.x exception.
What does SFC20 RET_VAL W#16#8091 mean in this context?
The destination area is shorter than the requested byte count. The runtime-constructed ANY is correct — it points at the right DB and the right offset — but the destination DB does not have enough bytes after that offset to receive the move. Reduce NumberOfBytes or use a larger DB.
Does the pattern work in SCL with optimised block access?
Yes. Optimised block access changes how the compiler lays out the DB, but the runtime OPN / DBNO / SFC20 sequence still uses the DB number and byte offset, which are unaffected by optimisation. The SCL source from the alternative-implementations section compiles and runs on S7-1500 with optimised access enabled.