Modbus Function Code 04 (Read Input Registers): Protocol Specification & Hex Analysis

An exhaustive engineering reference for Modbus Function Code 04 (0x04). Discover how to read 16-bit analog input registers, parse RTU and TCP hex byte frames, navigate 30001 vs 0x0000 addressing offsets, decode 32-bit IEEE 754 floats, and simulate analog sensors in Python.

In industrial telemetry, process control, and building automation, Modbus Function Code 04 (Read Input Registers / 0x04) is the designated protocol mechanism for reading 16-bit read-only analog data. Whenever a SCADA master polls an RTD temperature transmitter, an ultrasonic tank level sensor, an electrical power meter's live phase voltage, or a gas concentration analyzer, Function Code 04 is the primary protocol command used to acquire these analog process variables.

While holding registers (Function Code 03) serve as dual read/write storage for operational setpoints and tuning parameters, Input Registers represent the physical sensory input of the field device. Because input registers are read-only at the protocol layer, they protect sensitive calibration constants and live sensor measurements from accidental write operations initiated by rogue control scripts or erroneous PLC commands.

This guide provides an exhaustive engineering breakdown of Modbus Function Code 04 under the Modbus Application Protocol Specification V1.1b3. We examine the exact request and response PDU/ADU byte layouts, analyze frame hex captures across Modbus RTU and Modbus TCP, review 32-bit IEEE 754 floating-point byte ordering, and demonstrate how to simulate and test input registers using ModbusSimulator and Python.

1. Protocol Fundamentals: Input Registers vs Holding Registers

The standard Modbus data model partitions device memory into four primary tables. Each table maps to a distinct physical memory function:

Data Table Type Object Type Access Level Modicon Register Range Standard Read Command
Discretes Input Single bit (boolean) Read-Only 10001 – 19999 (1xxxx) FC 02 (Read Discrete Inputs / 0x02)
Coils Single bit (boolean) Read-Write 00001 – 09999 (0xxxx) FC 01 (Read Coils / 0x01)
Input Registers 16-bit word (analog) Read-Only 30001 – 39999 (3xxxx) FC 04 (Read Input Registers / 0x04)
Holding Registers 16-bit word (analog) Read-Write 40001 – 49999 (4xxxx) FC 03 (Read Holding Registers / 0x03)

Input registers represent 16-bit words (0x0000 to 0xFFFF), accommodating unsigned integers from 0 to 65,535 or two's complement signed integers from -32,768 to +32,767. Unlike holding registers, there is no corresponding "Write Input Register" function code in the Modbus standard. If an engineer attempts to issue a write command (FC 06 or FC 16) to an address within the 3xxxx range, compliant Modbus slave devices reject the command with an Exception Code 02 (Illegal Data Address) or Exception Code 01 (Illegal Function).

2. Addressing Conventions: Modicon 30001 vs Protocol Wire Offset 0x0000

A frequent source of commissioning errors when integrating Modbus hardware is the confusion between human-readable PLC register numbering and physical protocol wire offsets:

  • Modicon 1-Based Notation: Traditional PLC environments (Schneider Electric Modicon, GE Fanuc, AutomationDirect) assign a leading digit 3 to represent input registers, numbering registers from 30001 to 39999 (or extended 6-digit notation 300001 to 365535).
  • Wire-Level 0-Based Notation: In the raw binary frames transmitted over RS-485 serial cables or Ethernet TCP packets, register offsets are strictly 0-indexed starting at 0x0000.
PLC Register Name Decimal Wire Offset Hex Wire Offset (PDU Byte) Typical Industrial Variable
30001 (or 300001) 0 0x0000 Ambient Temperature Sensor (°C × 10)
30002 (or 300002) 1 0x0001 Relative Humidity (% RH × 10)
30003 (or 300003) 2 0x0002 Barometric Pressure (hPa)
30010 (or 300010) 9 0x0009 Motor Vibration RMS (mm/s)
30101 (or 300101) 100 0x0064 Line-to-Line Voltage V12 (Volts)

Engineering Rule of Thumb: When configuring SCADA drivers or writing custom Python scripts for instruments whose manuals specify "Address: 30001", subtract 1 to calculate the wire offset: 30001 - 30001 = 0x0000. If an instrument manual explicitly states "Register Offset: 0", send 0x0000 directly.

3. Function Code 04 Frame Structure & Byte Breakdown

The Modbus Protocol Data Unit (PDU) for Function Code 04 is identical across serial (RTU/ASCII) and Ethernet (TCP) network topologies. Network-specific headers and error check fields wrap the PDU to form the complete Application Data Unit (ADU).

