使用.NET8实现一个完整的串口通讯工具类

IT 文章45秒前更新 小编
0 0 0

本文主要讲解关于使用.NET8实现一个完整的串口通讯工具类相关内容,由优网导航(www.uonce.com)提供,欢迎关注收藏本站!

引言

串口通信(Serial Communication)在工业控制、物联网设备、嵌入式系统和自动化领域仍然广泛应用。.NET 8 提供了强大的 System.IO.Ports命名空间,使得实现串口通信变得简单高效。本文将详细介绍如何使用 .NET 8 实现一个功能完整的串口通信工具类,包含配置管理、数据收发、事件处理和错误处理等功能。

ad

程序员导航

优网导航旗下整合全网优质开发资源,一站式IT编程学习与工具大全网站

1. 串口通信工具类设计

首先,我们设计一个 SerialPortTool类,封装所有串口操作:

using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
 
public class SerialPortTool : IDisposable
{
    private SerialPort _serialPort;
    private CancellationTokenSource _cancellationTokenSource;
    private bool _isOpen = false;
    
    // 事件定义
    public event EventHandler<string> PortOpened;
    public event EventHandler<string> PortClosed;
    public event EventHandler<byte[]> DataReceived;
    public event EventHandler<string> MessageReceived;
    public event EventHandler<Exception> ErrorOccurred;
    
    // 配置属性
    public string PortName { get; private set; }
    public int BaudRate { get; private set; }
    public Parity Parity { get; private set; }
    public int DataBits { get; private set; }
    public StopBits StopBits { get; private set; }
    public Handshake Handshake { get; private set; }
    public int ReadTimeout { get; private set; }
    public int WriteTimeout { get; private set; }
    
    public bool IsOpen => _isOpen && _serialPort?.IsOpen == true;
    
    public SerialPortTool(string portName, int baudRate = 9600, 
                         Parity parity = Parity.None, int dataBits = 8, 
                         StopBits stopBits = StopBits.One, 
                         Handshake handshake = Handshake.None,
                         int readTimeout = 1000, int writeTimeout = 1000)
    {
        PortName = portName;
        BaudRate = baudRate;
        Parity = parity;
        DataBits = dataBits;
        StopBits = stopBits;
        Handshake = handshake;
        ReadTimeout = readTimeout;
        WriteTimeout = writeTimeout;
        
        _cancellationTokenSource = new CancellationTokenSource();
    }
    
    // 其余实现将在下面展开...
}

2. 实现串口打开和关闭

2.1 打开串口

public bool Open()
{
    if (IsOpen)
        return true;
 
    try
    {
        _serialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits)
        {
            Handshake = Handshake,
            ReadTimeout = ReadTimeout,
            WriteTimeout = WriteTimeout
        };
        
        _serialPort.Open();
        _isOpen = true;
        
        // 启动数据接收后台任务
        _ = Task.Run(() => ReceiveDataAsync(_cancellationTokenSource.Token));
        
        PortOpened?.Invoke(this, $"串口 {PortName} 已打开");
        return true;
    }
    catch (Exception ex)
    {
        ErrorOccurred?.Invoke(this, ex);
        Close();
        return false;
    }
}

ad

AI 工具导航

优网导航旗下AI工具导航,精选全球千款优质 AI 工具集

2.2 关闭串口

public void Close()
{
    try
    {
        _cancellationTokenSource.Cancel();
        _serialPort?.Close();
        _serialPort?.Dispose();
        _serialPort = null;
        _isOpen = false;
        
        PortClosed?.Invoke(this, $"串口 {PortName} 已关闭");
    }
    catch (Exception ex)
    {
        ErrorOccurred?.Invoke(this, ex);
    }
}

3. 实现数据发送和接收

3.1 发送数据

public bool Send(byte[] data)
{
    if (!IsOpen)
        return false;
 
    try
    {
        _serialPort.Write(data, 0, data.Length);
        return true;
    }
    catch (Exception ex)
    {
        ErrorOccurred?.Invoke(this, ex);
        Close();
        return false;
    }
}
 
public bool SendString(string message, Encoding encoding = null)
{
    encoding ??= Encoding.UTF8;
    byte[] data = encoding.GetBytes(message);
    return Send(data);
}
 
