乘風破浪,遇見最佳跨平臺跨終端框架.Net Core/.Net生態 - 串口通訊設計,使用System.IO.Ports包實現串口通訊和監聽

什麼是串口通信

串口通信是串口按位(bit)發送和接收字節的通信方式。

image

串口通信(Serial Communications)是指外設和計算機間,通過數據信號線 、地線、控制線等,按位進行傳輸數據的一種通訊方式。這種通信方式使用的數據線少,在遠距離通信中可以節約通信成本,但其傳輸速度比並行傳輸低。

串口是計算機上一種非常通用的設備通信協議。大多數計算機(不包括筆記本電腦)包含兩個基於RS-232的串口。

串口同時也是儀器儀表設備通用的通信協議;很多GPIB兼容的設備也帶有RS-232口。 同時,串口通信協議也可以用於獲取遠程採集設備的數據。

RS-232(ANSI/EIA-232標準)是IBM-PC及其兼容機上的串行連接標準。

可用於許多用途,比如連接鼠標、打印機 或者Modem,同時也可以接工業儀器儀表。

Nuget包

https://www.nuget.org/packages/System.IO.Ports

名稱 備註
System.IO.Ports >= .NET 6.0;
>= .NET Standard 2.0;
>= .NET Framework 4.6.2;

從兼容範圍來看,類庫從.NET Standard 2.0開始就能支持,.Net Core項目的話從.NET 6.0開始才能支持,剩下的就是.NET Framework 4.6.24.6.2就能支持。

所以,netcoreapp3.1是不能支持的。

System.IO.Ports 7.0.0 doesn't support netcoreapp3.1 and has not been tested with it. Consider upgrading your TargetFramework to net6.0 or later

image

.NET 6.0中需要安裝System.IO.Ports包。

dotnet add package System.IO.Ports

image

.NET Framework項目中默認就支持了。

image

基本使用

https://github.com/TaylorShi/HelloSerialPort

創建串口對象

示例代碼

private static SerialPort _serialPort = null;
static void Main(string[] args)
{
    _serialPort = new SerialPort();
}

獲取默認可用端口

示例代碼

var defaultPortName = _serialPort.PortName;
Console.WriteLine($"默認端口:{defaultPortName}");

輸出結果

默認端口:COM1

獲取所有可用端口

示例代碼

foreach (var portName in SerialPort.GetPortNames())
{
    Console.WriteLine($"可用端口:{portName}");
}

輸出結果

可用端口:COM1

獲取串行波特率

示例代碼

var defaultBaudRate = _serialPort.BaudRate;
Console.WriteLine($"默認串行波特率:{defaultBaudRate}");

輸出結果

默認串行波特率:9600

獲取和設置奇偶校驗檢查協議和協議枚舉

奇偶校驗位(Parity)枚舉清單

模式 數值 描述
None 0 不發生奇偶校驗檢查
Odd 1 設置奇偶校驗位,使位數等於奇數
Even 2 設置奇偶校驗位,使位數等於偶數
Mark 3 將奇偶校驗位保留爲1
Space 4 將奇偶校驗位保留爲0

示例代碼

var defaultParity = _serialPort.Parity;
Console.WriteLine($"默認奇偶校驗檢查協議:{defaultParity}");

foreach (var parity in Enum.GetNames(typeof(Parity)))
{
    Console.WriteLine($"可選奇偶校驗檢查協議:{parity}");
}

輸出結果

默認奇偶校驗檢查協議:None
可選奇偶校驗檢查協議:None
可選奇偶校驗檢查協議:Odd
可選奇偶校驗檢查協議:Even
可選奇偶校驗檢查協議:Mark
可選奇偶校驗檢查協議:Space

獲取和設置每個字節的標準數據位長度

示例代碼

var defaultDataBits = _serialPort.DataBits;
Console.WriteLine($"默認每個字節的標準數據位長度:{defaultDataBits}");

輸出結果

默認每個字節的標準數據位長度:8

獲取和設置每個字節的標準停止位數

停止位的數目(StopBits)枚舉清單

模式 數值 描述
None 0 不使用停止位
One 1 使用一個停止位
Two 2 使用兩個停止位
OnePointFive 3 使用1.5個停止位

示例代碼

var defaultStopBits = _serialPort.StopBits;
Console.WriteLine($"默認每個字節的標準停止位數:{defaultStopBits}");

foreach (var stopBit in Enum.GetNames(typeof(StopBits)))
{
    Console.WriteLine($"可選每個字節的標準停止位數:{stopBit}");
}

輸出結果

默認每個字節的標準停止位數:One
可選每個字節的標準停止位數:None
可選每個字節的標準停止位數:One
可選每個字節的標準停止位數:Two
可選每個字節的標準停止位數:OnePointFive

獲取和設置串行端口數據傳輸的握手協議

串行端口數據傳輸的握手協議(Handshake)枚舉清單

