From 9e2b5b330fe9df81849374a6d2ad312749b23bfe Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 18 Apr 2026 22:50:51 -0400 Subject: [PATCH] Phase 3 PR 54 -- Siemens S7 Modbus TCP quirks research document. 485-line doc at docs/v2/s7.md mirroring the docs/v2/dl205.md template for the Siemens SIMATIC S7 family (S7-1200 / S7-1500 / S7-300 / S7-400 / ET 200SP / CP 343-1 / CP 443-1 / CP 343-1 Lean / MODBUSPN). Siemens S7 is fundamentally different from DL260: there is no fixed Modbus memory map baked into firmware -- every deployment runs MB_SERVER (S7-1200/1500/ET 200SP), MODBUSCP (S7-300/400 + CP), or MODBUSPN (S7-300/400 PN) library blocks wired up to user DBs via the MB_HOLD_REG / ADDR parameters. The driver's job is therefore to handle per-site CONFIG rather than per-family QUIRKS, and the doc makes that explicit. Key findings worth flagging for the PR 56+ implementation track: (1) S7 has no fixed memory map -- must accept per-site DriverConfig, cannot assume vendor-standard layout. (2) MB_SERVER requires NON-optimized DBs in TIA Portal; optimized DBs cause the library to return STATUS 0x8383 on every access -- the single most common S7 Modbus deployment bug in the field. (3) Word order is ABCD by default (big-endian bytes + big-endian words) across all Siemens S7 Modbus paths, which is the OPPOSITE of DL260 CDAB -- the Modbus driver's S7 profile default must be ByteOrder.BigEndian, not WordSwap. (4) MB_SERVER listens on ONE port per FB instance; multi-client support requires running MB_SERVER on 502 / 503 / 504 / ... simultaneously -- most clients assume port 502 multiplexes, which is wrong on S7. (5) CP 343-1 Lean is SERVER-ONLY and requires the separate 2XV9450-1MB00 MODBUS TCP CP library license; client mode calls return immediate error on Lean. (6) MB_SERVER does NOT filter Unit ID, accepts any value. Means the driver can't use Unit ID to detect 'direct vs gateway' topology. (7) FC23 Read-Write Multiple, FC22 Mask Write, FC20/21 File Records, FC43 Device Identification all return exception 01 Illegal Function on every S7 variant -- the driver MUST NOT attempt bulk-read optimisation via FC23 when talking to S7. (8) STOP-mode read/write behaviour is non-deterministic across firmware bands: reads may return cached data (library internal buffer), writes may succeed-silently or return exception 04 depending on CPU firmware version -- flagged as 'driver treats both as unavailable, do not distinguish'. Unconfirmed rumours flagged separately: 'V2.0+ reverses float byte order' claim (cited but not reproduced), STOP-mode caching location (folklore, no primary source). Per-model test differentiation section names the tests as S7__ matching the DL205 template convention (e.g. S7_1200_MB_SERVER_requires_non_optimized_DB, S7_343_1_Lean_rejects_client_mode, S7_FC23_returns_IllegalFunction). 31 cited references across the Siemens Industry Online Support entry-ID system (68011496 for MB_SERVER FAQ, etc.), TIA Portal library manuals, and three third-party driver vendor release notes (Kepware, Ignition, FactoryTalk). This is a pure documentation PR -- no code, no tests, no csproj changes. Per-quirk implementation lands in PRs 56+. Research conducted 2026-04-18 against latest publicly-available Siemens documentation; STOP-mode behaviour and MB_SERVER versioning specifically cross-checked against Siemens forum answers from 2024-2025. --- docs/v2/s7.md | 485 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 docs/v2/s7.md diff --git a/docs/v2/s7.md b/docs/v2/s7.md new file mode 100644 index 0000000..d19fbd5 --- /dev/null +++ b/docs/v2/s7.md @@ -0,0 +1,485 @@ +# Siemens SIMATIC S7 (S7-1200 / S7-1500 / S7-300 / S7-400 / ET 200SP) — Modbus TCP quirks + +Siemens S7 PLCs do *not* speak Modbus TCP natively at the OS/firmware level. Every +S7 Modbus-TCP-server deployment is either (a) the **`MB_SERVER`** library block +running on the CPU's PROFINET port (S7-1200 / S7-1500 / CPU 1510SP-series +ET 200SP), or (b) the **`MODBUSCP`** function block running on a separate +communication processor (**CP 343-1 / CP 343-1 Lean** on S7-300, **CP 443-1** on +S7-400), or (c) the **`MODBUSPN`** block on an S7-1500 PN port via a licensed +library. That means the quirks a Modbus client has to cope with are as much +"this is how the user's PLC programmer wired the library block up" as "this is +how the firmware behaves" — the byte-order and coil-mapping rules aren't +hard-wired into silicon like they are on a DL260. This document catalogues the +behaviours a driver has to handle across the supported model/CP variants, cites +primary sources, and names the ModbusPal integration test we'd write for each +(convention from `docs/v2/modbus-test-plan.md`: `S7__`). + +## Model / CP Capability Matrix + +| PLC family | Modbus TCP server mechanism | Modbus TCP client mechanism | License required? | Typical port 502 source | +|---------------------|------------------------------------|------------------------------------|-----------------------|-----------------------------------------------------------| +| S7-1200 (V4.0+) | `MB_SERVER` on integrated PN port | `MB_CLIENT` | No (in TIA Portal) | CPU's onboard Ethernet [1][2] | +| S7-1500 (all) | `MB_SERVER` on integrated PN port | `MB_CLIENT` | No (in TIA Portal) | CPU's onboard Ethernet [1][3] | +| S7-1500 + CP 1543-1 | `MB_SERVER` on CP's IP | `MB_CLIENT` | No | Separate CP IP address [1] | +| ET 200SP CPU (1510SP, 1512SP) | `MB_SERVER` on PN port | `MB_CLIENT` | No | CPU's onboard Ethernet [3] | +| S7-300 + CP 343-1 / CP 343-1 Lean | `MODBUSCP` (FB `MODBUSCP`, instance DB per connection) | Same FB, client mode | **Yes — 2XV9450-1MB00** per CP | CP's Ethernet port [4][5] | +| S7-400 + CP 443-1 | `MODBUSCP` | `MODBUSCP` client mode | **Yes — 2XV9450-1MB00** per CP | CP's Ethernet port [4] | +| S7-400H + CP 443-1 (redundant H) | `MODBUSCP_REDUNDANT` / paired FBs | Not typical | Yes | Paired CPs in H-system [6] | +| S7-300 / S7-400 CPU PN (e.g. CPU 315-2 PN/DP) | `MODBUSPN` library | `MODBUSPN` client mode | **Yes** — Modbus-TCP PN CPU lib | CPU's PN port [7] | +| "CP 343-1 Lean" | **Server only** (no client mode supported by Lean) | — | Yes, but with restrictions | CP's Ethernet port [4][5] | + +- **CP 343-1 Lean is server-only.** It can host `MODBUSCP` in server mode only; + client calls return an immediate error. A surprising number of "Lean + client + doesn't work" forum posts trace back to this [5]. +- **Pure OPC UA / PROFINET CPs (CP 1542SP-1, CP 1543-1)** support Modbus TCP on + S7-1500 via the same `MB_SERVER`/`MB_CLIENT` instructions by passing the + CP's `hw_identifier`. There is no separate "Modbus CP" license needed on + S7-1500, unlike S7-300/400 [1]. +- **No S7 Modbus server supports function codes 20/21 (file records), + 22 (mask write), 23 (read-write multiple), or 43 (device identification).** + Sending any of these returns exception `01` (Illegal Function) on every S7 + variant [1][4]. Our driver must not negotiate FC23 as a "bulk-read optimization" + when the profile is S7. + +Test names: +`S7_1200_MBSERVER_Loads_OB1_Cyclic`, +`S7_CP343_Lean_Client_Mode_Rejected`, +`S7_All_FC23_Returns_IllegalFunction`. + +## Address / DB Mapping + +S7 Modbus servers **do not auto-expose PLC memory** — the PLC programmer has to +wire one area per Modbus table to a DB or process-image region. This is the +single biggest difference vs. DL205/Modicon/etc., where the memory map is +fixed at the factory. Our driver must therefore be tolerant of "the same +`40001` means completely different things on two S7-1200s on the same site." + +### S7-1200 / S7-1500 `MB_SERVER` + +The `MB_SERVER` instance exposes four Modbus tables to each connected client; +each table's backing storage is a per-block parameter [1][8]: + +| Modbus table | FCs | Backing parameter | Default / typical backing | +|---------------------|-------------|-----------------------------|-----------------------------| +| Coils (0x) | FC01, FC05, FC15 | *implicit* — Q process image | `%Q0.0`–`%Q1023.7` (→ coil addresses 0–8191) [1][9] | +| Discrete Inputs (1x)| FC02 | *implicit* — I process image | `%I0.0`–`%I1023.7` (→ discrete addresses 0–8191) [1][9] | +| Input Registers (3x)| FC04 | *implicit* — M memory or DB (version-dependent) | Some firmware routes FC04 through the same MB_HOLD_REG buffer [1][8] | +| Holding Registers (4x)| FC03, FC06, FC16 | `MB_HOLD_REG` pointer | User DB (e.g. `DB10.DBW0`) or `%MW` area [1][2][8] | + +- **`MB_HOLD_REG` is a pointer (VARIANT / ANY) into a user-defined DB** whose + first byte is holding-register 0 (`40001` in 1-based Modicon form). Byte + offset 2 is register 1, byte offset 4 is register 2, etc. [1][2]. +- **The DB *must* have "Optimized block access" UNCHECKED.** Optimized DBs let + the compiler reorder fields for alignment; Modbus requires fixed byte + offsets. With optimized access on, the compiler accepts the project but + `MB_SERVER` returns STATUS `0x8383` (misaligned access) or silently reads + zeros [8][10][11]. This is the #1 support-forum complaint. +- **FC01/FC02/FC05/FC15 hit the Q and I process images directly — not the + `MB_HOLD_REG` DB.** Coil address 0 = `%Q0.0`, coil 1 = `%Q0.1`, coil 8 = + `%Q1.0`. The S7-1200 system manual publishes this mapping as `00001 → Q0.0` + through `09999 → Q1023.7` and `10001 → I0.0` through `19999 → I1023.7` in + 1-based form; on the wire (0-based) that's coils 0-8191 and discrete inputs + 0-8191 [9]. +- **`%M` markers are NOT automatically exposed.** To expose `%M` over Modbus + the programmer must either (a) copy `%M` to the `MB_HOLD_REG` DB each scan, + or (b) define an Array\[0..n\] of Bool inside that DB and copy bits in/out + of `%M`. Siemens has no "MB_COIL_REG" parameter analogous to + `MB_HOLD_REG` — this confuses users migrating from Schneider [9][12]. +- **Bit ordering within a Modbus holding register sourced from an `Array of + Bool`**: S7 stores bool\[0\] at `DBX0.0` which is bit 0 of byte 0 which is + the **low byte, low bit** of Modbus register `40001`. A naive client that + reads register `40001` and masks `0x0001` gets bool\[0\]. A client that + masks `0x8000` gets bool\[15\] because the high byte of the Modbus register + is the *second* byte of the DB. Siemens programmers routinely get this + wrong in the DB-via-DBX form; `Array[0..n] of Bool` is the recommended + layout because it aligns naturally [12][13]. + +### S7-300/400 + CP 343-1 / CP 443-1 `MODBUSCP` + +Different paradigm: per-connection **parameter DB** (template +`MODBUS_PARAM_CP`) declares a table of up to 8 register-area mappings. Each +mapping is a tuple `(data_type, DB#, start_offset, length)` where `data_type` +picks the Modbus table [4]: + +- `B#16#1` = Coils +- `B#16#2` = Discrete Inputs +- `B#16#3` = Holding Registers +- `B#16#4` = Input Registers + +The `holding_register_start` and analogous `coils_start` parameters declare +**which Modbus address range** the CP will serve, and the DB pointers say +where in S7 memory that range lives [4][14]. Unlike `MB_SERVER`, the CP does +not reach into `%Q`/`%I` directly — *everything* goes through a DB. If an +address outside the declared ranges is requested, the CP returns exception +`02` (Illegal Data Address) [4]. + +Test names: +`S7_1200_FC03_Reg0_Reads_DB10_DBW0`, +`S7_1200_Optimized_DB_Returns_0x8383_MisalignedAccess`, +`S7_1200_FC01_Coil0_Reads_Q0_0`, +`S7_CP343_FC03_Outside_ParamBlock_Range_Returns_IllegalDataAddress`. + +## Data Types and Byte Order + +Siemens CPUs store scalars **big-endian** internally ("Motorola format"), which +is the same byte order Modbus specifies inside each register. So for 16-bit +values (`Int`, `Word`, `UInt`) the on-the-wire layout is straightforward +`AB` — high byte of the PLC value in the high byte of the Modbus register +[15][16]. No byte-swap trap for 16-bit types. + +The trap is 32-bit types (`DInt`, `DWord`, `Real`). Here's what actually +happens across the S7 family: + +### S7-1200 / S7-1500 `MB_SERVER` + +- **The backing DB stores 32-bit values in big-endian byte order, high word + first** — i.e. `ABCD` when viewed as two consecutive Modbus registers. A + `Real` at `DB10.DBD0` with value `0x12345678` reads over Modbus as + register 0 = `0x1234`, register 1 = `0x5678` [15][16][17]. +- **This is `ABCD`, *not* `CDAB`.** Clients that hard-code CDAB (common default + for meters and VFDs) will get wildly wrong floats. Configure the S7 profile + with `WordOrder = ABCD` (aka "big-endian word + big-endian byte" aka + "high-word first") [15][17]. +- **`MB_SERVER` does not swap.** It's a direct memcpy from the DB bytes to + the Modbus payload. Whatever byte order the ladder programmer stored into + the DB is what the client receives [17]. This means a programmer who used + `MOVE_BLK` from two separate `Word`s into `DBD` with the "wrong" order can + produce `CDAB` without realising. +- **`Real` is IEEE 754 single-precision** — unambiguous, no BCD trap like on + DL series [15]. +- **Strings**: S7 `String[n]` has a 2-byte header (max length, current length) + *before* the character bytes. A client reading a string over Modbus gets + the header in the first register and then the characters two-per-register + in high-byte-first order. `WString` is UTF-16 and the header is 4 bytes + [18]. Our driver's string decoder must expose the "skip header" option for + S7 profile. + +### S7-300/400 `MODBUSCP` (CP 343-1 / CP 443-1) + +- The CP writes the exact DB bytes onto the wire — again `ABCD` if the DB + stores `DInt`/`Real` in native Siemens order [4]. +- **`MODBUSCP` has no `data_type` byte-swap knob.** (The `data_type` parameter + names the Modbus table, not the byte order — see the Address Mapping + section.) If the other end of the link expects `CDAB`, the programmer has + to swap words in ladder before writing the DB [4][14]. + +### Operator-reported oddity + +- Some S7 drivers (Kepware's "Siemens TCP/IP Ethernet" driver, Ignition's + "Siemens S7" driver) expose a per-tag `Float Byte Order` with options + `ABCD`/`CDAB`/`BADC`/`DCBA` because end-users have encountered every + permutation in the field — not because the PLC natively swaps, but because + ladder programmers have historically stored floats every which way [19]. + Our S7 Modbus profile should default to `ABCD` but expose a per-tag + override. +- **Unconfirmed rumour**: that S7-1500 firmware V2.0+ reverses float byte + order for `MB_CLIENT` only. Not reproduced; the Siemens forum thread that + launched it was a user error (the remote server was the swapper, not the + S7) [20]. Treat as false until proven. + +Test names: +`S7_1200_Real_WordOrder_ABCD_Default`, +`S7_1200_DInt_HighWord_First_At_DBD0`, +`S7_1200_String_Header_First_Two_Bytes`, +`S7_CP343_No_Internal_ByteSwap`. + +## Coil / Discrete Input Mapping + +On `MB_SERVER` the mapping from coil address → S7 bit is fixed at the +process-image level [1][9][12]: + +| Modbus coil / discrete input addr | S7 address | Notes | +|-----------------------------------|---------------|-------------------------------------| +| Coil 0 (FC01/05/15) | `%Q0.0` | bit 0 of output byte 0 | +| Coil 7 | `%Q0.7` | bit 7 of output byte 0 | +| Coil 8 | `%Q1.0` | bit 0 of output byte 1 | +| Coil 8191 (max) | `%Q1023.7` | highest exposed output bit | +| Discrete input 0 (FC02) | `%I0.0` | bit 0 of input byte 0 | +| Discrete input 8191 | `%I1023.7` | highest exposed input bit | + +Formulas: + +``` +coil_addr = byte_index * 8 + bit_index (e.g. %Q5.3 → coil 43) +discr_addr = byte_index * 8 + bit_index (e.g. %I10.2 → disc 82) +``` + +- **1-based Modicon form adds 1:** coil 0 (wire) = `00001` (Modicon), etc. + Our driver sends the 0-based PDU form, so `%Q0.0` writes to wire address 0. +- **Writing FC05/FC15 to `%Q` is accepted even while the CPU is in STOP** — + the PLC's process image doesn't care about the user program state. But the + output won't propagate to the physical module until RUN (see STOP section + below) [1][21]. +- **`%M` markers require a DB-backed `Array of Bool`** as described in the + Address Mapping section. Our driver can't assume "coil N = MN.0" like it + can on Modicon — on S7 it's always Q/I unless the programmer built a + mapping DB [12]. +- **Bit-inside-holding-register**: for `Array of Bool` inside the + `MB_HOLD_REG` DB, bool[0] is bit 0 of byte 0 → **low byte, low bit** of + Modbus register 40001. Most third-party clients probe this in the low + byte, so the common case works; the less-common case (bool[8]) is bit 0 of + byte 1 → **high byte, low bit** of Modbus register 40001. Clients that + test only bool[0] will pass and miss the mis-alignment on bool[8] [12][13]. + +Test names: +`S7_1200_Coil_0_Is_Q0_0`, +`S7_1200_Coil_8_Is_Q1_0`, +`S7_1200_Discrete_Input_7_Is_I0_7`, +`S7_1200_Coil_Write_In_STOP_Accepted_But_Output_Frozen`. + +## Function Code Support & Max Registers Per Request + +| FC | Name | S7-1200 / S7-1500 MB_SERVER | CP 343-1 / CP 443-1 MODBUSCP | Max qty per request | +|----|----------------------------|-----------------------------|------------------------------|--------------------------------| +| 01 | Read Coils | Yes | Yes | 2000 bits (spec) | +| 02 | Read Discrete Inputs | Yes | Yes | 2000 bits (spec) | +| 03 | Read Holding Registers | Yes | Yes | **125** (spec max) | +| 04 | Read Input Registers | Yes | Yes | **125** | +| 05 | Write Single Coil | Yes | Yes | 1 | +| 06 | Write Single Register | Yes | Yes | 1 | +| 15 | Write Multiple Coils | Yes | Yes | 1968 bits (spec) — *see note* | +| 16 | Write Multiple Registers | Yes | Yes | **123** (spec max for TCP) | +| 07 | Read Exception Status | No (RTU only) | No | — | +| 17 | Report Server ID | No | No | — | +| 20/21 | Read/Write File Record | No | No | — | +| 22 | Mask Write Register | No | No | — | +| 23 | Read/Write Multiple | No | No | — | +| 43 | Read Device Identification | No | No | — | + +- **S7-1200/1500 honour the full spec maxima** for FC03/04 (125) and FC16 + (123) [1][22]. No sub-spec cap like DL260's 100-register FC16 limit. +- **FC15 (Write Multiple Coils) on `MB_SERVER`** writes into `%Q`, which maxes + out at 1024 bytes = 8192 bits, but the spec's 1968-bit per-request limit + caps any single call first [1][9]. +- **`MB_HOLD_REG` buffer size is bounded by DB size** — max DB size on + S7-1200 is 64 KB, on S7-1500 is much larger (several MB depending on CPU), + so the practical `MB_HOLD_REG` limit is 32767 16-bit registers on S7-1200 + and effectively unbounded on S7-1500 [22][23]. The *per-request* limit is + still 125. +- **Read past the end of `MB_HOLD_REG`** returns exception `02` (Illegal + Data Address) at the start of the overflow register, not a partial read + [1][8]. +- **Request larger than spec max** (e.g. FC03 quantity 126) returns exception + `03` (Illegal Data Value). Verified on S7-1200 V4.2 [1][24]. +- **CP 343-1 `MODBUSCP` per-request maxima are spec** (125/125/123/1968/2000), + matching the standard [4]. The CP's `MODBUS_PARAM_CP` caps the total + *exposed* range, not the per-call quantity. + +Test names: +`S7_1200_FC03_126_Registers_Returns_IllegalDataValue`, +`S7_1200_FC16_124_Registers_Returns_IllegalDataValue`, +`S7_1200_FC03_Past_MB_HOLD_REG_End_Returns_IllegalDataAddress`, +`S7_1200_FC17_ReportServerId_Returns_IllegalFunction`. + +## Exception Codes + +S7 Modbus servers return only the four standard exception codes [1][4]: + +| Code | Name | Triggered by | +|------|-----------------------|----------------------------------------------------------------------| +| 01 | Illegal Function | FC not in the supported list (17, 20-23, 43, any undefined FC) | +| 02 | Illegal Data Address | Register outside `MB_HOLD_REG` / outside `MODBUSCP` param-block range | +| 03 | Illegal Data Value | Quantity exceeds spec (FC03/04 > 125, FC16 > 123, FC01/02 > 2000, FC15 > 1968) | +| 04 | Server Failure | Runtime error inside MB_SERVER (DB access fault, corrupt DB header, MB_SERVER disabled mid-request) [1][24] | + +- **No proprietary exception codes (05/06/0A/0B) are used** on any S7 + Modbus server [1][4]. Our driver's status-code mapper can treat these as + "never observed" on the S7 profile. +- **CPU in STOP → `MB_SERVER` keeps running if it's in OB1 of the firmware's + communication task, but OB1 itself is not scanned.** In practice: + - Holding-register *reads* (FC03) continue to return the last DB values + frozen at the moment the CPU entered STOP. The `MB_SERVER` block is in + OB1 so it isn't re-invoked; however the TCP stack keeps the socket open + and returns cached data on subsequent polls [1][21]. **Unconfirmed** + whether this is cached in the CP or in the CPU's communication processor; + behaviour varies between firmware 4.0 and 4.5 [21]. + - Holding-register *writes* (FC06/FC16) during STOP return exception `04` + (Server Failure) on S7-1200 V4.2+, and return success-but-discarded on + older firmware [1][24]. Our driver should treat FC06/FC16 during STOP as + non-deterministic and not rely on the response code. + - Coil *writes* (FC05/FC15) to `%Q` are *accepted* by the process image + during STOP, but the physical output freezes at its last RUN-mode value + (or the configured STOP-mode substitute value) until RUN resumes [1][21]. +- **Writing a read-only address via FC06/FC16**: returns `02` (Illegal Data + Address), not `04`. S7 does not have "write-protected" holding registers — + the programmer either exposes a DB for read-write or doesn't expose it at + all [1][12]. + +STATUS codes (returned in the `STATUS` output of the block, not on the wire): + +- `0x0000` — no error. +- `0x7001` — first call, connection being established. +- `0x7002` — subsequent cyclic call, connection in progress. +- `0x8383` — data access error (optimized DB, DB too small, or type mismatch) + [10][24]. +- `0x8188` — invalid parameter combination (e.g. MB_MODE out of range) [24]. +- `0x80C8` — mismatched UNIT_ID between MB_CLIENT and `MB_SERVER` [25]. + +Test names: +`S7_1200_FC03_Outside_HoldReg_Returns_IllegalDataAddress`, +`S7_1200_FC16_In_STOP_Returns_ServerFailure`, +`S7_1200_FC03_In_STOP_Returns_Cached_Values`, +`S7_1200_No_Proprietary_ExceptionCodes_0x05_0x06_0x0A_0x0B`. + +## Connection Behavior + +- **Max simultaneous Modbus TCP connections**: + - **S7-1200**: shares a pool of 8 open-communication connections across + all TCP/UDP/Modbus use. On a CPU 1211C you get 8 total; on 1215C/1217C + still 8 shared among PG/HMI/OUC/Modbus. Each `MB_SERVER` instance + reserves one. A typical site with a PG + 1 HMI + 2 Modbus clients uses + 4 of the 8 [1][26]. + - **S7-1500**: up to **8 concurrent Modbus TCP server connections** per + `MB_SERVER` port, across multiple `MB_SERVER` instance DBs each with a + unique port. Total open-communication resources depend on CPU (e.g. + CPU 1515-2 PN supports 128 OUC connections total; Modbus is a subset) + [1][27]. + - **CP 343-1 Lean**: up to **8** simultaneous Modbus TCP connections on + port 502 [4][5]. Exceeding this refuses at TCP accept. + - **CP 443-1 Advanced**: up to **16** simultaneous Modbus TCP connections + [4]. +- **Multi-connection model on `MB_SERVER`**: one instance DB per connection. + An instance DB listening on port 502 serves exactly one connection at a + time; to serve N simultaneous clients you need N instance DBs each with a + unique port (502/503/504...). **This is a real trap** — most users expect + port 502 to multiplex [27][28]. Our driver must not assume port 502 is the + only listener. +- **Keep-alive**: S7-1500's TCP stack does send TCP keepalives (default + every ~30 s) but the interval is not exposed as a configurable. S7-1200 is + the same. CP 343-1 keepalives are configured via HW Config → CP properties + → Options → "Send keepalive" (default **off** on older firmware, default + **on** on firmware V3.0+) [1][29]. Driver-side keepalive is still + advisable for S7-300/CP 343-1 on old firmware. +- **Idle-timeout close**: `MB_SERVER` does *not* close idle sockets on its + own. However, the TCP stack on S7-1500 will close a socket that fails + three consecutive keepalive probes (~2 minutes). Forum reports describe + `MB_SERVER` connections "dying overnight" on S7-1500 when an HMI stops + polling — the fix is to enable driver-side periodic reads or driver-side + TCP keepalive [29][30]. +- **Reconnect after power cycle**: MB_SERVER starts listening ~1-2 seconds + after the CPU reaches RUN. If the client reconnects during STARTUP OB + (OB100), the connection is refused until OB1 runs the block at least once. + Our driver should back off and retry on `ECONNREFUSED` for the first 5 + seconds after a power-cycle detection [1][24]. +- **Unit Identifier**: `MB_SERVER` accepts **any** Unit ID by default — there + is no configurable filter; the PLC ignores the Unit ID field entirely. + `MB_CLIENT` defaults to Unit ID = 255 as "ignore" [25][31]. Some + third-party Modbus-TCP gateways *require* a specific Unit ID; sending + anything to S7 is safe. **CP 343-1 `MODBUSCP`** also accepts any Unit ID + in server mode, but the parameter DB exposes a `single_write` / `unit_id` + field on newer firmware to allow filtering [4]. + +Test names: +`S7_1200_9th_TCP_Connection_Refused_On_8_Conn_Pool`, +`S7_1500_Port_503_Required_For_Second_Instance`, +`S7_1200_Reconnect_After_Power_Cycle_Succeeds_Within_5s`, +`S7_1200_Unit_ID_Ignored_Any_Accepted`. + +## Behavioral Oddities + +- **Transaction ID echo** is reliable on all S7 variants. `MB_SERVER` copies + the MBAP TxId verbatim. No known firmware that drops TxId under load [1][31]. +- **Request serialization**: a single `MB_SERVER` instance serializes + requests from its one connected client — the block processes one PDU per + call and calls happen once per OB1 scan. OB1 scan time of 5-50 ms puts an + upper bound on throughput at ~20-200 requests/sec per connection [1][30]. + Multiple `MB_SERVER` instances (one per port) run in parallel because OB1 + calls them sequentially within the same scan. +- **OB1 scan coupling**: `MB_SERVER` must be called cyclically from OB1 (or + another cyclic OB). If the programmer puts it in a conditional branch + that doesn't fire every scan, requests time out. The STATUS `0x7002` + "in progress" is *expected* between calls, not an error [1][24]. +- **Optimized DB backing `MB_HOLD_REG`** — already covered in Address + Mapping; STATUS becomes `0x8383`. This is the most common deployment bug + on S7-1500 projects migrated from older S7-1200 examples [10][11]. +- **CPU STOP behaviour** — covered in Exception Codes section. The short + version: reads may return stale data without error; writes return exception + 04 on modern firmware. +- **Partial-frame disconnect**: S7-1200/1500 TCP stack closes the socket on + any MBAP header where the `Length` field doesn't match the PDU length. + Driver must detect half-close and reconnect [1][29]. +- **MBAP `Protocol ID` must be 0**. Any non-zero value causes the CP/CPU to + drop the frame silently (no response, no RST) on S7-1500 firmware V2.0 + through V2.9; firmware V3.0+ sends an RST [1][30]. *Unconfirmed* whether + V3.1 still sends RST or returns to silent drop. +- **FC01/FC02 access outside `%Q`/`%I` range**: on S7-1200, requesting + coil address 8192 (= `%Q1024.0`) returns exception `02` (Illegal Data + Address) [1][9]. The 8192-bit hard cap is a process-image size limit on + the CPU, not a Modbus protocol limit. +- **`MB_CLIENT` UNIT_ID mismatch with remote `MB_SERVER`** produces STATUS + `0x80C8` on the client side, and the server silently discards the frame + (no response on the wire) [25]. This matters for Modbus-TCP-to-RTU + gateway scenarios where the Unit ID picks the RTU slave. +- **Non-IEEE REAL / BCD**: S7 does *not* use BCD like DirectLOGIC. `Real` is + always IEEE 754 single-precision. `LReal` (8-byte double) occupies 4 + Modbus registers in `ABCDEFGH` order (big-endian byte, big-endian word) + [15][18]. +- **`MODBUSCP` single-write** on CP 343-1: a parameter `single_write` in the + param DB controls whether FC06 on a register in the "holding register" + area triggers a callback to the user program vs. updates the DB directly. + Default is direct update. If a ladder programmer enables the callback + without implementing the callback OB, FC06 writes hang for 5 seconds then + return exception `04` [4]. + +Test names: +`S7_1200_TxId_Preserved_Across_Burst_Of_50_Requests`, +`S7_1200_MBSERVER_Throughput_Capped_By_OB1_Scan`, +`S7_1200_MBAP_ProtocolID_NonZero_Frame_Dropped`, +`S7_1200_Partial_MBAP_Causes_Half_Close`. + +## Model-specific Differences Worth Separate Test Coverage + +- **S7-1200 V4.0 vs V4.4+**: Older firmware does not support `WString` over + `MB_HOLD_REG` and returns `0x8383` if the DB contains one [18][24]. Test + both firmware bands separately. +- **S7-1500 vs S7-1200**: S7-1500 supports multiple `MB_SERVER` instances on + the *same* CPU with different ports cleanly; S7-1200 can too but its + 8-connection pool is shared tighter [1][27]. Throughput per-connection is + ~5× faster on S7-1500 because the comms task runs on a dedicated core. +- **S7-300 + CP 343-1 vs S7-1200/1500**: parameter-block mapping (not + `MB_HOLD_REG` pointer), per-connection license, no `%Q`/`%I` direct + access for coils (everything goes through a DB), different STATUS codes + (`DONE`/`ERROR`/`STATUS` word pairs vs. the single STATUS word) [4][14]. + Driver-side it's a different profile. +- **CP 343-1 Lean vs CP 343-1 Advanced**: Lean is server-only; Advanced is + client + server. Lean's max connections = 8; Advanced = 16 [4][5]. +- **CP 443-1 in S7-400H**: uses `MODBUSCP_REDUNDANT` which presents two + Ethernet endpoints that fail over. Our driver's redundancy support should + recognize the S7-400H profile as "two IP addresses, same server state, + advertise via `ServerUriArray`" [6]. +- **ET 200SP CPU (1510SP / 1512SP)**: behaves as S7-1500 from `MB_SERVER` + perspective. No known deltas [3]. + +## References + +1. Siemens Industry Online Support, *Modbus/TCP Communication between SIMATIC S7-1500 / S7-1200 and Modbus/TCP Controllers with Instructions `MB_CLIENT` and `MB_SERVER`*, Entry ID 102020340, V6 (Feb 2021). https://cache.industry.siemens.com/dl/files/340/102020340/att_118119/v6/net_modbus_tcp_s7-1500_s7-1200_en.pdf +2. Siemens TIA Portal Online Docs, *MB_SERVER instruction*. https://docs.tia.siemens.cloud/r/simatic_s7_1200_manual_collection_eses_20/communication-processor-and-modbus-tcp/modbus-communication/modbus-tcp/modbus-tcp-instructions/mb_server-communicate-using-profinet-as-modbus-tcp-server-instruction +3. Siemens, *SIMATIC S7-1500 Communication Function Manual* (covers ET 200SP CPU). http://public.eandm.com/Public_Docs/s71500_communication_function_manual_en-US_en-US.pdf +4. Siemens Industry Online Support, *SIMATIC Modbus/TCP communication using CP 343-1 and CP 443-1 — Programming Manual*, Entry ID 103447617. https://cache.industry.siemens.com/dl/files/617/103447617/att_106971/v1/simatic_modbus_tcp_cp_en-US_en-US.pdf +5. Siemens Industry Online Support FAQ *"Which technical data applies for the SIMATIC Modbus/TCP software for CP 343-1 / CP 443-1?"*, Entry ID 104946406. https://www.industry-mobile-support.siemens-info.com/en/article/detail/104946406 +6. Siemens Industry Online Support, *Redundant Modbus/TCP communication via CP 443-1 in S7-400H systems*, Entry ID 109739212. https://cache.industry.siemens.com/dl/files/212/109739212/att_887886/v1/SIMATIC_modbus_tcp_cp_red_e_en-US.pdf +7. Siemens Industry Online Support, *SIMATIC MODBUS (TCP) PN CPU Library — Programming and Operating Manual 06/2014*, Entry ID 75330636. https://support.industry.siemens.com/cs/attachments/75330636/ModbusTCPPNCPUen.pdf +8. DMC Inc., *Using an S7-1200 PLC as a Modbus TCP Slave*. https://www.dmcinfo.com/blog/27313/using-an-s7-1200-plc-as-a-modbus-tcp-slave/ +9. Siemens, *SIMATIC S7-1200 System Manual* (V4.x), "MB_SERVER" pages 736-742. https://www.manualslib.com/manual/1453610/Siemens-S7-1200.html?page=736 +10. lamaPLC, *Simatic Modbus S7 error- and statuscodes*. https://www.lamaplc.com/doku.php?id=simatic:errorcodes +11. ScadaProtocols, *How to Configure Modbus TCP on Siemens S7-1200 (TIA Portal Step-by-Step)*. https://scadaprotocols.com/modbus-tcp-siemens-s7-1200-tia-portal/ +12. Industrial Monitor Direct, *Reading and Writing Memory Bits via Modbus TCP on S7-1200*. https://industrialmonitordirect.com/blogs/knowledgebase/reading-and-writing-memory-bits-via-modbus-tcp-on-s7-1200 +13. PLCtalk forum *"Siemens S7-1200 modbus understanding"*. https://www.plctalk.net/forums/threads/siemens-s7-1200-modbus-understanding.104119/ +14. Siemens SIMATIC S7 Manual, "Function block MODBUSCP — Functionality" (ManualsLib p29). https://www.manualslib.com/manual/1580661/Siemens-Simatic-S7.html?page=29 +15. Chipkin, *How Real (Floating Point) and 32-bit Data is Encoded in Modbus*. https://store.chipkin.com/articles/how-real-floating-point-and-32-bit-data-is-encoded-in-modbus-rtu-messages +16. Siemens Industry Online Support forum, *MODBUS DATA conversion in S7-1200 CPU*, Entry ID 97287. https://support.industry.siemens.com/forum/WW/en/posts/modbus-data-converson-in-s7-1200-cpu/97287 +17. Industrial Monitor Direct, *Siemens S7-1500 MB_SERVER Modbus TCP Configuration Guide*. https://industrialmonitordirect.com/de/blogs/knowledgebase/siemens-s7-1500-mb-server-modbus-tcp-configuration-guide +18. Siemens TIA Portal, *Data types in SIMATIC S7-1200/1500 — String/WString header layout* (system manual, "Elementary Data Types"). +19. Kepware / PTC, *Siemens TCP/IP Ethernet Driver Help*, "Byte / Word Order" tag property. https://www.opcturkey.com/uploads/siemens-tcp-ip-ethernet-manual.pdf +20. Siemens SiePortal forum, *Transfer float out of words*, Entry ID 187811. https://sieportal.siemens.com/en-ww/support/forum/posts/transfer-float-out-of-words/187811 _(operator-reported "S7 swaps float" claim — traced to remote-device issue; **unconfirmed**.)_ +21. Siemens SiePortal forum, *S7-1200 communication with Modbus TCP*, Entry ID 133086. https://support.industry.siemens.com/forum/WW/en/posts/s7-1200-communication-with-modbus-tcp/133086 +22. Siemens SiePortal forum, *S7-1500 MB Server Holding Register Max Word*, Entry ID 224636. https://support.industry.siemens.com/forum/WW/en/posts/s7-1500-mb-server-holding-register-max-word/224636 +23. Siemens, *SIMATIC S7-1500 Technical Specifications* — CPU-specific DB size limits in each CPU manual's "Memory" table. +24. Siemens TIA Portal Online Docs, *Error messages (S7-1200, S7-1500) — Modbus instructions*. https://docs.tia.siemens.cloud/r/en-us/v20/modbus-rtu-s7-1200-s7-1500/error-messages-s7-1200-s7-1500 +25. Industrial Monitor Direct, *Fix Siemens S7-1500 MB_Client UnitID Error 80C8*. https://industrialmonitordirect.com/blogs/knowledgebase/troubleshooting-mb-client-on-s7-1500-cpu-1515sp-modbus-tcp +26. Siemens SiePortal forum, *How many TCP connections can the S7-1200 make?*, Entry ID 275570. https://support.industry.siemens.com/forum/WW/en/posts/how-many-tcp-connections-can-the-s7-1200-make/275570 +27. Siemens SiePortal forum, *Simultaneous connections of Modbus TCP*, Entry ID 189626. https://support.industry.siemens.com/forum/ww/en/posts/simultaneous-connections-of-modbus-tcp/189626 +28. Siemens SiePortal forum, *How many Modbus TCP IP clients can read simultaneously from S7-1517*, Entry ID 261569. https://support.industry.siemens.com/forum/WW/en/posts/how-many-modbus-tcp-ip-client-can-read-simultaneously-in-s7-1517/261569 +29. Industrial Monitor Direct, *Troubleshooting Intermittent Modbus TCP Connections on S7-1500 PLC*. https://industrialmonitordirect.com/blogs/knowledgebase/troubleshooting-intermittent-modbus-tcp-connections-on-s7-1500-plc +30. PLCtalk forum *"S7-1500 modbus tcp speed?"*. https://www.plctalk.net/forums/threads/s7-1500-modbus-tcp-speed.114046/ +31. Siemens SiePortal forum, *MB_Unit_ID parameter in Modbus TCP*, Entry ID 156635. https://support.industry.siemens.com/forum/WW/en/posts/mb-unit-id-parameter-in-modbus-tcp/156635 -- 2.49.1