TIA Portal V13 Block Compare: S7-1200/1500 PUT Trigger Pattern

David Krause12 min read
SiemensTIA PortalTutorial / How-to
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

1. Problem Overview

Multi-CPU plants that use one S7-1500 (or S7-1500SP) as a head CPU and several S7-1200 / S7-300 stations as slaves waste a large amount of Profinet bandwidth when the head polls every station on a fixed cycle. The standard remedy is event-driven PUT/GET: a slave only transmits when its local process data actually changed. The challenge is the block compare stage that decides whether a change has occurred.

The reference architecture used throughout this article is:

  • 1 × CPU 1510SP-1 PN (software controller, ET200SP) acting as head.
  • 9 × CPU 1215C DC/DC/DC (firmware V4.x) acting as substations.
  • 1 × CPU 315-2 PN/DP (legacy station, S7-300, used only to demonstrate the STL alternative).
  • TIA Portal V13 SP1 (engineering).

Each slave holds a process image in DB101. The slave keeps DB102 as the previous-state shadow. On every PLC cycle the slave compares DB101 vs DB102, and only when a mismatch exists does it arm a PUT that ships DB101 to the head CPU. After the PUT completes, DB102 is refreshed with the new DB101 contents.

This pattern deliberately avoids GET polling. Each slave is the publisher of its own data; the head only consumes. Master-side polling on 9 slaves at 100 ms produces roughly 9 × 10 = 90 GET requests/s, whereas event-driven PUT at a typical 2 changes/min per slave yields about 0.3 PUT/s — a >200× traffic reduction.

2. Architecture and Communication Topology

1510SP-1 PN Head CPU PUT/GET client Profinet / S7 Connection 1215 Slave #1 1215 Slave #2 1215 Slave #3 ... 9 total

Figure 1 — 1 head + 9 slaves, PUT/GET over Profinet, event-driven only.

3. Prerequisites

  1. TIA Portal V13 SP1 (V13.0 + SP1) or higher; the SCL syntax used here is valid up to V17.
  2. Firmware ≥ V4.0 on the S7-1215 CPUs to support PEEK_DWORD/POKE_DWORD.
  3. S7-1500 CPU 1510SP-1 PN firmware ≥ V1.8 (for software controller PUT/GET limits).
  4. Configured S7 connections between head and each slave (PUT/GET permission enabled in the connection properties).
  5. Profinet network with the slaves visible in the device view (no DNS, fixed IP or DHCP reservation).
CPU 1510SP imposes a hard limit of 16 simultaneous PUT/GET connections for S7-1200/1500 partners. With 9 slaves + 1 spare you remain well inside the limit; check Siemens entry 109751826 for the current per-CPU maximum.

4. DB Configuration — Non-Optimized Access

Both DB101 (live) and DB102 (shadow) must be created with the "Optimized block access" attribute disabled. Optimized blocks hide the absolute byte layout, so PEEK/POKE and any %DBx.DBDy style address would not compile.

  1. In the project tree, right-click DB101 → Properties → Attributes.
  2. Uncheck Optimized block access.
  3. Confirm that Setpoint by user is selected so the block starts at offset 0.
  4. Repeat for DB102 with the identical structure (use Update block type if you keep one as the type).
Property DB101 (live) DB102 (shadow)
Optimized access Disabled Disabled
Accessible from HMI/OPC UA Enabled Disabled
Data block number 101 102
Size (initial) 20 bytes (5 DWORD) 20 bytes (5 DWORD)
Retain No No

Keep symbolic naming on each tag for HMI / TIA visibility, but the address space stays stable for the compare function.

5. SCL Block Compare Function Block

The reusable FB walks the source and shadow DBs in DWORD chunks and produces both a boolean change flag and a bitmask identifying which DWORD changed. Using DWORD granularity halves the loop iterations versus a byte compare and aligns naturally with the 32-bit Profinet payload typical of PUT/GET.

FUNCTION_BLOCK "FB_BlockCompare_Diff"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
VAR_INPUT
    i_dbSource : INT;       // source DB number (e.g. 101)
    i_dbTarget : INT;       // target DB number (e.g. 102)
    i_numDword : INT;       // number of DWORDs to compare (5 = 20 bytes)
END_VAR
VAR_OUTPUT
    o_changed  : BOOL;      // TRUE if any DWORD differs
    o_diffMask : DWORD;     // bit n = 1 → DWORD n differs
END_VAR
VAR
    s_srcDword  : DWORD;
    s_tgtDword  : DWORD;
    s_offset    : DINT;
    s_i         : INT;
