C#串口連接的讀取和發(fā)送詳解
一、串口連接的打開與關(guān)閉
串口,即COM口,在.NET中使用 SerialPort 類進(jìn)行操作。串口開啟與關(guān)閉,是涉及慢速硬件的IO操作,頻繁打開或關(guān)閉會(huì)影響整體處理速度,甚至導(dǎo)致打開或關(guān)閉串口失敗。非特殊情況,串口一次性打開后,在退出程序時(shí)關(guān)閉串口即可。在打開串口前,可以設(shè)置一些常用的參數(shù)。常用的參數(shù)如下:
(1)串口的接受/發(fā)送超時(shí)時(shí)間:ReadTimeout/WriteTimeout。
(2) 串口的接受/發(fā)送緩存區(qū)大小:ReadBufferSize/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ū)的部分會(huì)被直接丟棄。因此,如果需要使用串口傳送大文件,那接收方和發(fā)送方都需要將各自的緩沖區(qū)域設(shè)置的足夠大,以便能夠一次性存儲(chǔ)下大文件的二進(jìn)制數(shù)組。若條件限制,緩沖區(qū)域不能設(shè)置過(guò)大,那就需要在發(fā)送大文件的時(shí)候按照發(fā)送緩沖區(qū)大小分包去發(fā)送,接收方按順序把該數(shù)組組合起來(lái)形成接受文件的二進(jìn)制數(shù)組。
二、串口發(fā)送
SerialPort 類發(fā)送支持二進(jìn)制發(fā)送與文本發(fā)送,需要注意的是文本發(fā)送時(shí),需要知道轉(zhuǎn)換的規(guī)則,一般常用的是ASCII、UTF7、UTF-8、UNICODE、UTF32。具體代碼如下:
#region Send
/// <summary>
/// 發(fā)送消息(byte數(shù)組)
/// </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
三、串口接受
串口接受需要注意,消息接受與消息處理要代碼分離。不能把流程處理的代碼放入信息接受處,因?yàn)橄⑻幚砘蚨嗷蛏贂?huì)有耗時(shí),這會(huì)造成當(dāng)發(fā)送方發(fā)送過(guò)快時(shí),接受方的接受緩沖區(qū)會(huì)緩存多條消息。我們可以把接受到的消息放入隊(duì)列中,然后在外部線程中,嘗試去拿出該條消息進(jìn)行消費(fèi)。采用 “生產(chǎn)-消費(fèi)”模式。具體代碼如下:
#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>
/// 獲取串口接受到的內(nèi)容
/// </summary>
/// <param name="millisecondsToTimeout">取消息的超時(shí)時(shí)間</param>
/// <returns>返回byte數(shù)組</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
四、完整代碼與測(cè)試結(jié)果
串口工具類的完整代碼如下:
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>
/// 獲取當(dāng)前計(jì)算機(jī)的串行端口名的數(shù)組
/// </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>
/// 獲取串口接受到的內(nèi)容
/// </summary>
/// <param name="millisecondsToTimeout">取消息的超時(shí)時(shí)間</param>
/// <returns>返回byte數(shù)組</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數(shù)組)
/// </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ā)送總數(shù)統(tǒng)計(jì)
/// </summary>
public void ClearCount()
{
lock (_mux)
{
_sendCount = 0;
_receiveCount = 0;
}
}
/// <summary>
/// 關(guān)閉串口
/// </summary>
public void Close()
{
_serialPort.Close();
}
}
}
測(cè)試代碼如下:
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"該計(jì)算機(jī)可使用的串口列表:{string.Join(",", SSerialPort.GetPortNames())}");
Console.Write("請(qǐng)輸入需要打開的串口:");
string port = Console.ReadLine();
SSerialPort com = new SSerialPort(port, 57600);
Console.WriteLine($"串口 {port} 打開成功...");
Console.Write("請(qǐng)輸入需要打開的串口發(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:從添加延時(shí)可以測(cè)試到,接受消息和處理消息必須分不同線程處理。因?yàn)閷?duì)于消息的處理或多或少都需要耗時(shí),這樣容易造成消息處理不及時(shí)。而添加到隊(duì)列后,我們可以隨時(shí)取出處理
//System.Threading.Thread.Sleep(100*1);
}
Console.WriteLine($"總共接受 {com.ReceiveCount}");
}
Console.ReadKey();
}
}
使用串口工具測(cè)試如下,對(duì)于串口的接受如絲般順滑。當(dāng)我們?cè)谙⒅性黾訙y(cè)試延時(shí)后,就會(huì)發(fā)現(xiàn)當(dāng)串口工具繼續(xù)快速發(fā)送一段時(shí)間后關(guān)閉發(fā)送,發(fā)現(xiàn)使用隊(duì)列后,依然沒(méi)有丟失一條來(lái)自發(fā)送方的消息。
總結(jié)
到此這篇關(guān)于C#串口連接的讀取和發(fā)送的文章就介紹到這了,更多相關(guān)C#串口連接讀取和發(fā)送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于C#實(shí)現(xiàn)端口掃描器(單線程和多線程)
本文主要介紹了基于C#分別通過(guò)單線程和多線程實(shí)現(xiàn)端口掃描,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
C#中FormsAuthentication用法實(shí)例
這篇文章主要介紹了C#中FormsAuthentication用法實(shí)例,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-02-02
C#實(shí)現(xiàn)任意數(shù)據(jù)類型轉(zhuǎn)成json格式輸出
C#實(shí)現(xiàn)任意數(shù)據(jù)類型轉(zhuǎn)成json格式輸出。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-10-10
asp.net core 使用 tensorflowjs實(shí)現(xiàn) face recognition的源代碼
tensorflowjs,在該項(xiàng)目中使用了ml5js這個(gè)封裝過(guò)的機(jī)器學(xué)習(xí)JavaScript類庫(kù), 使用起來(lái)更簡(jiǎn)單,本文給大家分享asp.net core 使用 tensorflowjs實(shí)現(xiàn) face recognition的源代碼,需要的朋友參考下吧2021-06-06
使用C#代碼計(jì)算數(shù)學(xué)表達(dá)式實(shí)例
這段文字主要講述了如何使用C#語(yǔ)言來(lái)計(jì)算數(shù)學(xué)表達(dá)式,該程序通過(guò)使用Dictionary保存變量,定義了運(yùn)算符優(yōu)先級(jí),并實(shí)現(xiàn)了EvaluateExpression方法來(lái)執(zhí)行表達(dá)式計(jì)算,該方法通過(guò)查找優(yōu)先級(jí)最低的運(yùn)算符來(lái)拆分表達(dá)式,并遞歸調(diào)用自身來(lái)評(píng)估子表達(dá)式2025-01-01
C#使用NAudio錄音并導(dǎo)出錄音數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了C#如何使用NAudio實(shí)現(xiàn)錄音功能并導(dǎo)出錄音數(shù)據(jù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12
C#中foreach原理以及模擬的實(shí)現(xiàn)
這篇文章主要介紹了C#中foreach原理以及模擬的實(shí)現(xiàn)方法,備有詳盡的注釋,便于深入理解C#原理,需要的朋友可以參考下2014-10-10

