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