[英]How to deal with C# SerialPort read and write data perfectly?
代碼在 .Net4 上運行。
一個設備,一個操作:'Op_A',一個隊列:'QuData',三個命令:'Cmd_Req','Cmd_Res'和'Cmd_Report'。
這是我使用的代碼的簡化。
//The method DataEqual is to determine that the data in the two BYTE[] are the same.
bool isSend = false;
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if(isSend)
return;
byte[] Cmd_Report = new byte[serialPort.BytesToRead];
serialPort.Read(Cmd_Report, 0, serialPort.BytesToRead);
if (DataEqual(Cmd_Report, CMD_REPORT))
{
//var data = QuData.Dequeue();
//do something X about data then save data
}
}
public void DoWork()
{
//do something
byte[] Cmd_Req = new byte[10];
byte[] Cmd_Res = new byte[CMD_RES.Length];
isSend = true;
serialPort.Write(Cmd_Req, 0, Cmd_Req.Length );
stopWatch.Restart();
while(serialPort.BytesToRead < CMD_RES.Length && stopWatch.ElapsedMilliseconds< 1000)
System.Threading.Thread.Sleep(50);
int resCount = 0;
if (serialPort.BytesToRead < CMD_RES.Length)
resCount = serialPort.Read(Cmd_Res, 0, serialPort.BytesToRead);
else
resCount = serialPort.Read(Cmd_Res, 0, CMD_RES.Length);
isSend = false;
if (DataEqual(Cmd_Res, CMD_RES))
{
//create data, do something A about data
//QuData.Enqueue(data)
}
else
{
//do something B
}
}
我的問題:
DoWork
和“Op_A”並不總是按順序執行。 有時我需要多次調用DoWork
並等待'Op_A',然后在用戶執行'Op_A'時繼續調用DoWork
。 所以我必須知道Cmd_Req
是否成功,並確定設備是否報告Cmd_Report
。 但是在頻繁讀寫的時候,有時在SerialPort_DataReceived
方法中會讀取Cmd_Res
,而在DoWork
方法中會讀取Cmd_Report
。 這會導致我認為Cmd_Req
沒有成功執行或者設備沒有報告Cmd_Result
。 我該如何解決?編輯
“OP_A”是按下設備上的按鈕的動作。
根據您要按順序訪問串行端口的情況(一次一個)。 我從我的舊項目中提取了這段代碼,它應該對您有所幫助。
using System;
using System.IO.Ports;
using System.Linq;
using System.Threading;
public class SPHandler
{
/// <summary>
/// Your serial port
/// </summary>
private SerialPort _serialPort;
private int _timeOut, _timeOutDefault;
private AutoResetEvent _receiveNow;
/// <summary>
/// Possible device end responses such as \r\nOK\r\n, \r\nERROR\r\n, etc.
/// </summary>
private string[] _endResponses;
public SPHandler()
{
}
public void SetPort(string portName, int baudRate, int timeOut, string[] endResponses = null)
{
_timeOut = timeOut;
_timeOutDefault = timeOut;
_serialPort = new SerialPort(portName, baudRate);
_serialPort.Parity = Parity.None;
_serialPort.Handshake = Handshake.None;
_serialPort.DataBits = 8;
_serialPort.StopBits = StopBits.One;
_serialPort.RtsEnable = true;
_serialPort.DtrEnable = true;
_serialPort.WriteTimeout = _timeOut;
_serialPort.ReadTimeout = _timeOut;
if (endResponses == null)
_endResponses = new string[0];
else
_endResponses = endResponses;
}
public bool Open()
{
try
{
if (_serialPort != null && !_serialPort.IsOpen)
{
_receiveNow = new System.Threading.AutoResetEvent(false);
_serialPort.Open();
_serialPort.DataReceived += new SerialDataReceivedEventHandler(_serialPort_DataReceived);
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
if (e.EventType == SerialData.Chars)
{
_receiveNow.Set();
}
}
catch (Exception ex)
{
throw ex;
}
}
public bool Close()
{
try
{
if (_serialPort != null && _serialPort.IsOpen)
{
_serialPort.Close();
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
public string ExecuteCommand(string cmd)
{
_serialPort.DiscardOutBuffer();
_serialPort.DiscardInBuffer();
_receiveNow.Reset();
_serialPort.Write(cmd); // Sometimes + "\r" is needed. Depends on the device
string input = ReadResponse(); // Returns device response whenever you execute a command
_timeOut = _timeOutDefault;
return input;
}
private string ReadResponse()
{
string buffer = string.Empty;
try
{
do
{
if (_receiveNow.WaitOne(_timeOut, false))
{
string t = _serialPort.ReadExisting();
buffer += t;
}
} while (!_endResponses.Any(r => buffer.EndsWith(r, StringComparison.OrdinalIgnoreCase))); // Read while end responses are not yet received
}
catch
{
buffer = string.Empty;
}
return buffer;
}
}
用法:
SPHandler spHandler = new SPHandler();
spHandler.SetPort(params);
spHandler.Open();
string response = spHandler.ExecuteCommand("Cmd_Req");
if (response == "Cmd_Res")
{
// Inform user that operation OP_A is allowed
// Enqueue data
}
else
{
// Oh no! Cmd_Res failed?!
}
// ... etc.
spHandler.Close(); // If you need to
基本上,您在執行命令后立即等待響應。 這個SPHandler
問題在於它需要一個字符串命令和響應。 您可以將其轉換為讀取/發送字節。
您應該注意用於順序訪問的AutoResetEvent
。 即使您使用多線程,也會相應地進行處理。 對_receiveNow.WaitOne
的調用為您_receiveNow.WaitOne
了魔力。 也許這就是您需要在代碼中應用的全部內容,因為您當前的問題是同時讀取Cmd_Res
和Cmd_Req
。
編輯:
回頭看看你的代碼。 您可以簡單地取消注冊SerialPort_DataReceived
因為您只需要DoWork
來順序執行命令和處理響應。
受JohnEphraimTugado的代碼啟發,想到了解決方案,在應用場景中通過了測試。
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
public class SerialPortHandler
{
public delegate void OnReportHandler(byte[] data);
public delegate void OnReadExceptionHander(Exception error);
public delegate void OnHandlingExceptionHandler(Exception error);
public SerialPortHandler(string portName, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
: this(reportPredicate, dequeueFunc)
{
this._serialPort = new SerialPort(portName);
}
public SerialPortHandler(string portName, int baudRate, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
: this(reportPredicate, dequeueFunc)
{
this._serialPort = new SerialPort(portName, baudRate);
}
public SerialPortHandler(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
: this(reportPredicate, dequeueFunc)
{
this._serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
}
private SerialPortHandler(Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
{
_thrdRead = new Thread(new ThreadStart(Read));
_thrdHandle = new Thread(new ThreadStart(DataHandling));
_isRun = false;
_quCmdRespone = new Queue<byte[]>();
_quReceiveBuff = new Queue<byte>();
_cmdResponseReset = new AutoResetEvent(false);
_reportPredicate = reportPredicate;
_dequeueFunc = dequeueFunc;
}
SerialPort _serialPort;
Thread _thrdRead;
Thread _thrdHandle;
bool _isRun;
/// <summary>
/// Save all data read from the serial port
/// </summary>
Queue<byte> _quReceiveBuff;
/// <summary>
/// Save the response of the last command
/// </summary>
Queue<byte[]> _quCmdRespone;
AutoResetEvent _cmdResponseReset;
bool _isSending;
/// <summary>
/// A method to determine whether a byte[] is a spontaneous report of a serial port
/// </summary>
Predicate<byte[]> _reportPredicate;
/// <summary>
/// Dequeuing a command from the received data queue method
/// </summary>
Func<Queue<byte>, byte[]> _dequeueFunc;
/// <summary>
/// Called when the serial interface is actively reporting data.
/// </summary>
public event OnReportHandler OnReport;
public event OnReadExceptionHander OnReadException;
public event OnHandlingExceptionHandler OnHandlingException;
public bool IsOpen
{
get { return this._serialPort == null ? false : this._serialPort.IsOpen; }
}
/// <summary>
/// Read data from serial port.
/// </summary>
private void Read()
{
while (_isRun)
{
try
{
if (this._serialPort == null || !this._serialPort.IsOpen || this._serialPort.BytesToRead == 0)
{
SpinWait.SpinUntil(() => this._serialPort != null && this._serialPort.IsOpen && this._serialPort.BytesToRead > 0, 10);
continue;
}
byte[] data = new byte[this._serialPort.BytesToRead];
this._serialPort.Read(data, 0, data.Length);
Array.ForEach(data, b => _quReceiveBuff.Enqueue(b));
}
catch (InvalidOperationException)
{
if (!_isRun || this._serialPort ==null)
return;
else
this._serialPort.Open();
}
catch (Exception ex)
{
this.OnReadException?.BeginInvoke(new Exception(string.Format("An error occurred in the reading processing: {0}", ex.Message), ex), null, null);
}
}
}
/// <summary>
/// Data processing
/// </summary>
private void DataHandling()
{
while (_isRun)
{
try
{
if (_quReceiveBuff.Count == 0)
{
SpinWait.SpinUntil(() => _quReceiveBuff.Count > 0, 10);
continue;
}
byte[] data = _dequeueFunc(_quReceiveBuff);
if (data == null || data.Length == 0)
{
SpinWait.SpinUntil(() => false, 10);
continue;
}
if (_reportPredicate(data))
OnReport?.BeginInvoke(data, null, null); //If the data is spontaneously reported by the serial port, the OnReport event is called
else
{ //If the command response returned by the serial port, join the command response queue
if (_quCmdRespone.Count > 0)
_quCmdRespone.Clear(); //The queue is cleared to ensure that if a command timed out does not affect subsequent command results
_quCmdRespone.Enqueue(data);
_cmdResponseReset.Set();
}
}
catch (Exception ex)
{
this.OnHandlingException?.BeginInvoke(new Exception(string.Format("An error occurred in the data processing: {0}", ex.Message), ex), null, null);
}
}
}
/// <summary>
/// Read the response of the last command.
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
private byte[] ReadCommandResponse(int timeOut)
{
byte[] buffer = null;
if (_cmdResponseReset.WaitOne(timeOut, false))
buffer = _quCmdRespone.Dequeue();
return buffer;
}
/// <summary>
/// Send a command
/// </summary>
/// <param name="sendData">command buff</param>
/// <param name="receiveData">REF: response of command</param>
/// <param name="timeout">timeout(millseconds)</param>
/// <returns>count of response, -1: failure, -2: port is busy</returns>
public int SendCommand(byte[] sendData, ref byte[] receiveData, int timeout)
{
if (_isSending)
return -2;
if (this._serialPort.IsOpen)
{
try
{
_isSending = true;
_cmdResponseReset.Reset(); //update 11-13
this._serialPort.Write(sendData, 0, sendData.Length);
int ret = 0;
receiveData = ReadCommandResponse(timeout);
ret = receiveData == null ? -1 : receiveData.Length;
return ret;
}
catch (Exception ex)
{
throw new Exception(string.Format("Send command is failure:{0}", ex.Message), ex);
}
finally
{
_isSending = false;
}
}
return -1;
}
public bool Open()
{
if (this._serialPort == null || this._serialPort.IsOpen)
return false;
this._serialPort.Open();
_isRun = true;
_thrdRead.Start();
_thrdHandle.Start();
return true;
}
public bool Close()
{
_isRun = false;
if (_thrdHandle.IsAlive)
_thrdHandle.Join();
if (_thrdRead.IsAlive)
_thrdRead.Join();
if (this._serialPort == null)
return false;
if (this._serialPort.IsOpen)
this._serialPort.Close();
return true;
}
}
用法
SerialPortHandler spHandler;
public void Init()
{
SerialPortHandler spHandler = new SerialPortHandler("COM1", IsReport, DequeueResponse);
spHandler.OnReport += SpHandler_OnReport;
}
bool IsReport(byte[] data)
{
//Determines whether the command is Cmd_Reprot
return true;
}
byte[] DequeueResponse(Queue<byte> quReceive)
{
byte[] response = null;
//Dequeuing a valid response based protocol rules
return response;
}
private void SpHandler_OnReport(byte[] data)
{
if (DataEqual(Cmd_Report, CMD_REPORT))
{
//do something X about data then save data
}
}
public void DoWork()
{
//do something
byte[] Cmd_Req = null;
byte[] Cmd_Res = new byte[CMD_RES.Length];
int ret = spHandler.SendCommand(Cmd_Req, Cmd_Req, 1000);
if (ret > 0 && DataEqual(Cmd_Res, CMD_RES))
{
//create data, do something A about data
}
else
{
//do something B
}
}
nuget 包
安裝包 SerialHandler -版本 0.0.1-beta3
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.