S7-1200 to S7-1500 Heartbeat: PUT/GET Watchdog Configuration
A heartbeat between two Siemens S7-1200 and S7-1500 controllers is the lowest-cost, highest-reliability liveness check available on a Profinet/S7 LAN. The pattern is the same one used by Spectrum Controls gateway heartbeat tags and by every industrial protocol that supports a "deadman" (the controller, the SCADA, and the protocol bus itself all assume the peer will miss a cycle if it is no longer healthy). This reference shows how to design, code, configure, and verify a bidirectional heartbeat using only built-in PUT/GET instructions, the standard S7 connection resource, and the TIA Portal diagnostic buffer.
1. Heartbeat Architecture
A heartbeat is a small, periodic, verifiable data exchange between two controllers. The receiver increments (or toggles) a value, the sender reads it back, and the receiver confirms it is seeing fresh data from the sender. The minimum useful heartbeat contains three pieces of information:
- A monotonic counter that increments every scan on the producer side. A non-incrementing counter is, by itself, proof that the producer is stuck, halted, or disconnected.
- A watchdog timeout on the consumer side. If the counter has not changed in N milliseconds, the consumer declares the link dead and executes the configured fail-safe routine.
- A mutual qualifier so that both sides know the other side has just consumed the most recent value. A simple 1-bit "I saw your counter last cycle" flag is sufficient.
The key design decision is: do you need the heartbeat to advance every CPU scan, or only every Nth scan? PUT/GET instructions are not free; they consume connection resources and they trigger a partner-side update at the partner's cycle. If you set REQ every scan of a fast OB1, you will eventually starve the S7 connection queue on a CPU 1211C. If you set REQ only on a positive edge, the data you read back is the value the partner wrote at the time of its last successful PUT, not a fresh value.
2. Prerequisites
| Item | Minimum requirement | Notes |
|---|---|---|
| CPU 121xC | Firmware 4.2 or later | Older firmware lacks the GET/PUT multi-instance support used here |
| CPU 151x | Firmware 2.6 or later | Firmware 2.9 recommended for OPC UA coexistence |
| TIA Portal | V17 or later | Required for the multi-instance PUT/GET DB pattern |
| S7 connection resource | 1 PUT + 1 GET per direction | Set in Devices & Networks > S7 connection |
| PUT/GET access right | Permitted on partner | S7-1500 CPU > Properties > Protection & Security > Connection mechanisms |
| Profinet interface | Same subnet, reachable by TCP | Port 102 must be open; check corporate firewall if routed |
| IP plan | Static addresses, no DHCP | Heartbeat is meaningless if the partner is at the wrong IP |
3. S7 Communication: PUT/GET Building Blocks
PUT and GET are the only instructions that need a configured S7 connection in TIA Portal. They are the lowest-level, lowest-overhead, highest-priority way to move a small block of memory between two S7 controllers on the same subnet. The full instruction set is documented in the S7-1500 system manual and the S7-1200 system manual.
| Parameter | PUT | GET |
|---|---|---|
| REQ | BOOL, edge-triggered. One transfer per rising edge. | BOOL, edge-triggered. One read per rising edge. |
| ID | WORD, the local connection ID from TIA Portal | WORD, the local connection ID from TIA Portal |
| DONE | BOOL, TRUE for one cycle on success | BOOL, TRUE for one cycle on success |
| BUSY | BOOL, TRUE while transfer in progress | BOOL, TRUE while transfer in progress |
| ERROR | BOOL, TRUE with STATUS valid | BOOL, TRUE with STATUS valid |
| STATUS | WORD, 0x0000 on success, error code on failure | WORD, 0x0000 on success, error code on failure |
| ADDR_1 | Remote target (write to) | Remote source (read from) |
| SD_1 / RD_1 | Local source (write from) | Local destination (read into) |
The standard error codes for both blocks are identical. The most important ones for a heartbeat are listed below; full enumeration is in the S7-1500 system manual.
| STATUS (hex) | Meaning | Heartbeat action |
|---|---|---|
| 0x0000 | No error | None |
| 0x7000 | No job active | Trigger a new REQ |
| 0x7001 | Job started, BUSY=TRUE | Wait |
| 0x7002 | Job running | Wait |
| 0x80A1 | User data length error | Fix ADDR_1 area pointer |
| 0x80B0 | No free connection resource | Free a connection or reduce rate |
| 0x80C3 | Access denied (PUT/GET disabled) | Enable on partner CPU |
| 0x80C4 | Partner not reachable | Increment comm-fail counter, raise alarm |
| 0x80D0 / 0x80D1 | Partner CPU in STOP / cold start | Hold last good heartbeat, alarm |
| 0x80F0 | Internal error | Reboot, see diagnostic buffer |
4. TIA Portal: Configuring the S7 Connection
- Open the project containing both CPUs. In Devices & Networks, drag from the S7-1200 Profinet port to the S7-1500 Profinet port. TIA Portal auto-creates an S7 connection with the local ID assigned to the source CPU.
- Select the S7 connection line. In the properties pane, set Connection type = S7 connection, Active connection establishment = local endpoint active, and confirm the partner IP.
- Open the S7-1200's Properties > Protection & Security > Connection mechanisms and confirm Permit access with PUT/GET communication from remote partners is ticked. Repeat on the S7-1500.
- Compile and download both stations. Verify the connection in the Online & Diagnostics view: it should show Established within 10 seconds.
- Create the heartbeat data blocks. On each CPU, create an Optimized block global DB named
DB_HB_Localwith the structure shown in Section 6.
5. Heartbeat Tag Design
The smallest useful heartbeat payload is 12 bytes per side: 8 bytes for a DWord monotonic counter, 2 bytes for a watchdog timeout, and 2 bytes for status flags. In an optimized DB, the structure is symbolic and order-independent; in a standard DB the offset matters. Use a standard DB for PUT/GET because absolute addressing is mandatory for the remote ADDR_1 pointer.
5.1 Producer-side DB (non-optimized)
DATA_BLOCK "DB_HB_1200"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAIN
STRUCT
Counter : DINT; // Increments every scan, wraps at 2^31
TimeoutMs : INT; // 1500 typical for a 100 ms scan partner
Status : BYTE; // 0x01 = RUN, 0x02 = partner-seen, 0x80 = local fault
Spare : ARRAY[0..4] OF BYTE;
END_STRUCT;
END_DATA_BLOCK
5.2 Consumer-side DB (non-optimized)
DATA_BLOCK "DB_HB_Remote_1200"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAIN
STRUCT
LastCounter : DINT; // Last value seen from partner
DiffMs : DINT; // Milliseconds since last increment
WatchdogOK : BOOL; // TRUE while partner is healthy
Spare : ARRAY[0..3] OF BYTE;
END_STRUCT;
END_DATA_BLOCK
5.3 The counter wrap problem
A DINT counter wraps from 2 147 483 647 to -2 147 483 648. At 10 ms per increment that is roughly 24 days. Two practical mitigations:
- Compare
(NewCounter - LastCounter) MOD 2^31. Any result other than 1 means the counter moved by more than one tick or wrapped; both are valid as long as the absolute difference is plausible. - Reset the counter to 0 on cold restart, and require the consumer to accept the new value as authoritative for the next cycle. This is what the Siemens-recommended pattern does.
6. Implementing the Heartbeat in OB1
The simplest, most reliable implementation puts everything in OB1. The pattern below is the SCL version; the equivalent LAD uses a single network with an ADD box feeding the counter and a second network with the PUT block.
6.1 Producer logic (S7-1200, SCL)
// Network 1: increment local counter
IF "DB_HB_1200".Status <> 16#80 THEN
"DB_HB_1200".Counter := "DB_HB_1200".Counter + 1;
END_IF;
// Network 2: send to S7-1500 every scan, on REQ rising edge
"Inst_PUT_to_1500"(REQ := "_ScanTick_50ms",
ID := 1,
DONE=>"DB_HB_1200".DONE,
BUSY=>"DB_HB_1200".BUSY,
ERROR=>"DB_HB_1200".ERROR,
STATUS=>"DB_HB_1200".PUT_STATUS,
ADDR_1 := P#DB_HB_1500.DBX0.0 BYTE 12,
SD_1 := P#DB_HB_1200.DBX0.0 BYTE 12);
// Network 3: read back from S7-1500
"Inst_GET_from_1500"(REQ := "DB_HB_1200".DONE,
ID := 2,
DONE=>"DB_HB_Remote_1500".DONE,
BUSY=>"DB_HB_Remote_1500".BUSY,
ERROR=>"DB_HB_Remote_1500".ERROR,
STATUS=>"DB_HB_Remote_1500".GET_STATUS,
ADDR_1 := P#DB_HB_1200_Remote.DBX0.0 BYTE 12,
RD_1 := P#DB_HB_Remote_1500.DBX0.0 BYTE 12);
"_ScanTick_50ms" is a clock bit generated by a periodic interrupt OB (OB30 to OB38) configured for 50 ms. Driving PUT/GET with a fixed-frequency tick instead of a free-running OB1 scan decouples the heartbeat from CPU load and gives a stable, known maximum latency on both sides.
6.2 Watchdog evaluation (S7-1200, SCL)
// Network 4: detect counter movement
IF "DB_HB_Remote_1500".LastCounter <> "DB_HB_Remote_1500".NewCounter THEN
"DB_HB_Remote_1500".LastCounter := "DB_HB_Remote_1500".NewCounter;
"DB_HB_Remote_1500".DiffMs := 0;
"DB_HB_Remote_1500".WatchdogOK := TRUE;
ELSE
"DB_HB_Remote_1500".DiffMs := "DB_HB_Remote_1500".DiffMs + "OB1_Scan_ms";
IF "DB_HB_Remote_1500".DiffMs > "DB_HB_1200".TimeoutMs THEN
"DB_HB_Remote_1500".WatchdogOK := FALSE;
END_IF;
END_IF;
The symmetric block is duplicated on the S7-1500, swapping the connection IDs and the partner DB references.
7. Optimizing Scan Response with a Cyclic Interrupt OB
The OB1-only pattern above has one weakness: PUT/GET are non-preemptive. If a long-running FB blocks OB1, the heartbeat stalls. The Siemens-recommended fix is to move the REQ edge generation into a cyclic interrupt OB (OB30 to OB38) and let OB1 only toggle the trigger flag.
7.1 Why use a cyclic interrupt at all
Three reasons, in order of importance:
- Bounded latency. A cyclic interrupt OB is started by the CPU hardware timer; its start time is independent of OB1 duration. The maximum jitter is the cyclic period itself.
- Decoupling from heavy FBs. If OB1 is blocked for 200 ms by a recipe load, the cyclic interrupt still fires. The heartbeat will continue, and the operator will see the stalled OB1, not a confused comm error.
- Edge generation. PUT/GET consume one job per rising edge of REQ. Running them from a 50 ms cyclic interrupt gives a known, repeatable rate regardless of OB1 cycle time.
7.2 Configuration of OB30 (cyclic interrupt, 50 ms)
| Property | Value | Notes |
|---|---|---|
| OB number | OB30 | Available on both S7-1200 and S7-1500 |
| Phase offset | 0 ms | Set to 0 for the first cyclic interrupt |
| Cycle time | 50 ms | Configurable from 1 ms to 60 000 ms |
| Priority | 9 (default) | Lower than hardware interrupts (16+), higher than OB1 (1) |
| Watchdog time | 200 ms | 4x cycle to absorb one missed trigger |
7.3 Body of OB30 (SCL, S7-1200 side)
// Network 1: rising-edge one-shot for PUT REQ
IF "_HB_Tick_Old" = FALSE AND "_HB_Tick" = TRUE THEN
"_HB_PUT_REQ" := TRUE;
END_IF;
"_HB_Tick_Old" := "_HB_Tick";
"_HB_Tick" := TRUE;
// Network 2: PUT triggered
"Inst_PUT_to_1500"(REQ := "_HB_PUT_REQ",
ID := 1,
DONE=>"_HB_PUT_Done",
BUSY=>"_HB_PUT_Busy",
ERROR=>"_HB_PUT_Err",
STATUS=>"_HB_PUT_Status",
ADDR_1 := P#DB_HB_1500.DBX0.0 BYTE 12,
SD_1 := P#DB_HB_1200.DBX0.0 BYTE 12);
// Network 3: clear REQ when finished (or when BUSY has stayed too long)
IF "_HB_PUT_Done" OR "_HB_PUT_Err" THEN
"_HB_PUT_REQ" := FALSE;
END_IF;
// Network 4: GET, on PUT-DONE edge
IF "_HB_PUT_Done" THEN
"_HB_GET_REQ" := TRUE;
END_IF;
"Inst_GET_from_1500"(REQ := "_HB_GET_REQ",
ID := 2,
DONE=>"_HB_GET_Done",
BUSY=>"_HB_GET_Busy",
ERROR=>"_HB_GET_Err",
STATUS=>"_HB_GET_Status",
ADDR_1 := P#DB_HB_1200_Remote.DBX0.0 BYTE 12,
RD_1 := P#DB_HB_Remote_1500.DBX0.0 BYTE 12);
IF "_HB_GET_Done" OR "_HB_GET_Err" THEN
"_HB_GET_REQ" := FALSE;
END_IF;
8. Edge Cases and Failure Modes
8.1 Partner CPU in STOP
An S7-1500 in STOP keeps the S7 connection open and returns STATUS = 0x80D0 on GET. The counter does not advance. The watchdog times out at TimeoutMs (typical 1500 ms) and WatchdogOK goes FALSE. This is the correct behavior; do not treat ERROR = TRUE on GET as a connection failure, treat WatchdogOK = FALSE as the failure.
8.2 Partner unreachable (cable unplugged)
The local CPU keeps requesting. The first few requests return STATUS = 0x80C4 (partner not reachable). After the S7 keep-alive timeout (default 30 s, configurable in the S7 connection properties), the connection is torn down and PUT/GET return STATUS = 0x80B0 (no free connection resource) until the link is re-established. Reduce the keep-alive interval to 5 s for fast detection: open the S7 connection properties in TIA Portal and change Connection setup / monitoring.
8.3 OB1 scan longer than TimeoutMs
If OB1 takes longer than the watchdog timeout, the consumer will declare the producer dead while the producer is still incrementing correctly. The fix is to use the cycle time measurement (OB1 in the S7-1500 exposes OB1_PREV_CYCLE and OB1_CYCLE_TIME) and to set TimeoutMs to at least 3x the maximum measured OB1 cycle time on the producer side.
8.4 Multiple S7 connections sharing a single PUT/GET
The same PUT/GET instruction instance cannot be used for two partners; instantiate one PUT and one GET per partner. The S7-1200 connection budget is 8, so this is rarely a problem; on a CPU 1211C with several HMI tags it can be.
9. Profinet DeviceStates as a Lightweight Alternative
For systems where the only thing that matters is "is the partner CPU still on the network," the DeviceStates instruction (S7-1500) or the equivalent PNIO_ALARM blocks on the S7-1200 provide a hardware-driven answer without any application code. DeviceStates is documented in the S7-1500 system manual, chapter "Profinet diagnostics".
// S7-1500: check partner device state
"Inst_DeviceStates"(REQ := TRUE,
LADDR := 64, // HW ID of the S7-1200 PN port
MODE := 1, // 1 = all devices
RET_VAL => "_DS_RetVal",
BUSY => "_DS_Busy",
ERROR => "_DS_Err",
STATE => "_DS_State");
// _DS_State bit 0 = device 0 OK, 1 = device 0 not reachable, ...
DeviceStates is driven by the Profinet stack, not by application code, so it is immune to OB1 stalls. It cannot, however, detect a partner CPU that is in STOP (the Profinet link is still up). For full coverage, run DeviceStates and the PUT/GET heartbeat.
10. Diagnostic Buffer Analysis
When the heartbeat fails, the first place to look is the diagnostic buffer. From the S7 CPU's Online & Diagnostics view, sort by time descending and look for the following entries:
| Event ID | Meaning | Heartbeat symptom |
|---|---|---|
| 0x013C | Communication error, partner not reachable | Cable, switch, IP issue |
| 0x015B | S7 connection established | Recovery event |
| 0x0254 | Connection terminated (local) | PUT/GET returned 0x80B0 |
| 0x0282 | Watchdog of cyclic interrupt elapsed | Cyclic interrupt body too long |
| 0x35xx | Profinet IO fault | Switch port down, partner CPU stopped |
Detailed enumeration: Siemens KB 109751799 - Diagnostic buffer event IDs.
11. Performance and Tuning
| Cycle time of OB30 | PUT/GET load | Recommended for |
|---|---|---|
| 10 ms | ~6% of S7 connection bandwidth per direction | High-speed line, <50 ms safety response |
| 50 ms | ~1.5% per direction | Default; covers 95% of installations |
| 100 ms | <1% per direction | Many peers, HMI-heavy CPU |
| 500 ms | Negligible | Process cells where liveness within 2 s is acceptable |
The TimeoutMs should be at least 3x the OB30 cycle time and at least 3x the worst observed OB1 cycle time on the partner. A common value is 1500 ms with a 50 ms cyclic interrupt: 30x the cycle, ample margin for one or two missed ticks.
12. Verification and Commissioning Checklist
- Both stations are online; S7 connection shows Established in Online & Diagnostics.
- Monitor
DB_HB_1200.CounterandDB_HB_1500.Counterin a watch table. Both must increment at the OB30 rate. - Force a partner CPU STOP. Within
TimeoutMs+ one OB30 cycle, the localWatchdogOKmust go FALSE. - Restart the partner. Within one OB30 cycle after the partner comes back, both counters must resume incrementing and
WatchdogOKmust return TRUE. - Disconnect the patch cable. The local
PUT_STATUSmust transition to 0x80C4 within one keep-alive interval, andWatchdogOKmust go FALSE within 3 OB30 cycles of the new keep-alive interval (if the interval is shortened to 5 s). - Reconnect.
WatchdogOKmust return TRUE without operator intervention. - Check the diagnostic buffer. No recurring 0x0282 (cyclic interrupt watchdog) or 0x80B0 (no connection) entries should appear during normal operation.
TimeoutMs in the function specification. Many field issues come from a replacement CPU being installed with a different firmware that defaults the PUT/GET access to disabled.What is the minimum number of PUT/GET instructions for a bidirectional S7-1200/S7-1500 heartbeat?
Two: one PUT and one GET on each side, total four instructions. The PUT on the S7-1200 writes the local counter to the S7-1500; the GET on the S7-1200 reads the S7-1500's counter back. The reverse pair runs on the S7-1500. This gives each CPU its own monotonic counter and a watchdog on the partner's counter.
Why is my GET returning STATUS 0x80C3 even though the connection is established?
Access is denied. The S7-1500 default behavior on firmware 2.0 and later is to reject PUT/GET from any partner. Open the S7-1500 CPU's Properties > Protection & Security > Connection mechanisms and enable Permit access with PUT/GET communication from remote partners. Download the configuration and the GET will return 0x0000 within one cycle.
How fast should the OB30 cyclic interrupt run for a heartbeat?
50 ms is the recommended default. It keeps the PUT/GET load below 2% of the S7 connection bandwidth while giving a worst-case detection time of TimeoutMs + 50 ms. For safety circuits with sub-100 ms response, use 10 ms; for process cells where 1 s detection is fine, use 100 ms.
Can I use optimized DBs with PUT/GET between S7-1200 and S7-1500?
No. PUT/GET require absolute addresses in the ADDR_1 pointer, and absolute addressing is only guaranteed in non-optimized (standard) DBs. Create the heartbeat DBs with S7_Optimized_Access := 'FALSE' in the DB properties or in the source. All other process data can stay optimized.
My heartbeat works in OB1 but fails when I move it to a cyclic interrupt OB. Why?
Most commonly, the cyclic interrupt OB is firing faster than PUT/GET can complete (the BUSY bit is still TRUE when the next edge arrives). The PUT/GET instruction ignores re-triggers while BUSY is TRUE, so the request is simply lost. The fix is to gate the REQ input on the falling edge of BUSY, not on a free-running tick, or to slow the cyclic interrupt to a period longer than the worst PUT/GET response time (typically 30 to 50 ms over Profinet).