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
3to represent input registers, numbering registers from30001to39999(or extended 6-digit notation300001to365535). - 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.