Skip to content

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:

csharp
// 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:

csharp
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:

csharp
var temperature = await client.ReadInputRegistersAsync<short>(
    startAddress: 100,
    slaveAddress: 1
);

var voltage = await client.ReadInputRegistersAsync<float>(
    startAddress: 0,
    slaveAddress: 1
);

Raw register array:

csharp
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:

csharp
await client.WriteSingleRegisterAsync(
    registerAddress: 40001,
    value: 1234,
    slaveAddress: 1
);

Multiple Registers (Function Code 16)

Write multiple consecutive holding registers:

Raw array:

csharp
await client.WriteMultipleRegistersAsync(
    startAddress: 40001,
    data: new ushort[] { 100, 200, 300 },
    slaveAddress: 1
);

Type-safe write:

csharp
// 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):

csharp
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:

csharp
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:

csharp
await client.WriteSingleCoilAsync(
    coilAddress: 100,
    value: true,
    slaveAddress: 1
);

Write Multiple Coils (Function Code 15)

Write multiple consecutive coils:

csharp
await client.WriteMultipleCoilsAsync(
    startAddress: 100,
    data: new[] { true, false, true, true },
    slaveAddress: 1
);

Supported Types

Type-safe read/write operations support the following types:

TypeSizeDescription
ushort1 register (16-bit)Unsigned integer (0 to 65,535)
short1 register (16-bit)Signed integer (-32,768 to 32,767)
uint2 registers (32-bit)Unsigned integer
int2 registers (32-bit)Signed integer
ulong4 registers (64-bit)Unsigned long integer
long4 registers (64-bit)Signed long integer
float2 registers (32-bit)Single-precision floating point
double4 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:

ConventionCoilsDiscrete InputsInput RegistersHolding Registers
Modicon00001-0999910001-1999930001-3999940001-49999
Zero-based0-99980-99980-99980-9998

This library uses zero-based addressing. If your device documentation uses Modicon addressing (e.g., 40001), subtract the base value:

csharp
// 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 1 or 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
csharp
// 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);