Project - Master/Slave Modbus (RTU/TCP) Library for NETMF and Windows

The chip on the GHI Module automatically switches between send and receive. It is not necessary (and not possible) to set any delay.
The downside of the chip on the GHI Module is that it need 5V. Most other chips with read/write pin can work with 3V3.

I have released a bug fix version V1.0.2 which specially fixes some nasty issues when implementing a Modbus TCP Device.
The Sockets from disconnected clients were not closed, which caused some nasty side effects after a while.

1 Like

Hi all, I know it’s not needed because out of date but I have done ModbusAscii version. I haven’t a lot of time to test it. Do you want to give it a try? Also I haven’t modbus ascii equipment to test it… sorry… :wall:

Basically it convert buffer from ASCII to RTU and from RTU to ASCII in the interface so it has the same handling from outside as a RTU telegram… and use 1 byte LRC instead of 2 byte CRC, : at the beginning and \r\n at the end.

It’s the original code with added the ModbusAsciiInterface and you will find some changes in ModbusUtils static class for convertion and LRC.

[url]https://github.com/gremlinc5/ModbusLib[/url]

Let us now about it, I think if it works we could add it to the Reinhard Ostermeier repository here on GHI ;D

Do you have some tips on a modbus ascii device that I could buy ?

@ gremlinc5 - I do not have or know of any Modbus ASCII device.
If someone gets a Chance to test it I will add it, but as Long as it’s not tested I do not want to add it to my public repository.

Very few devices are Modbus ASCII these days. Modbus is an old protocol by today’s standards and if anything new comes out and supports it, it generally uses the RTU protocol. If you have to interface to an ASCII device it would be very unusual or old which from the fact you don’t have an ASCII device to test with would sort of indicate that. :slight_smile:

Hi all, today a person that I worked for say me that needs help with a PLC… that PLC is a Elsist Slimline PLC… thats has Modbus Ascii protocol support… what a luck! :smiley:

I will test it and make you know about it! 8)

1 Like

I used before Modbus RTU electric analizers for work, brand like Contrel (EMS-96 if I remember right) and Gavazzi. There is a lot of RTU out there. But writing Modbus Ascii interface makes me better understand other interfaces, I hope :slight_smile:

1 Like

TESTED!

Here the first stable:

[url]https://github.com/gremlinc5/ModbusLib[/url]

Tested with Read/Write Coils and Registers in Modbus Ascii, like:

using System;
using System.IO.Ports;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;

using Osre;
using Osre.Modbus;
using Osre.Modbus.Interface;

namespace MFConsoleModbusAsciiTest
{
    public class Program
    {
        public static void Main()
        {
            Debug.Print(Resources.GetString(Resources.StringResources.String1));

            var seriale = new SerialPort("COM2", 115200, Parity.None, 8, StopBits.One);
            
            seriale.Open();

            var asciiInterface = new ModbusAsciiInterface(seriale);
            var master = new ModbusMaster(asciiInterface);

            Debug.Print("Reading...");
            while (true)
            {
                var array = new ushort[32];
                var new_array = new byte[32];
                try
                {
                    master.WriteMultipleRegisters(1, 39999, new ushort[3] { 1234, 5678, 0 });
                    array = master.ReadHoldingRegisters(1, 39999, 2); // throws ModbusException on error
                    new_array = master.ReadCoils(1, 40003, 1);
                    master.WriteSingleCoil(1, 40003, true);
                    new_array = master.ReadCoils(1, 40003, 1);
                    Debug.Print("Read: " + array[0].ToString());
                }
                catch (Exception ex)
                {
                    Debug.Print("Error: " + ex.Message);
                }

                Thread.Sleep(2222);
            }
        }
    }
}

Elsist Slimline PLC registers start at 40000 address.

Hi all, could be used BitConverter on the data output from Reading functions? Or data need to be swapped? I see some example of BitConverter, like:

But in modbus array[0] isn’t the LSB… I think buffer must be swapped modbus is Big-endian… or I’m wrong…

@ gremlinc5 - The buffers Needs to be swaped for Modbus.
This is already done correctly in my library.

@ Reinhard Ostermeier -

Hi Reinhard