public async Task<bool> SendAsync(byte[] data)
{
    if (!IsOpen)
        return false;
 
    try
    {
        await _serialPort.BaseStream.WriteAsync(data, 0, data.Length);
        return true;
    }
    catch (Exception ex)
    {
        ErrorOccurred?.Invoke(this, ex);
        Close();
        return false;
    }
}
 
public async Task<bool> SendStringAsync(string message, Encoding encoding = null)
{
    encoding ??= Encoding.UTF8;
    byte[] data = encoding.GetBytes(message);
    return await SendAsync(data);
}

3.2 接收数据(后台任务)

private async Task ReceiveDataAsync(CancellationToken cancellationToken)
{
    byte[] buffer = new byte[4096];
    
    while (!cancellationToken.IsCancellationRequested && IsOpen)
    {
        try
        {
            // 异步读取数据
            int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
            
            if (bytesRead > 0)
            {
                // 复制接收到的数据
                byte[] receivedData = new byte[bytesRead];
                Array.Copy(buffer, receivedData, bytesRead);
                
                // 触发数据接收事件
                DataReceived?.Invoke(this, receivedData);
                
                // 转换为字符串并触发消息接收事件
                string message = Encoding.UTF8.GetString(receivedData);
                MessageReceived?.Invoke(this, message);
            }
        }
        catch (OperationCanceledException)
        {
            // 任务被取消,正常退出
            break;
        }
        catch (TimeoutException)
        {
            // 读取超时,继续等待
        }
        catch (Exception ex)
        {
            if (IsOpen) // 只在串口打开时报告错误
            {
                ErrorOccurred?.Invoke(this, ex);
            }
            break;
        }
    }
}

4. 完整工具类实现

下面是完整的 SerialPortTool类实现:

using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
public class SerialPortTool : IDisposable
{
    private SerialPort _serialPort;
    private CancellationTokenSource _cancellationTokenSource;
    private bool _isOpen = false;
    
    // 事件定义
    public event EventHandler<string> PortOpened;
    public event EventHandler<string> PortClosed;
    public event EventHandler<byte[]> DataReceived;
    public event EventHandler<string> MessageReceived;
    public event EventHandler<Exception> ErrorOccurred;
    
    // 配置属性
    public string PortName { get; private set; }
    public int BaudRate { get; private set; }
    public Parity Parity { get; private set; }
    public int DataBits { get; private set; }
    public StopBits StopBits { get; private set; }
    public Handshake Handshake { get; private set; }
    public int ReadTimeout { get; private set; }
    public int WriteTimeout { get; private set; }
    
    public bool IsOpen => _isOpen && _serialPort?.IsOpen == true;
    
    public SerialPortTool(string portName, int baudRate = 9600, 
                         Parity parity = Parity.None, int dataBits = 8, 
                         StopBits stopBits = StopBits.One, 
                         Handshake handshake = Handshake.None,
                         int readTimeout = 1000, int writeTimeout = 1000)
    {
        PortName = portName;
        BaudRate = baudRate;
        Parity = parity;
        DataBits = dataBits;
        StopBits = stopBits;
        Handshake = handshake;
        ReadTimeout = readTimeout;
        WriteTimeout = writeTimeout;
        
        _cancellationTokenSource = new CancellationTokenSource();
    }
    
