0
votes

I am having issues with a program that is getting unreliable data from a serial port, and I believe https://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport#comments

This article has some answers - the bytestoread value seems erratic, the callback isn't always called when data is there and all of the default serial port functions are unreliable, including the datareceived event. Exactly what I am experiencing in this program. In fact, all over the microsoft documentation for serial ports there are disclaimers and notes of failed functionality. What it doesn't have is solutions to these problems when they pop up in your applications.

However, when I attempt to use the provided solution in the article it seems to output the same first byte over and over forever: "pppppppppppppppppppppppppppp" I can confirm the first byte received is actually "p", but why it is not removing that byte after reading it and proceeding, or receiving any bytes after, I do not know. As far as I can tell it is the exact same solution proposed in the article. Here is the code:

private void Form1_Shown(object sender, EventArgs e)
    {
        try
        {
            sp.Open();
            SPDataHelper();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }


private void SPDataHelper()
    {
        byte[] buffer = new byte[8000];
        Action kickoffRead = null;

        kickoffRead = delegate
        {
            sp.BaseStream.BeginRead(buffer, 0, buffer.Length, delegate (IAsyncResult ar)
            {
                try
                {
                    int actualLength = sp.BaseStream.EndRead(ar);
                    byte[] received = new byte[actualLength];
                    Buffer.BlockCopy(buffer, 0, received, 0, actualLength);
                    sp_DataReceived(System.Text.Encoding.UTF8.GetString(received));//not called by the actual serialport anymore
                }
                catch (IOException exc)
                {
                    WriteUUTWindow("Exception: "+exc.ToString());
                }
                kickoffRead();
            }, null);
        };
        kickoffRead();
    }

When I put a breakpoint on the "actuallength" after its assignment it shows 1, suggesting it never reads after the first byte. The breakpoint for both that assignment and the callback are never reached again afterwards, but the application continues to spam "p". Any idea what's going on here?

1
I just tested your code in a simple console app and it worked fine.Johnny Mopp
The main problem is that either that the method outputting text is called from another place in your app or your debugging setup is broken. Focus on this one first. Once solved, the serial port problems are probably gone. The serial port implementation might be flawed but it doesn't disable breakpoints or call your methods out of nowhere.Codo
@Codo this is a windows forms app so it necessarily does come from another place in the app. But im not sure how that could cause this problem, especially the bit where actuallength comes out as 1Plaje
@JohnnyMopp thanks for trying it out. Well, it doesn't help me troubleshoot the issue but at least I know it can work in a console app which is useful informationPlaje
The following may be helpful: stackoverflow.com/questions/65957066/…user9938

1 Answers

2
votes

If you're interested in using BeginRead, try the following - it's been tested with a barcode scanner:

Create a class (name: HelperSerialPort.cs)

Option 1:

HelperSerialPort.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Ports;
using System.Diagnostics;

namespace SerialPortTest
{
    public enum PortBaudRate : int
    {
        Baud1200 = 1200,
        Baud2400 = 2400,
        Baud4800 = 4800,
        Baud9600 = 9600,
        Baud14400 = 14400,
        Baud19200 = 19200,
        Baud28800 = 28800,
        Baud38400 = 38400,
        Baud56000 = 56000,
        Baud57600 = 57600,
        Baud76800 = 76800,
        Baud115200 = 115200,
        Baud128000 = 128000,
        Baud230400 = 230400,
        Baud250000 = 250000,
        Baud256000 = 256000
    };

    public class HelperSerialPort
    {
        public SerialPort Port { get; private set; } = null;

        private string _dataReceived = string.Empty;

        public HelperSerialPort(string portName, PortBaudRate baudRate = PortBaudRate.Baud9600)
        {
            Initialize(portName, baudRate);
        }

        private void Initialize(string portName, PortBaudRate baudRate = PortBaudRate.Baud9600)
        {
            //create new instance
            this.Port = new SerialPort();

            //set properties
            Port.BaudRate = (int)baudRate;
            Port.DataBits = 8;
            Port.Parity = Parity.None; //use 'None' when DataBits = 8; if DataBits = 7, use 'Even' or 'Odd'
            Port.DtrEnable = true; //enable Data Terminal Ready
            Port.Handshake = Handshake.None;
            Port.PortName = portName;
            Port.ReadTimeout = 200; //used when using ReadLine
            Port.RtsEnable = true; //enable Request to send
            Port.StopBits = StopBits.One;
            Port.WriteTimeout = 50;

            //open
            Port.Open();

            Listen();
        }

        private void Listen()
        {
            byte[] buffer = new byte[65536];

            IAsyncResult result = Port.BaseStream.BeginRead(buffer, 0, buffer.Length, delegate (IAsyncResult ar)
            {
                try
                {
                    if (Port.IsOpen)
                    {
                        int bytesRead = Port.BaseStream.EndRead(ar);

                        byte[] received = new byte[bytesRead];
                        Buffer.BlockCopy(buffer, 0, received, 0, bytesRead);

                        _dataReceived = System.Text.Encoding.UTF8.GetString(received);
                        Debug.WriteLine("Info: " + DateTime.Now.ToString("HH:mm:ss:fff") + " - _dataReceived: " + _dataReceived);

                        Listen();
                    }
                    
                }
                catch (IOException ex)
                {
                    Debug.WriteLine("Error (Listen) - " + ex.Message);
                }
            }, null);
        }

        public void Dispose()
        {
            if (Port != null)
            {
                Port.Close();
                Port.Dispose();

                Port = null;
            }
        }
    }
}

Update

Here's another version which seems to work as well-it uses a slightly modified version of your code.

Option 2:

HelperSerialPort.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Ports;
using System.Diagnostics;

namespace SerialPortTest
{
    public enum PortBaudRate : int
    {
        Baud1200 = 1200,
        Baud2400 = 2400,
        Baud4800 = 4800,
        Baud9600 = 9600,
        Baud14400 = 14400,
        Baud19200 = 19200,
        Baud28800 = 28800,
        Baud38400 = 38400,
        Baud56000 = 56000,
        Baud57600 = 57600,
        Baud76800 = 76800,
        Baud115200 = 115200,
        Baud128000 = 128000,
        Baud230400 = 230400,
        Baud250000 = 250000,
        Baud256000 = 256000
    };


    public class HelperSerialPort
    {
        public SerialPort Port { get; private set; } = null;

        private string _dataReceived = string.Empty;

        public HelperSerialPort(string portName, PortBaudRate baudRate = PortBaudRate.Baud9600)
        {
            Initialize(portName, baudRate);
        }

        private void Initialize(string portName, PortBaudRate baudRate = PortBaudRate.Baud9600)
        {
            //create new instance
            this.Port = new SerialPort();

            //set properties
            Port.BaudRate = (int)baudRate;
            Port.DataBits = 8;
            Port.Parity = Parity.None; //use 'None' when DataBits = 8; if DataBits = 7, use 'Even' or 'Odd'
            Port.DtrEnable = true; //enable Data Terminal Ready
            Port.Handshake = Handshake.None;
            Port.PortName = portName;
            Port.ReadTimeout = 200; //used when using ReadLine
            Port.RtsEnable = true; //enable Request to send
            Port.StopBits = StopBits.One;
            Port.WriteTimeout = 50;

            //open
            Port.Open();

            SPDataHelper();
        }


        private void SPDataHelper()
        {
            byte[] buffer = new byte[65536];
            Action kickoffRead = null;

            kickoffRead = delegate
            {
                IAsyncResult result = Port.BaseStream.BeginRead(buffer, 0, buffer.Length, delegate (IAsyncResult ar)
                {
                    try
                    {
                        if (Port.IsOpen)
                        {
                            int bytesRead = Port.BaseStream.EndRead(ar);

                            byte[] received = new byte[bytesRead];
                            Buffer.BlockCopy(buffer, 0, received, 0, bytesRead);

                            _dataReceived = System.Text.Encoding.UTF8.GetString(received);
                            Debug.WriteLine("Info: " + DateTime.Now.ToString("HH:mm:ss:fff") + " - _dataReceived: " + _dataReceived);

                            kickoffRead();
                        }

                    }
                    catch (IOException ex)
                    {
                        Debug.WriteLine("Error (SPDataHelper) - " + ex.Message);
                    }
                }, null);
            };

            kickoffRead();
        }

        public void Dispose()
        {
            if (Port != null)
            {
                Port.Close();
                Port.Dispose();

                Port = null;
            }
        }
    }
}

Option 3

Here's an option that uses SerialPort DataReceived.

HelperSerialPort.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Ports;
using System.Diagnostics;

namespace SerialPortTest
{
    public enum PortBaudRate : int
    {
        Baud1200 = 1200,
        Baud2400 = 2400,
        Baud4800 = 4800,
        Baud9600 = 9600,
        Baud14400 = 14400,
        Baud19200 = 19200,
        Baud28800 = 28800,
        Baud38400 = 38400,
        Baud56000 = 56000,
        Baud57600 = 57600,
        Baud76800 = 76800,
        Baud115200 = 115200,
        Baud128000 = 128000,
        Baud230400 = 230400,
        Baud250000 = 250000,
        Baud256000 = 256000
    };

    public class HelperSerialPort : IDisposable 
    {
        public SerialPort Port { get; private set; } = null;

        private string _dataReceived = string.Empty;

        public HelperSerialPort(string portName, PortBaudRate baudRate = PortBaudRate.Baud9600)
        {
            Initialize(portName, baudRate);  
        }

        private void Initialize(string portName, PortBaudRate baudRate = PortBaudRate.Baud9600)
        {
            //create new instance
            this.Port = new SerialPort();

            //set properties
            Port.BaudRate = (int)baudRate;
            Port.DataBits = 8;
            Port.Parity = Parity.None; //use 'None' when DataBits = 8; if DataBits = 7, use 'Even' or 'Odd'
            Port.DtrEnable = true; //enable Data Terminal Ready
            Port.Handshake = Handshake.None;
            Port.PortName = portName;
            Port.ReadTimeout = 200; //used when using ReadLine
            Port.RtsEnable = true; //enable Request to send
            Port.StopBits = StopBits.One;
            Port.WriteTimeout = 50;

            //subscribe to events
            Port.DataReceived += Port_DataReceived;
            Port.ErrorReceived += Port_ErrorReceived;

            //open
            Port.Open();

        }

        private void Port_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            Debug.WriteLine("Error: (sp_ErrorReceived) - " + e.EventType);
        }

        private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            _dataReceived = Port.ReadExisting();
            Debug.WriteLine("Info: " + DateTime.Now.ToString("HH:mm:ss:fff") + " - _dataReceived: " + _dataReceived);
        }

        public void Dispose()
        {
            if (Port != null)
            {
                //unsubscribe from events
                Port.DataReceived -= Port_DataReceived;
                Port.ErrorReceived -= Port_ErrorReceived;

                Port.Close();
                Port.Dispose();

                Port = null;
            }
        }
    }
}

Usage:

HelperSerialPort helper = new HelperSerialPort("COM1");

Note: Ensure Dispose is called when you're finished with the SerialPort.