I’ve been using version 1 class library with my Netduino P2 on .NET MF 4.2 using Modbus TCP/IP to read PLC data.

Sending the command ā€œMaster.WriteSingleCoil(0, 7, false); // Write to PLC coil 0008ā€ works perfectly.

I’ve added code to read a holding register from the same PLC (Modicon 140CPU11302) as below.

Master.ReadHoldingRegisters(1, 40001, 1);

Looking at your class library, I’m not completely clear on how to retrieve the data word received from the PLC. The ModbusDevice.cs has the methods to read the incoming data.

Do you have have examples to retrieve the received holdingregister data?

See full code listing below.

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Collections;
using System.IO;
using System.IO.Ports;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Net.NetworkInformation;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoPlus;
using SecretLabs.NETMF.IO;
using Osre.Modbus;
using Osre.Modbus.Interface;
using Toolbox.NETMF;

namespace NetduinoModbusTCP
{
    public class Program
    {
        public static SPI.Configuration LCDSPI1;
        public static SerialPort GPSSerialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);

        //Intialize the PWM pin D9 for alarm driver output
        static PWM alarm = new PWM(PWMChannels.PWM_PIN_D9, 2000, .5, false); //Internal alarm speaker

        //Analog Variables
        public static double CheckBattVoltage = 0;
        static Microsoft.SPOT.Hardware.AnalogInput pinA0 = new Microsoft.SPOT.Hardware.AnalogInput(AnalogChannels.ANALOG_PIN_A0);//

        //Define new Interrupt Ports to external buttons on SCL, SDA, D4 and D7
        // NOTE: Set glitch filter to "false".  This is only needed to
        //       prevent switch bounce and in this case not needed.
        // NOTE: The pullup on the MCU is disabled and the onboard switch
        //       has a pulldown resistor so we set this to "Disabled".
        // NOTE: The switch has a pulldown resistor and is connected to 3.3 (logic HIGH)
        //       so the switch will show HIGH when pressed or LOW when released.
        // NOTE: Set the Interrupt to fire on edges levels (HIGH and LOW) *** verify all inputs ***.