Request PDU Specification

To poll input registers, the client transmits a 5-byte request PDU:

Field Name Length Valid Range Description
Function Code 1 byte 0x04 Command: Read Input Registers
Starting Address 2 bytes (High, Low) 0x0000 – 0xFFFF 0-based register offset of the first register to read
Quantity of Registers 2 bytes (High, Low) 0x0001 – 0x007D Number of 16-bit registers to read (1 to 125 registers)

Response PDU Specification

Upon receiving a valid request, the slave returns the requested register values:

Field Name Length Value Description
Function Code 1 byte 0x04 Echo of requested function code
Byte Count 1 byte 2 × N Total data bytes returned (N = quantity of registers, max 250)
Register Values N × 2 bytes 16-bit data words Analog register values formatted High Byte first, Low Byte second

4. Real-World Hex Packet Walkthroughs

Scenario A: Modbus RTU Serial Request & Response

Consider polling an RTU temperature transmitter assigned Slave ID 1. We wish to read two input registers starting at PLC address 30001 (wire offset 0x0000):

# Master Request (8 bytes over RS-485 serial):
01 04 00 00 00 02 71 CB

Byte Breakdown:
01    -> Slave Node Address (Device 1)
04    -> Function Code 04 (Read Input Registers)
00 00 -> Starting Register Address (Offset 0x0000 = Register 30001)
00 02 -> Quantity of Registers to read (2 registers = 4 bytes)
71 CB -> CRC-16 Checksum (Low byte 0x71, High byte 0xCB)

The slave device reads its internal analog-to-digital converters and responds:

# Slave Response (9 bytes over RS-485 serial):
01 04 04 09 C4 02 58 3F 22

Byte Breakdown:
01    -> Slave Node Address (Device 1)
04    -> Function Code 04 Echo
04    -> Byte Count (4 data bytes follow: 2 registers × 2 bytes/reg)
09 C4 -> Register 0 Value: 0x09C4 = 2500 decimal (250.0 °C / 10)
02 58 -> Register 1 Value: 0x0258 = 600 decimal (60.0% Relative Humidity)
3F 22 -> CRC-16 Checksum (Low byte 0x3F, High byte 0x22)

Scenario B: Modbus TCP Ethernet Request & Response

On Ethernet networks, Modbus RTU's Slave ID and CRC-16 are replaced by the 7-byte Modbus Application Protocol (MBAP) Header over standard port 502:

# Modbus TCP Client Request (12 bytes):
00 01 00 00 00 06 01 04 00 64 00 01

Byte Breakdown:
00 01 -> Transaction Identifier (0x0001)
00 00 -> Protocol Identifier (0x0000 = Standard Modbus)
00 06 -> Length Field (6 bytes follow: Unit ID + PDU)
01    -> Unit Identifier (Slave ID 1)
04    -> Function Code 04 (Read Input Registers)
00 64 -> Starting Register Address (0x0064 = 100 decimal -> Register 30101)
00 01 -> Quantity of Registers (1 register = 2 bytes)
# Modbus TCP Server Response (11 bytes):
00 01 00 00 00 05 01 04 02 08 FC

Byte Breakdown:
00 01 -> Transaction Identifier Echo
00 00 -> Protocol Identifier Echo
00 05 -> Length Field (5 bytes follow: Unit ID + Function + Byte Count + Data)
01    -> Unit Identifier
04    -> Function Code 04
02    -> Byte Count (2 bytes returned)
08 FC -> Register 100 Data: 0x08FC = 2300 decimal (230.0 VAC Line Voltage)

5. Decoding 32-Bit Floating Point Measurements (IEEE 754)

Many modern industrial sensors (flow meters, power analyzers, chemical monitors) output high-precision measurements as 32-bit single-precision floating point numbers formatted under the IEEE 754 standard.

Because Modbus registers are strictly 16-bit words, transmitting a 32-bit float requires two adjacent input registers. However, because the original 1979 Modbus specification did not specify word endianness, equipment manufacturers implement different word orders:

Endianness Mode Word Order Byte Sequence Common Equipment Manufacturers
Big-Endian (ABCD) High Word First, High Byte First Byte 1, Byte 2, Byte 3, Byte 4 Siemens S7, Yokogawa, Schneider Electric
Little-Endian (CDAB) Low Word First, High Byte First Byte 3, Byte 4, Byte 1, Byte 2 ABB, Modicon (alternate), Danfoss VFDs
Mid-Big (BADC) High Word First, Byte-Swapped Byte 2, Byte 1, Byte 4, Byte 3 Honeywell, Omron Sysmac
Mid-Little (DCBA) Low Word First, Byte-Swapped Byte 4, Byte 3, Byte 2, Byte 1 Legacy serial RTUs, custom embedded boards