    public bool Open()
    {
        if (IsOpen)
            return true;
 
        try
        {
            _serialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits)
            {
                Handshake = Handshake,
                ReadTimeout = ReadTimeout,
                WriteTimeout = WriteTimeout
            };
            
            _serialPort.Open();
            _isOpen = true;
            
            // 启动数据接收后台任务
            _ = Task.Run(() => ReceiveDataAsync(_cancellationTokenSource.Token));
            
            PortOpened?.Invoke(this, $"串口 {PortName} 已打开");
            return true;
        }
        catch (Exception ex)
        {
            ErrorOccurred?.Invoke(this, ex);
            Close();
            return false;
        }
    }
    
    public void Close()
    {
        try
        {
            _cancellationTokenSource.Cancel();
            _serialPort?.Close();
            _serialPort?.Dispose();
            _serialPort = null;
            _isOpen = false;
            
            PortClosed?.Invoke(this, $"串口 {PortName} 已关闭");
        }
        catch (Exception ex)
        {
            ErrorOccurred?.Invoke(this, ex);
        }
    }
    
    public bool Send(byte[] data)
    {
        if (!IsOpen)
            return false;
 
        try
        {
            _serialPort.Write(data, 0, data.Length);
            return true;
        }
        catch (Exception ex)
        {
            ErrorOccurred?.Invoke(this, ex);
            Close();
            return false;
        }
    }
    
    public bool SendString(string message, Encoding encoding = null)
    {
        encoding ??= Encoding.UTF8;
        byte[] data = encoding.GetBytes(message);
        return Send(data);
    }
    
    public async Task<bool> SendAsync(byte[] data)
    {
        if (!IsOpen)
            return false;
 
        try
        {
            await _serialPort.BaseStream.WriteAsync(data, 0, data.Length);
            return true;
        }
        catch (Exception ex)
        {
            ErrorOccurred?.Invoke(this, ex);
            Close();
            return false;
        }
    }
    
    public async Task<bool> SendStringAsync(string message, Encoding encoding = null)
    {
        encoding ??= Encoding.UTF8;
        byte[] data = encoding.GetBytes(message);
        return await SendAsync(data);
    }
    
    private async Task ReceiveDataAsync(CancellationToken cancellationToken)
    {
        byte[] buffer = new byte[4096];
        
        while (!cancellationToken.IsCancellationRequested && IsOpen)
        {
            try
            {
                int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
                
                if (bytesRead > 0)
                {
                    byte[] receivedData = new byte[bytesRead];
                    Array.Copy(buffer, receivedData, bytesRead);
                    
                    DataReceived?.Invoke(this, receivedData);
                    
                    string message = Encoding.UTF8.GetString(receivedData);
                    MessageReceived?.Invoke(this, message);
                }
            }
            catch (OperationCanceledException)
            {
                break;
            }
            catch (TimeoutException)
            {
                // 超时是正常情况,继续等待
            }
            catch (Exception ex)
            {
                if (IsOpen)
                {
                    ErrorOccurred?.Invoke(this, ex);
                }
                break;
            }
        }
    }
    
    #region IDisposable Implementation
    private bool _disposed = false;
 
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                Close();
                _cancellationTokenSource?.Dispose();
            }
            _disposed = true;
        }
    }
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    #endregion
}

ad

免费在线工具导航

优网导航旗下整合全网优质免费、免注册的在线工具导航大全

5. 使用示例

下面是如何使用串口通信工具类的示例:

class Program
{
    static async Task Main(string[] args)
    {
        // 获取可用串口列表
        string[] ports = SerialPort.GetPortNames();
        Console.WriteLine("可用串口:");
        foreach (string port in ports)
        {
            Console.WriteLine(port);
        }
        
        if (ports.Length == 0)
        {
            Console.WriteLine("没有找到可用串口");
            return;
        }
        
        // 使用第一个可用串口
        string selectedPort = ports[0];
        
        using var serialTool = new SerialPortTool(
            portName: selectedPort,
            baudRate: 115200,
            parity: Parity.None,
            dataBits: 8,
            stopBits: StopBits.One
        );
        
        // 订阅事件
        serialTool.PortOpened += (sender, message) => Console.WriteLine(message);
        serialTool.PortClosed += (sender, message) => Console.WriteLine(message);
        
        serialTool.DataReceived += (sender, data) => 
        {
            Console.WriteLine($"收到字节数据: {BitConverter.ToString(data)}");
        };
        
        serialTool.MessageReceived += (sender, message) => 
        {
            Console.WriteLine($"收到消息: {message}");
        };
        
        serialTool.ErrorOccurred += (sender, ex) => 
        {
            Console.WriteLine($"发生错误: {ex.Message}");
        };
        
        // 打开串口
        if (serialTool.Open())
        {
            Console.WriteLine("按 'S' 发送字符串,按 'B' 发送字节数据,按 'Q' 退出");
            
            while (true)
            {
                var key = Console.ReadKey(intercept: true).Key;
                
                if (key == ConsoleKey.S)
                {
                    Console.Write("输入要发送的字符串: ");
                    string message = Console.ReadLine();
                    serialTool.SendString(message);
                }
                else if (key == ConsoleKey.B)
                {
                    Console.Write("输入要发送的十六进制字节 (例如: 01 02 AA FF): ");
                    string hexInput = Console.ReadLine();
                    
                    try
                    {
                        byte[] data = ParseHexString(hexInput);
                        serialTool.Send(data);
                        Console.WriteLine($"已发送: {BitConverter.ToString(data)}");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"解析错误: {ex.Message}");
                    }
                }
                else if (key == ConsoleKey.Q)
                {
                    break;
                }
            }
            
            // 关闭串口(using语句也会自动调用Dispose)
            serialTool.Close();
        }
        else
        {
            Console.WriteLine("串口打开失败");
        }
    }
    
    private static byte[] ParseHexString(string hex)
    {
        hex = hex.Replace(" ", "").Replace("-", "");
        if (hex.Length % 2 != 0)
            throw new ArgumentException("十六进制字符串长度必须为偶数");
        
        byte[] bytes = new byte[hex.Length / 2];
        for (int i = 0; i < bytes.Length; i++)
        {
            string byteValue = hex.Substring(i * 2, 2);
            bytes[i] = Convert.ToByte(byteValue, 16);
        }
        return bytes;
    }
}

