Problem Overview: Modbus Coil Writes Silently Overwritten by LOGO! Program
When integrating a Siemens LOGO! 8 (BM series) logic module with a SIMATIC IoT2000 over Modbus TCP, the most common symptom reported in field deployments is that a Write Single Coil (function code 05) or Write Multiple Coils (function code 15) command issued from the IoT2000 appears to succeed on the wire (the TCP slave returns ACK 0x05 0xFF00), yet the physical output never changes state. Disabling the Ladder/Function Block Diagram (FBD) program on the LOGO! restores remote control immediately, which confirms that the issue is not a transport-layer fault but a scan-cycle precedence conflict between Modbus network signals and program-derived output state.
The user scenario typically looks like this:
- IoT2000 runs CODESYS 3.5 SPxx (or Node-RED with
node-red-contrib-modbus) as a Modbus TCP master. - LOGO! 8BM is configured as a Modbus TCP server (slave) on port 502.
- A simple program maps physical input I2 to digital output Q1 via an FBD or Ladder rung.
- Function code 05 write to the coil corresponding to Q1 has no effect while the program is active.
This article documents the root cause, the correct Modbus address mapping for the flag (M) area, and three production-grade remedies that allow simultaneous local (button/physical input) and remote (Modbus/IoT2000) control of the same output.
Root Cause: LOGO! Scan Cycle and Output-Stage Precedence
The Siemens LOGO! executes a deterministic cycle that processes the user program after it has already read peripheral and network inputs. The cycle, in order, is:
- Read inputs – The image table is populated with the live state of physical inputs (I1..I24), analog inputs (AI1..AI8), network inputs (NI1..NI64), and Modbus-typed network inputs.
- Execute program – The FBD/Ladder logic is evaluated using the populated input image; flag bits (M1..M64) and outputs (Q1..Q20) are computed.
- Write outputs – The output image is pushed to physical relays/transistors and to the network-output image (NQ).
This means that a Modbus master cannot directly set a physical output (Q1..Q20). The Modbus register/flag plane participates in the cycle as either a network input (read-only from the program side) or a flag (read/write) that the program must explicitly use. When the program contains a rung such as I2 → Q1 (or even an OR with a network input that is not wired), the FBD's computed value overwrites whatever the Modbus master attempted to write to Q1 in step 3.
Concretely: if you write function code 05 to the coil address associated with Q1, the LOGO! acknowledges the write because Modbus storage accepted the value, but the next cycle's program step rebuilds Q1 from I2 and re-asserts the output. From the master's perspective, the output never latches.
What Modbus can and cannot change
| LOGO! object | Modbus writable? | Effect of write |
|---|---|---|
| Physical input I1..I24 | No (read-only inputs) | Inputs are read-only from the Modbus side. Attempting FC05 returns ILLEGAL DATA ADDRESS or is silently ignored. |
| Flag bit M1..M64 | Yes (FC 05/15) | The flag value is updated in the Modbus image. Program must reference it as a network input or directly as a flag in FBD to take effect. |
| Physical output Q1..Q20 | No (read-only image) | Writes are accepted at the Modbus layer but immediately overwritten by the next program cycle. Causes the user-reported "stuck output" symptom. |
| Network input NI1..NI64 | Yes (via Modbus coil address) | Network inputs are the canonical remote-control surface. FBD references NIx to act on Qx. |
Understanding the LOGO! Modbus Memory Map
The LOGO! 8 Modbus TCP server exposes the following address space to the master. Offsets below are 0-based and match what CODESYS, Node-RED, libmodbus, and pymodbus clients use.
| Modbus address (0-based) | Modbus address (1-based) | Object | Size | Access |
|---|---|---|---|---|
| 0000–0015 | 1–16 | Physical digital outputs Q1..Q16 | 16 coils | Read-only (writes ignored at next cycle) |
| 0016–0019 | 17–20 | Physical digital outputs Q17..Q20 (BM extension) | 4 coils | Read-only |
| 0255 | 256 | Flag M1 | 1 coil | Read/Write (verified in field) |
| 0256 | 257 | Flag M2 | 1 coil | Read/Write |
| … | … | … | … | … |
| 0318 | 319 | Flag M64 | 1 coil | Read/Write |
| 8192–8215 | 8193–8216 | Discrete inputs I1..I24 | 24 coils | Read-only |
For holding-register (FC 03/06/16) access, the analog region begins at Modbus register address 0 (or 1 in 1-based) and includes the analog inputs (AI1..AI8), analog flags (AM1..AM64), and analog outputs (AQ1..AQ8). For pure digital coil control, only the coil plane matters.
Solution 1: Reference a Flag Bit (Mx) from the FBD
The cleanest remedy is to have the LOGO! program treat the Modbus write as a flag-bit source, then OR that flag with the physical input before driving the output.
FBD program steps
- Open LOGO! Soft Comfort and load the existing program.
- Insert an OR (B01) block with two inputs:
I2(physical button) andM1(Modbus flag). - Drive
Q1from the OR block output. - Save and download to the LOGO!.
Master-side write (CODESYS on IoT2000)
Configure a Modbus_TCP_Master device in the CODESYS project tree, add a ModbusCoil variable node, and address it as 0x2040 (decimal 8256, the M1 coil). The following IEC 61131-3 ST snippet drives the coil from a button variable:
PROGRAM PLC_PRG
VAR
bRemoteLightOn : BOOL; // linked to CODESYS HMI/IoT2000 button
bLightCoilOut : BOOL; // linked to Modbus master coil node (M1)
END_VAR
IF bRemoteLightOn THEN
bLightCoilOut := TRUE; // sets Modbus coil 8256 → M1 → OR → Q1
ELSE
bLightCoilOut := FALSE;
END_IF;
With this program, pressing the local button (I2) energizes Q1, and setting the Modbus coil at address 8256 from the IoT2000 also energizes Q1 via the OR logic. The output is latched as long as either source is active. To implement an OFF precedence (e.g., a remote stop must override a stuck button), change the FBD to an SR (set-dominant) flip-flop with M1 as the reset input.
Solution 2: Use a Network Input (NIx) in the FBD
LOGO! 8 supports dedicated Network Inputs (NI1..NI64) in the FBD editor, which are conceptually the same as flags but are surfaced in a separate palette and can be associated with a specific Modbus slave unit ID / coil number in the LOGO! configuration UI. This is the most idiomatic Siemens approach for Modbus-driven I/O.
LOGO! configuration
- In the LOGO! project, open Tools > Parameter VM Mapping (or in the editor: right-click an empty pin > Network Input).
- Select NI1. The property dialog asks for: Unit ID (the slave device number, usually 1 for the local LOGO!), Coil number (the Modbus coil offset to read), and Data type (BOOL for digital).
- Enter Coil number = 8256 to mirror M1, or a free coil slot if you want NI1 to be a true network input distinct from any flag.
- Replace the
M1reference in the OR block withNI1.
Network inputs are read at the start of every cycle, so the value seen by the FBD is the most recent Modbus coil state. The trade-off vs. plain flags is that network inputs require an explicit slave declaration; flags do not. For simple point-to-point integrations, flags are sufficient.
Alternative: use Node-RED on IoT2000
If the IoT2000 image is the Node-RED variant, the same pattern applies via node-red-contrib-modbus:
// modbus-write node configuration
{
"name": "Write Q1",
"unitid": 1,
"fc": "FC05",
"address": 8256,
"quantity": 1
}
// payload: [true] to energize, [false] to release
Wire the modbus-write node to any upstream trigger (MQTT, dashboard button, REST, schedule) and toggle the boolean payload to drive the LOGO! flag, which the FBD then ORs with the physical input.
Solution 3: Pure Master-Side OR Logic (no FBD change)
If the LOGO! program cannot be modified (e.g., locked by the panel builder), the master can implement the OR by always writing the result of (local I2 ∪ remote request). The slave LOGO! program can be reduced to a single Q1 = I2 rung, and the IoT2000 mirrors the physical input through its own digital input module (e.g., a LOGO! AM2 RTD module connected to the IoT2000's GPIO via a custom script). This approach is not recommended for production: it adds a second scan path (master + slave), increases latency, and the two clocks can disagree on a transient pulse. Use Solution 1 or 2 unless the program really cannot be touched.
Configuring the Modbus TCP Master on the IoT2000
The IoT2000 (IOT2050, IOT2040) ships with either a Yocto Linux (Example Image) or a SIMATIC Industrial OS image. The Modbus TCP master stack depends on the chosen runtime.
| Runtime | Library / Tool | Notes |
|---|---|---|
| CODESYS 3.5 SPxx | Modbus_TCP_Master library | Declare a slave device tree node, add a ModbusCoil variable, address 8256 (1-based) for M1. |
| Node-RED |
node-red-contrib-modbus v5.x |
Use the modbus-flex-write or modbus-write node, set Unit ID = 1, FC = FC05, Address = 8256. |
| Python | pymodbus 3.x |
client.write_coil(8256, True, slave=1). Note: pymodbus is 1-based by default for write_coil in v3.x. |
| Plain C / libmodbus | libmodbus 3.1.x |
modbus_write_bit(ctx, 8255, TRUE) (0-based; verify your binding). |
Connection parameters for a typical LOGO! 8BM deployment:
// pymodbus 3.x example
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.0.10', port=502, timeout=2)
client.connect()
# Drive Q1 (through M1 = 8256) ON
result = client.write_coil(8256, True, slave=1)
print(result)
# Drive Q1 OFF
result = client.write_coil(8256, False, slave=1)
print(result)
# Read back to verify
rr = client.read_coils(8256, 1, slave=1)
print(rr.bits[0])
client.close()
Always wrap the connect()/write_* calls in a retry-with-backoff block. The IoT2000 to LOGO! 8BM link is tolerant of brief TCP drops, but the LOGO! Modbus server does not queue writes; a lost write is lost.
Modbus Function Codes for Coil Operations
Per the Modbus Application Protocol Specification V1.1b3 and the Modbus Protocol Reference, the following function codes apply to discrete coils. All multi-byte values are big-endian (network byte order).
| FC | Name | Request PDU | Use case |
|---|---|---|---|
| 01 (0x01) | Read Coils | [Starting Address Hi][Lo][Quantity Hi][Lo] | Poll Q1..Q20 and M-flags in one transaction (max 2000 coils per MB). |
| 02 (0x02) | Read Discrete Inputs | Same as FC01 | Poll I1..I24. Logo! returns input image. |
| 05 (0x05) | Write Single Coil | [Addr Hi][Lo][0xFF][0x00 = ON, 0x00][0x00 = OFF] | Single boolean toggle. The reference confirms this is the canonical method for setting one coil. |
| 15 (0x0F) | Write Multiple Coils | [Addr Hi][Lo][Quantity Hi][Lo][Byte Count][Coil bytes] | Atomic multi-coil update. Useful for refreshing a group of flags in a single transaction. Reference: Beijer Electronics Modbus Interface. |
0xFF 0x00 (ON) or 0x00 0x00 (OFF). Any other pattern is a modbus exception 03 (ILLEGAL DATA VALUE). Some masters (older libmodbus forks) accept 0x01 0x00 – this is non-conformant and may be rejected by the LOGO! 8BM slave. Confirm the master's FC 05 encoding with a packet capture if the write returns an exception.Verification: End-to-End Test Procedure
- Build the FBD as described in Solution 1. Download to LOGO! and verify it enters RUN (green LED, no SF/DIAG error).
- From the IoT2000, send FC 05 to coil 8256 (M1) with value 0xFF00. Within one LOGO! cycle (typically 5–25 ms for Ladder, longer for complex FBD), Q1 should energize.
- Measure Q1 with a multimeter or scope – you should see 24 V DC (or 230 V AC if wired to a relay output) at the load terminals.
- Send FC 05 with value 0x0000. Q1 should de-energize.
- Press the physical button wired to I2. Q1 should energize regardless of the current M1 state.
- Press the button and simultaneously send FC 05 ON. Release the button. Q1 should remain ON (the OR block keeps it latched via M1). This confirms the dual-source pattern.
- Send FC 01 (Read Coils) at address 8256, quantity 1. The response should reflect the last value written – this is your round-trip verification.
- Send FC 05 to address 0000 (Q1 directly). Confirm that the response is ACK but the next FC 01 read at 0000 returns the program's computed Q1 value, not the written one. This documents the override behavior for future troubleshooting.
Common Pitfalls and Edge Cases
| Symptom | Likely cause | Resolution |
|---|---|---|
| Master reports success but Q1 never changes | FBD overwrites the Q-coil image every cycle | Route Modbus write through a flag (M1) or network input (NI1) and OR with physical input in FBD. |
| Master reports Exception 02 ILLEGAL DATA ADDRESS | Coil address outside LOGO! map (e.g., 9999) | Verify the address against the table above; flags M1..M64 live in the 8256–8319 range (1-based) on the verified firmware. |
| Master reports Exception 03 ILLEGAL DATA VALUE | FC05 value field is not 0xFF00 or 0x0000 | Hard-code the two valid values in the master; check any custom driver that accepts a raw boolean. |
| Network input dialog asks for unit ID and coil number with no defaults | First-time network input use; LOGO! does not auto-fill | Enter Unit ID = 1 (local LOGO!), Coil number = 8256 for M1 mirror. Click Apply. |
| LOGO! in STOP / SF LED on | Program download interrupted or invalid block | Re-download the project; check the SD card and battery if the SF persists. Note that LOGO! in STOP does not process Modbus writes – the coil image is frozen. |
| Write works in benchmark, fails under load | LOGO! Modbus server allows one connection at a time on TCP/502 | Re-use a single Modbus connection; do not spawn a new client per request. Implement a connection pool in the master. |
| IoT2000 cannot resolve LOGO! hostname | mDNS / DNS issue on the IoT network | Use static IP for LOGO! in the project's Ethernet settings. The LOGO! 8BM supports DHCP but the IP is not visible in some discovery tools. |
| Address mapping differs from one LOGO! to the next | Firmware revision difference (0BA8 vs 8BM ES3) | Read coils 8256 and 8257 at commissioning; whichever toggles with a known M1 reference is the correct one. Document the offset in the project README. |
Related Documentation
- LOGO! 8 System Manual (Siemens Support) – master reference for the BM series hardware, memory map, and scan cycle.
- SIMATIC IoT2050 / IoT2000 Operating Instructions – hardware variants, supported Linux images, network configuration.
- Modbus Application Protocol Specification V1.1b3 – authoritative protocol reference for FC 01/02/05/15.
- Modbus Protocol Reference (modbustools.com) – compact PDU tables and example traces.
- Beijer Electronics Modbus Interface (DIO/GL-997X) – independent reference for the Write Multiple Coils (0x0F) encoding.
FAQ
Why can a Modbus master not directly change LOGO! physical output Q1?
LOGO! executes a fixed scan cycle: read inputs → evaluate program → write outputs. A write to the Q-coil address is accepted by the Modbus layer but is overwritten on the next cycle by whatever the FBD/Ladder program computes for Q1. The program must explicitly consume a Modbus-reachable object (flag Mx or network input NIx) and feed it into the output via logic such as an OR block.
What is the correct Modbus address for the M1 flag in a LOGO! 8BM?
On the deployed firmware (verified in field on a Siemens LOGO! 8BM with CODESYS as master), M1 maps to Modbus coil address 8256 (1-based) / 0x2040. Earlier support documentation sometimes cited 8257; the offset can vary by firmware. Read coil 8256 at commissioning and confirm that the bit mirrors the FBD-driven M1 value before relying on the address in production code.
What is the difference between using a flag (Mx) and a network input (NIx)?
Both are writable from the Modbus master and both are read by the FBD on every cycle. Network inputs add a configuration dialog where you declare the slave Unit ID and the specific Modbus coil number to bind, making the wiring explicit and auditable. Flags are simpler to use and require no slave binding, which is preferable for a single-master, single-slave topology. Choose NIx when the program may be replicated across multiple LOGO! slaves with different coil numbering.
Which Modbus function code should I use to toggle a single output?
Use function code 05 (Write Single Coil) for one bit. Set the value field to 0xFF00 for ON or 0x0000 for OFF; any other pattern is a modbus exception 03. For a synchronous update of several flags, use function code 15 (0x0F, Write Multiple Coils), which guarantees that all bits in the range are written in the same LOGO! cycle and avoids a transient state where only some flags have updated.
Can Node-RED on the IoT2000 replace CODESYS for this integration?
Yes. The same flag-OR FBD is required on the LOGO! regardless of the master. On the IoT2000, install node-red-contrib-modbus, configure a modbus-write node with Unit ID 1, FC 05, Address 8256, and wire its boolean payload to any trigger (MQTT, dashboard, schedule). The transport is identical; the choice is a function of which runtime is already deployed on the IoT2000 image.
Does the LOGO! 8BM support more than one concurrent Modbus TCP connection?
The built-in Modbus TCP server accepts a single TCP connection on port 502. Opening a second client while the first is connected may either reject the new connection or displace the existing one, depending on firmware. Re-use a single long-lived client socket in the master application; do not open a new socket per write. This is a common cause of intermittent "write succeeded but state is wrong" behavior in high-frequency polling scenarios.