乘风破浪,遇见最佳跨平台跨终端框架.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}");

参考

posted @ 2022-11-27 18:02  TaylorShi  阅读(1689)  评论(0编辑  收藏  举报