Modbus RTU slave in TinyCLR v2.x?

Hi,

I could really use being able to use multiple SC20260 SoM’s as Modbus RTU slave units. Is this even possible? Are there any examples available for this? I only came across a modbus master example until now.

Thanks,

Remco

You should be able to but note that the Modbus implementation is C# open source TinyCLR-Libraries/GHIElectronics.TinyCLR.Devices.Modbus at dev · ghi-electronics/TinyCLR-Libraries · GitHub It was contributed by the community ad we welcome any further testing/contribution.

Thanks Gus, much appreciated. I will give it a try.

1 Like

I don’t know if this has been fixed but there was a timing issue with the Modbus slave library when used with RTU over UART. TCP is fine. If your only devices on the bus are your own based on the library, it works fine. If you add another device that responds quicker but within the Modbus spec, your own devices based on the library has issues decoding the other devices reply as it misses the first 1 or 2 bytes of the reply. This reply is of course ignored, but the slave still has to process it as a Modbus message. This was when I was using with the G120 so the SITcore modules with their faster processing power may not have this issue.

I will soon be building a couple of Modbus sensor interfaces using the SCM20260N modules and plan to try and check and fix this timing issue if it still exists.

Hi Dave, I am super excited to hear that you will be testing this. We were planning to have a number of 30 SCM20260 modules communicating with a modbus master. So if I read your reply, we should be fine probably since it has the same type of devices on the bus.

I suppose I would have to use the “ModbusDevice” class if I want to use it in the firmware? Do you have some example code on how to to declare the slave modbus function and how to program it? I only want to cyclically read/write some holding registers from the master node. Thanks!

The code I currently have is from .NETMF 4.3 but if you can work out how to port it, here is the slave handler direct form my old G120 system. It basically just handles passing back values from the arrays you configure. There are calls to handle changes in the write registers. I’ll be porting this over to the other board once I get it built.

using System;
using Microsoft.SPOT;
using Osre.Modbus;
using Osre.Modbus.Interface;
using Microsoft.SPOT.Hardware;
using System.IO.Ports;

namespace GaugeInterface
{
    class ModbusSlave : ModbusDevice
    {
        // 
        // Input registers are the calibration values
        //
        public ushort[] inputRegistersCal = new ushort[40];
        public ushort[] inputRegistersSensor = new ushort[15];
        public bool[] coils = new bool[4];

        private CoilsChangedHandler onCoilsChanged;
        private InputRegistersChangedHandler onInputsChanged;

        /// <summary>
        /// Represents the delegate for the coils change event
        /// </summary>
        /// <param name="sender"></param>
        public delegate void CoilsChangedHandler(ModbusSlave sender);

        /// <summary>
        /// Represents the delegate for the intput registers changed event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="calRegisters">Set if the calibration register changed</param>
        public delegate void InputRegistersChangedHandler(ModbusSlave sender, bool calRegisters);

        /// <summary>
        /// Raised when the coils are changed
        /// </summary>
        public event CoilsChangedHandler CoilsChanged;

        /// <summary>
        /// Raised when the inputs are changed
        /// </summary>
        public event InputRegistersChangedHandler RegistersChanged;

        public ModbusSlave(IModbusInterface intf, byte ModbusID, object syncObject = null)
            : base(intf, ModbusID, syncObject)
        {
            this.onCoilsChanged = this.OnCoilsChanged;
            this.onInputsChanged = this.OnInputsChanged;
        }

        protected override string OnGetDeviceIdentification(ModbusObjectId objectId)
        {
            switch (objectId)
            {
                case ModbusObjectId.VendorName:
                    return "Axon Instruments";
                case ModbusObjectId.ProductCode:
                    return "AX-100";
                case ModbusObjectId.MajorMinorRevision:
                    return "1.0";
                case ModbusObjectId.VendorUrl:
                    return "http://www.axoninstruments.biz";
                case ModbusObjectId.ProductName:
                    return "AXGIF";
                case ModbusObjectId.ModelName:
                    return "GAUGE";
                case ModbusObjectId.UserApplicationName:
                    return "GaugeModbusInterface";
            }
            return null;
        }

        protected override ModbusConformityLevel GetConformityLevel()
        {
            return ModbusConformityLevel.Regular;
        }

        protected override ModbusErrorCode OnWriteSingleCoil(bool isBroadcast, ushort address, bool value)
        {
            if(address < 5001 || address > 5003)
            {
                return ModbusErrorCode.IllegalDataAddress;
            }
            coils[address - 5001] = value;

            this.OnCoilsChanged(this);

            return ModbusErrorCode.NoError;
        }

        protected override ModbusErrorCode OnWriteMultipleCoils(bool isBroadcast, ushort startAddress, ushort outputCount, byte[] values)
        {
            return ModbusErrorCode.IllegalFunction;
        }

