Skip to main content

Modbus

Modbus is an industrial communications protocol originally developed for talking to PLCs. It defines a simple request/response model where one master (sometimes called client) initiates transfers, and one or more slaves (sometimes called servers) respond. TinyCLR supports two transports:

NuGet packages: GHIElectronics.TinyCLR.Devices.Modbus for the API, and GHIElectronics.TinyCLR.Pins for pin name constants. Modbus RTU also pulls in GHIElectronics.TinyCLR.Devices.Uart.

Modbus RTU

Modbus RTU is asynchronous serial Modbus that uses RS-485 as its physical layer. One master can address up to 32 slaves on the bus.

The example below targets the SC20100S Dev Board with UART5 wired to an RS-485 transceiver. The board acts as the master and reads one holding register from a slave at address 10.

using System.Diagnostics;
using System.Threading;
using GHIElectronics.TinyCLR.Devices.Modbus;
using GHIElectronics.TinyCLR.Devices.Modbus.Interface;
using GHIElectronics.TinyCLR.Devices.Uart;
using GHIElectronics.TinyCLR.Pins;

var serial = UartController.FromName(FEZBit.UartPort.Uart6);

var uartSetting = new UartSetting(){
BaudRate = 19200,
DataBits = 8,
Parity = UartParity.None,
StopBits = UartStopBitCount.One,
Handshaking = UartHandshake.None,
};

serial.SetActiveSettings(uartSetting);
serial.Enable();

IModbusInterface mbInterface = new ModbusRtuInterface(serial, 19200, 8, UartStopBitCount.One, UartParity.None);
var mbMaster = new ModbusMaster(mbInterface);

ushort[] reply = null;
int count = 0;

while (count < 5){
var mbTimeout = false;

try{
// ReadHoldingRegisters(slaveAddress, startAddress, count, timeoutMs)
reply = mbMaster.ReadHoldingRegisters(10, 0, 1, 3333);
count++;
}
catch (System.Exception){
Debug.WriteLine("Modbus timeout");
mbTimeout = true;
}

if (!mbTimeout){
Debug.WriteLine("Modbus: " + reply[0].ToString());
}
Thread.Sleep(1000);
}

Modbus IP

Modbus IP (also called Modbus TCP) carries Modbus frames inside standard Ethernet packets — far more bandwidth and far longer reach than RTU's serial bus.

The example below sets up the SITCore device as a Modbus slave (server) listening on the standard Modbus TCP port (502). Clients (such as a SCADA system) connect and read coils or holding registers.

First, derive a class from ModbusDevice and override the request handlers — at minimum OnGetDeviceIdentification and whichever read/write methods your slave should support:

using System.Diagnostics;
using GHIElectronics.TinyCLR.Devices.Modbus;

public class MyModbusSlave : ModbusDevice{
public MyModbusSlave(byte deviceAddress, object syncObject = null)
: base(deviceAddress, syncObject) { }

protected override string OnGetDeviceIdentification(ModbusObjectId objectId){
switch (objectId)
{
case ModbusObjectId.VendorName: return "GHI Electronics";
case ModbusObjectId.ProductCode: return "101";
case ModbusObjectId.MajorMinorRevision: return "1.0";
case ModbusObjectId.VendorUrl: return "ghielectronics.com";
case ModbusObjectId.ProductName: return "SITCore";
case ModbusObjectId.ModelName: return "SC20100S";
case ModbusObjectId.UserApplicationName: return "Modbus Slave Test";
}
return null;
}

protected override ModbusConformityLevel GetConformityLevel() => ModbusConformityLevel.Regular;

protected override ModbusErrorCode OnReadCoils(bool isBroadcast, ushort startAddress, ushort coilCount, byte[] coils){
try{
for (int n = 0; n < coilCount; ++n)
{
coils[n] = 1;
}
Debug.WriteLine("Master read coils");
return ModbusErrorCode.NoError;
}
catch{
Debug.WriteLine("Error in OnReadCoils");
return base.OnReadCoils(isBroadcast, startAddress, coilCount, coils);
}
}

protected override ModbusErrorCode OnReadHoldingRegisters(bool isBroadcast, ushort startAddress, ushort[] registers){
try{
for (int n = 0; n < registers.Length; ++n){
registers[n] = 65530; // Test value.
}
Debug.WriteLine("Master read holding registers - " + registers[0].ToString());
return ModbusErrorCode.NoError;
}
catch{
Debug.WriteLine("Error in OnReadHoldingRegisters");
return base.OnReadHoldingRegisters(isBroadcast, startAddress, registers);
}
}
// Override other On<ModbusFunction> methods here as needed —
// OnWriteCoils, OnWriteHoldingRegisters, OnReadInputRegisters, etc.
}

Then instantiate the slave and start a TCP listener on port 502:

ModbusDevice modbusDevice = new MyModbusSlave(deviceAddress: 248);
var mbListener = new ModbusTcpListener(modbusDevice, port: 502, maxConnections: 5, idleTimeoutMs: 1000);

Thread.Sleep(100);

modbusDevice.Start();

The listener accepts up to 5 concurrent client connections and drops idle connections after 1 second of inactivity. Adjust those values to match the deployment.

API reference

NamespaceDescription
GHIElectronics.TinyCLR.Devices.ModbusModbus RTU master and slave classes