Skip to main content
Version: 1.0.8

Siemens S7

QUBIQ talks to Siemens controllers with classic S7comm over ISO-on-TCP (TCP port 102). It reaches S7-300, S7-400, and S7-1200/1500 non-optimized blocks by absolute address.

New in 1.0.5

Before you start: the two TIA Portal settings​

Almost every "connected but cannot read" report in the entire S7 ecosystem comes down to one of these two, and QUBIQ cannot change either of them from here. Fix them in TIA Portal first.

  1. Permit access with PUT/GET communication. CPU properties → Protection & Security → Connection mechanisms. Without it the CPU accepts your TCP connection and then refuses every read.

  2. Optimized block access: OFF, on every data block you intend to read. Right-click the DB → Properties → Attributes. An optimized block has no stable byte layout, so absolute addressing cannot reach it at all — there is nothing for DB1.DBW0 to mean.

Symbolic access to optimized blocks (S7COMM-Plus) is not supported.

Configuration​

FieldDescription
NameConnection identifier.
HostCPU IP address, on its PROFINET interface.
Port102. Effectively never anything else.
CPU familyA convenience that presets rack and slot. Never sent to the CPU.
Rack0 for virtually every modern CPU.
Slot1 for S7-1200/1500. 2 for most S7-300/400.
Connect / read / write timeoutSocket and request deadlines.
Auto-reconnectRe-establishes a dropped link with backoff. On by default.
The wrong slot is the most common cause of a timeout

A connection that times out with no further detail is usually addressing the wrong slot, not a blocked port. S7-1200/1500 are rack 0 slot 1; S7-300/400 are usually slot 2. Pick your CPU family and the form fills both in.

An S7 CPU has few connection resources and releases them slowly. QUBIQ opens exactly one connection per configured link and multiplexes every tag onto it, rather than dialling per read.

The address grammar​

Siemens' own notation, not an invention. Addresses are case-insensitive, and the German mnemonics E (Eingang) and A (Ausgang) are accepted wherever I and Q are.

AreaBitByteWord (16-bit)Double word (32-bit)
Data blockDB1.DBX0.5DB1.DBB0DB1.DBW0DB1.DBD0
Flags / Merker (M)M4.2MB4MW4MD4
Inputs (I / E)I0.0IB0IW0ID0
Outputs (Q / A)Q0.1QB0QW0QD0
Timers (T)——T5—
Counters (C)——C10—

Strings live only in a data block: DB1.STRING0.32 — an S7 STRING at byte 0 with a declared capacity of 32. The capacity is the one the PLC-side declaration reserves; a read that guesses it returns the wrong number of bytes.

M4 on its own is rejected, deliberately

In Siemens notation M4.2 is a bit and MB4 is a byte. M4 is neither. QUBIQ refuses it and names both things it could have meant, rather than silently picking one and giving you a tag that reads the wrong width forever.

The offset is a BYTE offset, not an index​

MW4 is the word at byte 4 — that is MB4 and MB5 together. The next word along is MW6, not MW5. The same applies to MD4, which spans bytes 4–7, so the next double word is MD8. Overlapping addresses are legal and occasionally intended; more often they are a bug.

Data types​

The tag's data type on the General tab decides how the bytes are decoded — and, from the address builder, how wide the address is. The two cannot disagree, because the builder derives the address suffix from the type you declared.

S7 typeAddress suffixQUBIQ data typeBytes
BOOLX / bit notationboolean1 bit
BYTE, USINTBbyte1
SINTBsbyte1
INTWint162
WORD, UINTWuint162
DINTDint324
DWORD, UDINTDuint324
REALDfloat4
STRINGSTRING<offset>.<capacity>string2 + capacity

LREAL, LINT and the other 64-bit types are deliberately absent. They exist on an S7-1500 but only inside optimized blocks, which absolute addressing cannot reach — offering them would produce an address that parses and never reads.

Worked examples: a tag for every area​

