Operations
Reading Registers
Holding Registers (Function Code 03)
Holding registers are read/write registers typically used for configuration values and real-time data.
Type-safe read:
// Read 32-bit float (2 registers)
var power = await client.ReadHoldingRegistersAsync<float>(
startAddress: 12,
slaveAddress: 1
);
// Read 32-bit signed integer (2 registers)
var count = await client.ReadHoldingRegistersAsync<int>(
startAddress: 50,
slaveAddress: 1
);
// Read 16-bit unsigned integer (1 register)
var status = await client.ReadHoldingRegistersAsync<ushort>(
startAddress: 100,
slaveAddress: 1
);Raw register array:
var registers = await client.ReadHoldingRegistersAsync(
startAddress: 0,
numberOfPoints: 10,
slaveAddress: 1
);Input Registers (Function Code 04)
Input registers are read-only registers typically used for sensor readings and measurements.
Type-safe read:
var temperature = await client.ReadInputRegistersAsync<short>(
startAddress: 100,
slaveAddress: 1
);
var voltage = await client.ReadInputRegistersAsync<float>(
startAddress: 0,
slaveAddress: 1
);Raw register array:
var registers = await client.ReadInputRegistersAsync(
startAddress: 0,
numberOfPoints: 5,
slaveAddress: 1
);Writing Registers
Single Register (Function Code 06)
Write a single 16-bit holding register:
await client.WriteSingleRegisterAsync(
registerAddress: 40001,
value: 1234,
slaveAddress: 1
);Multiple Registers (Function Code 16)
Write multiple consecutive holding registers:
Raw array:
await client.WriteMultipleRegistersAsync(
startAddress: 40001,
data: new ushort[] { 100, 200, 300 },
slaveAddress: 1
);Type-safe write:
// Write 32-bit float (2 registers)
await client.WriteHoldingRegistersAsync<float>(
startAddress: 40001,
value: 123.45f,
slaveAddress: 1
);
// Write 32-bit integer (2 registers)
await client.WriteHoldingRegistersAsync<int>(
startAddress: 50,
value: -500,
slaveAddress: 1
);Coils and Discrete Inputs
Read Coils (Function Code 01)
Coils are read/write single-bit values (on/off, true/false):
var coils = await client.ReadCoilsAsync(
startAddress: 0,
numberOfPoints: 10,
slaveAddress: 1
);
// coils is bool[] array
if (coils[0])
{
Console.WriteLine("Coil 0 is ON");
}Read Discrete Inputs (Function Code 02)
Discrete inputs are read-only single-bit values:
var inputs = await client.ReadInputsAsync(
startAddress: 0,
numberOfPoints: 8,
slaveAddress: 1
);
// inputs is bool[] array
foreach (var input in inputs)
{
Console.WriteLine($"Input: {input}");
}Write Single Coil (Function Code 05)
Write a single coil value:
await client.WriteSingleCoilAsync(
coilAddress: 100,
value: true,
slaveAddress: 1
);Write Multiple Coils (Function Code 15)
Write multiple consecutive coils:
await client.WriteMultipleCoilsAsync(
startAddress: 100,
data: new[] { true, false, true, true },
slaveAddress: 1
);Supported Types
Type-safe read/write operations support the following types:
| Type | Size | Description |
|---|---|---|
ushort | 1 register (16-bit) | Unsigned integer (0 to 65,535) |
short | 1 register (16-bit) | Signed integer (-32,768 to 32,767) |
uint | 2 registers (32-bit) | Unsigned integer |
int | 2 registers (32-bit) | Signed integer |
ulong | 4 registers (64-bit) | Unsigned long integer |
long | 4 registers (64-bit) | Signed long integer |
float | 2 registers (32-bit) | Single-precision floating point |
double | 4 registers (64-bit) | Double-precision floating point |
TIP
All multi-register operations automatically handle byte/word ordering based on the configured RegisterPacking option.
Addressing
Modbus uses zero-based addressing internally, but different vendors use different addressing conventions in their documentation:
| Convention | Coils | Discrete Inputs | Input Registers | Holding Registers |
|---|---|---|---|---|
| Modicon | 00001-09999 | 10001-19999 | 30001-39999 | 40001-49999 |
| Zero-based | 0-9998 | 0-9998 | 0-9998 | 0-9998 |
This library uses zero-based addressing. If your device documentation uses Modicon addressing (e.g., 40001), subtract the base value:
// Device doc says "voltage at address 40001"
// Use zero-based: 40001 - 40001 = 0
var voltage = await client.ReadHoldingRegistersAsync<float>(
startAddress: 0,
slaveAddress: 1
);
// Device doc says "power at address 40013"
// Use zero-based: 40013 - 40001 = 12
var power = await client.ReadHoldingRegistersAsync<float>(
startAddress: 12,
slaveAddress: 1
);Slave Addresses
The slaveAddress parameter identifies which device on the Modbus network to communicate with:
- Modbus TCP: Often uses slave address
1or matches the device IP last octet - Valid range: 1-247 (0 is broadcast, 248-255 are reserved)
- Check your device documentation for the correct slave/unit ID
// Common in Modbus TCP
var value = await client.ReadHoldingRegistersAsync<float>(0, slaveAddress: 1);
// Some devices use different IDs
var value = await client.ReadHoldingRegistersAsync<float>(0, slaveAddress: 10);