        static InterruptPort Upbutton = new InterruptPort(Pins.GPIO_PIN_D4, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
        static InterruptPort Enterbutton = new InterruptPort(Pins.GPIO_PIN_SDA, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
        static InterruptPort Downbutton = new InterruptPort(Pins.GPIO_PIN_SCL, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
        static InterruptPort GPSDataReady = new InterruptPort(Pins.GPIO_PIN_D7, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptNone);

        // Define new Output Ports for front panel Red and Green LED's
        static OutputPort LED1 = new OutputPort(Pins.GPIO_PIN_D5, true);
        static OutputPort LED2 = new OutputPort(Pins.GPIO_PIN_D6, true);

        //Declare timers, Modbus TCP interfaces and devices
        public static Timer ReadRegisters;

        public static ModbusTcpInterface tcpInterface = new ModbusTcpInterface("10.0.0.100");
        public static ModbusMaster Master = new ModbusMaster(tcpInterface);

        public static MyModbusDevice device = new MyModbusDevice(ModbusConst.TcpDeviceAddress);
        

        //PLC Registers
        public static int Coil_Address = 5;
        public static bool Coil_State = false;


        public static void Main()
        {
            // Configure SPI1 port for LCD communications
            LCDSPI1 = new SPI.Configuration(
                    Pins.GPIO_PIN_D10, //Chip Select pin
                    false,     // Chip Select Active State low
                    0,         // Chip Select Setup Time 0us
                    0,      // Chip Select Hold Time 0us
                    true,     // Clock Idle State
                    true,      // Clock Edge
                    100,       // Clock Rate in kHz
                    SPI_Devices.SPI1); //SPI Module

            // Delay for x seconds to allow LCD to stablize.
            DelayxmS(2);


            // Create an event handler for the push buttons and GPS Data Ready signal
            Upbutton.OnInterrupt += new NativeEventHandler(Upbutton_OnInterrupt);
            Enterbutton.OnInterrupt += new NativeEventHandler(Enterbutton_OnInterrupt);
            Downbutton.OnInterrupt += new NativeEventHandler(Downbutton_OnInterrupt);
            GPSDataReady.OnInterrupt += new NativeEventHandler(GPSDataReady_OnInterrupt);



            //Setup the board static IP address
            NetworkInterface.GetAllNetworkInterfaces()[0]
                .EnableStaticIP("10.0.0.99", "255.255.255.0", "10.0.0.1");

            string localip = NetworkInterface.GetAllNetworkInterfaces()[0]
                .IPAddress;

            Debug.Print("The local IP address of your Netduino Plus is " + localip);

            //Display system prompt
            PrintSystemReady();

            //Read SD Card text file
            //ReadSDCard(); *** Add updated method when needed ***

            //Read Analog Input
            ReadBatteryVoltage();
            Alarm(1000);

            //Display Main Menu
            PrintMainMenu();

            //Thread.Sleep is necessary to keep the program running
            Thread.Sleep(Timeout.Infinite);
            
        }

        public static void InitTimers()
        {

            TimerCallback RegisterDelegate = new TimerCallback(HoldingRegisters);
            ReadRegisters = new Timer(RegisterDelegate, null, 1000, 1000);

            var listener = ModbusTcpInterface.StartDeviceListener(device);         
            device.Start();
            
            
        }

        // Use timer delegate to read four holding registers per 1 second
        public static void HoldingRegisters(Object stateInfo)
        {
            // Create an object of type Globals.
            Globals ArrayClass = new Globals();

            Master.ReadHoldingRegisters(1, 40001, 1);

                        
            //byte[] Hold_Reg_Data = Tools.UShortsToBytes(Hold_Reg_Result);

            DelayxmS(500);

            DisplayArray(ArrayClass.ClrScreen);
            DisplayArray(ArrayClass.CursorPosition1);
            //DisplayArray(Hold_Reg_Data);
            
        }



        public static void ReadBatteryVoltage()
        {
            // Create an object of type Globals.
            Globals ArrayClass = new Globals();

            double maxADCVoltage = 3.3; // Maximum full scale voltage at analog input A0
            double maxBattVoltage = 10.58; // Maximum NiMH battery voltage after full charge
            double value = 0;
            double tempvalue = 0;
            int maxAdcValue = 4096; // ADC is 12-bit Resolution
            int CalcCounter = 10;

            // Calculate battery voltage from 10 analog input A0 readings

            while (CalcCounter != 0)
            {
                int rawValue = pinA0.ReadRaw(); // Binary value read from analog input A0
                double aValue = (rawValue * maxADCVoltage); // A0 Binary value multiplied by 3.3
                value = aValue / maxAdcValue; // A0 input voltage value from 0 to 3.3
                tempvalue = tempvalue + value;
                CalcCounter--;
            }

            value = tempvalue / 10;
            double VoltageScale = value / maxADCVoltage; // Voltage scale factor from 0 to 1
            double BattVoltage = (VoltageScale * maxBattVoltage); // Calculated NiMH battery voltage from voltage scale

            double CheckBattVoltage = System.Math.Round(BattVoltage * 10000.0) / 10000.0;

            string BatteryVoltage = CheckBattVoltage.ToString(); // Convert battery voltage to a string format

            byte[] BatteryData = Encoding.UTF8.GetBytes(BatteryVoltage);

            if (CheckBattVoltage < 9.6)
            {
                DisplayArray(ArrayClass.CursorPosition1);
                DisplayArray(ArrayClass.Message4);
                DisplayArray(ArrayClass.CursorPosition2);
                DisplayArray(BatteryData);
            }
            else
                DisplayArray(ArrayClass.CursorPosition1);
            DisplayArray(ArrayClass.Message5);
            DisplayArray(ArrayClass.CursorPosition2);
            DisplayArray(BatteryData);


        }




        public static void GPSDataReady_OnInterrupt(uint port, uint data, DateTime time)
        {
            //Add future code here...          

            
        }


        //Print System ready message
        public static void PrintSystemReady()
        {
            // Create an object of type Globals.
            Globals ArrayClass = new Globals();

            DisplayArray(ArrayClass.LCDBrightness);
            DisplayArray(ArrayClass.LCDContrast);
            DisplayArray(ArrayClass.TurnOnBlinkingCursor);
            DisplayArray(ArrayClass.TurnOnUnderlineCursor);
            DisplayArray(ArrayClass.ClrScreen);
            Coil_State = true;

        }


        //PrintMainMenu in order to select main menu items with button entry
        public static void PrintMainMenu()
        {

            // Create an object of type Globals.
            Globals ArrayClass = new Globals();

            //Clear LCD Screen
            DisplayArray(ArrayClass.ClrScreen);

            //Display Main Menu Line1 if MainMenuInstance = 1
            if (Globals.MainMenuInstance == 1)
            {
                DisplayArray(ArrayClass.CursorPosition1);
                DisplayArray(ArrayClass.Menu1);
            }


        }


        //PrintSubMenu to increase or decrease LCD brightness and contast
        public static void PrintSubMenu()
        {
            // Create an object of type Globals.
            Globals ArrayClass = new Globals();

            //Clear LCD Screen
            DisplayArray(ArrayClass.ClrScreen);

            //Display Main Menu Line1
            DisplayArray(ArrayClass.CursorPosition1);
            DisplayArray(ArrayClass.Menu2);

            //Display Main Menu Line2
            DisplayArray(ArrayClass.CursorPosition2);
            DisplayArray(ArrayClass.Menu3);

        }


        public static void Upbutton_OnInterrupt(uint port, uint data, DateTime time)
        {

            // Create an object of type Globals.
            Globals ArrayClass = new Globals();


            if (Globals.SubMenuInstance == 1)
            {
                if (Globals.SubMenuIndex > 1 || Globals.SubMenuIndex < 3)
                {
                    Globals.SubMenuIndex--;
                    Globals.UpToggle = true;
                }
                else Globals.SubMenuIndex = 1;

                if (Globals.SubMenuIndex == 3)
                {
                    Globals.SubMenuIndex--;
                    Globals.UpToggle = true;
                    PrintSubMenu();
                }

                switch (Globals.SubMenuIndex)
                {
                    case 1:
                        //Set cursor at first character at line 1
                        DisplayArray(ArrayClass.CursorPosition1);
                        break;
                    case 2:
                        //Set cursor at first character at line 2
                        DisplayArray(ArrayClass.CursorPosition2);
                        break;
                    default:
                        break;
                }
            }


        }

        public static void Enterbutton_OnInterrupt(uint port, uint data, DateTime time)
        {

            // Create an object of type Globals.
            Globals ArrayClass = new Globals();


            // Determine if the MainMenuInstance is set to initialize the SubMenu
            if (Globals.MainMenuInstance == 1)
            {
                PrintSubMenu();
                Globals.SubMenuInstance = 1;
                Globals.MainMenuInstance = 0;

            }

            if (Globals.SubMenuInstance == 1)
            {
                switch (Globals.SubMenuIndex)
                {
                    case 1:
                        //Start the TCP Server
                        StartTCPServer();
                        break;
                    case 2:
                        //Start the Modbus TCP Server
                        StartModbusTCP();
                        break;
                    case 3:
                        //Toggle the LCD Backlight On/Off
                        ToggleLCDLight();
                        break;
                    default:
                        break;
                }

            }
        }



        public static void Downbutton_OnInterrupt(uint port, uint data, DateTime time)
        {
            // Create an object of type Globals.
            Globals ArrayClass = new Globals();


            if (Globals.SubMenuInstance == 1)
            {
                if (Globals.SubMenuIndex < 4)
                {
                    Globals.SubMenuIndex++;
                    Globals.DownToggle = true;

                }
                else Globals.SubMenuIndex = 1;
                PrintSubMenu();

                switch (Globals.SubMenuIndex)
                {
                    case 1:
                        //Set cursor at first character at line 1
                        DisplayArray(ArrayClass.CursorPosition1);
                        break;
                    case 2:
                        //Set cursor at first character at line 2
                        DisplayArray(ArrayClass.CursorPosition2);
                        break;
                    case 3:
                        //Set cursor at first character at line 1
                        DisplayArray(ArrayClass.ClrScreen);
                        DisplayArray(ArrayClass.CursorPosition1);
                        DisplayArray(ArrayClass.Menu4);
                        break;
                    default:
                        break;
                }
            }


        }

        //Start TCP Server
        public static void StartTCPServer()
        {
            LED1.Write(false); // turn on the LED1
            DelayxmS(500);
            LED1.Write(true); // turn off the LED1

                      
        }


        //Start ModbusTCP Server
        public static void StartModbusTCP()
        {
            LED2.Write(false); // turn on the LED2
            DelayxmS(500);
            LED2.Write(true); // turn off the LED2

            InitTimers();

        }


        //Toggle LCD BackLight
        public static void ToggleLCDLight()
        {
            // Create an object of type Globals.
            Globals ArrayClass = new Globals();

            if (Globals.LCDLight == false)
            {
                DisplayArray(ArrayClass.LCDLightOff);
                Master.WriteSingleCoil(0, 7, false); // Write to PLC coil 0008

            }
            else 
            { 
                DisplayArray(ArrayClass.LCDBrightness);
                Master.WriteSingleCoil(0, 7, true); // Write to PLC coil 0008
            }

            Globals.LCDLight = !Globals.LCDLight;
            Coil_State = !Coil_State;
        }


        //Display received bytes from external TCP client
        public static void DisplayTCPData(byte[] array)
        {
            // Create an object of type Globals.
            Globals ArrayClass = new Globals();


            byte[] LineNumber = new byte[1];
            byte[] Space = new byte[] { 0x20 };

            //Array.Copy(a, 1, b, 0, 3);
            // array = source array
            // i = start index in source array
            // LineNumber = destination array
            // 0 = start index in destination array
            // 1 = elements to copy
            Array.Copy(array, 0, LineNumber, 0, 1);

            //Determine which LCD line number to display
            String LineNumberString = new String(System.Text.UTF8Encoding.UTF8.GetChars(LineNumber));

            Int32 LCDLineNumber = Int32.Parse(LineNumberString);

            switch (LCDLineNumber)
            {
                case 1:
                    //Display LCD Display Line1 ASCII Text

                    DisplayArray(ArrayClass.CursorPosition1);
                    byte[] LCDLine1 = new byte[20];
                    Array.Copy(array, 1, LCDLine1, 0, 20);
                    DisplayArray(LCDLine1);

                    DisplayArray(ArrayClass.CursorPosition2);

                    break;
                case 2:
                    //Display LCD Display Line2 ASCII Text

                    byte[] LCDLine2 = new byte[20];
                    Array.Copy(array, 1, LCDLine2, 0, 20);
                    DisplayArray(LCDLine2);

                    DisplayArray(ArrayClass.CursorPosition1);

                    break;
                default:
                    break;
            }


        }

        //DisplayArray for displaying LCD data array contents for menus and data received from an external TCP client
        public static void DisplayArray(byte[] arr)
        {

            // Create an object of type Globals.
            Globals ArrayClass = new Globals();

            //Send Array contentd to LCD display over SPI1 port
            using (SPI spi = new SPI(LCDSPI1))
            {

                //Array.Copy(a, 1, b, 0, 3);
                // arr = source array
                // i = start index in source array
                // LCDData = destination array
                // 0 = start index in destination array
                // 1 = elements to copy
                for (int i = 0; i < arr.Length; i++)
                {
                    Array.Copy(arr, i, ArrayClass.LCDData, 0, 1);
                    int decValue = ArrayClass.LCDData[0];

                    if (decValue == 254 || decValue >= 32 || decValue <= 127)
                    {
                        spi.Write(ArrayClass.LCDData);
                        DelayxmS(1);
                    }

                }
                spi.Dispose();
            }
        }

        //Turn on alarm speaker for x seconds
        //Turn on alarm speaker for 2 seconds
        public static void Alarm(int OnTime)
        {
            //Turn on alarm speaker for 2 kHz PWM
            alarm.Start();
            DelayxmS(OnTime);
            alarm.Stop();

        }

        //Delay method for LCD display stablization
        public static void DelayxmS(int Multx)
        {
            Globals.xmSMultiplier = Multx * 1;
            Thread.Sleep(Globals.xmSMultiplier);
        }

    }


    // implement slave device
    public class MyModbusDevice : ModbusDevice
    {
        public MyModbusDevice(byte deviceAddress, object syncObject = null)
            : base(deviceAddress, syncObject)
        { }

        public MyModbusDevice(IModbusInterface intf, byte deviceAddress, object syncObject = null)
            : base(intf, deviceAddress, syncObject)
        { }

        protected override string OnGetDeviceIdentification(ModbusObjectId objectId)
        {
            switch (objectId)
            {
                case ModbusObjectId.VendorName:
                    return "MeManufacturer";
                case ModbusObjectId.ProductCode:
                    return "1";
                case ModbusObjectId.MajorMinorRevision:
                    return "1.0";
                case ModbusObjectId.VendorUrl:
                    return "http://www.MeManufacturer.org";
                case ModbusObjectId.ProductName:
                    return "MyModbusDevice";
                case ModbusObjectId.ModelName:
                    return "1";
                case ModbusObjectId.UserApplicationName:
                    return "MyModbusApplication";
            }
            return null;
        }

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

        protected override ModbusErrorCode OnWriteSingleCoil(bool isBroadcast, ushort address, bool value)
        {
            if (false) // check address here
            {
                return ModbusErrorCode.IllegalDataAddress;
            }

            // set coil value here

            return ModbusErrorCode.NoError;
        }

        protected override ModbusErrorCode OnReadCoils(bool isBroadcast, ushort startAddress, ushort coilCount, byte[] coils)
        {
            if (false) // check coil count here
            {
                return ModbusErrorCode.IllegalDataValue;
            }
            if (false) // check address here
            {
                return ModbusErrorCode.IllegalDataAddress;
            }

            // write coil values into parameter coils here

            return ModbusErrorCode.NoError;
        }

        // override any On<ModusFunction> methods here
    }
}

@ djr2077 - Why don’t you use the return value of ReadHoldingRegisters()?
its a ushort[] holding the number of Registers you have requested.

Hi djr2077,

could be that your timer will overlap in case of timeout? Standard Timeout in the library is 2000 ms, your timer fire every 1000 ms… in case of timeout timer will fire 2 times…

I like much more Threads instead of Timers for external data exchange… I like more Timers for LCD refresh as an example…

In standard .NET for external data exchange it could be used the BackGroundWorker it’s a thread.

What do you think boys? I could be wrong! :wink:

I started playing around with this library, but I’d like to write a transport for a WIZnet 5100 instead of .net sockets. I saw an older post where someone might have done this with a WIZnet 5100:

https://www.ghielectronics.com/community/forum/topic?id=6563

But I think the post is so old the link to the example code is broken. Anyone happen to have that or a similar example?

I’m looking to implement Function Codes 0x07 and 0x08 for and am trying to figure them out.
both look like a broadcast event or are they master only?
next the comment on them is Serial Line Only. does this mean RTU/ASCII only or should they work with TCP/RTU as well?

TIA.
BTW, pulled your git version, great work.

@ smgvbest - I think my master implementation already has both function codes implemented.

Hi
I’m working on the slave side and it does not appear to be implemented there.
If I’m wrong please correct me? I see it on the master side as you say, just not on the slave side.

@ smgvbest - No, I didn’t implement it on the slave side. But it should be straight Forward if you take the other slave side implementations and the master side as templates/hints.

@ Reinhard
Yep, that’s what I’ve been working on.

I thought I would post on here in case others are using this library and run into the same issues. I’ve already contacted the author on the issue.

I used this library in slave mode with a single device on the bus and it works well with no lost packets.

Yesterday I connected a Variable Speed Drive to the same bus and now the slave does not respond. Digging into the code I have spotted that it does not like the data being sent by the VSD. What is happening is that the request to the VSD and the reply are being received at the same time by the Modbus driver and the CRC fails so it goes back around for more data. If I check the buffer before the CRC check I can see the 2 complete messages are there, the request to the VSD and it’s reply.

Looking at the scope, there is a 20ms gap after the request before the VSD responds so this should be more than enough time gap for the driver to detect the end of the telegram but it doesn’t appear to be working. It should be 3.5 character times which in this case is 960uS per character at 9200 bps so around 3ms max. This could be down to the timing waiting for the end of the telegram.

I’ve setup a similar setup in my office and I get the exact same issue with a similar VSD and my device.

I am working on this today as I need this working ASAP. If anyone else has seen this issue and you fixed it, can you post back here please.

1 Like