C# 串口通讯中 SerialPort 类的关键参数和使用方法
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 类的参数并遵循上述使用方法,可以实现稳定可靠的串口通讯,适用于工业控制、嵌入式设备交互、传感器数据采集等场景。
