本文主要讲解关于使用.NET8实现一个完整的串口通讯工具类相关内容,由优网导航(www.uonce.com)提供,欢迎关注收藏本站!
引言
串口通信(Serial Communication)在工业控制、物联网设备、嵌入式系统和自动化领域仍然广泛应用。.NET 8 提供了强大的 System.IO.Ports
命名空间,使得实现串口通信变得简单高效。本文将详细介绍如何使用 .NET 8 实现一个功能完整的串口通信工具类,包含配置管理、数据收发、事件处理和错误处理等功能。

程序员导航
优网导航旗下整合全网优质开发资源,一站式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; } }

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 }

免费在线工具导航
优网导航旗下整合全网优质免费、免注册的在线工具导航大全
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进行身份认证和授权的实现
在国内具身智能与机器人领域,要是说起有代表性的企业,宇树科技(Unitree)绝对是榜上有名的。最近,这家行业标杆企业有个新动向引起了业内不少讨论——它给合作伙伴发了一封公司名称变更函,咱们来看看具体内容。 一、宇树科技名称变更:常规调整还是IPO信号? 1.…