Constructing an S7-SCL ANY Pointer for BSEND on S7-300 CPUs
When you call BSEND (FB12) or BRCV (FB13) for S7 communication between two S7-315-2 PN/DP CPUs, the SD_1 / RD_1 parameters require a fully populated ANY pointer. The ANY is a 10-byte data structure that tells the CPU which area, which DB or bit-memory, and how many bytes to ship. Most field failures on the first attempt come from assembling the 4-byte address portion incorrectly, especially with the bit-address field, the high-nibble 1000 marker, or the operand-area code (ZZZ).
This reference walks through the correct construction in S7-SCL using the AT overlay technique, shows the failed attempts that are typically posted in support tickets, and provides verified examples for BLKMOV, BSEND, SFC14 (GET), and SFC15 (PUT).
1. ANY Pointer Layout for S7-300/400
An ANY pointer that points to a global DB uses the area-crossing format. Internally the 10 bytes are organized as follows:
| Byte | Bit 7..0 | Meaning | Typical value (global DB, BYTE data) |
|---|---|---|---|
| 0 | 10 S S S S S S | Syntax ID (S = sub-type) | 16#10 (S7-300/400 ANY) |
| 1 | 0000 T T T T | Transport size (datatype) | 16#02 = BYTE, 16#04 = WORD, 16#05 = DWORD, 16#07 = REAL, 16#09 = DINT |
| 2, 3 | count high / low | Number of items (not bytes!) | 16#020E = 526 bytes of BYTE |
| 4, 5 | DB high / low | DB number (0 for non-DB) | 16#0005 = DB5, 16#0002 = DB2 |
| 6..9 | area-crossing pointer | 32-bit pointer (see below) | 16#8400A5E0 for DB5.DBX5308.0 |
For the 32-bit area-crossing pointer (bytes 6-9), the canonical layout is:
Byte 6 (MSB): 1000 0ZZZ
Byte 7: 0000 0YYY
Byte 8: YYYY YYYY
Byte 9 (LSB): YYYY YXXX
Where:
-
ZZZ = operand area code:
2#000(bit memory / P),2#001(input I),2#010(output Q),2#011(M/PIW),2#100(global DB),2#101(instance DI),2#111(local L). - YYYY YYYY YYYY YYYY = 19-bit byte address.
-
XXX = 3-bit bit address (must be
0for BLKMOV, BSEND, SFC14/15; SFC20 BLKMOV refuses misaligned addresses).
For a global DB the byte 6 high nibble is fixed at 1000 and ZZZ is 100, so byte 6 evaluates to 16#84 — not 14, not 04, and not 80. This is the single most common assembly error.
2. Why the Original Attempts Failed
A typical first attempt posted in support tickets is shown below. The user was trying to move 526 bytes (16#20E) from DB5 to DB2 using BLKMOV.
// INCORRECT - bit address not zero, ZZZ wrong
#S_TEST01.Syntax := 16#10;
#S_TEST01.Datatype := 16#02;
#S_TEST01.Count := 16#20E;
#S_TEST01.DB_NR := 16#05;
#S_TEST01."Pointer" := dw#16#000014BC; // BYTE 0..3 of pointer area
#S_TEST02.Syntax := 16#10;
#S_TEST02.Datatype := 16#02;
#S_TEST02.Count := 16#20E;
#S_TEST02.DB_NR := 16#02;
#S_TEST02."Pointer" := dw#16#00000000;
Three problems exist in this fragment:
- The high nibble
1is missing from byte 6, so ZZZ is read as000(bit memory area) instead of100(global DB). - The
14BCvalue has bit address1100(12, decimal) — BLKMOV refuses non-zero bit addresses and posts SF Bx"BLKMOV: Source area error". - The 32-bit value 16#000014BC is being interpreted as a byte/bit address, giving byte 0x14BC / 8 = 663.5 (an impossible half byte).
A second attempt used dw#16#84A5E0. Although the ZZZ code is now correct, the constant is only 3 bytes — SCL zero-extends it to 16#84A5E000, yielding byte address 1,358,848, which is well outside the S7-300 address space (max 16,383). The CPU rejects this with a DB-not-found / addressing error in the diagnostic buffer.
The corrected constant is 16#8400A5E0 for DB byte 5308.0:
| Byte | Hex | Binary | Meaning |
|---|---|---|---|
| 6 (MSB) | 0x84 | 1000 0100 | DB area marker + ZZZ=100 (global DB) |
| 7 | 0x00 | 0000 0000 | Top 4 bits of byte address (00) |
| 8 | 0xA5 | 1010 0101 | Mid byte address |
| 9 (LSB) | 0xE0 | 1110 0000 | Low 8 byte-address bits + bit address 000 |
Reconstructing: YYYY...Y = 000 0000 1010 0101 1110 0 = 0x14BC (5308 decimal). Bit address 000 = 0. The pointer resolves to DB5.DBX5308.0.
3. The SCL AT-Overlay Technique
The cleanest, vendor-recommended way to assemble an ANY in S7-SCL is to declare the structure, then use AT to overlay a STRUCT/DWORD view onto the same memory. This is documented in the official Siemens FAQ "How can you construct an ANY pointer in S7-SCL using the AT function?" (Siemens Support entry ID 21946854).
3.1 TYPE definition
TYPE UDT_ANY
STRUCT
Syntax : BYTE; // 16#10 for S7-300/400 ANY
Datatype : BYTE; // 16#02 = BYTE
Count : WORD; // number of items
DB_NR : WORD; // DB number, 0 for non-DB areas
"Pointer" : DWORD; // 32-bit area-crossing pointer
END_STRUCT;
END_TYPE
3.2 FB/FC local declarations
VAR_TEMP
S_TEST01 : UDT_ANY; // source ANY
S_TEST02 : UDT_ANY; // destination ANY
Temp_1 : ANY; // generic ANY for BLKMOV input
Temp_2 : ANY;
T_Test : INT; // BLKMOV return value
END_VAR
3.3 Construct the source ANY for DB5.DBX5308.0, 526 BYTE
#S_TEST01.Syntax := 16#10;
#S_TEST01.Datatype := 16#02;
#S_TEST01.Count := 16#020E; // 526 bytes
#S_TEST01.DB_NR := 16#0005; // DB5
#S_TEST01."Pointer" := DW#16#8400A5E0; // DBX5308.0
3.4 Construct the destination ANY for DB2.DBX0.0, 526 BYTE
#S_TEST02.Syntax := 16#10;
#S_TEST02.Datatype := 16#02;
#S_TEST02.Count := 16#020E;
#S_TEST02.DB_NR := 16#0002; // DB2
#S_TEST02."Pointer" := DW#16#84000000; // DBX0.0
3.5 Assign to the generic ANY and call BLKMOV
#Temp_1 := #S_TEST01;
#Temp_2 := #S_TEST02;
#T_Test := BLKMOV(SRCBLK := #Temp_1,
DSTBLK := #Temp_2);
IF #T_Test <> 0 THEN
// 0 = no error, <> 0 = ENO = FALSE; consult RET_VAL table
// 16#8091 = source area invalid, 16#8092 = dest area invalid
// 16#80B1 = count = 0, 16#80B4 = source/dest overlap
END_IF;
AT overlay works because the ANY type occupies the same 10 bytes in the same order as your STRUCT. Re-casting via simple assignment is only valid on S7-300/400 (firmware V2.0 and later for the 315-2 PN/DP). For S7-1500 the compiler refuses implicit ANY-to-STRUCT conversion; use the Variant type and MOVE_BLK_VARIANT (SFC14127) instead.4. Adapting the Pattern for BSEND (FB12)
BSEND expects the SD_1 parameter as an ANY pointing to the source area. The same UDT_ANY structure works without modification.
// S7-300/400 BSEND call signature (S7 Communication, FB12)
// LEN := number of bytes to send
// R_ID := 16-bit connection identifier
// DONE, ERROR, STATUS: standard BSEND outputs
#BsendID := BSEND(
REQ := #bStart,
R := #bReset,
LEN := 526,
DONE := #bDone,
ERROR := #bError,
STATUS := #wStatus,
SD_1 := #Temp_1); // pre-built ANY of length 526
Key BSEND constraints:
-
LEN must equal the sum of the
Countfield in your ANY × transport-size. For BYTE transport size, LEN = Count. For WORD transport size, LEN = Count × 2. Mismatched LEN returns STATUS = 16#001A. - REQ is edge-triggered; supply a positive edge per send.
- BSEND is part of the S7 Communication family; you need a configured S7 connection in NetPro with the same
R_IDon both sides. - Data is sent in chunks of up to 480 bytes; BSEND segments automatically.
5. Equivalent Patterns for SFC14 (GET) and SFC15 (PUT)
The SFC14/15 SDT (any-pointer) parameter is constructed identically. Re-use the UDT_ANY definition.
// SFC15 PUT - write 526 bytes from local DB2.DBX0.0 to partner DB5.DBX5308.0
#wStatus := PUT(
REQ := TRUE,
IOID := B#16#54, // B#16#54 = output (PUT)
LADDR := W#16#0100, // configured logical I/O address
RECNUM := 0, // record number 0
DONE := #bDone,
ERROR := #bError,
STATUS := #wStatus,
RECORD := #Temp_1); // ANY of length 526
// SFC14 GET - read 526 bytes from partner DB into local DB
#wStatus := GET(
REQ := TRUE,
IOID := B#16#54,
LADDR := W#16#0100,
RECNUM := 0,
DONE := #bDone,
ERROR := #bError,
STATUS := #wStatus,
RECORD := #Temp_2); // destination ANY
6. ANY Pointer Construction Cheat Sheet
| Target address | Pointer (bytes 6-9) | Byte 6 high nibble | ZZZ |
|---|---|---|---|
| DB5.DBX0.0 | DW#16#84000000 | 1000 (0x8) | 100 (DB) |
| DB5.DBX5308.0 | DW#16#8400A5E0 | 1000 | 100 |
| DB2.DBX100.0 | DW#16#84000064 | 1000 | 100 |
| MW100 (bit memory) | DW#16#000000C8 | 0000 | 000 (P/M) |
| IW10 (inputs) | DW#16#01000014 | 0000 | 001 (I) |
| QW20 (outputs) | DW#16#02000028 | 0000 | 010 (Q) |
| DI10.DBB0 (instance DB) | DW#16#85000000 | 1000 | 101 (DI) |
| Local L 8.0 | DW#16#87000010 | 1000 | 111 (L) |
Note the high nibble is 0 for I, Q, M; the high nibble is 1 (giving 0x8X) for DB, DI, and L. Forgetting to OR in the 0x80_000000 high bit is the single most common reason BSEND reports STATUS = 16#0010 or 16#0022 at runtime.
7. Diagnostic Buffer and BLKMOV/BSEND Error Codes
When the ANY is malformed, the CPU records an event in the diagnostic buffer and the FC/FB output returns a non-zero value. The most common codes (per the TIA Portal help for SFC20 BLKMOV and BSEND documentation):
| RET_VAL / STATUS | Block | Meaning | Typical cause |
|---|---|---|---|
| 16#0000 | All | No error | — |
| 16#8091 | BLKMOV | Source area invalid | ZZZ wrong, DB number wrong, address out of range |
| 16#8092 | BLKMOV | Destination area invalid | Same as above on DSTBLK side |
| 16#80A0 | BLKMOV | Source/dest DB not loaded | DB5/DB2 not present in S7-300 work memory |
| 16#80B1 | BLKMOV | Count = 0 | Forgot to populate Count field |
| 16#80B4 | BLKMOV | Source and dest overlap | Same DB used for both with overlapping ranges |
| 16#0001 / 16#7000 | BSEND | Communication fault / not started | Connection unconfigured; check NetPro |
| 16#0010 | BSEND | Length error | LEN does not match Count × transport-size |
| 16#001A | BSEND | Pointer error / partner buffer too small | Bad ANY on SD_1 or partner LEN < SD_1 length |
| 16#0022 | BSEND | Negative acknowledgment from partner | Receiver could not write the data (wrong DB / size) |
| 16#0081 | BSEND | SD_1 / RD_1 pointer error | SyntaxID wrong, transport-size unknown, alignment not 0 |
8. Verification Procedure
After deploying the corrected code to the S7-315-2 PN/DP, perform the following checks in this order.
-
Online > Monitor / Modify on the FB. Confirm
#S_TEST01."Pointer"reads asDW#16#8400A5E0(or your target value). Any other value means the assignment was truncated by a different datatype. - Open the DB5 watch table and verify byte 5308 contains the data you expect. The first byte of the payload should be visible at offset 5308.
- Trigger BLKMOV once and inspect the BLKMOV return value; it must be 16#0000. If RET_VAL = 16#80A0, download DB2 and DB5 again — the DBs are not present in the load image.
- Trigger BSEND with REQ := TRUE and watch STATUS. STATUS = 16#0000 with DONE = TRUE means the partner received the 526 bytes; the partner's BRCV will report DONE = TRUE on its LEN parameter.
- For SFC14/15, run online > diagnostics > "Communication" on the partner CPU to confirm the configured S7 connection is established before the first PUT/GET. STATUS = 16#0001 with ERROR = TRUE typically indicates the connection has been torn down.
9. S7-1500 and TIA Portal Notes
The technique above applies to S7-300/400 with STEP 7 V5.x or TIA Portal V13+. On S7-1500, the optimizer removes the absolute byte addresses that the legacy ANY pointer relies on, so the bit-level assembly shown in Section 3 will not work in optimized blocks.
For S7-1500, prefer one of:
- Declare the source as a fully qualified
DBtag (e.g."Data".SendBuffer) and pass it directly to BSEND — the compiler builds the ANY automatically. - Use
VARIANTinputs and the system blocksMOVE_BLK_VARIANT,Serialize, andDeserialize. - Use S7-1500's
PUT/GETinstructions in the "Communication" palette — they accept aVarianttag and aLENparameter without any manual ANY construction.
10. Field-Proven Cautions
-
Byte vs. word count. The
Countfield stores the number of items of the declared transport size, not the byte count. A 16#020E count with Datatype = 16#02 (BYTE) is 526 bytes; the same count with Datatype = 16#04 (WORD) is 1,052 bytes. BSEND will reject LEN = 526 in the second case with STATUS = 16#0010. - Bit address must be zero for BLKMOV, BSEND, SFC14, SFC15. Only a few legacy FBs (e.g. some version of the IEC timers) accept a non-zero bit address. If you see STATUS 16#0081 from BSEND, check the low 3 bits of the pointer word first.
- S7-315-2 PN/DP limits. The S7-300 CPU has a maximum DB size of 32 KB and a maximum process-image I/O area. Byte address 5308 is well within the limit; do not exceed 32,511 (16 KB - 1) for byte pointers on the 315 family. Exceeding this range posts a "Substitution error in user program" event.
-
DB number 0 means "non-DB operand area" (M, I, Q, L, PIW). Setting
DB_NR := 16#0000together with a ZZZ of 16#4 (DB) is contradictory and will be rejected at runtime. - Reserved bytes 10-11 of the full 12-byte ANY are not used by the S7-300/400 runtime; SCL's UDT_ANY 10-byte STRUCT is sufficient. On S7-1500, the ANY length differs in some contexts; consult the TIA Portal documentation before porting code.
11. Quick Self-Check Before Going Online
| Check | Pass condition |
|---|---|
| Syntax byte = 16#10 | Yes |
| Transport size matches data type | BYTE → 0x02, WORD → 0x04, DWORD → 0x05, REAL → 0x07, DINT → 0x09 |
| Count = number of items, not bytes | For 526 bytes: Count = 526 (0x20E), not 526 ÷ 2 |
| DB_NR matches the destination DB | DB5 → 0x0005 |
| Byte 6 high nibble = 1000 for DB/DI/L | 0x84 for DB, 0x85 for DI, 0x87 for L |
| Byte 6 high nibble = 0000 for I/Q/M/PIW | 0x00 for M, 0x01 for I, 0x02 for Q |
| Bit address (low 3 bits of LSB) = 0 | 0x...0, 0x...8, 0x...0, never 1, 2, 3, 4, 5, 6, 7 |
| Byte address within CPU limits | ≤ 32,511 on S7-300, ≤ 65,535 on S7-400 |
With the corrected DW#16#8400A5E0 pointer in place, BSEND transfers the 526-byte payload from DB5 to the partner's BRCV buffer with STATUS = 16#0000 and DONE = TRUE. The CPU diagnostic buffer shows no further events related to the S7 connection.
What is the difference between an area-internal and area-crossing pointer in an S7 ANY?
An area-internal pointer is a 32-bit value that lives entirely inside one operand area (DB, M, I, etc.) and is used inside the CPU for internal addressing. An area-crossing pointer is the 4 bytes (bytes 6-9) of an ANY that explicitly names the operand area in the high nibble (1 0 0 0 ZZZ) and combines a 19-bit byte address with a 3-bit bit address. BSEND, BLKMOV, SFC14, and SFC15 only accept area-crossing pointers.
Why does BLKMOV return 16#8091 even though my DB exists?
The most common reason is that the high nibble of the pointer's MSB (byte 6) is not 0x8, so the CPU reads the operand area as something other than a global DB and cannot resolve the address. Verify Pointer = DW#16#84_xxxxx_xx0 for any global-DB ANY; a value of 0x04, 0x14, or 0x00 in byte 6 will be interpreted as bit memory, PII, or PIO respectively.
Can I assign an ANY pointer as a 3-byte constant and let SCL zero-extend it?
No. SCL zero-extends a 3-byte (6-hex-digit) constant to a 4-byte value by appending two zero hex digits at the LSB, which shifts the byte address by 8 bits and produces an unreachable address. Always supply a full 4-byte (8-hex-digit) constant such as DW#16#8400A5E0 or build the pointer via a STRUCT/AT overlay.
Does the Count field hold the number of bytes or items?
It holds the number of items of the declared transport size. For Datatype 16#02 (BYTE), 526 bytes = Count 16#020E. For Datatype 16#04 (WORD), 526 words = 1,052 bytes = Count 16#020E (i.e., 526 words, not 526 bytes). BSEND's LEN parameter, on the other hand, always counts bytes.
Can I reuse the same UDT_ANY definition for BSEND, BRCV, SFC14 GET, and SFC15 PUT?
Yes. All four FBs/FCs expect the same 10-byte ANY structure on their data parameter (SD_1, RD_1, RECORD). The only differences are the length of the call (LEN for BSEND) and the IOID / LADDR configuration for SFC14/15. The UDT_ANY-based construction is therefore portable across the S7 Communication family on S7-300/400.