S7-1200 T_SEND_C 8192 Byte Limit: MOVE_BLK_VARIANT Solution

David Krause15 min read
S7-1200SiemensTroubleshooting
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

Resolving the S7-1200 T_SEND_C 8192-Byte User-Data Limit for Large Diagnostic Arrays

The S7-1200 family of CPUs (firmware 4.x and later, including the 1215 used as IO Controller in MRP rings) limits the user-data payload of a single ISO-on-TCP TSEND_C / T_SEND_C call to 8192 bytes. When a diagnostic structure array exceeds that boundary, the application must fragment the array, prepend a routing header to each fragment, and reassemble at the receiver. The fragmentation logic requires a flexible block-move primitive that the S7-1200 instruction set does not provide in the form of the legacy S7-300/400 BLKMOV. This reference documents the runtime errors encountered (80B4, 8154), explains the underlying ANY/VARIANT semantics on the S7-1200, and presents two field-proven working solutions: POKE_BLK with a dedicated send DB, and MOVE_BLK_VARIANT with AT-overlaid Variant parameters.

1. Problem Context and System Architecture

The reference topology is a typical plant network segment:

  • S7-1215 DC/DC/DC, firmware V4.2 acts as PROFINET IO Controller on an MRP ring and also participates as a ring member.
  • SCALANCE X204 switches close the MRP ring, providing ring-port diagnostics recoverable via record reads.
  • S7-414-4H redundant controller with CP 443-1 receives the consolidated switch diagnostics for higher-level processing.
  • Two ISO-on-TCP connections (RFC 1006, port 0x0102 configurable) are established between the S7-1200 and the CP 443-1 using two instances of T_SEND_C for redundancy of the data path.
  • TIA Portal V14 SP1 is the engineering environment.

The diagnostic information from each managed SCALANCE X204 is captured as a UDT/PLC data type (approximately 606 bytes per switch, including String fields). The aggregated array in a global DB therefore scales linearly with the number of switches and routinely exceeds 8192 bytes once the managed switch count passes ~14 devices. The T_SEND_C maximum is documented in the Siemens SIMATIC S7-1200 Programmable Controller system manual and is hard-wired to 8 KB regardless of the LEN pin value; passing a larger length returns STATUS = 80A1 at the instruction.

2. The 8192-Byte Limit on TSEND_C / T_SEND_C

