In industrial automation and SCADA systems, Function Code 01 (Read Coils / 0x01) is the foundational Modbus command for retrieving the binary state of digital outputs, solenoid valves, motor contactors, alarm beacons, and internal boolean flags. Whether communicating over two-wire RS-485 serial loops (Modbus RTU) or high-speed industrial Ethernet (Modbus TCP), mastering the bit-packing mechanics and byte structures of FC01 is critical for automation engineers.
This engineering guide provides an exhaustive breakdown of Function Code 01: request/response packet data units (PDU), application data units (ADU) across RTU and TCP, bit packing and endianness conventions, error handling, Python client scripts, and zero-hardware simulation using ModbusSimulator.
1. Coil Memory Model: 0xxxx Addressing & Wire Offsets
In the classic Modicon PLC memory architecture, Modbus registers and discrete bits are segregated into four distinct primary tables. Coils occupy the 0xxxx range:
| Table Type | Object Type | Modicon PLC Address | Wire Offset (Hex) | Access Rights | Standard Read FC |
|---|---|---|---|---|---|
| Discrete Coils | Single bit (Binary Output / Flag) | 00001 – 09999 / 065536 | 0x0000 – 0xFFFF | Read / Write | FC01 (0x01) |
| Discrete Inputs | Single bit (Binary Physical Input) | 10001 – 19999 / 165536 | 0x0000 – 0xFFFF | Read Only | FC02 (0x02) |
| Input Registers | 16-bit Word (Analog Input) | 30001 – 39999 / 365536 | 0x0000 – 0xFFFF | Read Only | FC04 (0x04) |
| Holding Registers | 16-bit Word (Configuration / Setpoint) | 40001 – 49999 / 465536 | 0x0000 – 0xFFFF | Read / Write | FC03 (0x03) |
Crucial 1-Based vs 0-Based Addressing Rule: Documentation written for SCADA configuration (e.g. Ignition, Wonderware, WinCC) often refers to coils as 00001, 00020, or 00100. However, the physical Modbus protocol frame carries an unsigned 16-bit offset from 0x0000 to 0xFFFF.
- Coil 00001 translates to wire address
0x0000(0 in decimal). - Coil 00020 translates to wire address
0x0013(19 in decimal). - Coil 00101 translates to wire address
0x0064(100 in decimal).
2. Protocol Data Unit (PDU) Specification
The Protocol Data Unit (PDU) is protocol-independent—it remains identical whether encapsulated inside a Modbus RTU serial frame or a Modbus TCP socket packet.
Request PDU Structure
The client requests a starting coil address and the total count of contiguous coils to read (from 1 to 2,000):
| Field Name | Length | Valid Range | Description |
|---|---|---|---|
| Function Code | 1 Byte | 0x01 |
Defines Read Coils operation |
| Starting Address | 2 Bytes | 0x0000 to 0xFFFF |
0-based address of the first coil (Big-Endian) |
| Quantity of Coils | 2 Bytes | 0x0001 to 0x07D0 |
Number of coils to read (1 to 2,000 decimal) |
Response PDU Structure
The server responds with the function code, the number of data bytes following, and the bit-packed coil status bytes:
| Field Name | Length | Value | Description |
|---|---|---|---|
| Function Code | 1 Byte | 0x01 |
Echoes requested function code |
| Byte Count (N) | 1 Byte | (Quantity + 7) / 8 |
Number of data bytes required to hold all requested bits |
| Coil Status | N Bytes | Variable | Packed binary states (1 = ON, 0 = OFF) |
3. Bit Packing Algorithm: LSB-First Ordering
Unlike holding registers where data is transferred in 16-bit words (2 bytes per register), coils are packed as discrete bits (8 coils per byte) to maximize communication efficiency over bandwidth-constrained serial links.
The bit-ordering convention in Modbus is Least Significant Bit (LSB) first:
Byte 1:
Bit 0: Starting Address + 0 (Lowest addressed coil)
Bit 1: Starting Address + 1
Bit 2: Starting Address + 2
Bit 3: Starting Address + 3
Bit 4: Starting Address + 4
Bit 5: Starting Address + 5
Bit 6: Starting Address + 6
Bit 7: Starting Address + 7 (Highest addressed coil in first byte)
Byte 2:
Bit 0: Starting Address + 8
Bit 1: Starting Address + 9
...
Unused trailing bits in the final byte are padded with binary 0.
Worked Example: Reading 10 Coils (Coils 20 through 29)
Assume a client requests 10 coils starting at address 19 (wire address 0x0013). The physical states in the slave PLC are:
- Coil 20 (Addr 19): ON (1)
- Coil 21 (Addr 20): OFF (0)
- Coil 22 (Addr 21): ON (1)
- Coil 23 (Addr 22): ON (1)
- Coil 24 (Addr 23): OFF (0)
- Coil 25 (Addr 24): OFF (0)
- Coil 26 (Addr 25): ON (1)
- Coil 27 (Addr 26): OFF (0)
- Coil 28 (Addr 27): ON (1)
- Coil 29 (Addr 28): ON (1)
Packing Byte 1 (Coils 20–27): Bits [7..0] = 0 1 0 0 1 1 0 1 = 0x4D.
Packing Byte 2 (Coils 28–29): Coil 28 is Bit 0 (1), Coil 29 is Bit 1 (1). Bits 2 through 7 are zero-padded: 0 0 0 0 0 0 1 1 = 0x03.
Total data payload returned: 0x02 (Byte Count = 2), followed by 0x4D 0x03.
4. Modbus RTU vs Modbus TCP Hex Frame Breakdown
Modbus RTU Hex Packet Analysis
In Modbus RTU over RS-485 serial, the PDU is wrapped with a 1-byte Slave ID and a 2-byte CRC-16 checksum:
// Modbus RTU Request: Read 10 Coils starting at wire address 0x0013 (Coil 20) from Slave ID 1
[01] Slave Address (Device 1)
[01] Function Code 01 (Read Coils)
[00 13] Starting Address (0x0013 = decimal 19 -> Coil 20)
[00 0A] Quantity of Coils (0x000A = 10 coils)
[CE 0C] CRC-16 Checksum (Low Byte: 0xCE, High Byte: 0x0C)
// Modbus RTU Response:
[01] Slave Address (Device 1)
[01] Function Code 01 (Read Coils)
[02] Byte Count (2 bytes of coil data)
[4D] Data Byte 1: Coils 20..27 (0b01001101)
[03] Data Byte 2: Coils 28..29 (0b00000011, padded)
[92 5D] CRC-16 Checksum
Modbus TCP Hex Packet Analysis
In Modbus TCP over Ethernet (port 502), the frame replaces the serial CRC and Slave ID with a 7-byte MBAP (Modbus Application Protocol) header:
// Modbus TCP Request:
[00 01] Transaction Identifier (Echoed by server)
[00 00] Protocol Identifier (0x0000 = Modbus Protocol)
[00 06] Length Field (6 bytes follow: Unit ID + PDU)
[01] Unit Identifier (Device ID 1)
[01] Function Code 01 (Read Coils)
[00 13] Starting Address (0x0013 = 19)
[00 0A] Quantity of Coils (0x000A = 10)
// Modbus TCP Response:
[00 01] Transaction Identifier (Matches request)
[00 00] Protocol Identifier (0x0000)
[00 05] Length Field (5 bytes follow: Unit ID + FC + ByteCount + 2 Data Bytes)
[01] Unit Identifier (1)
[01] Function Code 01 (Read Coils)
[02] Byte Count (2)
[4D 03] Data Bytes (Coils 20..29)
5. Modbus Exception Codes for Function Code 01
When a request cannot be processed by the server, the server sets the most significant bit of the function code (0x01 | 0x80 = 0x81) and appends a 1-byte Exception Code:
| Exception Code | Name | Root Cause in FC01 | How to Fix |
|---|---|---|---|
0x01 |
Illegal Function | The slave device firmware does not support coil operations (e.g., pure sensor with only analog input registers). | Verify slave capability matrix; use FC04 or FC03 instead. |
0x02 |
Illegal Data Address | Starting address does not exist, or starting address + quantity exceeds device limits. | Check 1-based vs 0-based offset; ensure requested coil index is defined on slave. |
0x03 |
Illegal Data Value | Quantity of coils is 0 or greater than 2,000 (0x07D0). | Ensure requested count is within 1 to 2,000 coils per transaction. |
0x04 |
Slave Device Failure | Internal hardware I/O module fault or communication failure between PLC CPU and digital output rack. | Inspect PLC hardware rack diagnostics, digital output fuse status, and internal memory faults. |
6. Python pymodbus Implementation
Here is a production-ready Python script using pymodbus (v3.x) to connect to a Modbus TCP server, read 16 discrete coils, and print each coil status with bit positions:
from pymodbus.client import ModbusTcpClient
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 502
SLAVE_UNIT_ID = 1
client = ModbusTcpClient(SERVER_HOST, port=SERVER_PORT)
if client.connect():
print(f"[*] Connected to Modbus TCP Server at {SERVER_HOST}:{SERVER_PORT}")
# Request 16 coils starting at wire offset 0 (PLC Coil 00001 to 00016)
start_address = 0
coil_count = 16
response = client.read_coils(address=start_address, count=coil_count, slave=SLAVE_UNIT_ID)
if not response.isError():
bits = response.bits[:coil_count]
print(f"[+] Successfully read {len(bits)} coils:")
for idx, state in enumerate(bits):
plc_addr = f"0{start_address + idx + 1:04d}"
status_text = "ON (1)" if state else "OFF (0)"
print(f" Coil {plc_addr} (Offset {start_address + idx:04d}): {status_text}")
else:
print(f"[-] Modbus Error received: {response}")
client.close()
else:
print("[-] Failed to establish connection to Modbus server.")
7. Simulating Modbus Coils with ModbusSimulator
Testing PLC digital control logic, SCADA alarm graphics, or automated Python polling scripts without physical field equipment is challenging. Wire-tripping physical limit switches or cycling 480V motor starters during testing poses safety hazards and equipment wear.
With ModbusSimulator, automation engineers can configure a complete virtual digital I/O environment in seconds:
- Virtual 0xxxx Coil Table: Simulate up to 65,536 independent discrete coils on both Modbus TCP and RTU interfaces.
- Interactive Interactive Toggles: Click any coil cell to instantly toggle state between 0 and 1, with real-time UI status reflection.
- Automated Pulse & Pattern Generators: Configure periodic square waves, alternating bit sequences, or fault injection triggers to test supervisory alarm responses.
- Dual Server & Client Modes: Run simultaneous Master and Slave simulations on a single workstation to debug client-side driver implementations without external hardware.
- Raw Hex Traffic Monitor: View live incoming FC01 request packets and outgoing responses with millisecond timestamps and CRC validation.