Overview
Siemens S7-300/400 communication blocks FB12 "BSEND" and FB13 "BRCV" ship from the Standard Library > System Function Blocks and form the buffered, bi-directional S7 communication pair used to exchange variable-length data records between two CPUs. Wrapping both blocks inside a single user function block (for example FB100) keeps the call surface compact, centralises the connection-ID, error handling, and status evaluation, and allows the application code to treat the S7 link as one logical channel.
The pattern works fine for scalar parameters (INT, BOOL, WORD), but breaks down when the application wants to expose a single ANY input on the wrapper and forward it to the inner SD_1 (FB12) and RD_1 (FB13) parameters. The reason is that the ANY pointer is not passed by value; STEP 7 stores it in the calling block's local stack and supplies the called FB with a cross-reference pointer to that local area. Direct symbolic assignment of an IN-OUT ANY to another IN-OUT ANY therefore fails silently: STATUS stays W#16#0000, DONE never sets, and ERROR never sets, because the inner FBs never see a valid source/destination area.
This article shows two production-quality methods for routing an ANY from the wrapper FB to the inner BSEND/BRCV instances: a TEMP-based byte copy and an AR2-relative direct read. Both rely on the multi-instance mechanism for the inner FBs.
Prerequisites
- STEP 7 V5.4 or later (the STL examples use the classic AWL syntax). For TIA Portal V16+ the same logic applies; the BSEND/BRCV instances are dragged from Instructions > Communication > S7 Communication.
- S7-300 or S7-400 CPU with a configured S7 connection in NetPro / Devices & Networks. See the official application example at How do you configure and program an S7 connection and the "BSEND" and "BRCV" instructions.
- Standard Library blocks:
FB12 BSEND(multi-instance capable),FB13 BRCV(multi-instance capable),FB15 PUT(optional, for asymmetric variant). Confirm availability in the PCS 7 environment per What should you watch out for when using the blocks BSEND and BRCV in the PCS 7 environment. - For S7-1500 / S7-1200 G2, use the TIA Portal example at Program example for BSEND & BRCV (S7-1500, S7-1200 G2); the same encapsulation logic applies because TSEND_C/TRCV_C in TIA V16+ accept the ANY pointer by reference and the wrapper pattern transfers unchanged.
- Working knowledge of address registers
AR1/AR2and the local stack layout described in Programming with STEP 7 V5.4, Appendix A.3.4.5.
How STEP 7 Stores the ANY Parameter
When the OB1 calls FB100 with a literal ANY such as P#DB1.DBX0.0 BYTE 5, the compiler emits a 10-byte ANY constant and parks it in the calling block's TEMP area. The first 4 bytes of the called FB's local stack contain the cross-reference P#Lx.0 pointing to the 10 bytes. Inside the FB, the formal parameter DataArea is therefore accessed through that 4-byte descriptor; it is not a direct pointer to the data area.
| Byte offset | Length | Content | Example value |
|---|---|---|---|
| 0 | 2 | Syntax ID (10h for ANY) | 10 02 |
| 2 | 2 | Length in bytes | 00 05 |
| 4 | 2 | DB number (0 for non-DB) | 00 01 |
| 6 | 4 | Area + byte address (byte.bit encoded) | 84 00 00 00 |
To pass that ANY to the inner BSEND/BRCV instances, you must copy those 10 bytes from the calling block's local stack into a TEMP ANY in FB100 and then assign that TEMP symbolically to SD_1 / RD_1. A direct wire from DataArea to SD_1 does not work because both parameters are formal IN-OUTs and the compiler cannot chain cross-references of equal depth.
The AR2 Trap in Multi-Instance FBs
STEP 7 uses AR2 to store the multi-instance offset of the currently executing FB. As soon as you touch AR2 in your own STL without saving it first, every subsequent symbolic access to instance-DB variables resolves to the wrong offset. The official Siemens FAQ on register/ACCU handling documents the full set of opcodes that overwrite AR1, AR2, and the accumulators:
- Which operations overwrite the content of ACCU or registers in STEP 7?
- How do you parameterize blocks, which have "ANY"-type or "Pointer" type parameters?
Save/restore AR2 around any direct register manipulation, or stay inside the LAR1 / T W[AR1,P#x.y] idiom that the compiler can supervise. Solution 1 below uses the safer idiom.
Solution 1: TEMP ANY with Byte-by-Byte Copy
Add a single TEMP ANY in the wrapper FB and copy the 10 bytes from the formal parameter's local location to that TEMP. Then assign the TEMP symbolically to the inner instances. This is the recommended pattern because the compiler can still relocate AuxAny without breaking the code.
FB100 declaration (top portion):
FUNCTION_BLOCK FB100
TITLE = 'S7 Communication Wrapper (BSEND + BRCV)'
VERSION : '1.1'
VAR_INPUT
DataArea : ANY; // Source/Destination ANY from caller
ID : WORD; // Connection ID from NetPro
R_ID : DWORD; // Request ID, must match on both sides
REQ : BOOL; // Rising edge starts BSEND
EN_R : BOOL; // Enable BRCV reception
END_VAR
VAR
BS_inst : BSEND; // Multi-instance: FB12
BR_inst : BRCV; // Multi-instance: FB13
DoneX : BOOL;
ErrorX : BOOL;
StatusX : WORD;
END_VAR
VAR_TEMP
AuxAny : ANY; // Working copy of the caller ANY
SaveAR2 : DWORD; // AR2 save slot (only for Solution 2)
END_VAR
Network 1 - Copy the 10 bytes of the caller's ANY into AuxAny:
L P##DataArea // Pointer to the IN parameter descriptor (4 bytes)
LAR1 // AR1 now points to the 4-byte cross-reference
L P##AuxAny // Pointer to the TEMP ANY (10 bytes)
LAR2 // AR2 -> AuxAny (touched AR2, restore later!)
L W [AR1,P#0.0] // Syntax ID + length high word
T W [AR2,P#0.0]
L D [AR1,P#2.0] // Length low word + DB number
T D [AR2,P#2.0]
L D [AR1,P#6.0] // Area + byte address
T D [AR2,P#6.0]
The first 4 bytes of DataArea in the local stack are the cross-reference P#Lx.0. Indirectly reading with L W[AR1,P#0.0] therefore yields the first 2 bytes of the actual 10-byte ANY (skipping the cross-reference header). The displacements P#2.0 and P#6.0 map exactly to the ANY layout shown above.
Network 2 - Call BSEND with the working copy:
CALL BS_inst
REQ := REQ
R := FALSE
ID := ID
R_ID := R_ID
DONE := DoneX
ERROR := ErrorX
STATUS:= StatusX
SD_1 := AuxAny // symbolic assignment of the working ANY
LEN := 0 // 0 = use the full length from SD_1
Network 3 - Call BRCV with the same working copy:
CALL BR_inst
EN_R := EN_R
ID := ID
R_ID := R_ID
NDR := NDR_X
ERROR := ErrorBR
STATUS:= StatusBR
RD_1 := AuxAny // shared buffer for receive
LEN := LenRcv
Because both BSEND and BRCV read AuxAny symbolically, the compiler emits the correct cross-reference at call time. The previous failure (DONE=0, STATUS=0, ERROR=0) is replaced with a real status word such as W#16#0000 after the job completes or an explicit error code such as W#16#000A for an unknown connection.
Solution 2: AR2-Based Direct Forwarding (Advanced)
If you must avoid the TEMP copy (for example to keep determinism in a fast OB), forward the ANY by keeping AR2 intact and re-loading it from the instance DB after the manipulation. The block is rarely worth the maintenance burden, but is included for completeness:
L P##DataArea
LAR1
TAR2 SaveAR2 // save multi-instance offset
L W [AR1,P#0.0]
T W [AR1,P#0.0] // no-op: shows the pattern only
LAR2 SaveAR2 // restore multi-instance offset
CALL BS_inst
SD_1 := DataArea // now safe, AR2 restored
Any direct STL that touches AR1 with LAR1 inside a multi-instance FB must end with a matching LAR1 back to the instance offset, or the next symbolic access will corrupt the variable image. The two Siemens links above list every opcode that clobbers AR1 and AR2.
Diagnostics and Common Faults
| Observed behaviour | Likely root cause | Remedy |
|---|---|---|
| DONE=0, ERROR=0, STATUS=0 indefinitely | ANY not forwarded; inner FB sees an empty descriptor | Apply Solution 1 (TEMP copy) or Solution 2 (AR2 restore) |
| STATUS = W#16#0001 / 0002 / 000A | Connection issues, see BSEND/BRCV manual | Verify ID from NetPro, partner CPU in RUN, R_ID match |
| STATUS = W#16#8085 / 8090 | Length / type mismatch in the ANY (often DBX outside instance DB) | Check that the caller's ANY is fully inside a real DB or M area, not a TEMP |
| Wrapper compiles, online shows wrong source data | AR2 was clobbered; symbolic reads return shifted offsets | Wrap each LAR2 in save/restore, or use Solution 1 only |
| Error only after stop/Run of CPU | Multi-instance DB was re-initialised; AR2 not reloaded | Add initialisation network at the top of FB100 that calls BS_inst with R := TRUE on first scan |
For PCS 7 users, Siemens flags additional constraints: BSEND/BRCV must be called in OB1 (or a cyclic OB of the same priority class) at a defined interval to maintain the receive side, and the connection must be configured as S7 connection in NetPro, not as a TCP connection. These are detailed in the PCS 7-specific FAQ linked above.
Step-by-Step Commissioning Procedure
- Open NetPro and create an S7 connection between the two stations. Note the
IDshown in the connection properties (hex, e.g.W#16#0001). - Generate a new
DB10in the partner CPU that mirrors the data layout. EnsureDB10length is greater than or equal to the highestDataArealength used in the application. - Create
FB100in the program, declareBS_inst : BSENDandBR_inst : BRCVas STAT (multi-instance), and add the TEMPAuxAnyand IN parameters as listed in the declaration above. - Paste Networks 1-3 from Solution 1 into
FB100and recompile. The instance DB forFB100will contain the BSEND/BRCV instance data automatically. - Call
FB100fromOB1withDataArea := P#DB10.DBX0.0 BYTE 20, the sameIDfrom step 1, a matchingR_ID, and a 1-Hz clock onREQto drive the send. - Download both stations and place the partner CPU in RUN. Observe
StatusXin VAT: a successful first send returnsW#16#0000and setsDoneX := TRUEfor one cycle. - Trigger a receive on the partner by writing into its
DB10from a third station or an HMI script and watchBR_inst.NDRrise on the wrapper FB.LenRcvreports the actual bytes received.
Verification
Confirm correct behaviour with the following checks in VAT or with the online monitor:
- After
REQrising edge,BS_inst.DONEis TRUE for one OB1 cycle,BS_inst.STATUS = W#16#0000, and the partner's receive data block has been overwritten with the sent bytes. - After the partner writes back,
BR_inst.NDRis TRUE for one cycle,BR_inst.STATUS = W#16#0000, and the wrapper'sDataAreashows the new values. - Force a wrong
ID(e.g.W#16#00FF):BS_inst.ERRORmust rise andBS_inst.STATUSmust readW#16#000A(unknown connection) orW#16#0001(low-priority setup fault) - this proves the wrapper is actually executing the inner FB and not returning a frozen zero status. - Cross-check the
DImonitor on the instance DB ofFB100: theAuxAnyTEMP is volatile and invisible in the instance DB; only the multi-instance data ofBS_instandBR_instmust appear.
Performance and Stack Depth Notes
Copying 10 bytes per call adds roughly 0.5 microseconds of OB1 time on an S7-315-2 PN/DP and is below the 1 ms jitter threshold for cyclic communication. The TEMP footprint is 12 bytes (10 for the ANY plus 4 for the cross-reference pointer, aligned). For a wrapper that fans out to N parallel BSEND/BRCV pairs, allocate a separate AuxAny per pair; do not reuse one TEMP across calls because BSEND and BRCV both read the ANY asynchronously between OB1 cycles.
The local stack depth of OB1 grows by the sum of TEMP usage of all FBs in the call chain. Adding 12 bytes to FB100 is negligible on S7-300 (default 256-byte local stack per priority class) but should be measured on S7-400 CPUs in heavily nested OBs.
Migrating to TIA Portal and S7-1500
On S7-1500 with TIA Portal V16 and later, BSEND and BRCV are available as BSEND / BRCV instructions in the Communication > S7 Communication palette and accept ANY parameters through their SD_1 / RD_1 inputs. The wrapper pattern translates directly: declare an InOut of type Variant on the wrapper, convert to ANY via VariantToAny (SCL) or the legacy byte copy (STL), and assign to the inner instructions. For S7-1500 firmware V2.5+ you may alternatively use the optimised TSEND_C / TRCV_C blocks, but they are TCP-based and not drop-in replacements for the S7-connection semantics of BSEND/BRCV.
The TIA Portal sample at Program example for BSEND & BRCV (S7-1500, S7-1200 G2) shows a full two-CPU project that compiles against the same wrapper contract described in this article.
Safety and Operational Caveats
BSEND and BRCV operate on configured S7 connections only. They do not provide any authentication or encryption; for untrusted networks use the S7 Communication (Secure) variants introduced with TIA Portal V18 / S7-1500 firmware V3.0, or front-end the link with a VPN / SCALANCE firewall. Treat the ANY parameter on the wrapper as a potential injection vector: validate Area byte and DB number against an allow-list before passing it to the inner blocks.
Multi-instance FBs that use AR2 for parameter passing must be compiled in the same OB priority class as their caller. Mixing priority classes (for example calling FB100 from OB35 and from OB82) is legal but each caller must use its own instance DB; the multi-instance offset is stored per instance, not per call site.
FAQ
Why does FB12 / FB13 stay at STATUS = W#16#0000 with DONE = 0 and ERROR = 0 when I assign the ANY directly?
STEP 7 stores the formal ANY in the calling block's local stack and gives the called FB only a 4-byte cross-reference. A direct symbolic wire between two IN-OUT ANY parameters cannot chain the cross-reference; the inner FB sees an empty descriptor and returns the zero status. Copy the 10 bytes into a TEMP ANY first (Solution 1) or restore AR2 and assign symbolically (Solution 2).
Can I avoid the TEMP copy and just assign the formal parameter to SD_1 in SCL?
No. The same cross-reference rule applies in SCL. Use the VariantToAny / VariantGet conversion block in TIA Portal or paste the STL byte copy from Solution 1 into an STL section of the SCL FB.
Do FB12 and FB13 need to be in the same call priority?
They can be called from any cyclic OB, but the wrapper FB must be called in the same priority class on every cycle, otherwise the receive side will time out. PCS 7 V9 calls both in OB1 by convention - see the PCS 7 FAQ for BSEND/BRCV linked above.
Why does the wrapper compile but STATUS shows W#16#8085 or W#16#8090?
The ANY length or the area / DB combination is invalid. Confirm the caller's ANY points to a real DB area, not a TEMP, and that the byte/bit encoding in bytes 6-9 of the ANY is correct (e.g. 84 00 00 00 for DB 1, byte 0). A common mistake is using the DBX address of a multi-instance DB - the inner BSEND then cannot resolve the DB number at run time.
Is the same pattern valid for PUT (FB15) and GET (FB16)?
Yes. PUT and GET also accept ANY pointers at ADDR_1..ADDR_4, and the same TEMP-copy pattern applies. The PCS 7 FAQ linked above documents the additional constraint that PUT/GET need a different connection type than BSEND/BRCV; both can coexist on the same S7 connection configured as "S7 connection" in NetPro.