Each of these is the full recipe — create the tag, set its data type on General, then bind it on Binding.

A REAL in a data block​

A burner temperature at DB10.DBD20.

WhereSetting
General → Data typefloat
Binding → ProtocolSiemens S7
Binding → AreaData Block (DB)
Binding → DB10
Binding → Offset20
Resulting addressDB10.DBD20

An INT in a data block​

A batch counter at DB10.DBW0. Same as above with data type int16 and offset 0 — the address builder writes DB10.DBW0 because a 2-byte type takes the W suffix.

A BOOL in a data block​

Interlock bit 5 of byte 0: data type boolean, DB 10, offset 0, bit 5 → DB10.DBX0.5.

A flag word (M)​

A setpoint the PLC program keeps in flag memory: data type int16, area Flags / Merker (M), offset 200 → MW200.

For a single flag bit — say a "motor running" latch at M300.0 — use data type boolean, offset 300, bit 0.

For a 32-bit flag value, data type float and offset 100 gives MD100.

A process input (I / E)​

A limit switch on the first input byte: data type boolean, area Inputs (I / E), offset 0, bit 3 → I0.3. Typing E0.3 is accepted and normalises to the same address.

An analogue input word is IW64 — data type int16, offset 64.

A process output (Q / A)​

A contactor coil: data type boolean, area Outputs (Q / A), offset 0, bit 1 → Q0.1. A0.1 is accepted for the same thing.

A timer and a counter​

Timers and counters are numbered, not byte-addressed, so there is no offset arithmetic and no width to choose: T5 is timer 5 and C10 is counter 10. Set the data type to int16; the area fixes the width.

A string​

A recipe name at DB20.STRING0.32: data type string, DB 20, offset 0, declared capacity 32. The capacity must match the PLC-side declaration.

Writing​

Writes go through the same binding. Two areas refuse a write before it reaches the wire, and that is deliberate:

  • Process inputs (%I) — the CPU owns the input process image and overwrites it at the start of every scan. A write would succeed, be erased microseconds later, and report success. Write to %Q or to a data block instead.
  • Timers and counters — the same reasoning.

A write to a read-only or non-existent block is refused by the CPU, and that refusal is reported against the tag without disturbing the connection or any other tag on it.

What happens to a setpoint while the driver is busy Changed in 1.0.6​

Three things a plant notices, and none of them needed configuring:

  • A write goes in front of queued polling. Both drivers talk to a CPU one exchange at a time, and an operator's setpoint used to wait behind however many scheduled reads happened to be in the queue. Nothing can interrupt an exchange already on the wire — but the wait is now bounded, and for S7 it is taken per scan run rather than per cycle, or a setpoint would still wait out a whole scan group.
  • One unreachable CPU no longer stalls writes to the others. Every connection's requests used to be handled in sequence, so a handler dialling a dead panel held up every other controller's write — and the caller was then told worker unavailable about a worker that was perfectly healthy, merely queued behind somebody else's problem. Each request is now handled on its own, bounded, and a request past that bound is refused rather than queued.
  • A value whose meaning is not obvious is refused, not guessed. Writing 2 or maybe to a BOOL used to be a successful write of OFF — a wrong value that reports success, which is worse than a refusal because nobody goes looking for it.

A write that could only be applied in halves is refused New in 1.0.6​

S7 offers no atomicity across requests. A value longer than the CPU's negotiated PDU is split into several, and if one fails there is no indication of how much was applied — which for a STRING is the worst available shape: the new length byte goes out in the first request and the tail of the old value survives in the bytes that never got written, so the block holds neither value and its own length byte says which one it is not.

So the write is refused up front, naming the numbers and the remedy:

DB20.STRING0.254 needs 256 bytes and this CPU negotiated a 240-byte PDU, so the
write would be split into several requests that can fail half-applied — reduce
the declared length

This only ever fires on a small PDU. A classic S7-300/400 negotiates 240 bytes, leaving room for about 205, so only a near-maximum STRING reaches it. An S7-1200/1500 agrees 480 or 960 and never does.

