在嵌入式系统、物联网(IoT)和工业通讯领域,串口通讯仍然是一种广泛使用的通信方式。本文将深入探讨如何使用C#实现一个健壮、高效的异步串口通讯管理器。
异步编程是现代.NET应用程序的重要特性,它允许:
我们的串口管理器将具备以下特性:
C#public class SerialPortManager : IDisposable
{
private SerialPort _serialPort;
private readonly string _portName;
private readonly int _baudRate;
private CancellationTokenSource _cancellationTokenSource;
// 数据接收事件
public event EventHandler<byte[]> DataReceived;
public SerialPortManager(string portName, int baudRate)
{
_portName = portName;
_baudRate = baudRate;
_cancellationTokenSource = new CancellationTokenSource();
}
}
C#public async Task StartAsync()
{
try
{
await ConnectSerialPortAsync();
}
catch (Exception ex)
{
LogError($"启动失败:{ex.Message}");
throw;
}
}
private async Task ConnectSerialPortAsync()
{
_serialPort = new SerialPort(_portName, _baudRate)
{
ReadTimeout = 500,
WriteTimeout = 500,
ReadBufferSize = 4096,
WriteBufferSize = 4096
};
_serialPort.Open();
_ = StartAsyncReading();
}
C#private async Task StartAsyncReading()
{
byte[] buffer = new byte[4096];
while (_serialPort.IsOpen && !_cancellationTokenSource.Token.IsCancellationRequested)
{
int bytesToRead = _serialPort.BytesToRead;
if (bytesToRead > 0)
{
int readBytes = await _serialPort.BaseStream.ReadAsync(
buffer, 0, bytesToRead, _cancellationTokenSource.Token);
ProcessReceivedData(buffer.Take(readBytes).ToArray());
}
await Task.Delay(50, _cancellationTokenSource.Token);
}
}
C#public async Task<bool> SendDataAsync(byte[] data)
{
if (_serialPort?.IsOpen == true)
{
await _serialPort.BaseStream.WriteAsync(
data, 0, data.Length, _cancellationTokenSource.Token);
return true;
}
return false;
}
C#public async Task ExampleUsage()
{
var serialManager = new SerialPortManager("COM3", 9600);
// 订阅数据接收事件
serialManager.DataReceived += (sender, data) => {
Console.WriteLine($"接收到数据: {BitConverter.ToString(data)}");
};
// 启动串口
await serialManager.StartAsync();
// 发送数据
byte[] dataToSend = new byte[] { 0x01, 0x02, 0x03 };
await serialManager.SendDataAsync(dataToSend);
}
C#using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
namespace AppSerialPortTry
{
/// <summary>
/// 串口管理器
/// </summary>
public class SerialPortManager : IDisposable
{
private SerialPort _serialPort;
private readonly string _portName;
private readonly int _baudRate;
private CancellationTokenSource _cancellationTokenSource;
// 数据接收事件
public event EventHandler<byte[]> DataReceived;
public SerialPortManager(string portName, int baudRate)
{
_portName = portName;
_baudRate = baudRate;
_cancellationTokenSource = new CancellationTokenSource();
}
/// <summary>
/// 异步启动串口通信
/// </summary>
public async Task StartAsync()
{
try
{
await ConnectSerialPortAsync();
}
catch (Exception ex)
{
LogError($"启动失败:{ex.Message}");
throw;
}
}
/// <summary>
/// 连接串口
/// </summary>
private async Task ConnectSerialPortAsync()
{
try
{
// 创建串口实例
_serialPort = new SerialPort(_portName, _baudRate)
{
ReadTimeout = 500,
WriteTimeout = 500,
ReadBufferSize = 4096,
WriteBufferSize = 4096
};
// 配置串口事件
_serialPort.DataReceived += SerialPort_DataReceived;
_serialPort.ErrorReceived += SerialPort_ErrorReceived;
// 打开串口
_serialPort.Open();
LogInfo($"串口 {_portName} 连接成功");
// 启动异步读取
_ = StartAsyncReading();
}
catch (Exception ex)
{
LogError($"连接串口失败:{ex.Message}");
throw;
}
}
/// <summary>
/// 异步读取数据
/// </summary>
private async Task StartAsyncReading()
{
try
{
byte[] buffer = new byte[4096];
while (_serialPort != null && _serialPort.IsOpen && !_cancellationTokenSource.Token.IsCancellationRequested)
{
int bytesToRead = _serialPort.BytesToRead;
if (bytesToRead > 0)
{
bytesToRead = Math.Min(bytesToRead, buffer.Length);
int readBytes = await _serialPort.BaseStream.ReadAsync(buffer, 0, bytesToRead, _cancellationTokenSource.Token);
if (readBytes > 0)
{
byte[] receivedData = new byte[readBytes];
Array.Copy(buffer, receivedData, readBytes);
ProcessReceivedData(receivedData);
}
}
await Task.Delay(50, _cancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
// 正常取消
}
catch (Exception ex)
{
LogError($"异步读取数据错误:{ex.Message}");
}
}
/// <summary>
/// 处理接收的数据
/// </summary>
private void ProcessReceivedData(byte[] data)
{
string hexData = BitConverter.ToString(data);
LogInfo($"接收到数据:{hexData}");
// 触发数据接收事件
DataReceived?.Invoke(this, data);
}
/// <summary>
/// 串口错误事件
/// </summary>
private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
LogError($"串口错误:{e.EventType}");
}
/// <summary>
/// 数据接收事件
/// </summary>
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
// 根据不同的接收模式处理
switch (e.EventType)
{
case SerialData.Chars:
// 有字符可读
int bytesToRead = _serialPort.BytesToRead;
if (bytesToRead > 0)
{
byte[] buffer = new byte[bytesToRead];
_serialPort.Read(buffer, 0, bytesToRead);
// 可以触发一个事件或进行进一步处理
ProcessReceivedData(buffer);
}
break;
case SerialData.Eof:
LogWarning("串口接收到文件结束符");
break;
default:
LogInfo($"接收到未处理的事件类型: {e.EventType}");
break;
}
}
catch (Exception ex)
{
LogError($"串口数据接收事件处理错误:{ex.Message}");
}
}
/// <summary>
/// 发送数据
/// </summary>
public async Task<bool> SendDataAsync(byte[] data)
{
try
{
if (_serialPort != null && _serialPort.IsOpen)
{
await _serialPort.BaseStream.WriteAsync(data, 0, data.Length, _cancellationTokenSource.Token);
return true;
}
return false;
}
catch (Exception ex)
{
LogError($"发送数据失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 关闭串口
/// </summary>
public void Close()
{
_cancellationTokenSource.Cancel();
if (_serialPort != null)
{
try
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
// 取消事件绑定
_serialPort.DataReceived -= SerialPort_DataReceived;
_serialPort.ErrorReceived -= SerialPort_ErrorReceived;
}
catch (Exception ex)
{
LogError($"关闭串口时发生错误:{ex.Message}");
}
}
}
/// <summary>
/// 实现 IDisposable
/// </summary>
public void Dispose()
{
Close();
_cancellationTokenSource?.Dispose();
_serialPort?.Dispose();
}
#region 日志方法
private void LogInfo(string message)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"[INFO] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
Console.ResetColor();
}
private void LogWarning(string message)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"[WARNING] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
Console.ResetColor();
}
private void LogError(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"[ERROR] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
Console.ResetColor();
}
#endregion
}
}
C#using System.IO.Ports;
namespace AppSerialPortTry
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("串口通信监控演示程序");
RunSerialPortMonitorAsync().Wait();
Console.ReadKey();
}
static async Task RunSerialPortMonitorAsync()
{
var serialPortManager = new SerialPortManager("COM1", 9600);
await serialPortManager.StartAsync();
await serialPortManager.SendDataAsync(System.Text.Encoding.Default.GetBytes(" Hello World!"));
serialPortManager.DataReceived += (sender, e) =>
{
Console.WriteLine(System.Text.Encoding.Default.GetString(e));
};
}
}
}
通过使用异步编程模式,我们创建了一个既灵活又高效的串口通讯管理器。这种实现方式不仅提高了代码的可读性,还显著改善了应用程序的性能和响应能力。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!