In industrial control, commanding discrete actuators—such as tripping an emergency shutdown contactor, turning on an auxiliary cooling fan, starting a lubrication pump, or resetting a fault relay—requires Modbus Function Code 05 (Write Single Coil / 0x05). Defined under the official Modbus Application Protocol Specification V1.1b3, Function Code 05 enables a master/client to set an individual 1-bit binary output entity to either an ON or OFF state across serial (Modbus RTU/ASCII) or Ethernet (Modbus TCP) networks.
While conceptually simple, Function Code 05 contains specific protocol peculiarities—most notably the requirement for the non-intuitive 16-bit constant 0xFF00 to command an ON state (rather than 0x0001), the mandatory exact echo response, and 1-based documentation versus 0-based wire offset translations. In this engineering guide, we examine the byte-by-byte frame architecture of FC05, analyze wire captures, review exception codes, write a Python automation script, and simulate virtual coils using ModbusSimulator.
1. The Modbus Coil Memory Model (0xxxx Range)
In the Modbus memory map, binary data is segregated into two primary tables:
| Table Name | Object Type | Access | PLC Reference | Read Function Code | Write Function Code |
|---|---|---|---|---|---|
| Discretes Input | Single bit | Read-Only | 10001 – 19999 (1xxxx) | FC 02 (0x02) | None (Read-Only) |
| Coils (Discrete Outputs) | Single bit | Read-Write | 00001 – 09999 (0xxxx) | FC 01 (0x01) | FC 05 (0x05), FC 15 (0x0F) |
Coils represent 1-bit read-write boolean variables. In physical PLCs and remote I/O modules, coils drive solid-state relays, solenoid valves, and digital output terminals. In software, coils frequently serve as command triggers, supervisory override switches, and handshake acknowledge flags.
1-Based Addressing vs 0-Based PDU Wire Offsets
A frequent source of PLC integration error stems from the difference between human-readable documentation notation and actual wire offsets:
- PLC Documentation Address (1-based): Coils are numbered starting from
00001. Coil 1 is00001, Coil 17 is00017, and Coil 160 is00160. - PDU Wire Offset (0-based): The physical Modbus protocol data unit transmits a 16-bit zero-based index. Address
00001is transmitted as0x0000. Address00017is transmitted as0x0010(decimal 16).
2. Protocol Data Unit (PDU) Specification for FC05
Under Modbus Specification V1.1b3, the Protocol Data Unit (PDU) is independent of the underlying physical transmission layer (serial RS-485 vs TCP/IP).
Request PDU Structure
| Byte Offset | Field Name | Length | Valid Values | Description |
|---|---|---|---|---|
| 0 | Function Code | 1 Byte | 0x05 |
Specifies Write Single Coil |
| 1 – 2 | Output Address | 2 Bytes | 0x0000 to 0xFFFF |
16-bit zero-based address of the coil to write (Big-Endian / MSB first) |
| 3 – 4 | Output Value | 2 Bytes | 0xFF00 or 0x0000 |
0xFF00 commands ON; 0x0000 commands OFF |
Critical Rule: The 0xFF00 ON Value Constant
Unlike modern programming languages where boolean true is represented as 0x0001, Modbus protocol rules explicitly mandate 0xFF00 for ON and 0x0000 for OFF. Transmitting 0x0001 will cause the remote slave to reject the packet with an Exception Code 03 (Illegal Data Value)!
Normal (Echo) Response PDU
The normal response to Function Code 05 is an exact mirror echo of the request PDU, returned immediately after the coil state has been updated in the slave device's internal memory:
| Byte Offset | Field Name | Length | Hex Value | Description |
|---|---|---|---|---|
| 0 | Function Code | 1 Byte | 0x05 |
Echo of requested function code |
| 1 – 2 | Output Address | 2 Bytes | Same as request | Echo of target coil address |
| 3 – 4 | Output Value | 2 Bytes | Same as request | Echo of written state (0xFF00 or 0x0000) |
3. Wire Packet Breakdowns: Modbus RTU vs Modbus TCP
Scenario: Energize Coil at Address 00160 (Turn ON)
Suppose our master needs to turn ON a motor contactor wired to PLC coil 00160 on Slave ID 1.
The zero-based wire offset is 160 - 1 = 159 = 0x009F.
The ON command constant is 0xFF00.
Modbus RTU Hex Packet Breakdown
[Request Frame - 8 Bytes]
01 05 00 9F FF 00 8D EB
Byte 0: 0x01 -> Slave ID (Unit Identifier 1)
Byte 1: 0x05 -> Function Code 05 (Write Single Coil)
Byte 2-3: 0x00 0x9F -> Output Address 0x009F (Decimal 159 -> Coil 00160)
Byte 4-5: 0xFF 0x00 -> Output Value 0xFF00 (Command Coil ON)
Byte 6-7: 0x8D 0xEB -> CRC-16 Checksum (Low byte 0x8D, High byte 0xEB)
[Normal Response Frame - 8 Bytes]
01 05 00 9F FF 00 8D EB
(Exact echo confirming coil 159 is successfully set to ON)
Modbus TCP Hex Packet Breakdown
In Modbus TCP, the frame is encapsulated in the 7-byte MBAP (Modbus Application Protocol) header, eliminating the trailing CRC-16 checksum:
[Request Frame - 12 Bytes]
00 01 00 00 00 06 01 05 00 9F FF 00
Byte 0-1: 0x00 0x01 -> Transaction Identifier (0x0001)
Byte 2-3: 0x00 0x00 -> Protocol Identifier (0x0000 = Modbus Protocol)
Byte 4-5: 0x00 0x06 -> Length (6 bytes remaining: Unit ID + PDU)
Byte 6: 0x01 -> Unit Identifier (Slave ID 1)
Byte 7: 0x05 -> Function Code 05
Byte 8-9: 0x00 0x9F -> Output Address (Coil 159)
Byte 10-11:0xFF 0x00 -> Output Value (ON)
[Normal Response Frame - 12 Bytes]
00 01 00 00 00 06 01 05 00 9F FF 00
(Exact echo with matching Transaction ID confirming execution)
4. Modbus Exception Codes Encountered with FC05
When a slave server cannot execute a Write Single Coil command, it replies with an Exception Response. In the response, the most significant bit (MSB) of the function code is set to 1 (0x05 + 0x80 = 0x85), followed by a 1-byte Exception Code:
| Exception Code | Name | Root Cause in FC05 | Corrective Action |
|---|---|---|---|
0x01 |
Illegal Function | The slave device firmware does not support writing coils, or coil access is completely disabled. | Verify PLC firmware documentation; confirm whether coils are read-only in this device profile. |
0x02 |
Illegal Data Address | The requested Output Address does not exist in the configured coil memory boundary. | Verify 1-based vs 0-based offset; check the slave's maximum configured coil limit. |
0x03 |
Illegal Data Value | The master sent an Output Value other than 0xFF00 or 0x0000 (e.g., sending 0x0001). |
Ensure client library strictly transmits 0xFF00 for True and 0x0000 for False. |
0x04 |
Slave Device Failure | An unrecoverable hardware or memory write error occurred inside the PLC when updating the physical relay. | Inspect PLC diagnostic logs, check power supply rails, and verify hardware relay module status. |
5. Python Implementation Using pymodbus
Automating coil writes in Python is straightforward using the industrial-standard pymodbus library. The following script connects to a Modbus TCP server, commands a coil ON, verifies the echo response, and then turns it OFF:
from pymodbus.client import ModbusTcpClient
import time
# Connect to Modbus TCP Server / Simulator
client = ModbusTcpClient(host='127.0.0.1', port=502)
if not client.connect():
print("[-] Failed to connect to Modbus TCP server.")
exit(1)
coil_address = 159 # Wire offset 159 -> PLC Coil 00160
unit_id = 1
try:
# 1. Turn Coil ON (PDU sends 0xFF00)
print(f"[+] Commanding Coil {coil_address} -> ON (0xFF00)...")
response_on = client.write_coil(address=coil_address, value=True, slave=unit_id)
if response_on.isError():
print(f"[-] Modbus Exception: {response_on}")
else:
print(f"[✓] Success! Echo received: Address {response_on.address}, Value: {response_on.value}")
time.sleep(2)
# 2. Turn Coil OFF (PDU sends 0x0000)
print(f"[+] Commanding Coil {coil_address} -> OFF (0x0000)...")
response_off = client.write_coil(address=coil_address, value=False, slave=unit_id)
if response_off.isError():
print(f"[-] Modbus Exception: {response_off}")
else:
print(f"[✓] Success! Echo received: Address {response_off.address}, Value: {response_off.value}")
finally:
client.close()
print("[*] Connection closed.")
6. Simulating Function Code 05 in ModbusSimulator
Before connecting a software client or SCADA driver to live electrical contactors or critical valves, automation engineers use ModbusSimulator to safely test FC05 operations:
- Interactive Visual Relay Coils: Monitor coils in real-time on an interactive grid. When an external client writes 0xFF00, the virtual coil indicator lights up instantly with zero hardware risk.
- Automated Value Echo Verification: Built-in protocol validation verifies that the echo frame conforms exactly to V1.1b3 specifications.
- Exception Simulation: Configure intentional write protections or out-of-range boundaries to test how your supervisory control client handles Exception Codes 02 and 03.
- Dual RTU & TCP Engines: Test serial RS-485 COM ports and TCP sockets simultaneously on Windows 10 and 11.
Test Your Modbus Systems Today
Download ModbusSimulator to simulate virtual Modbus RTU and TCP slaves, script register and coil values, and debug PLC networks without hardware.
Download ModbusSimulator Free →Frequently Asked Questions (FAQs)
What is Modbus Function Code 05?
Modbus Function Code 05 (0x05) is the standard command used by a master/client to write a discrete binary state (ON or OFF) to a single coil (0xxxx) on a remote slave/server.
Why does Modbus FC05 use 0xFF00 for ON instead of 0x0001?
Under Section 6.5 of the Modbus V1.1b3 specification, 0xFF00 is the mandatory 16-bit constant for ON, and 0x0000 is for OFF. Sending 0x0001 triggers an Exception Code 03 (Illegal Data Value).
What is the normal response to a Function Code 05 request?
The normal response from the slave is an exact echo of the request PDU (Function Code + Output Address + Output Value written).
What is the difference between address 00001 and wire address 0x0000?
00001 is the 1-based human documentation address. The physical communication frame transmits a zero-based offset, so 00001 is transmitted as 0x0000.
What causes Modbus Exception Code 02 with Function Code 05?
Exception Code 02 (Illegal Data Address) indicates that the requested coil address does not exist in the slave device's allocated memory space.
What is the difference between Function Code 05 and Function Code 15?
Function Code 05 writes a single coil using the 0xFF00/0x0000 constant. Function Code 15 (0x0F) writes a block of multiple coils (1 to 1,968) in a single frame, packed 8 per byte.
Can Function Code 05 write to Discrete Inputs?
No. Discrete Inputs (1xxxx) are strictly read-only by protocol definition. FC05 only writes to Coils (0xxxx).