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?
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.