1. Problem Overview
A Siemens S7-400 station equipped with a CP 443-1 communicates with an external test unit (ETU) running on a PC. The PLC operates as the Modbus TCP client and the ETU is the server. The ETU exposes a single Ethernet interface, a single IP address, and a single TCP port (502). Six logical Modbus slaves (Unit IDs 1 through 6) are multiplexed behind that single endpoint, and every slave presents the same Modbus register map. The current implementation uses one ModbusCP function block per Unit ID, with three data blocks per slave (holding, input, coils). Each ModbusCP instance is called six times in a cyclic OB1 (20 ms) to read or write the required registers, producing a total round-trip time of approximately 4 seconds for the full 36-telegram cycle.
The two practical questions are:
- Can the cycle time be reduced?
- Can a single ModbusCP instance be used while still routing each Unit ID's payload to a different data block or to a different address range within the same data block?
Both questions are answered yes. The remainder of this article documents the architecture, the runtime parameterisation, the library version prerequisites, and the verification procedure required to realise that change on a CP 443-1 with the S7-OpenModbus/TCP library.
2. Root Cause Analysis
The 4-second cycle is not a property of the Modbus TCP protocol itself. It is a consequence of three cumulative design choices:
| Culprit | Mechanism | Impact |
|---|---|---|
| Six parallel ModbusCP instances | Each instance opens its own AG_SEND/AG_RECV pair via AG_LSEND/AG_LRECV. The CP 443-1 firmware and the CPU's communication stack allow only a bounded number of parallel jobs. | The OS schedules the telegrams serially behind that bound, inflating turnaround time. |
| Static UNIT binding at compile time | ModbusCP is configured with a fixed Unit ID; the runtime cannot switch from slave 1 to slave 6 in the same buffer. | Six FBs are needed to reach six slaves, multiplying the AG_LSEND/AG_LRECV call count. |
| Calling ModbusCP six times per Unit ID | Six telegrams (holding read, input read, coils read, etc.) per slave, repeated for six slaves. | 36 telegrams per cycle. In client mode each telegram consumes two CPU scan evaluations (one to issue AG_LSEND, one to evaluate AG_LRECV). |
Operating in client mode, one Modbus telegram requires two CPU scan evaluations on the S7-400 (one cycle to drive the send, one cycle to receive the response). When the ETU is slow to answer, additional scans pile up. Server mode halves that cost to a single scan, but switching to server mode is not an option when the ETU is the slave endpoint.
For reference, the official S7-OpenModbus/TCP CP 4xx manual specifies the maximum number of parallel AG_LSEND/AG_LRECV jobs permitted per CP; exceeding this number causes the FB to return an error code and the call is effectively delayed until a job slot frees. The fastest way to lift the bottleneck is therefore to collapse the six ModbusCP instances into one and to switch the UNIT input dynamically at runtime.
3. Target Architecture: One TCP Connection, One FB Instance
Because the ETU listens on a single IP and a single port, exactly one TCP connection is required. In NetPro (or HW Config with the CP 443-1) configure:
- One TCP connection of type "TCP native" or use the connection configured by the S7-OpenModbus/TCP wizard.
- Partner IP: the ETU's address.
- Partner port: 502.
- Local port: any unused port in the 2000–5000 range typically used by S7-OpenModbus/TCP.
Once a single connection is in place, a single instance of ModbusCP (FB 8 in the V2.0 / V4.x library) is sufficient for all six Unit IDs. The Unit ID is a runtime input to the FB, not a connection property.
FB8 "ModbusCP" with corresponding instance DB DB8. The instance DB holds the connection ID, the receive buffer pointer, and the internal state machine. The wizard in STEP 7 generates this instance DB and binds the connection identifier automatically.4. Routing Six Unit IDs Through a Single FB
The core trick is to use the data block parameterised in OB100 (startup OB) as a generic receive buffer for any Unit ID. After every successful telegram, the application code performs a BLKMOV (SFC 20) to copy the relevant slice of the receive buffer into the destination area reserved for that particular Unit ID.
This pattern is the Siemens-recommended way to share one instance across multiple logical slaves and is documented in the S7-OpenModbus/TCP function manual. The same buffer can be reused because the ModbusCP FB refills it on each call. Two implementation variants are valid:
- Single destination DB, offset by Unit ID: every Unit ID has its own reserved offset inside one consolidated DB. Useful when the register map is identical across slaves.
- Per-Unit destination DBs: each Unit ID owns a dedicated DB and BLKMOV targets a different DB number. Useful when downstream logic expects discrete DB boundaries.
The schematic is the same in both cases:
+---------------------------+
| ModbusCP (single FB) |
| UNIT := iUnitIdx |
+-----------+---------------+
|
v
+---------------------------+
| Receive Buffer DB | (parameterised in OB100)
+-----------+---------------+
|
BLKMOV (SFC 20)
|
+-----------+---------------------...----------+
| | | |
v v v v
DB for Unit 1 DB for Unit 2 ... DB for Unit 6
(offset 0..n) (offset 0..n) (offset 0..n)
5. Cycle Budget After Consolidation
With the redesigned architecture the cycle budget can be approximated. The deterministic part is the number of Modbus telegrams; the variable part is the ETU response latency.
| Metric | Before (6 FBs, 6 calls each) | After (1 FB, 6 calls per Unit, 6 Units) |
|---|---|---|
| ModbusCP instances | 6 | 1 |
| Telegrams per cycle | 36 | 36 (unchanged; driven by data volume, not by FBs) |
| Parallel AG_LSEND/AG_LRECV jobs | up to 12 active at once | 2 active (one send, one receive) |
| Client mode scans per telegram | 2 | 2 (protocol limit) |
| Measured end-to-end cycle | ~4 s in OB1 / 20 ms | Typically < 1 s with V4.x on a healthy CP 443-1, subject to ETU latency |
Reducing the parallel job count from up to twelve to a stable two is the dominant optimisation. The 4-second figure measured in the field is largely accounted for by AG_LSEND/AG_LRECV queueing at the CP 443-1 when six FBs contend for slots.
6. Step-by-Step Implementation
6.1 Prerequisites
- STEP 7 V5.5+ project containing the S7-400 station and CP 443-1.
- S7-OpenModbus/TCP library V4.x installed. If the package on hand only shows ModbusCP V1.1 and V2.0, download the current V4.1 from Siemens Product Support under entry ID 22662104 ("S7-OpenModbus/TCP, CP 4xx").
- One free data block that will act as the universal receive buffer. Size it to the largest single Modbus response payload (e.g., 100 words for 200 bytes of holding registers).
- Six destination data blocks (or one DB with six reserved ranges) for the per-Unit slices.
6.2 NetPro Configuration
- Open NetPro and select the CP 443-1.
- Delete any auto-generated ModbusCP connections created by the previous FBs.
- Insert a single TCP connection to the ETU IP, remote port 502.
- Compile and download the connection table (HW Config > PLC > Download > Connection).
6.3 OB100 Startup
Initialise the receive buffer DB pointer and the connection ID in OB100. The library wizard normally writes the necessary initialisation; verify the following tags are present in the instance DB:
// DB8 (ModbusCP instance) symbols of interest
static ID : INT := 1; // connection ID from NetPro
static pReceiveBuf : ANY; // pointer to OB100-parameterised receive DB
static iUnit : INT := 1; // current Unit ID (1..6)
6.4 OB1 Cyclic Code
Call the single ModbusCP once per telegram. After each call, BLKMOV the freshly received slice into the destination area for the current Unit. The example below shows two telegrams per Unit (holding registers and coils) and assumes a per-Unit destination DB.
FUNCTION_BLOCK FB_ModbusCycle
VAR
iUnit : INT; // 1..6
telegramIdx : INT; // 0..5 (6 telegrams per Unit)
ok : BOOL;
END_VAR
BEGIN
// Outer loop: 6 Unit IDs
FOR iUnit := 1 TO 6 DO
// Inner loop: 6 telegrams per Unit (read holding, read input,
// write coils, etc.; adjust to project)
FOR telegramIdx := 0 TO 5 DO
// Set Unit before each telegram
"DB8".iUnit := iUnit;
// Call ModbusCP (FB8) - single instance, dynamic UNIT
"ModbusCP_DB"( // DB number generated by the wizard
UNIT := "DB8".iUnit,
FUNC := telegram_function[telegramIdx],
ADDR := telegram_address[telegramIdx],
QUANTITY := telegram_quantity[telegramIdx],
DB_NO := receive_buffer_DB,
DONE => ok
);
IF ok THEN
// Move the freshly filled receive buffer slice to the
// destination DB reserved for this Unit
CASE iUnit OF
1: SFC20(
SRCBLK := receive_buffer_DB,
SRCDB := telegram_offset[telegramIdx],
RET_VAL := #retVal,
DSTBLK := "DB_Unit1",
DSTDB := telegram_offset[telegramIdx]);
2: SFC20(... target "DB_Unit2" ...);
// ... 3..6
END_CASE;
END_IF;
END_FOR;
END_FOR;
END_FUNCTION_BLOCK
6.5 Replace the Old Six-FB Layout
- Delete the six legacy
ModbusCPinstance DBs and their call sites in OB1. - Cross-reference any other code that pointed to the legacy receive DBs and redirect it to the new per-Unit destination DBs.
- Download the program and the connection configuration.
7. Library Version Considerations
The original installation media contains two FB versions of the client:
| Version | Symbol | Status | Notes |
|---|---|---|---|
| ModbusCP V1.1 | FB1 / FB2 in legacy library | Obsolete | Limited parallel-job handling. Does not expose the same runtime UNIT switching as V2.0/V4.x. |
| ModbusCP V2.0 | FB8 in legacy library | Functional | Sufficient for the single-FB, multi-Unit pattern shown above. UNIT input is writable at runtime. |
| S7-OpenModbus/TCP V4.1 (CP 4xx) | FB8 in current library | Recommended | Optimised state machine, better handling of slow partners, and updated error codes. Download from Siemens Product Support. |
If the existing program was built against V1.1, migrate the call interface to V2.0 (or V4.1) before applying the architectural change. Mixing versions inside the same OB1 is not supported. See the official S7-OpenModbus/TCP CP 4xx manual for the migration matrix.
AG_CNTRL in addition to the legacy AG_LSEND/AG_LRECV path. CP 443-1 variants with the appropriate firmware support this. Confirm the CP firmware release supports AG_CNTRL before activating it; older CP 443-1 firmware may still require the AG_LSEND/AG_LRECV path used in V2.0.8. Verifying Telegram Timing with Wireshark
Regardless of code changes, the protocol exchange must be measured end-to-end. The standard tool is Wireshark with the modbus dissector enabled.
- Mirror the ETU switchport to a laptop running Wireshark (SPAN port or TAP).
- Apply a capture filter:
tcp.port == 502. - Trigger one cycle of the application and stop the capture.
- Inspect the time delta column between a
Modbus/TCP > Queryand the matchingModbus/TCP > Response. The delta is the true server response latency. The remaining cycle time is the client-side queueing cost.
Acceptable target: client-side queueing of less than 5 ms per telegram at OB1 = 20 ms when the partner responds in < 50 ms. If queueing exceeds 50 ms, the AG_LSEND/AG_LRECV job pool is saturated and the consolidation described in section 4 is mandatory.
9. Parameter Reference
| ModbusCP input | Type | Meaning | Field-proven value for this application |
|---|---|---|---|
| UNIT | INT | Modbus slave address sent in the MBAP header | 1..6, switched per telegram at runtime |
| FUNC | INT | Modbus function code | 1, 2, 3, 5, 6, 15, 16 (per project) |
| ADDR | DINT | Starting Modbus address (0-based) | Project-specific |
| QUANTITY | INT | Number of coils/registers | Project-specific |
| DB_NO | INT | Data block number for the transfer | Universal receive buffer DB |
| DONE | BOOL | Completion flag (1 cycle pulse) | Use as trigger for BLKMOV |
| ERROR | BOOL | Error flag | Latch into HMI status word |
| STATUS | WORD | Detailed error code | See library manual, e.g., W#16#0001 connection fault |
10. Troubleshooting Matrix
| Symptom | Likely cause | Corrective action |
|---|---|---|
| Cycle time stays at 4 s after consolidation | Multiple ModbusCP instances still active, or the old receive DBs are still being written to | Cross-reference the project for residual FB calls and DB references. Delete the legacy instance DBs. |
| ModbusCP returns STATUS = W#16#80C8 | AG_LSEND/AG_LRECV job pool exhausted | Reduce telegrams per OB1. Stagger telegrams across multiple OB1 passes. |
| Wrong data lands in destination DB | BLKMOV triggered before DONE pulse or wrong offset | Move BLKMOV into the DONE branch; recompute the slice offsets. |
| Connection drops after a few hours | Partner timeout shorter than the new cycle time | Match the Modbus "response timeout" of the FB to the worst-case ETU latency. Wireshark the gap between consecutive queries. |
| UNIT 1 always answers; Units 2..6 return exception 0x0A | ETU does not support runtime Unit switching on a single port | Verify with the ETU vendor. Some embedded stacks only honour the configured Unit until restarted. |
| Compile error: "FB8 unknown" | Library not installed or wrong version | Install S7-OpenModbus/TCP V4.x and re-open the S7 program. |
11. Verification Procedure
- Open STEP 7 and the online view of the S7-400. Monitor the cycle time of OB1 with the diagnostics buffer; it should drop measurably after the new instance count is downloaded.
- Start Wireshark on the mirrored port. Confirm the TCP source port is stable (one connection), and that the MBAP Unit Identifier field cycles through 1..6.
- Force each destination DB in the PLCSIM or online watch table and confirm that the values from the corresponding Unit ID land at the correct offset.
- Force a deliberate partner timeout by unplugging the ETU. The
ERRORandSTATUSoutputs of ModbusCP must reflect the connection fault; the application must not hang. - Replug the ETU and confirm automatic recovery without a CPU restart.
12. Field-Proven Caveats
- Do not call the FB in OB1 unconditionally with a high OB1 rate. With 6 Units and 6 telegrams each, even at 20 ms that is 1800 telegram starts per minute. Gate the call from a slower clock (100 ms or 200 ms) to keep the AG_LSEND/AG_LRECV pool healthy.
- Keep the receive buffer DB non-optimised (classic DB, not S7-1200/1500 optimised). The library writes absolute byte offsets into the DB; optimised access can break those offsets.
-
Do not reuse the same receive buffer slot for two telegrams in the same OB1 pass. BLKMOV must read the buffer only after
DONEgoes high. Otherwise a fast read-modify-read sequence can write stale data to the destination. - Watch STATUS W#16#80C8 and STATUS W#16#80C9. They are the ModbusCP-specific error codes for job pool exhaustion and connection loss. The library manual lists the full set.
13. Standards and Reference Material
The implementation conforms to the Modbus Application Protocol Specification, which defines the MBAP header and the Unit Identifier field that the S7-OpenModbus/TCP library exposes as the UNIT input. The Modbus Organization maintains the authoritative specification and is the only place to verify the behaviour of the Unit Identifier on a single TCP connection. The Siemens product manual and the STEP 7 Online Help are the authoritative sources for FB8 / DB8 behaviour, error codes, and connection configuration on the CP 443-1.
Key official references:
- S7-OpenModbus/TCP CP 4xx, library V4.1 (Siemens Product Support)
- SIMATIC NET CP 443-1 (6GK7 443-1EX11-0XE0) manual
- Modbus Application Protocol Specification V1.1b3 (Modbus Organization)
- Modbus Messaging on TCP/IP Implementation Guide V1.0b (Modbus Organization)
Why does my CP 443-1 take 4 seconds to cycle 36 Modbus telegrams?
The 4-second figure is dominated by AG_LSEND/AG_LRECV job pool contention, not by the wire. Running six ModbusCP instances in parallel over-saturates the CP's job pool. Collapsing to one ModbusCP instance and switching the UNIT input at runtime brings the cycle time down to the actual partner response time plus a small per-telegram overhead.
Can one ModbusCP FB address six different Unit IDs?
Yes. The Modbus Unit Identifier is a per-telegram field of the MBAP header. FB8 in the S7-OpenModbus/TCP V2.0 and V4.x libraries accepts UNIT as a runtime input, so the same FB instance can be reused for any number of slaves that share the TCP connection. Use a single receive buffer DB and BLKMOV the slice into the per-Unit destination DB after each DONE pulse.
Which S7-OpenModbus/TCP version should I use?
Use the current V4.1 release from Siemens Product Support (entry ID 22662104). V1.1 is obsolete and lacks the runtime UNIT switching and error reporting you need. V2.0 is functional for the single-FB pattern but V4.1 has a leaner state machine and improved handling of slow partners.
How many telegrams can one FB issue per OB1 scan?
Limit it to one ModbusCP call per OB1 pass. A Modbus telegram consumes two scan evaluations in client mode (one to send, one to receive), so back-to-back calls inside a single scan can overlap and overrun the job pool. Drive the call from a slower clock (e.g., a 100 ms or 200 ms pulse) to keep the system healthy.
How do I confirm the optimisation worked?
Mirror the ETU port and run Wireshark with a filter of tcp.port == 502. After the change, you should see one TCP connection, the MBAP Unit Identifier cycling 1..6, and the inter-telegram delta dominated by the partner's response time. The CPU's online diagnostics buffer will also show a shorter OB1 cycle and reduced communication load.