模式 數值 描述
None 0 沒有用於握手的控件
XOnXOff 1 使用XON/XOFF軟件控制協議。發送XOFF控制以停止數據傳輸。發送XON控制以繼續傳輸。使用這些軟件控制,而不是使用請求發送(RTS)和清除發送(CTS)硬件控制
RequestToSend 2 使用請求發送(RTS)硬件流控制。RTS發出信號,指出數據可用於傳輸。如果輸入緩衝區已滿,RTS行將被設置爲false。當輸入緩衝區中有更多可用空間時,RTS行將被設置爲true
RequestToSendXOnXOff 3 同時使用請求發送(RTS)硬件控制和XON/XOFF軟件控制

示例代碼

var defaultHandshake = _serialPort.Handshake;
Console.WriteLine($"默認串行端口數據傳輸的握手協議:{defaultHandshake}");

foreach (var handshake in Enum.GetNames(typeof(Handshake)))
{
    Console.WriteLine($"可選串行端口數據傳輸的握手協議:{handshake}");
}

輸出結果

默認串行端口數據傳輸的握手協議:None
可選串行端口數據傳輸的握手協議:None
可選串行端口數據傳輸的握手協議:XOnXOff
可選串行端口數據傳輸的握手協議:RequestToSend
可選串行端口數據傳輸的握手協議:RequestToSendXOnXOff

獲取和設置讀操作的超時時間

示例代碼

var defaultReadTimeout = _serialPort.ReadTimeout;
Console.WriteLine($"默認讀取操作未完成時發生超時之前的毫秒數:{defaultReadTimeout}");

// 設置讀操作的超時時間(毫秒數)
_serialPort.ReadTimeout = 500;

輸出結果

默認讀取操作未完成時發生超時之前的毫秒數:-1

獲取和設置寫操作的超時時間

示例代碼

var defaultWriteTimeout = _serialPort.WriteTimeout;
Console.WriteLine($"默認寫入操作未完成時發生超時之前的毫秒數:{defaultWriteTimeout}");

// 設置寫操作的超時時間(毫秒數)
_serialPort.WriteTimeout = 500;

輸出結果

默認寫入操作未完成時發生超時之前的毫秒數:-1

打開串口

示例代碼

_serialPort.Open();
Console.WriteLine($"串口是否開啓:{_serialPort.IsOpen},端口名稱:{_serialPort.PortName}");

輸出結果

串口是否開啓:True,端口名稱:COM1

接收串口消息

循環標記

private static bool _continue;

讀取串口消息

public static void Read()
{
    while (_continue)
    {
        try
        {
            string message = _serialPort.ReadLine();
            Console.WriteLine($"接收到消息:{message}");
        }
        catch (TimeoutException) { }
    }
}

開啓一個線程來循環讀取消息

Thread readThread = new Thread(Read);
_continue = true;
readThread.Start();

寫入串口消息

示例代碼

_serialPort.WriteLine("message");

收到消息事件

示例代碼

// 已通過由SerialPort對象表示的端口接收了數據
_serialPort.DataReceived += SerialPort_DataReceived;

收到消息

private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    string message = sp.ReadExisting();
    Console.WriteLine($"接收到消息:{message}");
}

發送錯誤事件

示例代碼

// 由SerialPort對象表示的端口上發生了錯誤
_serialPort.ErrorReceived += SerialPort_ErrorReceived;

收到錯誤

private static void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
    Console.WriteLine($"串口消息錯誤類型:{e.EventType}");
}

錯誤枚舉

public enum SerialError
{
    TXFull = 0x100,
    RXOver = 1,
    Overrun = 2,
    RXParity = 4,
    Frame = 8
}

非數據信號事件

示例代碼

// 由SerialPort對象表示的端口上發生了非數據信號事件
_serialPort.PinChanged += SerialPort_PinChanged;

收到改變

private static void SerialPort_PinChanged(object sender, SerialPinChangedEventArgs e)
{
    Console.WriteLine($"非數據信號改變類型:{e.EventType}");
}

事件枚舉

public enum SerialPinChange
{
    CtsChanged = 8,
    DsrChanged = 0x10,
    CDChanged = 0x20,
    Ring = 0x100,
    Break = 0x40
}

關閉串口

示例代碼

_serialPort.Close();
Console.WriteLine($"串口是否開啓:{_serialPort.IsOpen},端口名稱:{_serialPort.PortName}");

加強異常

public static void Read()
{
    while (_continue)
    {
        try
        {
            string message = _serialPort.ReadLine();
            Console.WriteLine($"接收到消息:{message}");
        }
        catch (TimeoutException) { }
        catch (Exception) { _continue = false; }
    }
}

輸出結果

串口是否開啓:False,端口名稱:COM1

進階

定義事件參數

internal class PortMessageEventArgs : EventArgs
{
    public byte[] Data { get; private set; }

    public PortMessageEventArgs(byte[] data)
    {
        this.Data = data;
    }
}

定義配置

