Holding registers (address range 40001-49999) are the workhorse of Modbus communication. They store process variables, setpoints, configuration parameters—anything that needs to be both read and written by master devices. If you're testing a PLC, SCADA system, or industrial IoT gateway, you'll spend most of your time working with holding registers.
This guide explains how holding registers work, when to use function codes FC03 and FC16, and how to test them effectively using a simulator.
Holding registers are 16-bit read/write registers used to store numerical data in Modbus devices. Each register holds a value from 0 to 65,535 (unsigned) or -32,768 to +32,767 (signed).
Holding registers use addresses 40001-49999 in the Modbus specification. However, internally, they're 0-indexed. Address 40001 corresponds to internal address 0, 40002 to address 1, and so on. This causes endless confusion. Always check your device's documentation for its addressing convention.
| Function Code | Name | Description | Use Case |
|---|---|---|---|
| FC03 | Read Holding Registers | Read 1-125 consecutive registers | Poll sensor values, read configuration |
| FC06 | Write Single Register | Write one 16-bit value to one register | Update a single setpoint |
| FC16 | Write Multiple Registers | Write 1-123 consecutive registers | Bulk configuration, recipe download |
| FC23 | Read/Write Multiple Registers | Read and write in one transaction | Atomic read-modify-write operations |
Most applications use FC03 (Read) and FC16 (Write Multiple). FC06 is less common because FC16 can write a single register too. FC23 is rare—only newer devices support it.
FC03 is the most commonly used Modbus function code. A master sends a request specifying:
The slave responds with the requested register values as 16-bit integers.
Request (Master → Slave):
Function Code: 0x03 (FC03)
Starting Address: 0x0000 (register 40001, 0-indexed)
Quantity: 0x0001 (read 1 register)
Response (Slave → Master):
Function Code: 0x03
Byte Count: 0x02 (2 bytes = 1 register)
Register Value: 0x00E2 (226 decimal = 22.6°C if scaled by 10)
If the device stores temperature as an integer (22.6°C → 226), you apply the scaling factor after reading.
To read 10 consecutive temperature sensors stored in registers 40001-40010:
Function Code: 0x03
Starting Address: 0x0000
Quantity: 0x000A (10 registers)
Response: 20 bytes (10 registers × 2 bytes each)
This returns all 10 values in one transaction, reducing network overhead compared to 10 individual FC03 requests.
FC16 writes 1-123 consecutive holding registers in a single transaction. The master specifies:
Request (Master → Slave):
Function Code: 0x10 (FC16)
Starting Address: 0x0031 (register 40050, 0-indexed as 49)
Quantity: 0x0001 (write 1 register)
Byte Count: 0x02
Register Value: 0x01F4 (500 decimal = 50.0°C setpoint if scaled by 10)
Response (Slave → Master):
Function Code: 0x10
Starting Address: 0x0031
Quantity: 0x0001 (confirms 1 register written)
Writing 5 registers at once (configuration block for a VFD):
Starting Address: 0x0064 (register 40101)
Quantity: 0x0005 (5 registers)
Values: [1500, 3000, 100, 50, 0] → Motor speed limits, accel/decel rates
FC16 is atomic—all registers are written together. If any register fails, the entire operation fails (though not all devices implement this correctly).
Holding registers store 16-bit integers, but real-world data often requires other formats:
Native format. Unsigned: 0-65,535. Signed: -32,768 to +32,767.
Two consecutive registers combine to form a 32-bit value. Example: Registers 40001-40002 store a large counter value (0 to 4,294,967,295).
Endianness matters: Big-endian (high word first) vs little-endian (low word first). Modbus standard is big-endian, but some devices use little-endian. Always check.
IEEE 754 floating-point values require 2 registers. Example: Precise temperature reading 23.456°C stored as float.
Byte order variations: ABCD (big-endian), DCBA (little-endian), BADC, CDAB. You must match the device's byte order or data looks like garbage.
Text data (device name, serial number) can be stored as ASCII characters—2 characters per register. Example: "MOTOR-A" uses 4 registers.
You've programmed a PLC with a register map:
Use a holding register simulator in master mode to:
Your SCADA polls 20 holding registers every second from 30 devices. Before deployment, verify the network can handle this load.
Set up a holding register simulator in slave mode with 20 registers mapped. Configure the SCADA to poll it. Monitor:
You're integrating a flow meter that reports flow rate as a 32-bit float in registers 40005-40006. The documentation says "Modbus TCP big-endian" but values read as 1.4e+38 (clearly wrong).
Test with a simulator:
Two SCADA systems write to the same holding register setpoint. You need to detect conflicts.
Run a slave simulator with a register that logs all write operations with timestamps. Review the log to see if two masters write different values within milliseconds of each other.
To simulate a device with holding registers:
ModbusSimulator Slave Setup:
1. Launch in Slave Mode
2. Protocol: Modbus TCP (or RTU/ASCII if testing serial)
3. Port: 502 (TCP) or COM port (RTU/ASCII)
4. Unit ID: 1
5. Configure Register Map:
- Register 40001: Value 2250 (temperature 22.5°C)
- Register 40002: Value 1850 (temperature 18.5°C)
- Register 40003: Value 500 (flow rate 50.0 L/min)
- Register 40010: Value 2000 (setpoint 20.0°C)
6. Start Server → Listening on port 502
Now a master can connect and read/write these registers.
To test a real PLC or another simulator:
ModbusSimulator Master Setup:
1. Launch in Master Mode
2. Connection: TCP → IP 192.168.1.100, Port 502, Unit ID 1
3. Read Configuration:
- Function Code: FC03
- Starting Address: 40001
- Quantity: 10 registers
4. Click "Read" → View register values in the table
5. Write Configuration:
- Function Code: FC16
- Starting Address: 40010
- Value: 2300 (new setpoint 23.0°C)
6. Click "Write" → Verify PLC updates
Cause: The register address doesn't exist in the slave's memory map.
Fix: Check the device documentation. Some PLCs start at 40001, others at 40000. Some use 0-based indexing internally.
Cause: You're writing a value outside the allowed range (e.g., writing 70000 to a register with a max of 1000).
Fix: Check the register's valid range in the device manual. Respect min/max limits.
Cause: Endianness mismatch for 32-bit values (integers or floats).
Fix: Toggle byte order in your simulator or SCADA software. Try ABCD → DCBA → BADC → CDAB until values make sense.
Cause: Some devices store holding register values in RAM, not flash.
Fix: Check if the device has a "Save Configuration" function code or separate configuration registers that persist to non-volatile memory.
ModbusSimulator provides a complete environment for testing holding registers with both master and slave modes in one application.
One license includes both master and slave for $99 (competitors charge $129 each). 30-day free trial with all features unlocked. No subscription, lifetime updates included.
Simulate any Modbus device • Test FC03/FC16 operations • Free 30-day trial
Download Free TrialHolding registers (40001-49999) are read/write. Input registers (30001-39999) are read-only. Use holding registers for setpoints and configuration. Use input registers for sensor data that the PLC doesn't allow external writes to.
No. Input registers are read-only via FC04. Attempting to write with FC06/FC16 to addresses 30001-39999 will return exception 0x02 (Illegal Data Address) on most devices.
FC03: Read up to 125 registers in one request.
FC16: Write up to 123 registers in one request.
Limits exist to keep Modbus frames under 256 bytes.
The slave responds with exception code 0x02 (Illegal Data Address). This is normal—it means the register isn't mapped.
Yes, if interpreted as signed 16-bit integers (-32,768 to +32,767). The Modbus protocol doesn't specify signed vs unsigned—it's up to the device manufacturer. Always check documentation.
Learn more: Modbus Poll vs ModbusSimulator comparison · Complete guide to all 4 Modbus register types
Publishing technical documentation? IndexFlow automates Google indexing to get your pages crawled faster.