6. 高级功能扩展

6.1 添加帧处理功能

对于需要处理特定帧格式的应用,可以添加帧处理功能:

public class FramedSerialPortTool : SerialPortTool
{
    private readonly byte[] _frameDelimiter;
    private List<byte> _buffer = new List<byte>();
    
    public FramedSerialPortTool(string portName, byte[] frameDelimiter, 
                               int baudRate = 9600, Parity parity = Parity.None, 
                               int dataBits = 8, StopBits stopBits = StopBits.One, 
                               Handshake handshake = Handshake.None,
                               int readTimeout = 1000, int writeTimeout = 1000)
        : base(portName, baudRate, parity, dataBits, stopBits, handshake, readTimeout, writeTimeout)
    {
        _frameDelimiter = frameDelimiter;
        this.DataReceived += OnRawDataReceived;
    }
    
    public new event EventHandler<byte[]> FrameReceived;
    
    private void OnRawDataReceived(object sender, byte[] data)
    {
        _buffer.AddRange(data);
        ProcessBuffer();
    }
    
    private void ProcessBuffer()
    {
        while (true)
        {
            // 查找帧分隔符
            int delimiterIndex = FindDelimiter(_buffer.ToArray(), _frameDelimiter);
            
            if (delimiterIndex == -1)
                break;
                
            // 提取完整帧
            byte[] frameData = new byte[delimiterIndex];
            Array.Copy(_buffer.ToArray(), frameData, delimiterIndex);
            
            // 从缓冲区中移除已处理的数据(包括分隔符)
            _buffer.RemoveRange(0, delimiterIndex + _frameDelimiter.Length);
            
            // 触发帧接收事件
            FrameReceived?.Invoke(this, frameData);
        }
    }
    
    private int FindDelimiter(byte[] data, byte[] delimiter)
    {
        for (int i = 0; i <= data.Length - delimiter.Length; i++)
        {
            bool found = true;
            for (int j = 0; j < delimiter.Length; j++)
            {
                if (data[i + j] != delimiter[j])
                {
                    found = false;
                    break;
                }
            }
            if (found)
                return i;
        }
        return -1;
    }
    
    public bool SendFrame(byte[] frameData)
    {
        byte[] framedData = new byte[frameData.Length + _frameDelimiter.Length];
        Array.Copy(frameData, framedData, frameData.Length);
        Array.Copy(_frameDelimiter, 0, framedData, frameData.Length, _frameDelimiter.Length);
        
        return Send(framedData);
    }
    
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.DataReceived -= OnRawDataReceived;
        }
        base.Dispose(disposing);
    }
}

6.2 添加自动重连功能

对于需要长时间运行的串口应用,可以添加自动重连功能:

public class AutoReconnectSerialPortTool : SerialPortTool
{
    private Timer _reconnectTimer;
    private readonly TimeSpan _reconnectInterval;
    private int _reconnectAttempts = 0;
    private const int MAX_RECONNECT_ATTEMPTS = 10;
    
    public AutoReconnectSerialPortTool(string portName, TimeSpan reconnectInterval,
                                      int baudRate = 9600, Parity parity = Parity.None, 
                                      int dataBits = 8, StopBits stopBits = StopBits.One, 
                                      Handshake handshake = Handshake.None,
                                      int readTimeout = 1000, int writeTimeout = 1000)
        : base(portName, baudRate, parity, dataBits, stopBits, handshake, readTimeout, writeTimeout)
    {
        _reconnectInterval = reconnectInterval;
        this.PortClosed += OnPortClosed;
    }
    
    private void OnPortClosed(object sender, string message)
    {
        if (_reconnectAttempts < MAX_RECONNECT_ATTEMPTS)
        {
            _reconnectTimer = new Timer(AttemptReconnect, null, _reconnectInterval, Timeout.InfiniteTimeSpan);
        }
    }
    
    private void AttemptReconnect(object state)
    {
        _reconnectAttempts++;
        
        if (Open())
        {
            _reconnectAttempts = 0; // 重置重试计数器
            _reconnectTimer?.Dispose();
            _reconnectTimer = null;
        }
    }
    
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _reconnectTimer?.Dispose();
            this.PortClosed -= OnPortClosed;
        }
        base.Dispose(disposing);
    }
}

7. 串口通信最佳实践

1.​​资源管理​​:

  • 始终使用 using语句或手动调用 Dispose()确保资源释放
  • 在不再需要时关闭串口连接

2.​​错误处理​​:

  • 处理所有可能的异常(端口不存在、权限问题、设备断开等)
  • 使用事件机制通知上层应用错误发生

3.线程安全​​:

  • 串口事件可能在后台线程触发,确保UI操作在正确的线程执行
  • 使用同步机制保护共享资源

4.​​性能优化​​:

  • 使用异步方法避免阻塞UI线程
  • 合理设置缓冲区大小平衡内存使用和性能
  • 避免在事件处理中执行耗时操作

5.​​配置管理​​:

  • 保存和加载串口配置(波特率、数据位等)
  • 提供配置验证功能

8. 总结

本文介绍了如何使用 .NET 8 实现一个功能完整的串口通信工具类,包含以下核心功能:

1.​​串口管理​​:打开、关闭和状态监控

2.​​数据收发​​:支持同步和异步的字节数组和字符串传输

3.​​事件通知​​:提供串口状态变化、数据接收和错误通知

4.​​资源管理​​:正确实现 IDisposable接口

5.​​错误处理​​:健壮的异常处理和错误通知机制

通过这种封装,我们可以在不同的项目中轻松重用串口通信功能,而无需重复编写底层代码。此外,通过继承和扩展,可以轻松添加如帧处理、自动重连等高级功能。

在工业自动化、物联网设备通信和嵌入式系统开发中,这种封装方式能够显著提高开发效率和代码质量,是开发串口通信应用的理想起点。

以上就是使用.NET8实现一个完整的串口通讯工具类的详细内容,更多关于.NET8串口通讯工具类的资料请关注优网导航其它相关文章!

您可能感兴趣的文章:

  • .NET 8实现modbus通讯工具类封装的操作方法
  • 在.NET 8 中使用中介模式优雅处理多版本 API 请求的实现方案
  • .NET 8 中的 Keyed Services解锁依赖注入的新方式(最新推荐)
  • .NET8 gRPC实现高效100G大文件断点续传工具
  • .NET8中使用JWT进行身份认证和授权的实现

相关推荐: 宇树科技2025年最新薪资体系与招聘标准详解

在国内具身智能与机器人领域,要是说起有代表性的企业,宇树科技(Unitree)绝对是榜上有名的。最近,这家行业标杆企业有个新动向引起了业内不少讨论——它给合作伙伴发了一封公司名称变更函,咱们来看看具体内容。 一、宇树科技名称变更:常规调整还是IPO信号? 1.…

© 版权声明

相关文章

暂无评论

暂无评论...