internal class PortDataAdapterOptions
{
    /// <summary>
    /// 端口
    /// </summary>
    public string PortName { get; set; }

    /// <summary>
    /// 串行波特率
    /// </summary>
    public int BaudRate { get; set; }

    /// <summary>
    /// 奇偶校驗檢查協議
    /// </summary>
    public Parity? Parity { get; set; }

    /// <summary>
    /// 每個字節的標準數據位長度
    /// </summary>
    public int DataBits { get; set; }

    /// <summary>
    /// 每個字節的標準停止位數
    /// </summary>
    public StopBits? StopBits { get; set; }

    /// <summary>
    /// 讀操作的超時時間(毫秒數)
    /// </summary>
    public int ReadTimeout { get; set; }

    /// <summary>
    /// 寫操作的超時時間(毫秒數)
    /// </summary>
    public int WriteTimeout { get; set; }
}

提取接口

internal interface IPortDataAdapter
{
    event EventHandler<PortMessageEventArgs> MessageReceived;

    void Config(PortDataAdapterOptions options);

    void Open();

    void Close();

    void Send(byte[] contents, int offset, int length);

    void Send(byte[] contents);

    void Send(string content);
}

定製實現

internal class PortDataAdapter : IPortDataAdapter
{
    public event EventHandler<PortMessageEventArgs> MessageReceived;

    private SerialPort _serialPort;
    private const int _baudRate = 115200;
    private const int _dataBits = 8;
    private const Parity _parity = Parity.None;
    private const StopBits _stopBits = StopBits.One;
    private const int _readTimeout = 500;
    private const int _writeTimeout = 500;
    private Thread _thread;
    private bool _continue;

    public PortDataAdapter() { }

    public void Config(PortDataAdapterOptions options)
    {
        if (options == null) throw new ArgumentNullException("options");
        if (string.IsNullOrEmpty(options.PortName)) throw new ArgumentNullException("options.PortName");

        _serialPort = new SerialPort(options.PortName);
        _serialPort.BaudRate = options.BaudRate > 0 ? options.BaudRate : _baudRate;
        _serialPort.Parity = options.Parity ?? _parity;
        _serialPort.DataBits = options.DataBits > 0 ? options.DataBits : _dataBits;
        _serialPort.StopBits = options.StopBits ?? _stopBits;
        _serialPort.ReadTimeout = options.ReadTimeout > 0 ? options.ReadTimeout : _readTimeout;
        _serialPort.WriteTimeout = options.WriteTimeout > 0 ? options.WriteTimeout : _writeTimeout;
    }

    public void Open()
    {
        _serialPort.Open();
        Console.WriteLine($"串口是否開啓:{_serialPort.IsOpen},端口名稱:{_serialPort.PortName}");

        _continue = true;
        _thread = new Thread(Read);
        _thread.IsBackground = true;
        _thread.Start();
        Console.WriteLine($"端口名稱:{_serialPort.PortName},消息監聽開始");
    }

    private void Read()
    {
        while (_continue)
        {
            try
            {
                if(_serialPort.BytesToRead > 0)
                {
                    var data = new byte[_serialPort.BytesToRead];
                    _serialPort.Read(data, 0, data.Length);
                    Console.WriteLine($"接收到消息");
                    MessageReceived?.Invoke(this, new PortMessageEventArgs(data));
                }
            }
            catch (TimeoutException) { }
            catch (Exception) { _continue = false; }
        }
    }

    public void Send(string content)
    {
        var contents = UTF8Encoding.GetEncoding("utf-8").GetBytes(content);
        Send(contents);
    }

    public void Send(byte[] contents)
    {
        Send(contents, 0, contents.Length);
    }

    public void Send(byte[] contents, int offset, int length)
    {
        if (_serialPort == null) throw new ArgumentNullException("_serialPort");
        if (!_serialPort.IsOpen) throw new ArgumentNullException("_serialPort is Closed");
        _serialPort.Write(contents, offset, length);
    }

    public void Close() 
    {
        _continue = false;
        _thread.Join();
        _serialPort.Close();
        Console.WriteLine($"串口是否開啓:{_serialPort.IsOpen},端口名稱:{_serialPort.PortName}");
    }
}

模擬調用

var options = new PortDataAdapterOptions
{
    PortName = "COM1"
};
IPortDataAdapter portDataAdapter = new PortDataAdapter();
portDataAdapter.Config(options);
portDataAdapter.MessageReceived += PortDataAdapter_MessageReceived;
portDataAdapter.Open();
portDataAdapter.Send("haha");
portDataAdapter.Close();
private static void PortDataAdapter_MessageReceived(object sender, PortMessageEventArgs e)
{

}

原子操作

以原子操作方式,設置爲指定的值並返回原始值

var age = 25;
Console.WriteLine($"origin age:{age}");
var data = Interlocked.Exchange(ref age, 0);
Console.WriteLine($"data result:{data}");
Console.WriteLine($"new age:{age}");

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章