SerialPort 类是 C# 中处理串口通讯的核心类,位于 System.IO.Ports 命名空间下,封装了串口通讯的底层细节。下面详细介绍其关键参数、使用方法及示例代码。
一、SerialPort 类的关键参数
1. 基本通讯参数(必须与设备匹配)
这些参数是串口通讯的基础,必须与连接的硬件设备(如单片机、传感器、PLC 等)设置完全一致,否则会导致通讯失败或数据乱码。
| 参数名 | 类型 | 说明 | 常见值 |
|---|---|---|---|
PortName |
string |
串口号,标识要使用的物理串口 | "COM1"、"COM3"、"/dev/ttyUSB0"(Linux) |
BaudRate |
int |
波特率,数据传输速率(位 / 秒) | 9600、19200、38400、115200 |
DataBits |
int |
数据位,每个字节的数据长度 | 7、8(最常用 8 位) |
StopBits |
StopBits 枚举 |
停止位,用于标记一个数据帧的结束 | StopBits.One(1 位,最常用)、StopBits.Two、StopBits.OnePointFive |
Parity |
Parity 枚举 |
校验位,用于简单的错误检测 | Parity.None(无校验,最常用)、Parity.Odd(奇校验)、Parity.Even(偶校验) |
2. 流控制参数
用于控制数据传输的节奏,防止接收方缓冲区溢出。
| 参数名 | 类型 | 说明 |
|---|---|---|
Handshake |
Handshake 枚举 |
握手协议(流控制方式) |
Handshake.None |
无流控制(默认) | |
Handshake.XOnXOff |
软件流控制(通过特殊字符 XON/XOFF 控制) | |
Handshake.RequestToSend |
硬件流控制(RTS/CTS 信号) | |
Handshake.RequestToSendXOnXOff |
混合流控制 |
3. 超时参数
控制读写操作的等待时间,避免程序无限阻塞。
| 参数名 | 类型 | 说明 |
|---|---|---|
ReadTimeout |
int |
读取超时时间(毫秒),-1 表示无限等待 |
WriteTimeout |
int |
写入超时时间(毫秒),-1 表示无限等待 |
4. 其他常用参数
| 参数名 | 类型 | 说明 |
|---|---|---|
NewLine |
string |
用于 ReadLine() 和 WriteLine() 方法的换行符 |
Encoding |
Encoding |
字符编码,用于字符串与字节的转换 |
IsOpen |
bool(只读) |
指示串口是否已打开 |
二、SerialPort 类的核心使用方法
1. 初始化与打开串口
using System;
using System.IO.Ports;
using System.Text;
// 创建 SerialPort 实例
SerialPort serialPort = new SerialPort();
// 配置基本参数
serialPort.PortName = "COM3"; // 串口号
serialPort.BaudRate = 9600; // 波特率
serialPort.DataBits = 8; // 数据位
serialPort.StopBits = StopBits.One; // 停止位
serialPort.Parity = Parity.None; // 校验位
// 配置超时和编码
serialPort.ReadTimeout = 500; // 读取超时 500ms
serialPort.WriteTimeout = 500; // 写入超时 500ms
serialPort.Encoding = Encoding.UTF8; // 字符编码
// 打开串口
try
{
if (!serialPort.IsOpen)
{
serialPort.Open();
Console.WriteLine("串口已打开");
}
}
catch (Exception ex)
{
Console.WriteLine($"打开串口失败: {ex.Message}");
}
2. 数据发送方法
发送字符串
// 发送字符串(不带换行)
serialPort.Write("Hello, Serial Port!");
// 发送字符串(带换行,适用于按行解析的设备)
serialPort.WriteLine("Hello with newline");
发送字节数组(推荐用于二进制协议)
// 发送字节数组(例如 Modbus 协议指令)
byte[] data = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x04 };
serialPort.Write(data, 0, data.Length); // 从索引0开始,发送全部长度
3. 数据接收方法
方法一:事件驱动(推荐,非阻塞)
通过 DataReceived 事件异步接收数据,不阻塞主线程。
// 绑定数据接收事件
serialPort.DataReceived += SerialPort_DataReceived;
// 事件处理方法
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
SerialPort sp = (SerialPort)sender;
// 方法1:读取所有可用的字符串
string receivedData = sp.ReadExisting();
Console.WriteLine($"收到字符串: {receivedData}");
// 方法2:按行读取(需设备发送换行符)
// string receivedLine = sp.ReadLine();
// Console.WriteLine($"收到一行: {receivedLine}");
// 方法3:读取字节数组(推荐用于二进制数据)
// int bytesToRead = sp.BytesToRead;
// byte[] buffer = new byte[bytesToRead];
// int bytesRead = sp.Read(buffer, 0, bytesToRead);
// Console.WriteLine($"收到 {bytesRead} 字节: {BitConverter.ToString(buffer, 0, bytesRead)}");
}
catch (Exception ex)
{
Console.WriteLine($"接收数据错误: {ex.Message}");
}
}
方法二:同步读取(阻塞式)
在单独线程中同步读取数据,适用于简单场景。
// 在单独线程中执行,避免阻塞UI
Task.Run(() =>
{
while (serialPort.IsOpen)
{
try
{
byte[] buffer = new byte[1024];
int bytesRead = serialPort.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
Console.WriteLine($"同步收到 {bytesRead} 字节");
}
}
catch (TimeoutException)
{
// 超时属于正常情况,继续等待
}
catch (Exception ex)
{
Console.WriteLine($"同步读取错误: {ex.Message}");
break;
}
}
});
4. 关闭串口与资源释放
// 关闭串口并释放资源
if (serialPort != null && serialPort.IsOpen)
{
// 解绑事件(可选)
serialPort.DataReceived -= SerialPort_DataReceived;
serialPort.Close(); // 关闭串口
serialPort.Dispose(); // 释放资源
Console.WriteLine("串口已关闭");
}
三、完整示例:串口通讯工具类
using System;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
public class SerialPortUtility
{
private SerialPort _serialPort;
private bool _isConnected = false;
// 数据接收事件(供外部订阅)
public event Action<byte[]> DataReceived;
public event Action<string> LogMessage;
/// <summary>
/// 初始化串口参数
/// </summary>
/// <param name="portName">串口号</param>
/// <param name="baudRate">波特率</param>
/// <param name="dataBits">数据位</param>
/// <param name="stopBits">停止位</param>
/// <param name="parity">校验位</param>
public SerialPortUtility(string portName, int baudRate = 9600,
int dataBits = 8, StopBits stopBits = StopBits.One,
Parity parity = Parity.None)
{
_serialPort = new SerialPort
{
PortName = portName,
BaudRate = baudRate,
DataBits = dataBits,
StopBits = stopBits,
Parity = parity,
ReadTimeout = 500,
WriteTimeout = 500,
Encoding = Encoding.UTF8
};
// 绑定数据接收事件
_serialPort.DataReceived += OnDataReceived;
}
/// <summary>
/// 打开串口
/// </summary>
/// <returns>是否成功</returns>
public bool Open()
{
try
{
if (!_serialPort.IsOpen)
{
_serialPort.Open();
_isConnected = true;
LogMessage?.Invoke($"串口 {_serialPort.PortName} 已打开,波特率 {_serialPort.BaudRate}");
return true;
}
return false;
}
catch (Exception ex)
{
LogMessage?.Invoke($"打开串口失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 关闭串口
/// </summary>
public void Close()
{
try
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
}
catch (Exception ex)
{
LogMessage?.Invoke($"关闭串口失败: {ex.Message}");
}
finally
{
_isConnected = false;
_serialPort?.Dispose();
LogMessage?.Invoke($"串口 {_serialPort.PortName} 已关闭");
}
}
/// <summary>
/// 发送字节数组
/// </summary>
/// <param name="data">要发送的字节数组</param>
/// <returns>是否成功</returns>
public bool SendData(byte[] data)
{
if (!_isConnected || data == null || data.Length == 0)
return false;
try
{
_serialPort.Write(data, 0, data.Length);
LogMessage?.Invoke($"发送 {data.Length} 字节: {BitConverter.ToString(data)}");
return true;
}
catch (Exception ex)
{
LogMessage?.Invoke($"发送数据失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 发送字符串
/// </summary>
/// <param name="text">要发送的字符串</param>
/// <returns>是否成功</returns>
public bool SendText(string text)
{
if (string.IsNullOrEmpty(text))
return false;
byte[] data = _serialPort.Encoding.GetBytes(text);
return SendData(data);
}
/// <summary>
/// 数据接收事件处理
/// </summary>
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
int bytesToRead = _serialPort.BytesToRead;
if (bytesToRead <= 0)
return;
byte[] buffer = new byte[bytesToRead];
int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
if (bytesRead > 0)
{
LogMessage?.Invoke($"收到 {bytesRead} 字节: {BitConverter.ToString(buffer, 0, bytesRead)}");
DataReceived?.Invoke(buffer); // 触发外部事件
}
}
catch (Exception ex)
{
LogMessage?.Invoke($"接收数据错误: {ex.Message}");
}
}
/// <summary>
/// 获取当前电脑可用的串口号列表
/// </summary>
/// <returns>串口号数组</returns>
public static string[] GetAvailablePorts()
{
return SerialPort.GetPortNames();
}
}
四、使用注意事项
-
参数匹配:串口的波特率、数据位、停止位、校验位必须与连接的设备完全一致,这是通讯成功的前提。
-
线程安全 :在 WinForms/WPF 等 UI 框架中,
DataReceived事件在非 UI 线程触发,更新 UI 时需使用Invoke或Dispatcher切换到 UI 线程。 -
异常处理:串口操作可能抛出多种异常(如端口被占用、超时、设备断开等),必须添加完善的异常处理。
-
资源释放 :程序退出或不再使用串口时,务必调用
Close()和Dispose()方法释放资源,否则可能导致端口被长期占用。 -
数据解析:串口传输的原始数据是字节流,需根据设备协议(如 Modbus-RTU)进行解析,注意处理数据边界和校验。
-
抗干扰:工业环境中建议使用 RS485 总线并添加终端电阻,减少电磁干扰导致的数据错误。
通过正确配置 SerialPort 类的参数并遵循上述使用方法,可以实现稳定可靠的串口通讯,适用于工业控制、嵌入式设备交互、传感器数据采集等场景。