In industrial automation systems, monitoring binary digital inputs—such as emergency stop buttons, inductive proximity sensors, motor overload contacts, and liquid level float switches—relies directly on Modbus Function Code 02 (Read Discrete Inputs / 0x02). Defined under the official Modbus Application Protocol Specification V1.1b3, Function Code 02 enables a client/master to poll between 1 and 2,000 contiguous 1-bit read-only inputs from a remote server/slave in a single request frame.
Whether you are commissioning a new SCADA polling driver, programming a PLC gateway, or debugging communication timeouts over RS-485 serial buses, understanding the exact bit-packing logic, zero-based wire addressing, and protocol exception handling of Function Code 02 is essential. This engineering guide breaks down the protocol specification, analyzes raw hexadecimal packet traces for Modbus RTU and Modbus TCP, provides working Python code, and demonstrates how to simulate discrete inputs in ModbusSimulator.
1. The Modbus Memory Model: Coils vs Discrete Inputs
The Modbus protocol defines four distinct register tables. Discrete Inputs occupy a specialized role as read-only physical status points:
| Data Table | Object Type | Read/Write Access | Modicon 1-Based Address | Wire Address Offset | Standard Function Codes |
|---|---|---|---|---|---|
| Discrete Inputs | 1-bit binary flag | Read-Only | 10001 – 19999 (or 165536) | 0x0000 – 0xFFFF |
FC02 (0x02) |
| Coils (Outputs) | 1-bit binary flag | Read / Write | 00001 – 09999 (or 065536) | 0x0000 – 0xFFFF |
FC01 (Read), FC05 (Write 1), FC15 (Write N) |
| Input Registers | 16-bit word | Read-Only | 30001 – 39999 (or 365536) | 0x0000 – 0xFFFF |
FC04 (0x04) |
| Holding Registers | 16-bit word | Read / Write | 40001 – 49999 (or 465536) | 0x0000 – 0xFFFF |
FC03 (Read), FC06 (Write 1), FC16 (Write N) |
Key Architectural Distinction: While both Coils (FC01) and Discrete Inputs (FC02) return binary 1 or 0 values, Discrete Inputs are strictly read-only from the network. They directly mirror the state of physical opto-isolated digital inputs on PLC terminal strips or RTU expansion racks. If a master attempts to issue a write command to an address mapped in the discrete input table, an industrial slave must respond with an error.
2. Protocol Data Unit (PDU) Specification
Under the Modbus standard, the Protocol Data Unit (PDU) is independent of the underlying physical transmission layer (whether RS-485 serial, RS-232, or Ethernet TCP/IP).
Request PDU Format (Client → Server)
The request PDU consists of exactly 5 bytes:
| Field Name | Length | Hex Value Range | Description |
|---|---|---|---|
| Function Code | 1 Byte | 0x02 |
Command identifier for Read Discrete Inputs |
| Starting Address | 2 Bytes (Big-Endian) | 0x0000 – 0xFFFF |
0-based offset of the first discrete input requested |
| Quantity of Inputs | 2 Bytes (Big-Endian) | 0x0001 – 0x07D0 |
Number of contiguous inputs to read (1 to 2,000) |
Response PDU Format (Server → Client)
The normal response PDU contains a byte count followed by the packed input data bytes:
| Field Name | Length | Hex Value Range | Description |
|---|---|---|---|
| Function Code | 1 Byte | 0x02 |
Echoes the requested function code |
| Byte Count (N) | 1 Byte | 0x01 – 0xFA |
Number of data bytes following (computed as ceil(Quantity / 8)) |
| Input Status | N Bytes | N Bytes |
Packed binary values, 8 inputs per byte (LSB-first) |
3. Bit-Packing Algorithm & Hex Frame Analysis
Because discrete inputs are 1-bit values, Modbus packs up to 8 inputs into each payload byte to optimize industrial transmission bandwidth. The lowest-addressed input is placed into the Least Significant Bit (Bit 0) of the first data byte. If the requested quantity is not an exact multiple of 8, the unused high-order bits in the final byte are padded with zeros.
Worked Example: Reading 12 Discrete Inputs in Modbus RTU
Suppose an automation client wants to read 12 discrete inputs starting at PLC address 10197 from Slave ID 1.
- Slave ID:
0x01 - Function Code:
0x02 - Starting Address: 10197 in 1-based notation corresponds to wire offset
10197 - 10001 = 196 = 0x00C4. - Quantity: 12 inputs =
0x000C.
Request Frame (Modbus RTU Hex):
01 02 00 C4 00 0C B9 D6
| Byte | Hex | Protocol Field | Description |
|---|---|---|---|
| 1 | 01 | Slave Address | Target Slave Device ID 1 |
| 2 | 02 | Function Code | FC02 Read Discrete Inputs |
| 3-4 | 00 C4 | Starting Address | Offset 196 (Address 10197) |
| 5-6 | 00 0C | Quantity of Inputs | 12 inputs requested |
| 7-8 | B9 D6 | CRC-16 | Cyclic Redundancy Check (Low byte first) |
Simulated Input States:
Assume the 12 inputs on the server have the following states:
- Inputs 10197 – 10204 (first 8 inputs):
ON, OFF, ON, ON, OFF, ON, OFF, ON→ Binary:10101101= 0xAD - Inputs 10205 – 10208 (last 4 inputs):
ON, OFF, ON, OFF→ Binary:00000101(padded with 4 leading zeros) = 0x05
Response Frame (Modbus RTU Hex):
01 02 02 AD 05 78 8B
| Byte | Hex | Protocol Field | Description |
|---|---|---|---|
| 1 | 01 | Slave Address | Echoes Slave ID 1 |
| 2 | 02 | Function Code | Echoes Function Code 02 |
| 3 | 02 | Byte Count | 2 data bytes follow (ceil(12/8) = 2) |
| 4 | AD | Input Status (Byte 1) | Binary 10101101 (Inputs 10197 to 10204) |
| 5 | 05 | Input Status (Byte 2) | Binary 00000101 (Inputs 10205 to 10208 + padding) |
| 6-7 | 78 8B | CRC-16 | Calculated frame checksum |
4. Modbus TCP ADU Format (MBAP Header)
When transmitted over Ethernet networks using Modbus TCP (Port 502), the 2-byte CRC checksum is discarded. Instead, a 7-byte Modbus Application Protocol (MBAP) Header precedes the PDU:
[Transaction ID: 2B] [Protocol ID: 2B (0x0000)] [Length: 2B] [Unit ID: 1B] [PDU: 5B]
For the identical 12-input request over TCP with Transaction ID 0x0001:
00 01 00 00 00 06 01 02 00 C4 00 0C
00 01: Transaction Identifier00 00: Protocol Identifier (0 = Modbus)00 06: Length (6 bytes remaining: Unit ID + PDU)01: Unit Identifier (Slave address)02 00 C4 00 0C: Function Code 02 PDU
5. Error Handling & Exception Codes
When a slave device cannot successfully process a Function Code 02 request, it responds with an Exception Response. The function code in the response has its most significant bit set (0x02 + 0x80 = 0x82), followed by a 1-byte Exception Code:
| Exception Code | Name | Typical Cause with FC02 | Troubleshooting & Fix |
|---|---|---|---|
0x01 |
Illegal Function | The target device does not support FC02 or has discrete inputs disabled in its firmware. | Verify device documentation; poll as Holding Registers (FC03) or Coils (FC01) if inputs are mapped as words. |
0x02 |
Illegal Data Address | Starting address offset is out of range, or (Starting Address + Quantity) exceeds the device table limit. | Check zero-based vs one-based offset (e.g. 10001 vs 0x0000). Ensure the highest address exists in the PLC memory map. |
0x03 |
Illegal Data Value | Quantity is 0 or exceeds 2,000 (0x07D0). | Ensure 1 <= Quantity <= 2000 in your client polling configuration. |
0x04 |
Slave Device Failure | An unrecoverable error occurred in the slave controller while reading physical I/O expansion racks. | Inspect physical PLC I/O rack communication, power supplies, and optocoupler module status lights. |
6. Python Pymodbus Implementation Example
The following script demonstrates how to poll discrete inputs using the popular pymodbus library over Modbus TCP or RTU:
from pymodbus.client import ModbusTcpClient
import time
def read_discrete_sensors(host='127.0.0.1', port=502, slave_id=1):
client = ModbusTcpClient(host=host, port=port)
if not client.connect():
print(f"[-] Connection failed to {host}:{port}")
return
try:
# Read 16 discrete inputs starting at wire address 0 (PLC address 10001)
response = client.read_discrete_inputs(address=0, count=16, slave=slave_id)
if response.isError():
print(f"[-] Modbus Error: {response}")
return
# response.bits is a list of booleans [True, False, True, ...]
bits = response.bits[:16]
print(f"[+] Successfully polled {len(bits)} Discrete Inputs from Slave {slave_id}:")
for idx, state in enumerate(bits):
wire_addr = idx
plc_addr = 10001 + idx
status_text = "ACTIVE (1)" if state else "INACTIVE (0)"
print(f" Input {plc_addr} [Wire 0x{wire_addr:04X}]: {status_text}")
finally:
client.close()
if __name__ == '__main__':
read_discrete_sensors()
7. Simulating Discrete Inputs with ModbusSimulator
Testing SCADA dashboards and PLC communication routines before physical hardware is delivered is best accomplished using ModbusSimulator. You can configure multi-device virtual slaves with full discrete input table support:
- Multi-Slave Support: Run dozens of simulated slaves on separate Unit IDs or TCP ports simultaneously.
- Dynamic Scripting & Simulation: Automatically toggle discrete inputs using sine waves, random triggers, or custom timed sequences to test SCADA alarm annunciations.
- Live Hex Packet Sniffer: Inspect incoming FC02 requests and verify bit-packing responses directly in the real-time frame monitor without needing external Wireshark captures.
- Cross-Protocol Conversion: Test Modbus RTU serial (via virtual COM ports) and Modbus TCP seamlessly on Windows 10 and 11.
Test Your Modbus Systems Today
Download ModbusSimulator to simulate virtual Modbus RTU and TCP slaves, script register values, and debug PLC networks without hardware.
Download ModbusSimulator Free →Frequently Asked Questions (FAQs)
What is Modbus Function Code 02?
Modbus Function Code 02 (0x02) is the standard command used by a master/client to read the binary ON/OFF status of contiguous discrete inputs (1 to 2,000) from a remote slave/server.
What is the difference between Discrete Inputs (FC02) and Coils (FC01)?
Discrete Inputs (1xxxx) are read-only physical digital inputs (switches, sensors). Coils (0xxxx) are read/write entities representing physical output relays or internal control flags.
How are discrete inputs packed into bytes in the response?
Inputs are packed 8 per byte, starting with the least significant bit (LSB) representing the lowest address requested. Any remaining bits in the final byte are padded with zeros.
What is the difference between address 10001 and wire address 0x0000?
10001 is the 1-based documentation notation. On the communication wire, addresses are transmitted as zero-based 16-bit values, meaning 10001 is transmitted as 0x0000.
What causes Modbus Exception 02 with Function Code 02?
Exception 02 (Illegal Data Address) indicates the starting address does not exist on the slave, or the range requested exceeds the configured discrete input address space.
What causes Modbus Exception 03 with Function Code 02?
Exception 03 (Illegal Data Value) occurs when the requested quantity is 0 or greater than 2,000, violating the protocol limits.
Can a Modbus master write to Discrete Inputs?
No. Discrete Inputs are strictly read-only by specification. For read/write binary flags, use Coils with Function Codes 01, 05, and 15.