END_VAR
BEGIN
    o_changed  := FALSE;
    o_diffMask := DWORD#16#0;

    // Guard against out-of-range inputs
    IF (i_dbSource < 1) OR (i_dbTarget < 1) THEN
        RETURN;
    END_IF;
    IF (i_numDword < 1) OR (i_numDword > 64) THEN
        RETURN;                       // 64 DWORD = 256 bytes max here
    END_IF;

    FOR s_i := 0 TO i_numDword - 1 DO
        s_offset   := INT_TO_DINT(s_i) * 4;
        s_srcDword := PEEK_DWORD(area := 16#84,
                                  dbNumber := i_dbSource,
                                  byteOffset := s_offset);
        s_tgtDword := PEEK_DWORD(area := 16#84,
                                  dbNumber := i_dbTarget,
                                  byteOffset := s_offset);
        IF s_srcDword <> s_tgtDword THEN
            o_diffMask := o_diffMask OR SHL(IN := DWORD#1, N := s_i);
            o_changed  := TRUE;
        END_IF;
    END_FOR;
END_FUNCTION_BLOCK;

The two PEEK calls use the area code 16#84, which is the standard "DB area, byte access" selector used internally by PEEK/POKE on S7-1200/1500. A complete table is maintained in the SCL PEEK/POKE reference (entry 57374718).

6. Change Detection and Copy-to-Shadow Logic

Instantiate the FB in OB1 (or in a cyclic interrupt OB if the slave has heavy math) and use the boolean change signal to drive a rising-edge trigger for the PUT and for the DB101 → DB102 refresh. The refresh must occur after the PUT completes — otherwise the next cycle's compare sees the same value as the sent one and never arms again.

// --- Input tag DB ---
DATA_BLOCK "iDB_Compare"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAIN
  i_dbSource : INT  := 101;
  i_dbTarget : INT  := 102;
  i_numDword : INT  := 5;
END_DATA_BLOCK

// --- Static tags ---
DATA_BLOCK "sDB_CompareState"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAIN
  stat_changedPrev : BOOL;
  stat_changePulse : BOOL;
  stat_diffMask    : DWORD;
  stat_putReq      : BOOL;
  stat_copyDone    : BOOL;
END_DATA_BLOCK
// In OB1 (SCL segment)
"inst_FB_BlockCompare_Diff"(i_dbSource := "iDB_Compare".i_dbSource,
                            i_dbTarget := "iDB_Compare".i_dbTarget,
                            i_numDword := "iDB_Compare".i_numDword);

"sDB_CompareState".stat_diffMask := "inst_FB_BlockCompare_Diff".o_diffMask;

"sDB_CompareState".stat_changePulse :=
        "inst_FB_BlockCompare_Diff".o_changed
        AND NOT "sDB_CompareState".stat_changedPrev;

"sDB_CompareState".stat_changedPrev :=
        "inst_FB_BlockCompare_Diff".o_changed;

// Arm PUT on rising edge of the change flag
IF "sDB_CompareState".stat_changePulse THEN
    "sDB_CompareState".stat_putReq := TRUE;
END_IF;

// Copy DB101 → DB102 only after PUT finished OK
IF "sDB_CompareState".stat_copyDone THEN
    FOR #s_i := 0 TO "iDB_Compare".i_numDword - 1 DO
        #s_offset := INT_TO_DINT(#s_i) * 4;
        #s_srcDword := PEEK_DWORD(area := 16#84,
                                   dbNumber := "iDB_Compare".i_dbSource,
                                   byteOffset := #s_offset);
        POKE_DWORD(area := 16#84,
                   dbNumber := "iDB_Compare".i_dbTarget,
                   byteOffset := #s_offset,
                   value := #s_srcDword);
    END_FOR;
    "sDB_CompareState".stat_copyDone := FALSE;
END_IF;

7. PUT Instruction Setup

Drop the standard PUT instruction (Instructions → Communication → S7 Communication) in OB1. The minimum parameter wiring is shown below; refer to the SIMATIC S7-1500 Communication function manual for full descriptions.

Parameter Direction Data type Wiring Note
REQ IN BOOL stat_putReq Rising edge starts the transfer.
ID IN WORD Configured connection ID 1–255 per connection.
DONE OUT BOOL stat_putDone TRUE for one cycle on success.
ERROR OUT BOOL stat_putErr TRUE on failure.
STATUS OUT WORD stat_putStatus Hex error code (see §11).
ADDR_1 IN_OUT ANY P#DB101.DBX0.0 BYTE 20 Source on the slave.
SD_1 IN_OUT ANY P#DB201.DBX0.0 BYTE 20 Destination on the head.
LEN IN_OUT INT 20 Bytes; max 400 with S7-1200, 8192 with S7-1500.

On DONE rising edge, set stat_copyDone := TRUE and stat_putReq := FALSE. On ERROR, clear stat_putReq, latch stat_putStatus and raise an HMI bit so the operator can clear the alarm manually after root-cause analysis.

8. S7-300 STL Alternative (Peek / Poke with ANY Pointer)

S7-300 does not expose PEEK_DWORD/POKE_DWORD in SCL. Replace the loop with STL in a dedicated FB:

FUNCTION FC 100 : VOID
// Inputs in TEMP: srcDB (INT), tgtDB (INT), numDW (INT), idx (INT)
// Output flag: stat_changed (BOOL), stat_diffMask (DWORD)

      L     #srcDB
      T     #tDB                           // current DB number
      L     P#DBX 0.0                      // start pointer
      T     #pSrc                          // AR1 later

      L     #tgtDB
      T     #tDB
      L     P#DBX 0.0
      T     #pTgt

      L     0
      T     #idx
      L     #numDW
      T     #limit

LOOP: NOP   0
      L     #idx
      L     #limit
      >=I                                     // idx >= numDW → done
      JC     ENDLP

      OPN   DB [#srcDB]                    // open source
      L     DBW [AR1,P#0.0]                // read 16-bit at AR1+0
      T     #wSrc                          // (use DBW; for DWORD, do two reads)

      OPN   DB [#tgtDB]
      L     DBW [AR1,P#0.0]
      T     #wTgt

      L     #wSrc
      L     #wTgt
      <>I
      JC    SETBIT

NEXT: L     #idx
      +     1
      T     #idx
      L     P#4.0                          // next DWORD
      +AR1  #pSrc                          // advance pointer
      +AR1  #pTgt
      JU    LOOP

SETBIT: SET
      =     #stat_changed
      L     #idx
      SLW   1                              // bit per DWORD
      L     #stat_diffMask
      OW
      T     #stat_diffMask
      JU    NEXT

ENDLP: NOP   0
      BE

Use AUF DB [#srcDB] with the variable DB number from a static tag. Note that on S7-300, the DB must be loaded with AUF DB [...] or compiled to a fixed DB; an ANY pointer built dynamically is the standard workaround when the DB number is parameterised.

Avoid LAR1 P#DBX 0.0 on S7-300 if you intend to address bytes and words. Use separate area-cross pointers (P# + +AR1) and remember that AR1 / AR2 must be saved/restored at FB entry/exit to avoid corrupting the calling environment.

9. 1510SP Software Controller Limitations

The S7-1500 Software Controller inherits the S7-1500 instruction set but the runtime is shared with Windows / the host. Several field-confirmed caveats:

  • The application example 40556214 (s7-komm_sync) is not compatible with 1510SP as a head CPU; it was designed for S7-1500 hardware controllers. Re-implement the comm logic in the user program as shown above.
  • The Any-Pointer variant of the PUT/GET instructions (POKE_BLK, BLKMOV with dynamic ANY) can return STATUS 80A1 ("DB does not exist on partner") during warm restart while the WinAC service re-attaches. Wrap every PUT/GET call with a retry counter (max 3, 200 ms apart).
  • Software controller cycle times are jittery under heavy host load; place the compare FB in OB1 (not OB35) to keep it consistent with PUT/GET ordering.
  • The 1510SP will reset all active S7 connections when its host enters standby or hibernation; head-side tags must tolerate gaps (use a watchdog in the head DB).

10. Network Traffic Optimization

Even after event-driven PUT, several tuning levers remain. The Profinet update budget on a 100 Mbit/s Profinet segment is dominated by the PUT/GET packet overhead (~80 bytes per call) when payloads are tiny; merging 9 slaves' payloads into a single 160-byte PUT (slave 1 = PUT, slave 2 = GET into shared buffer, etc.) is not supported by the protocol — each S7 connection remains independent.

Optimization Mechanism Typical gain
Event-driven PUT Compare + rising-edge trigger (this article) ~200× vs 100 ms polling
Coalesce multiple DBs Combine DB101 + DB103 into DB110; resize PUT LEN to 40 bytes ~40% fewer packets
Rising-edge filter on bits Use a per-bit "dirty" bit set when a value changes; compare only when the dirty bit is set ~10× CPU reduction on noisy data
Update-time tuning Profinet IO update 1 ms (head) vs 4 ms (slaves) lowers burst impact Stable under load
Suppress redundant bits Hysteresis / dead-band before writing into DB101 Eliminates chatter-induced PUTs

A per-bit dirty mask is built exactly the same way as the compare FB but on the producer side — every time you write a value into DB101, also set the corresponding bit in a DWORD mask. The compare loop short-circuits: if dirtyMask = 0, skip the loop entirely.

11. Verification and Commissioning

  1. Online → Monitor / Modify: Open DB101, change a bit, observe stat_changed going TRUE for one cycle, stat_putReq arming, then PUT DONE firing.
  2. Trace: In TIA Portal, configure a trace on stat_changed, stat_putReq, stat_putDone, stat_diffMask. Run for 60 s; you must see edges only on state changes, not on every cycle.
  3. Wireshark on the Profinet segment (SPAN port): the count of S7 Comm PUT packets should match the count of stat_putReq rising edges within ±1.
  4. Head-side validation: In the head CPU, create DB201 (non-optimized, 20 bytes) and bind it to the receiving SD_1 slot. Cross-check DB201 against the slave's DB101 via a separate GET (debug only, disable in production).
  5. Latency budget: end-to-end (slave write → head read) typically ≤ 30 ms with 9 slaves and 100 Mbit Profinet. If higher, check OB1 scan time and 1510SP host CPU usage.

12. Troubleshooting Matrix

Symptom Likely cause Diagnostic Fix
PUT STATUS = 80A1 Destination DB on partner missing Head DB201 not present or optimized Create non-optimized DB201 of same length
PUT STATUS = 80B1 LEN exceeds partner bounds LEN > 400 on S7-1200 Cap LEN at 400 for S7-1200 destinations
PUT STATUS = 80A4 Connection not configured Devices & Networks → Connections empty Add S7 connection with PUT/GET permission
Compare always TRUE DB102 never written Online monitor DB102 vs DB101 Ensure stat_copyDone branch executes after PUT DONE
Compare never TRUE DB101 never changes Force a tag, watch trace Confirm the HMI/write logic actually updates DB101
Compile error "PEEK not allowed" DB optimized DB properties Disable optimized access on DB101/102
STL: AREA OVERFLOW AR1 not saved at FB start First lines of FB Add TAR1/LAR1 save/restore
1510SP resets connections hourly Host standby or WinAC restart Windows event log Disable host sleep, watchdog on head DB
High CPU on 1215 Loop runs every OB1 cycle Task runtime > 5 ms Add dirtyMask pre-check

13. FAQ

Do I really need to disable optimized block access on DB101 and DB102?

Yes. PEEK/POKE on S7-1200/1500 and any indirect STL addressing require non-optimized blocks; otherwise the compiler rejects the call with "PEEK not allowed" or "Area length error". Keep the symbolic view for HMI; only the storage layout changes.

Why not just use GET from the head instead of PUT from every slave?

GET-from-head forces 9 slaves × N Hz polling traffic, regardless of whether data changed. With 9 slaves at 100 ms, the head generates roughly 90 GET/s; event-driven PUT reduces this to a few per minute per slave — a >200× reduction. The slave is also a better owner of "what just changed" than the head.

Can the same pattern run on an S7-1500 CPU that is not the 1510SP software controller?

Yes — the SCL compare, PEEK/POKE and PUT logic are identical on S7-1511, S7-1515 and S7-1518 hardware controllers. The 1510SP-specific caveats in §9 are the only differences (host jitter, warm-restart STATUS 80A1).

What is the maximum payload per PUT between an S7-1200 and S7-1500?

400 bytes when an S7-1200 is either side; 8192 bytes when both endpoints are S7-1500. For 9 slaves each pushing ≤ 256 bytes, a single PUT is well within the limit. Reference: SIMATIC S7-1500 Communication manual.

How do I handle the case where DB101 grows from 20 to 80 bytes after commissioning?

Either change i_numDword from 5 to 20 and update both DBs and the PUT LEN (16 → 80), or — preferred — wire the FB's i_numDword to a tag that you compute at startup from the DB header length. Read the DB length via DB_GET_LENGTH in SCL (S7-1500 only) or by PEEK_WORD at offset 0 on non-optimized DBs (each DB header stores its own length in bytes at offset 0 on S7-300/400).

Does the PUT trigger need a rising-edge or is level-sensitive triggering acceptable?

Always use a rising-edge trigger (REQ = TRUE for one cycle). Level-sensitive REQ on the SIMATIC PUT keeps the connection occupied and prevents other connections from being serviced. Use a R_TRIG on stat_changed if you prefer FBD over SCL.

Back to blog