If a temperature measurement of 123.45 °C has the IEEE 754 hex representation 0x42F6E666:

  • In Big-Endian (ABCD): Register 30001 = 0x42F6, Register 30002 = 0xE666
  • In Little-Endian (CDAB): Register 30001 = 0xE666, Register 30002 = 0x42F6

6. Modbus Exception Codes for Function Code 04

When a slave cannot fulfill an FC04 request, it sets the most significant bit of the function code (0x04 + 0x80 = 0x84) and returns a 1-byte Exception Code:

Exception Code Exception Name Root Cause in Function Code 04 How to Fix
0x01 Illegal Function The slave does not support Function Code 04 or has no 3xxxx table configured. Check slave documentation. Try polling holding registers using FC 03 instead.
0x02 Illegal Data Address The requested starting address or register count exceeds the slave's defined 3xxxx address range. Verify whether the slave uses 0-based or 1-based addressing. See our Illegal Data Address fix guide.
0x03 Illegal Data Value The quantity of registers requested is 0, or exceeds 125 registers (0x007D). Ensure requested register count is between 1 and 125.
0x04 Slave Device Failure The slave encountered an unrecoverable internal hardware or ADC read error while sampling sensors. Check sensor wiring, power supply, and instrument health diagnostics.

7. Python Implementation: Polling Input Registers with PyModbus

Here is a production-ready Python script using the modern pymodbus library (v3.x) to read input registers and decode both 16-bit integers and 32-bit IEEE 754 floats:

from pymodbus.client import ModbusTcpClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder

# Connect to Modbus TCP Server or Gateway on Port 502
client = ModbusTcpClient("127.0.0.1", port=502)

if client.connect():
    print("[+] Successfully connected to Modbus TCP Server.")

    # Read 4 Input Registers starting at offset 0 (PLC 30001 to 30004)
    # slave=1 specifies Unit ID 1
    response = client.read_input_registers(address=0, count=4, slave=1)

    if not response.isError():
        raw_registers = response.registers
        print(f"[*] Raw Register Words: {[hex(r) for r in raw_registers]}")

        # Decode Register 0 as a scaled integer (e.g., Temperature = Val / 10)
        temp_scaled = raw_registers[0] / 10.0
        print(f"[*] Register 30001 (Temperature): {temp_scaled:.1f} °C")

        # Decode Registers 2 & 3 as a 32-bit IEEE 754 Float (Big-Endian ABCD)
        decoder = BinaryPayloadDecoder.fromRegisters(
            raw_registers[2:4],
            byteorder=Endian.BIG,
            wordorder=Endian.BIG
        )
        flow_rate = decoder.decode_32bit_float()
        print(f"[*] Registers 30003-30004 (Liquid Flow Rate): {flow_rate:.3f} m³/h")
    else:
        print(f"[-] Modbus Exception Received: {response}")

    client.close()
else:
    print("[-] Connection to Modbus Server failed.")

8. Simulating Input Registers with ModbusSimulator

Testing PLC integration scripts, SCADA screens, and edge IIoT gateways against physical sensors during plant commissioning is often risky or impossible. Physical pressure transducers cannot be manually forced to simulated fault levels without high-pressure test rigs.

With ModbusSimulator, you can create a realistic, responsive virtual test environment on Windows in under 60 seconds:

  • Comprehensive 3xxxx Input Register Tables: Configure up to 65,536 read-only analog registers with realistic process initial values.
  • Automated Dynamic Signal Profiles: Assign automated waveform generators (Sine Wave, Triangle Ramp, Random Walk, Gaussian Noise) to input registers to test SCADA trends and high/low alarm trigger limits.
  • Integrated Hex Packet Logger: Inspect incoming Function Code 04 requests and outgoing responses in real time with timestamped millisecond precision.
  • Fault Injection Testing: Deliberately simulate Exception 02 (Illegal Address) or Exception 04 (Device Failure) responses to test how your supervisory software handles field instrument faults.

Simulate Modbus Input Registers with Zero Hardware

Download ModbusSimulator for Windows today. Run multi-slave Modbus TCP and RTU simulations with live hex logging and dynamic analog sensor profiles.

Download Free 30-Day Trial →