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.TwoStopBits.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();
    }
}
​

四、使用注意事项

  1. 参数匹配:串口的波特率、数据位、停止位、校验位必须与连接的设备完全一致,这是通讯成功的前提。

  2. 线程安全 :在 WinForms/WPF 等 UI 框架中,DataReceived 事件在非 UI 线程触发,更新 UI 时需使用 InvokeDispatcher 切换到 UI 线程。

  3. 异常处理:串口操作可能抛出多种异常(如端口被占用、超时、设备断开等),必须添加完善的异常处理。

  4. 资源释放 :程序退出或不再使用串口时,务必调用 Close()Dispose() 方法释放资源,否则可能导致端口被长期占用。

  5. 数据解析:串口传输的原始数据是字节流,需根据设备协议(如 Modbus-RTU)进行解析,注意处理数据边界和校验。

  6. 抗干扰:工业环境中建议使用 RS485 总线并添加终端电阻,减少电磁干扰导致的数据错误。

通过正确配置 SerialPort 类的参数并遵循上述使用方法,可以实现稳定可靠的串口通讯,适用于工业控制、嵌入式设备交互、传感器数据采集等场景。

相关推荐
白山云北诗2 小时前
网站被攻击了怎么办?如何进行DDoS防御?
开发语言·网络安全·php·ddos·防ddos·防cc
程序定小飞2 小时前
基于springboot的作业管理系统设计与实现
java·开发语言·spring boot·后端·spring
Jonathan Star2 小时前
NestJS 是基于 Node.js 的渐进式后端框架,核心特点包括 **依赖注入、模块化架构、装饰器驱动、TypeScript 优先、与主流工具集成** 等
开发语言·javascript·node.js
晓庆的故事簿2 小时前
windows下载和使用minio,结合java和vue上传文件
java·开发语言
猫头虎2 小时前
永久免费白嫖多个域名,一键托管Cloudflare,免费申请SSL加密证书,轻松建站、搭建线路伪装
服务器·开发语言·网络·数据库·python·网络协议·ssl
无敌最俊朗@3 小时前
C++后端总览
开发语言
多喝开水少熬夜3 小时前
堆相关算法题基础-java实现
java·开发语言·算法
7澄13 小时前
Java 集合框架:List 体系与实现类深度解析
java·开发语言·vector·intellij-idea·集合·arraylist·linkedlist
mit6.8244 小时前
一些C++的学习资料备忘
开发语言·c++