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:

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:

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).