C#串口連接的讀取和發(fā)送詳解
一、串口連接的打開與關閉
串口,即COM口,在.NET中使用 SerialPort 類進行操作。串口開啟與關閉,是涉及慢速硬件的IO操作,頻繁打開或關閉會影響整體處理速度,甚至導致打開或關閉串口失敗。非特殊情況,串口一次性打開后,在退出程序時關閉串口即可。在打開串口前,可以設置一些常用的參數。常用的參數如下:
(1)串口的接受/發(fā)送超時時間:ReadTimeout/WriteTimeout。
(2) 串口的接受/發(fā)送緩存區(qū)大?。篟eadBufferSize/WriteBufferSize。
具體代碼如下:
// Open Com _serialPort = new SerialPort(com, baud); if (_serialPort.IsOpen) _serialPort.Close(); // Set the read / write timeouts _serialPort.ReadTimeout = 500; _serialPort.WriteTimeout = 500; // Set read / write buffer Size,the default of value is 1MB _serialPort.ReadBufferSize = 1024 * 1024; _serialPort.WriteBufferSize = 1024 * 1024; _serialPort.Open(); // Discard Buffer _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer();
需要注意的是超出緩沖區(qū)的部分會被直接丟棄。因此,如果需要使用串口傳送大文件,那接收方和發(fā)送方都需要將各自的緩沖區(qū)域設置的足夠大,以便能夠一次性存儲下大文件的二進制數組。若條件限制,緩沖區(qū)域不能設置過大,那就需要在發(fā)送大文件的時候按照發(fā)送緩沖區(qū)大小分包去發(fā)送,接收方按順序把該數組組合起來形成接受文件的二進制數組。
二、串口發(fā)送
SerialPort 類發(fā)送支持二進制發(fā)送與文本發(fā)送,需要注意的是文本發(fā)送時,需要知道轉換的規(guī)則,一般常用的是ASCII、UTF7、UTF-8、UNICODE、UTF32。具體代碼如下:
#region Send /// <summary> /// 發(fā)送消息(byte數組) /// </summary> /// <param name="buffer"></param> /// <param name="offset"></param> /// <param name="count"></param> public void Send(byte[] buffer, int offset, int count) { lock (_mux) { _serialPort.Write(buffer, offset, count); _sendCount += (count - offset); } } /// <summary> /// 發(fā)送消息(字符串) /// </summary> /// <param name="encoding">字符串編碼方式,具體方式見<see cref="Encoding"/></param> /// <param name="message"></param> public void Send(Encoding encoding , string message) { lock (_mux) { var buffer = encoding.GetBytes(message); _serialPort.Write(buffer, 0, buffer.Length); _sendCount += buffer.Length; } } #endregion
三、串口接受
串口接受需要注意,消息接受與消息處理要代碼分離。不能把流程處理的代碼放入信息接受處,因為消息處理或多或少會有耗時,這會造成當發(fā)送方發(fā)送過快時,接受方的接受緩沖區(qū)會緩存多條消息。我們可以把接受到的消息放入隊列中,然后在外部線程中,嘗試去拿出該條消息進行消費。采用 “生產-消費”模式。具體代碼如下:
#region Receive private void PushMessage() { _serialPort.DataReceived += (sender, e) => { lock (_mux) { if (_serialPort.IsOpen == false) return; int length = _serialPort.BytesToRead; byte[] buffer = new byte[length]; _serialPort.Read(buffer, 0, length); _receiveCount += length; _messageQueue.Enqueue(buffer); _messageWaitHandle.Set(); } }; } /// <summary> /// 獲取串口接受到的內容 /// </summary> /// <param name="millisecondsToTimeout">取消息的超時時間</param> /// <returns>返回byte數組</returns> public byte[] TryMessage(int millisecondsToTimeout = -1) { if (_messageQueue.TryDequeue(out var message)) { return message; } if (_messageWaitHandle.WaitOne(millisecondsToTimeout)) { if (_messageQueue.TryDequeue(out message)) { return message; } } return default; } #endregion
四、完整代碼與測試結果
串口工具類的完整代碼如下:
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; namespace SerialportDemo { public class SSerialPort { private SerialPort _serialPort; private readonly ConcurrentQueue<byte[]> _messageQueue; private readonly EventWaitHandle _messageWaitHandle; private int _receiveCount, _sendCount; private readonly object _mux; public int ReceiveCount { get => _receiveCount; } public int SendCount { get => _sendCount; } public SSerialPort(string com, int baud ) { // initialized _mux=new object(); _receiveCount = 0; _sendCount = 0; _messageQueue = new ConcurrentQueue<byte[]>(); _messageWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); // Open Com OpenCom(com.ToUpper(),baud); // Receive byte PushMessage(); } private void OpenCom(string com, int baud) { // Open Com _serialPort = new SerialPort(com, baud); if (_serialPort.IsOpen) _serialPort.Close(); // Set the read / write timeouts _serialPort.ReadTimeout = 500; _serialPort.WriteTimeout = 500; // Set read / write buffer Size,the default of value is 1MB _serialPort.ReadBufferSize = 1024 * 1024; _serialPort.WriteBufferSize = 1024 * 1024; _serialPort.Open(); // Discard Buffer _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer(); } #region Static /// <summary> /// 獲取當前計算機的串行端口名的數組 /// </summary> /// <returns></returns> public static string[] GetPortNames() { return SerialPort.GetPortNames(); } #endregion #region Receive private void PushMessage() { _serialPort.DataReceived += (sender, e) => { lock (_mux) { if (_serialPort.IsOpen == false) return; int length = _serialPort.BytesToRead; byte[] buffer = new byte[length]; _serialPort.Read(buffer, 0, length); _receiveCount += length; _messageQueue.Enqueue(buffer); _messageWaitHandle.Set(); } }; } /// <summary> /// 獲取串口接受到的內容 /// </summary> /// <param name="millisecondsToTimeout">取消息的超時時間</param> /// <returns>返回byte數組</returns> public byte[] TryMessage(int millisecondsToTimeout = -1) { if (_messageQueue.TryDequeue(out var message)) { return message; } if (_messageWaitHandle.WaitOne(millisecondsToTimeout)) { if (_messageQueue.TryDequeue(out message)) { return message; } } return default; } #endregion #region Send /// <summary> /// 發(fā)送消息(byte數組) /// </summary> /// <param name="buffer"></param> /// <param name="offset"></param> /// <param name="count"></param> public void Send(byte[] buffer, int offset, int count) { lock (_mux) { _serialPort.Write(buffer, offset, count); _sendCount += (count - offset); } } /// <summary> /// 發(fā)送消息(字符串) /// </summary> /// <param name="encoding">字符串編碼方式,具體方式見<see cref="Encoding"/></param> /// <param name="message"></param> public void Send(Encoding encoding , string message) { lock (_mux) { var buffer = encoding.GetBytes(message); _serialPort.Write(buffer, 0, buffer.Length); _sendCount += buffer.Length; } } #endregion /// <summary> /// 清空接受/發(fā)送總數統計 /// </summary> public void ClearCount() { lock (_mux) { _sendCount = 0; _receiveCount = 0; } } /// <summary> /// 關閉串口 /// </summary> public void Close() { _serialPort.Close(); } } }
測試代碼如下:
class Program { static void Main(string[] args) { Console.WriteLine($"該計算機可使用的串口列表:{string.Join(",", SSerialPort.GetPortNames())}"); Console.Write("請輸入需要打開的串口:"); string port = Console.ReadLine(); SSerialPort com = new SSerialPort(port, 57600); Console.WriteLine($"串口 {port} 打開成功..."); Console.Write("請輸入需要打開的串口發(fā)送的消息:"); string text = Console.ReadLine(); while (true) { com.Send(Encoding.Default, text); Console.WriteLine($"總共發(fā)送 {com.SendCount}"); var message = com.TryMessage(); if (message != null) { Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss fff")} {Encoding.Default.GetString(message)}"); //// TEST:從添加延時可以測試到,接受消息和處理消息必須分不同線程處理。因為對于消息的處理或多或少都需要耗時,這樣容易造成消息處理不及時。而添加到隊列后,我們可以隨時取出處理 //System.Threading.Thread.Sleep(100*1); } Console.WriteLine($"總共接受 {com.ReceiveCount}"); } Console.ReadKey(); } }
使用串口工具測試如下,對于串口的接受如絲般順滑。當我們在消息中增加測試延時后,就會發(fā)現當串口工具繼續(xù)快速發(fā)送一段時間后關閉發(fā)送,發(fā)現使用隊列后,依然沒有丟失一條來自發(fā)送方的消息。
總結
到此這篇關于C#串口連接的讀取和發(fā)送的文章就介紹到這了,更多相關C#串口連接讀取和發(fā)送內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!