When the CPU refuses Changed in 1.0.7​

A CPU can refuse a request in two places: per address, in the item's own return code (a missing DB, an offset past the end), or for the whole request, in the reply header — which is how a protection level that forbids variable services at all is reported, most often because PUT/GET is not permitted. Both arrive over a socket that is working perfectly.

Before 1.0.7 the header kind was mistaken for a network fault, with the worst available result: the exchange had succeeded, so the connection was never marked down and its badge stayed green, while every scan cycle stopped at the first request and left the whole scan group without values — permanently, since there was no outage to recover from.

It is now handled like any other CPU refusal. The tags in the refused request go Bad with the reason, named in words — CPU refused the request: error class 0x87 (access denied) — with the PUT/GET hint that goes with it, and the rest of the scan carries on. A refusal during the handshake is different and is treated as one: a CPU out of connection resources refuses the session itself, and that is a dead connection.

A reply cut short no longer reads as data. When the CPU's reply ended before every requested address had been answered, the addresses after the cut came back as a successful read of nothing — no data and no error, which nothing downstream could tell from a real value. Every unanswered address is now marked failed, including those in a request that was never sent because the connection went down first. The same applies to the per-address results of a multi-address write.

Scattered addresses in few round trips Changed in 1.0.8​

Addresses close together in one data block were already read as one run. Runs in different data blocks, or in M, I and Q, each cost a round trip of their own: a scan of 200 scattered tags was 200 exchanges a cycle, which on a 100 ms scan group is the CPU's whole communication budget.

The driver now packs those runs into multi-variable read requests, sized to the PDU the CPU negotiated — each request bounded by item count, request size and reply size, so a 240-byte PDU on an S7-300, S7-1200 or LOGO! is not overrun. In testing, 59 scattered data blocks were read in 3 round trips a cycle instead of 59. Timers, counters and anything larger than one PDU are still read on their own.

What a refusal costs is unchanged:

  • A refusal of one address stays with that address.
  • A refusal of a whole packed request — protection, resources — falls back to reading its runs one by one, so one refused address cannot blank the others it shared a request with.
  • A transport failure ends the cycle at once, as before.
  • An operator write still waits at most one exchange.

A down CPU is not hammered Changed in 1.0.7​

A CPU has few connection resources and releases them slowly, which is why reconnection backs off. But only the poller honoured that backoff: every Read now and every write against an unreachable CPU dialled again immediately, waiting out a full connect timeout each time — a binding form refreshing a live preview could open a new attempt on every refresh. They now respect the same schedule: inside the backoff window they fail at once, with the error from the last attempt, rather than dialling again.

Changing one connection's configuration also no longer stalls the others in the worker while its poller winds down.

Confirming an address before you bind it​

The binding form has a Read now button beside the address. It reads that one address on the connection the driver already holds and shows both the decoded value and the raw bytes.

Use the bytes. A value that looks wrong is usually a width or byte-order mismatch, and that is obvious in the hex and invisible in the number. If the read succeeds but the decode fails, the address is right and the declared type is wrong — a one-field fix.

Diagnostics​

SymptomCause
Connects, every read failsPUT/GET not permitted, or optimized block access still on.
object does not exist (wrong DB/area?)The DB number does not exist on that CPU.
address out of rangeThe offset is past the end of the block.
access deniedBlock protection, or PUT/GET.
CPU refused the request: error class …The CPU refused the whole request rather than one address — most often 0x87, access denied: PUT/GET. See when the CPU refuses.
Badge green, a whole scan group emptyOn a build before 1.0.7, a whole-request refusal. Fixed — the tags now go Bad with the reason.
Timeout with no detailWrong rack/slot, or no free connection resource on the CPU.
A value that is plausible but wrongWidth mismatch — check the data type against the address suffix.

A refusal reported by the CPU concerns only the address that caused it. Other tags on the same connection keep updating, and the connection stays up.