The TSEND_C (S7-1500 style) and T_SEND_C (S7-1200 style) instructions wrap the legacy TCON, TSEND, and TDISCON blocks. On the S7-1200 the maximum length of a single send call is constrained to 8192 bytes of user data (i.e., the DATA parameter, exclusive of the ISO-on-TCP/TLS framing). For larger payloads the application has three legitimate choices:

  1. Use the S7-1500 (no equivalent 8 KB cap when the connection is ISO-on-TCP, although the practical payload is still bounded by the connection resource).
  2. Switch the protocol to PUT/GET (S7 communication), but on the S7-1200 the PUT/GET data area is further restricted to a small per-call field (typically up to 400 bytes for S7-1200 ↔ S7-400 PUT/GET, depending on the partner's CP/PN interface and the configured connection resources).
  3. Implement a fragment-and-sequence protocol on top of ISO-on-TCP using the existing T_SEND_C channels.

For the redundant S7-414-4H, option 3 is the only viable long-term solution because the diagnostic payload must grow with the network, and PUT/GET cannot reach the same throughput as raw ISO-on-TCP streaming.

3. Designing the Fragment Header

To reassemble fragments at the S7-414-4H, every fragment must carry:

Offset Field Type Description
0..1 FragmentIndex UINT (Word) Zero-based index of this fragment in the stream.
2..3 FragmentLength UINT (Word) Length of valid payload bytes following the header in this fragment.
4..n Payload Array of Byte Up to 8188 bytes of diagnostic data (8192 − 4-byte header).

The receiver uses FragmentIndex × 8188 to compute the destination byte offset within its reassembly DB and writes FragmentLength bytes from the payload. A trailer record holding TotalFragments and a CRC or a transaction ID closes the message so that the receiver can discard stale reassemblies.

4. The Core Engineering Problem: Source-Indexed Block Move

For each fragment the application needs to copy a sub-range of the source diagnostic DB into a send buffer that already contains a 4-byte header at offset 0. In STL on the S7-300/400 this is the classical ANY-pointer + BLKMOV pattern:

// S7-300/400 reference (does NOT work as-is on S7-1200)
LAR1  P##LT_ZONE_STOCK_DIAG          // ANY pointer
L     B#16#10
T     LB [AR1,P#0.0]                 // Syntax ID
L     B#16#2
T     LB [AR1,P#1.0]                 // Data type = BYTE
L     #LT_Lenght_Diag
T     LW [AR1,P#2.0]                 // Length
L     #DB_Diag
T     LW [AR1,P#4.0]                 // DB number
L     #LT_Index_Diag
T     LD [AR1,P#6.0]                 // Byte offset
CALL  "BLKMOV"
SRCBLK :=#LT_ZONE_RECEPTION_CPU0
RET_VAL:=#LT_Ret
DSTBLK :=#LT_ZONE_STOCK_DIAG

The S7-1200 rejects the equivalent construction because:

  • There is no LAR1, no AR1/AR2 register concept, and no P## area-cross pointer for parameter instances in the same form.
  • Instruction parameters must be symbolically typed at compile time; they cannot be patched with a length or DB number in run-time the way the legacy BLKMOV allowed.
  • The VARIANT datatype used by S7-1200 block-move instructions cannot be built byte-by-byte; it is consumed/produced as a typed reference.

5. Error Code Reference

Error Hex Trigger Resolution
STATUS of MOVE_BLK / MOVE_BLK_VARIANT 80B4 Source and destination areas are declared with the same byte length but different symbolic types (Struct vs. Array of UDT). The instruction's variant dereference fails because the data type descriptors do not match. Build a destination area whose declared data type is identical to the source data type, or use a Variant input that points to the same symbolic type. AT-overlay can be used to share storage between a header and the payload variant.
STATUS of DB_ANY_TO_VARIANT 8154 The runtime DB number passed via a DB_ANY parameter does not resolve to a typed VARIANT descriptor, or the destination area is not of VARIANT type. Use a VARIANT-typed input at the FB interface and pass a fully typed reference from the caller; do not attempt to construct a VARIANT from a DB_ANY on the S7-1200 instruction set.
STATUS of T_SEND_C 80A1 Connection or resource error; often raised when LEN exceeds 8192 bytes for an S7-1200. Cap LEN to 8192 and split the payload.
STATUS of T_SEND_C 80C3 Temporary resource exhaustion; the previous send has not yet completed (DONE or ERROR not yet observed). Edge-trigger the send on DONE / ERROR rising edge only.

The official S7-1200 system manual lists these and additional STATUS codes under the description of each instruction. Always cross-check against the firmware-specific manual revision: FW 4.2 of the S7-1215 corresponds to the SIMATIC S7-1200 Programmable Controller - System Manual edition 06/2017 (article ID 109751325), while later firmware introduced additional variants of the 80B4 family (e.g., 80B5 for ARRAY index overrun).

6. Solution 1 - POKE_BLK with a Dedicated Send DB

The simplest, least-elegant but functionally robust approach is to provide a global DB whose layout exactly matches the fragment frame:

// "DB_SendBuffer" - global DB, only used for transmission
TYPE "UDT_SendFragment"
  STRUCT
    FragmentIndex  : UINT;        // 0..1
    FragmentLength : UINT;        // 2..3
    Payload        : ARRAY[0..8187] OF BYTE;   // 4..8191
  END_STRUCT;
END_TYPE

DATA_BLOCK "DB_SendBuffer"
  STRUCT
    Frame : UDT_SendFragment;
  END_STRUCT;
BEGIN
END_DATA_BLOCK

The application code populates Frame.FragmentIndex and Frame.FragmentLength directly. The 8188-byte payload is then written using POKE_BLK from the source diagnostic DB starting at the calculated byte offset:

// SCL fragment for a single POKE_BLK
"POKE_BLK"(
  SRC := #srcVariant,        // Variant pointing at "DB_Diag.Switches[i]"
  OFFSET := 0,               // always 0 because SRC is already the sub-range
  DEST := #destVariant,      // Variant pointing at DB_SendBuffer.Frame.Payload
  DEST_OFFSET := 0,
  COUNT := UINT_TO_DINT(#FragmentLength));
IF "POKE_BLK".STATUS <> 0 THEN
  // 80B4 still possible if the SRC and DEST declared types disagree
  // 80B1 if the Variant itself is invalid
  // 80B2 if OFFSET/COUNT run off the end of DEST
  #LastError := "POKE_BLK".STATUS;
END_IF;

Pros:

  • No AT-overlay gymnastics; layout is static.
  • Single send buffer DB can be reused across all fragments - just refit it each cycle.
  • Maps directly to the DATA parameter of T_SEND_C using a symbolic reference to DB_SendBuffer.Frame.

Cons:

  • Requires a global DB; cannot live inside the FB instance, which is the S7-1200 idiomatic style for encapsulated logic.
  • One extra work-memory copy is performed by POKE_BLK because both areas are not the same declared type (Byte vs. PLC data type).

7. Solution 2 - MOVE_BLK_VARIANT with AT-Overlaid Variant

For an FB-encapsulated implementation, the pattern is to declare a local structure with a Variant overlaid on the payload:

// Inside the FB that performs the fragmentation
VAR
  // Header (always at the lowest addresses)
  Header : STRUCT
    FragmentIndex  : UINT;
    FragmentLength : UINT;
  END_STRUCT;
  // Variant whose data is the byte storage for the header + payload
  // The Variant input is filled by the caller with the source fragment
  SourceVariant : Variant;       // InOut, supplied from outside
  // Local UDT describing the frame as one piece
  Frame : STRUCT
    Header : STRUCT
      FragmentIndex  : UINT;
      FragmentLength : UINT;
    END_STRUCT;
    Payload : ARRAY[0..8187] OF BYTE;
  END_STRUCT;
  // AT-view that reinterprets the Frame as a single Variant-compatible block
  FrameAsVariant AT Frame : Variant;  // NOTE: requires both to be same length
END_VAR

However, the original poster reports that overlaying a Struct and a Variant is not allowed at compile time on the S7-1200 firmware 4.2 because the size of the Variant is 0 at compile time and TIA rejects the size mismatch. The working compromise documented in the field thread is to declare a second Variant right after the header Struct and pass that Variant to MOVE_BLK_VARIANT:

VAR
  Header : STRUCT
    FragmentIndex  : UINT;
    FragmentLength : UINT;
  END_STRUCT;
  // Direct Variant that receives the fragment payload from the caller
  // Caller passes a Variant referencing "DB_Diag.Switches[i]"
  PayloadVariant : Variant;          // InOut
  // The send frame is built by writing Header directly, then invoking
  // MOVE_BLK_VARIANT from PayloadVariant into Frame.Payload (an
  // ARRAY OF BYTE of identical declared length, hence 80B4 is avoided).
  Frame : STRUCT
    Header   : STRUCT
      FragmentIndex  : UINT;
      FragmentLength : UINT;
    END_STRUCT;
    Payload  : ARRAY[0..8187] OF BYTE;
  END_STRUCT;
END_VAR

// Body (excerpt)
#Frame.Header.FragmentIndex  := #CurrentIndex;
#Frame.Header.FragmentLength := #CurrentLength;

#opStatus := "MOVE_BLK_VARIANT"(
  SRC := #PayloadVariant,           // Variant of PLC data type 'SwitchDiag'
  COUNT := UINT_TO_DINT(#CurrentLength),
  SRC_OFFSET := 0,
  DEST := #Frame.Payload,           // ARRAY OF BYTE - this still yields 80B4
  DEST_OFFSET := 0,
  ERR => #opErr);

The 80B4 still occurs if the SRC variant is a UDT_SwitchDiag and the DEST is an ARRAY OF BYTE. The fix documented by the original engineer is to make the destination an ARRAY[0..n] of the same UDT that the source variant points to, then convert the result byte-by-byte or via a second Variant on the caller side. A cleaner pattern that resolves 80B4 in TIA V14 SP1 is the following two-step approach:

// Step 1: copy the sub-range of the source DB into a temporary UDT array
//         (this is allowed because both SRC and DEST are 'ARRAY of UDT')
"MOVE_BLK_VARIANT"(
  SRC := #PayloadVariant,
  COUNT := UINT_TO_DINT(#CurrentLength / SIZEOF(UDT_SwitchDiag)),
  DEST := #TempSwitchArray,        // ARRAY[0..n] OF UDT_SwitchDiag
  ERR  => #opErr);

// Step 2: serialize the UDT array into the byte buffer of the frame
//         using a separate Variant that the caller pre-built over Frame.Payload
"MOVE_BLK_VARIANT"(
  SRC := #TempSwitchArray,         // ARRAY OF UDT_SwitchDiag
  DEST := #FrameAsBytes,           // Variant over the same memory
  ERR  => #opErr);

The key insight: MOVE_BLK_VARIANT refuses a copy when SRC and DEST have declared types that differ, even if they are the same size. Aligning the declared types at both ends (both ARRAY of the same UDT, or both plain BYTE arrays) is the only path to a clean STATUS = 0 return on the S7-1200 instruction set.

8. Why PUT/GET Is Not a Substitute

The question "why not use PUT?" is natural but fails for the following reasons:

  1. Payload cap. S7-1200 PUT is limited in practice to small per-call fields when targeting an S7-400 via CP 443-1. Large arrays must still be fragmented.
  2. No fragmentation primitives. PUT/GET is request/response; sequencing fragments requires the same application-level header/ack protocol as ISO-on-TCP streaming.
  3. Connection resource cost. Each PUT connection reserves a slot in the S7-1200 connection table. ISO-on-TCP with T_SEND_C is more efficient in bulk data scenarios.
  4. CP 443-1 role. The CP 443-1 supports S7 communication but the throughput and the maximum PDU size are still bounded; ISO-on-TCP gives finer control.

9. Implementation Pattern for the FB

The recommended FB structure is:

FUNCTION_BLOCK "FB_SwitchDiagSender"
VAR_INPUT
  // Switch index to start from
  StartIndex : UINT;
  // Total switches to send
  SwitchCount : UINT;
END_VAR
VAR_IN_OUT
  // Source DB handle, declared as a Variant by the caller
  SourceVariant : Variant;
END_VAR
VAR
  // Per-cycle state
  CurrentIndex    : UINT;
  CurrentLength   : UINT;
  FrameBusy       : BOOL;
  FrameDone       : BOOL;
  FrameError      : BOOL;
  LastStatus      : WORD;

  // Send frame layout
  Frame : STRUCT
    Header : STRUCT
      FragmentIndex  : UINT;
      FragmentLength : UINT;
    END_STRUCT;
    Payload : ARRAY[0..8187] OF BYTE;
  END_STRUCT;

  // Connection 1 / 2 instances (T_SEND_C)
  TSend_A : T_SEND_C;
  TSend_B : T_SEND_C;
END_VAR

BEGIN
  // 1. On rising edge of Trigger, compute fragment for CurrentIndex
  // 2. Use MOVE_BLK_VARIANT to fill Frame.Payload from SourceVariant
  // 3. Write Frame.Header.FragmentIndex / FragmentLength
  // 4. Latch TSend_A.REQ on the next cycle if A is idle, else TSend_B
  // 5. On DONE rising edge, increment CurrentIndex and reset REQ
END_FUNCTION_BLOCK

Each cycle the FB emits one fragment of up to 8188 payload bytes. The receiver FB on the S7-414-4H mirrors the structure and assembles fragments in a global DB of size Array[0..MaxSwitches-1] of UDT_SwitchDiag.

10. Verification Procedure

  1. Watch table check. Force CurrentIndex = 0, set SwitchCount = 1. Observe Frame.Header.FragmentIndex updating to 0 and Frame.Payload[0..605] mirroring the first switch's UDT bytes.
  2. Online & Diagnostics. Open the Online & Diagnostics view of the S7-1215, expand CommunicationConnections, and verify that both T_SEND_C instances report Status = 08 (Connection established) and that DONE toggles after each fragment.
  3. Wireshark capture. Mirror the SCALANCE X204 ring port to a laptop with Wireshark. Filter on tcp.port == 102 and confirm that every ISO-on-TCP PDU carries exactly 8192 bytes (the last fragment may be shorter) and that the first 4 bytes of the payload are the header words.
  4. Receiver consistency. On the S7-414-4H side, place a watchpoint on the reassembly DB and verify that the diagnostic bytes written for fragment 0 match the source bytes byte-for-byte.
  5. Throughput sanity. The S7-1200 with firmware 4.2 supports up to 8 ISO-on-TCP connections; the two send connections use 2 of those. Document the count so a future migration does not exceed the connection limit (8 active connections, 8 passive, etc., per the S7-1200 system manual).

11. Field-Proven Pitfalls

  • 80B4 is type-driven, not length-driven. Two areas of identical byte length still produce 80B4 if the declared types differ. Always align the declared types (both ARRAY of UDT or both ARRAY of BYTE).
  • Variant dereference cost. Each MOVE_BLK_VARIANT call performs a runtime type check; the call is slower than a statically typed MOVE_BLK. For tight cycle budgets, prefer the static variant.
  • Connection limits. The S7-1200 reserves connection resources statically when the TSEND_C instances are loaded. Exceeding the resource budget at run time raises 80A1.
  • String UDT alignment. The diagnostic UDT contains String fields. String occupies 2 bytes of header + N characters. When serializing via MOVE_BLK_VARIANT, the byte layout is preserved, but if the receiver uses a STRING-typed DB the receiving PLC must allocate the same maximum length, otherwise the trailing bytes of the last fragment will not fit.
  • Header endianness. S7-1200 is little-endian; the S7-414 is little-endian. WORD/UINT fields are byte-swapped identically, so a naive byte copy works. If a third-party receiver is ever introduced, swap the bytes explicitly.
  • Reset behaviour. After FrameError rise, do not automatically re-send the same fragment. Increment the retry counter and, after N retries, raise a top-level alarm. Duplicate sends are silently accepted by the receiver if the fragment index is not validated.

12. Alternative Architectural Choices

If the application is being built from scratch on newer hardware, the S7-1500 removes many of these constraints:

  • The TSEND_C payload limit on S7-1500 is higher (typically 64 KB) and the instruction natively supports structured types.
  • MOVE_BLK_VARIANT on S7-1500 accepts AT-overlay of Variant on a Struct of identical length; the S7-1200 FW 4.2 build does not.
  • If an S7-1515/1516 is acceptable at the IO Controller position, the same T_SEND_C + MOVE_BLK_VARIANT pattern compiles cleanly without the FB gymnastics documented above.

For the S7-1215 specifically, the documented two-solution pattern (POKE_BLK with a dedicated global DB, or MOVE_BLK_VARIANT with a temporary UDT-array buffer) is the only reliable path. Both have been verified in the field on firmware 4.2 with TIA V14 SP1.

Cross-reference. The pattern of writing structured data into a byte buffer for transmission is conceptually identical to the data-block-to-data-block copy pattern described in Siemens support entry 49717873 - Data transfer between two data blocks for the S7-1200/S7-1500. For logging-style use cases where the data is destined to remain on the S7-1200 (rather than be transmitted), the recipes / data-logging overview for S7-1200/S7-1500 in TIA Portal documents the equivalent CSV-based mechanism on the memory card - see Data logging - Overview (S7-1200, S7-1500).

Why does T_SEND_C on the S7-1200 refuse payloads larger than 8192 bytes?

The S7-1200 instruction set (firmware 4.2 on the S7-1215) limits the user-data payload of a single T_SEND_C / TSEND_C call to 8192 bytes for ISO-on-TCP, TCP, and UDP. Passing LEN > 8192 returns STATUS = 80A1 and the data is not sent. Fragment the payload at the application layer and prepend a routing header to each fragment.

What does error 80B4 from MOVE_BLK_VARIANT mean?

STATUS 80B4 indicates that the source and destination areas are of the same byte length but their declared data types differ (for example, ARRAY OF UDT vs. ARRAY OF BYTE, or two distinct UDTs of identical size). The variant dereference cannot reconcile the descriptors. Match the declared types on both sides of the call, or use a Variant that the caller pre-built over a memory area of the matching declared type.

Can DB_ANY be converted to VARIANT on the S7-1200?

DB_ANY_TO_VARIANT is intended to take the DB number known at runtime and produce a typed VARIANT reference. On firmware 4.2 it can return 8154 when the destination is not of VARIANT type or when the DB number does not resolve. The robust pattern is to use a Variant-typed InOut at the FB interface and let the caller supply a fully typed reference symbolically - the S7-1200 then performs the type check at compile time and the runtime call is unambiguous.

Why is the legacy BLKMOV with an ANY pointer not available on the S7-1200?

The S7-1200 dropped register-based STL addressing (LAR1, AR1/AR2, P##) and the corresponding BLKMOV semantics. Block moves on the S7-1200 must be expressed with typed parameters, and any runtime variation of source/destination/offset must be expressed through VARIANT inputs. The closest functional equivalent of the S7-400 BLKMOV on the S7-1200 is POKE_BLK or MOVE_BLK_VARIANT with Variants whose declared types match.

Can the S7-1215 FW 4.2 act as PROFINET IO Controller and run an ISO-on-TCP sender at the same time?

Yes. The S7-1215 supports PROFINET IO Controller mode with MRP ring participation and concurrently up to 8 active ISO-on-TCP connections (subject to connection resource limits documented in the S7-1200 system manual, article ID 109751325). The two T_SEND_C instances for the redundant S7-414-4H link and the IO Controller role share the same PROFINET interface but do not contend for connection resources.

Back to blog