        protected override ModbusErrorCode OnWriteSingleRegister(bool isBroadcast, ushort address, ushort value)
        {
            if (address < 3004 || address > 3008)
            {
                if (address < 4001 || address > 4041)
                {
                    return ModbusErrorCode.IllegalDataAddress;
                }
                else
                {
                    inputRegistersCal[address - 4001] = value;

                    this.OnInputsChanged(this, (address >= 4001) ? true : false);

                    return ModbusErrorCode.NoError;
                }
            }
            inputRegistersSensor[address - 3001] = value;

            this.OnInputsChanged(this, (address >= 4001) ? true : false);

            return ModbusErrorCode.NoError;
        }

        protected override ModbusErrorCode OnReadInputRegisters(bool isBroadcast, ushort startAddress, ushort[] registers)
        {
            int len;

            if (startAddress > 4000)
            {
                len = 40 - (startAddress - 4001);   // Max read of 30 registers if start is zero, otherwise adjust it
            }
            else
            {
                len = 15 - (startAddress - 3001);   // Max read of 15 registers if start is zero, otherwise adjust it
            }
            if (startAddress < 3001 || startAddress > 3008)
            {
                if (startAddress < 4001 || startAddress > 4041)
                {
                    return ModbusErrorCode.IllegalDataAddress;
                }
                else
                {
                    if (registers.Length > len)
                    {
                        return ModbusErrorCode.IllegalDataAddress;
                    }
                    for (int index = 0; index < registers.Length; index++)
                    {
                        registers[index] = inputRegistersCal[(startAddress - 4001) + index];
                    }
                    return ModbusErrorCode.NoError;
                }
            }
            if (registers.Length > len)
            {
                return ModbusErrorCode.IllegalDataAddress;
            }
            for (int index = 0; index < registers.Length; index++)
            {
                registers[index] = inputRegistersSensor[(startAddress - 3001) + index];
            }
            return ModbusErrorCode.NoError;
        }

        protected override ModbusErrorCode OnReadCoils(bool isBroadcast, ushort startAddress, ushort coilCount, byte[] coils)
        {
            int len = (startAddress - 5001) + coilCount;

            if(len > 4)
            {
                return ModbusErrorCode.IllegalDataAddress;
            }
            if (startAddress < 5001 || startAddress > 5005)
            {
                return ModbusErrorCode.IllegalDataAddress;
            }
            byte mask = 1;
            int start = startAddress - 5001;

            coils[0] = 0;
            for (int index = 0; index < coilCount; index++)
            {
                if(this.coils[start++])
                {
                    coils[0] |= mask;
                }
                mask *= 2;
            }
            return ModbusErrorCode.NoError;
        }

        protected override ModbusErrorCode OnReadHoldingRegisters(bool isBroadcast, ushort startAddress, ushort[] registers)
        {
            return ModbusErrorCode.IllegalFunction;
        }

        protected override ModbusErrorCode OnWriteMultipleRegisters(bool isBroadcast, ushort startAddress, ushort[] registers)
        {
            int len;

            if (startAddress > 4000)
            {
                len = 40 - (startAddress - 4001);   // Max read of 30 registers if start is zero, otherwise adjust it
            }
            else
            {
                len = 15 - (startAddress - 3001);   // Max read of 15 registers if start is zero, otherwise adjust it
            }
            if (startAddress < 3004 || startAddress > 3008)
            {
                if (startAddress < 4001 || startAddress > 4041)
                {
                    return ModbusErrorCode.IllegalDataAddress;
                }
                else
                {
                    if(registers.Length > len)
                    {
                        return ModbusErrorCode.IllegalDataAddress;
                    }
                    for (int index = 0; index < registers.Length; index++)
                    {
                        inputRegistersCal[(startAddress - 4001) + index] = registers[index];
                    }
                    this.OnInputsChanged(this, true);

                    return ModbusErrorCode.NoError;
                }
            }
            if (registers.Length > len)
            {
                return ModbusErrorCode.IllegalDataAddress;
            }
            for (int index = 0; index < registers.Length; index++)
            {
                inputRegistersSensor[(startAddress - 3001) + index] = registers[index];
            }
            this.OnInputsChanged(this, false);

            return ModbusErrorCode.IllegalFunction;
        }

        private void OnCoilsChanged(ModbusSlave sender)
        {
            if (this.CoilsChanged != null)
                this.CoilsChanged(sender);
        }

        private void OnInputsChanged(ModbusSlave sender, bool calRegisters)
        {
            if (this.RegistersChanged != null)
                this.RegistersChanged(sender, calRegisters);
        }
    }
}

And that is why we use GitHub issues. :grin:

1 Like