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:
- Modbus RTU — over an RS-485 serial bus.
- Modbus IP — over Ethernet (Modbus TCP).
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
| Namespace | Description |
|---|---|
| GHIElectronics.TinyCLR.Devices.Modbus | Modbus RTU master and slave classes |