LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

C# SerialPort 使用详解

freeflydom
2026年6月4日 10:30 本文热度 4902

前言

在工业控制、物联网、嵌入式开发等领域,串口通信(Serial Port Communication)是连接串行设备(如条码扫描器、GPS接收器等)与计算机的重要手段。C# 提供了内置的 SerialPort 类,简化了串口开发的流程。本文将详细介绍如何在C#中使用SerialPort类进行串口通信。


一、什么是 SerialPort?

1. 定义

System.IO.Ports.SerialPort 类(简称 SerialPort)是 .NET 框架中用于串口通信的核心类,它提供了对串行端口的访问,允许我们进行数据的读写、配置串口参数以及处理事件等操作。

2. 串行通信简介

串口通信(Serial Communication)是通过单条数据线按顺序传输数据的通信方式,具有接线简单、成本低廉的特点。在工业控制、物联网设备、传感器等领域广泛应用,波特率范围常见为 9600 9600 9600 到 115200 115200 115200。

  • 在工业自动化、嵌入式系统开发等场景中,串口通信(Serial Communication)仍是硬件交互的核心技术之一。
  • 串行通信是一种数据按位顺序传输的通信方式,常用于连接各种串行设备。
  • C# 通过 System.IO.Ports.SerialPort 类为开发者提供了简洁高效的串口操作接口,支持参数配置、数据读写及事件处理等功能。

3. SerialPort核心功能

  • 端口配置参数:波特率、数据位、校验位、停止位等。
  • 数据读写操作:同步或异步发送/接收数据。
  • 事件驱动通信:通过 DataReceived 事件实时响应数据到达。

4. 典型应用场景

  • 工业设备通信
  • 传感器数据采集
  • 嵌入式系统交互

二、常用的属性和方法

1. 常用属性

属性名类型/说明
PortNamestring 串口名称(如 COM1、COM3)。
BaudRateint 波特率,表示数据传输速率。
常用的有9600、19200、38400、57600以及115200等
ParityParity 奇偶校验类型
如 None无奇偶校验、Even偶检验、Odd奇检验。
DataBitsint 数据位数。
常见数据位为8位,如需要特别设置,还可设置位5位,6位,7位
StopBitsStopBits 停止位数
如 None 不使用停止位One 1个停止位、Two2个停止位、OnePointFive1.5个停止位)。
HandshakeHandshake 握手协议(如 None、RequestToSend、XOnXOff)。
EncodingEncoding 数据编码方式(如 Encoding.UTF8)。
ReadTimeoutint 读取操作超时时间(毫秒),默认为 InfiniteTimeout。
WriteTimeoutint 写入操作超时时间(毫秒),默认为 InfiniteTimeout。
IsOpenbool 表示串口是否已打开。
NewLinestring 定义行结束符(如 "\r\n"),用于 ReadLine() 和 WriteLine()。
DtrEnablebool 控制数据终端就绪(DTR)信号状态。
RtsEnablebool 控制请求发送(RTS)信号状态。
ReceivedBytesThresholdint 触发 DataReceived 事件的最小字节数。

2. 常用方法

方法名说明
Open()打开串口。
Close()关闭已打开的串口。
Read(byte[] buffer, int offset, int count)从串口读取指定字节数到缓冲区。
ReadByte()读取单个字节(返回 int,范围 0-255,失败返回 -1)。
ReadLine()读取一行数据,直到遇到 NewLine 定义的结束符。
ReadExisting()读取接收缓冲区中所有可用数据(不阻塞)。
Write(string text)写入字符串到串口(自动按 Encoding 编码)。
Write(byte[] buffer, int offset, int count)写入字节数组到串口。
DiscardInBuffer()清空输入缓冲区中的数据。
DiscardOutBuffer()清空输出缓冲区中的数据。

3. 常用事件

事件名说明
DataReceived当接收到数据且字节数达到 ReceivedBytesThreshold 时触发。
ErrorReceived当串口发生错误(如奇偶校验错误)时触发。

4. 其他重要属性(扩展)

属性名类型/说明
BytesToReadint 接收缓冲区中已接收的字节数。
BytesToWriteint 输出缓冲区中待发送的字节数。
ReadBufferSizeint 设置输入缓冲区大小(默认 4096)。
WriteBufferSizeint 设置输出缓冲区大小(默认 2048)。

三、SerialPort 基础

1. 添加命名空间引用

在使用SerialPort类之前,需要先添加对System.IO.Ports命名空间的引用:

using System.IO.Ports;

2. 初始化与参数配置

串口通信的核心是正确配置参数,确保与硬件设备匹配,否则通信失败。

1)方式1:构造函数

using System.IO.Ports;
// 创建对象并指定端口名称(如COM3)
SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);

2)方式2:属性配置

// 可选:手动设置参数(适用于动态配置)
SerialPort serialPort = new SerialPort();
serialPort.PortName = "COM3";       // 端口号
serialPort.BaudRate = 9600;         // 波特率
serialPort.Parity = Parity.None;    // 校验位
serialPort.DataBits = 8;            // 数据位
serialPort.StopBits = StopBits.One; // 停止位

3. 打开与关闭串口

1)打开串口

在进行数据传输之前,需要打开串行端口:

serialPort.Open(); // 打开串口

优化一下:加上try catch 做异常处理

try 
{
    if (!serialPort.IsOpen)
    {
        serialPort.Open();
        Console.WriteLine("端口已打开");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"打开失败:{ex.Message}");
}

2)关闭串口

数据传输完成后,应及时关闭串行端口。务必在程序退出前调用Close

serialPort.Close(); // 关闭端口

优化一下:加上try catch 做异常处理

try 
{
    if (serialPort.IsOpen)
    {
        // 关闭端口
        serialPort.Close();
        Console.WriteLine("端口已关闭");
        
        // 销毁serialPort对象
		serialPort.Dispose();
    }
}
catch (Exception ex)
{
    Console.WriteLine($"关闭失败:{ex.Message}");
}

4. 读取和写入数据

1)写入数据

▶ 写入字符串数据
  • 使用Write()方法:将指定的字符串写入串行端口。
  • 使用WriteLine方法:将指定的字符串和 SerialPort.NewLine 值写入串行端口。
serialPort.Write("Hello, Serial!");
// 自动添加换行符:发送字符串并添加换行符(WriteLine)
serialPort.WriteLine("Hello, Serial Port!");
▶ 写入字节流(字节数组)数据
// 发送字节数组
byte[] data = Encoding.UTF8.GetBytes("Test Data");
serialPort.Write(data, 0, data.Length);
byte[] data = { 0x01, 0xFF, 0xAB }; // 十六进制数据
serialPort.Write(data, 0, data.Length); // 发送字节

2)读取数据

可以通过Read()、ReadLine()等方法从串口读取数据。

// 读取指定字节数
byte[] buffer = new byte[1024];
int bytesRead = serialPort.Read(buffer, 0, buffer.Length);
string received = Encoding.UTF8.GetString(buffer, 0, bytesRead);
// 读取一行数据(依赖 NewLine 配置)
string line = serialPort.ReadLine();

5. 处理数据接收事件

通过 DataReceived 事件实时接收串口数据。当有数据到达时,DataReceived事件会被触发。可以通过注册该事件来实时处理接收到的数据:

1)使用方式1

// 注册 DataReceived 事件
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    string received = sp.ReadExisting(); // 读取所有可用数据
    Console.WriteLine($"Received: {received}");
}

2)使用方式2

serialPort.DataReceived += SerialPort_DataReceived;
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
   		//首先实例化一个字节数组
        // 字节数组的大小:通过BytesToRead 属性,获取得到缓冲区中数据的字节数
       	byte[] buffer = new byte[serialPort.BytesToRead];
		//再通过 Read() 方法 读取缓存区内全部数据,并将数据写入字节数组中
       	serialPort.Read(buffer, 0, buffer.Length);
       	
       	// 解码
       	string received = Encoding.ASCII.GetString(buffer); 
       	Console.WriteLine(received);
}

3)使用方式3

serialPort.DataReceived += (sender, e) => 
{
    if (e.EventType == SerialData.Chars)
    {
        byte[] buffer = new byte[serialPort.BytesToRead];
        serialPort.Read(buffer, 0, buffer.Length);
        string data = Encoding.ASCII.GetString(buffer);
        Console.WriteLine($"收到数据:{data}");
    }
};

以上三种方式,基本原理一致,只是部分的读取数据方式和事件定义的方式稍有不同而已。

6. 完整示例

示例1:简单示例

using System;
using System.IO.Ports;
using System.Windows.Forms;
public class SerialPortDemo
{
	//配置并创建SerialPort实例
    private SerialPort sp = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
    public void Start()
    {
        try
        {
        	// 注册数据接收事件
            sp.DataReceived += DataReceivedHandler;
			
			// 打开串口
            sp.Open();
			//写入数据
            sp.WriteLine("Start Communication");
        }
        catch (Exception ex)
        {
            MessageBox.Show($"初始化失败:{ex.Message}");
        }
    }
	
	// 接收数据
    private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
    {
    	// 读取数据
        string data = sp.ReadExisting();
        Console.WriteLine($"实时数据:{data}");
    }
	// 关闭端口
    public void Stop() => sp.Close();
}

示例2:WinForm 示例

以下是一个完整的示例,展示如何在C#中使用SerialPort类进行串口通信。

using System;
using System.IO.Ports;
using System.Windows.Forms;
namespace SerialPortExample
{
    public partial class MainForm : Form
    {
        private SerialPort serialPort;
        public MainForm()
        {
            InitializeComponent();
            InitializeSerialPort();
        }
        private void InitializeSerialPort()
        {
            serialPort = new SerialPort();
            serialPort.PortName = "COM1";
            serialPort.BaudRate = 9600;
            serialPort.Parity = Parity.None;
            serialPort.DataBits = 8;
            serialPort.StopBits = StopBits.One;
            serialPort.Handshake = Handshake.None;
            serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
        }
        private void btnOpen_Click(object sender, EventArgs e)
        {
            try
            {
                if (!serialPort.IsOpen)
                {
                    serialPort.Open();
                    MessageBox.Show("串口已打开");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("打开串口时出错: " + ex.Message);
            }
        }
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (serialPort.IsOpen)
            {
                string message = txtSend.Text;
                serialPort.WriteLine(message);
                txtReceive.AppendText("发送: " + message + Environment.NewLine);
            }
            else
            {
                MessageBox.Show("请先打开串口");
            }
        }
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort sp = (SerialPort)sender;
            string indata = sp.ReadExisting();
            this.Invoke(new Action(() =>
            {
                txtReceive.AppendText("接收: " + indata);
            }));
        }
        private void btnClose_Click(object sender, EventArgs e)
        {
            if (serialPort.IsOpen)
            {
                serialPort.Close();
                MessageBox.Show("串口已关闭");
            }
        }
    }
}

示例3:扫码枪数据接收

SerialPort port = new("COM3", 115200, Parity.None, 8, StopBits.One);
port.DataReceived += (s, e) => 
{
    byte[] barcode = new byte[port.BytesToRead];
    port.Read(barcode, 0, barcode.Length);
    // 解析扫码枪数据(通常以回车结尾)
    if (barcode.Last() == 0x0D) 
    {
        string code = Encoding.ASCII.GetString(barcode);
        ProcessBarcode(code);
    }
};

四、SerialPort 进阶

1. 高级参数配置

1)配置超时时间

防止长时间阻塞(单位:毫秒)。

// 设置读取超时为 1000ms
serialPort.ReadTimeout = 1000;
// 设置写入超时
serialPort.WriteTimeout = 500;

2)缓冲区设置

ReceivedBytesThreshold:指定触发 DataReceived 事件的字节阈值,即触发 DataReceived 事件的最小字节数。

serialPort.ReceivedBytesThreshold = 2;	//默认值 为 1

3)其余参数配置

sp.Encoding = Encoding.ASCII; 	// 编码格式
sp.NewLine = "\r\n";       		// 换行符定义

2. 获取所有可用 COM 端口

可以通过SerialPort.GetPortNames()方法获取系统中所有可用的串行端口名称:

// 获取所有可用 COM 端口
string[] ports = SerialPort.GetPortNames();
foreach (var port in ports)
{
    Console.WriteLine(port);
}

3. ErrorReceived事件

当串行通信中发生错误(如帧错误/缓冲区溢出)时,ErrorReceived事件会被触发:

serialPort.ErrorReceived += new SerialErrorReceivedEventHandler(serialPort_ErrorReceived);
private static void serialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
    if ((e.EventType & SerialError.Frame) == SerialError.Frame)
    {
        Console.WriteLine("Frame error detected.");
    }
    if ((e.EventType & SerialError.Overrun) == SerialError.Overrun)
    {
        Console.WriteLine("Overrun error detected.");
    }
    if ((e.EventType & SerialError.RXParity) == SerialError.RXParity)
    {
        Console.WriteLine("Parity error detected.");
    }
}
sp.ErrorReceived += (sender, e) => 
{
    Console.WriteLine($"错误类型:{e.EventType}");
};

4. 异常处理

使用try-catch块捕获和处理可能的异常,确保程序的稳定性:

try
{
    serialPort.Open();
    serialPort.WriteLine("Hello, Serial Port!");
    serialPort.Close();
}
catch (Exception ex)
{
    Console.WriteLine("Error: " + ex.Message);
}
// 常见异常类型
try 
{
    // 串口操作代码
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine("端口访问被拒绝");
}
catch (TimeoutException ex) 
{
    Console.WriteLine("操作超时");
}
catch (IOException ex)
{
    Console.WriteLine("I/O错误");
}

5. 跨线程处理

为了能够实时响应串口接收到的数据,可以使用DataReceived事件。需要注意的是,该事件在单独的线程中触发,若要更新UI控件,涉及到跨线程,需要使用Invoke方法。

private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
   SerialPort sp = (SerialPort)sender;
   string received = sp.ReadExisting();
    // 数据接收后,通过委托更新UI(假设在 WinForms 中更新TextBox)
    this.Invoke(new Action(() =>
    {
        textBox.Text += received ;
    }));
}

6. 多线程处理

推荐使用生产者-消费者模式:

BlockingCollection<string> dataQueue = new BlockingCollection<string>();
// 生产者线程
void DataReceivedHandler(...)
{
    dataQueue.Add(receivedData);
}
// 消费者线程
Task.Run(() => 
{
    foreach (var data in dataQueue.GetConsumingEnumerable())
    {
        // 处理数据
    }
});

五、常见问题与注意事项

1. 注意事项

  • 参数一致性:确保双方设备的波特率、数据位、停止位、校验位一致。
  • 权限问题:在访问串口时,需要确保程序有相应的权限,否则可能会抛出异常。
  • 线程安全:在DataReceived事件中处理数据时,由于它是在单独的线程中触发的,若要更新UI控件,必须使用Invoke方法来确保线程安全
  • 异常处理:使用 try-catch 处理 Open()、Read()、Write() 可能抛出的异常。
  • 缓冲区管理:通过 DiscardInBuffer 和 DiscardOutBuffer 清空缓冲区,避免数据残留。
    • 避免重复打开同一端口,关闭前调用 sp.DiscardInBuffer() 清空缓存。
  • 硬件流控制
    在高速传输场景中启用 Handshake.RequestToSend,避免数据丢失。

2. 常见问题

  • 端口无法打开
    • 检查设备是否占用(如其他程序已连接)
    • 确认串口号是否正确(虚拟串口可能动态变化)
    • 异常处理:try...catch(IOException)捕获端口不存在错误
  • 数据接收不完整
    • 使用BytesToRead确保读取全部缓存数据
    • 添加延迟(如Thread.Sleep(50))等待完整帧
    • 硬件延迟:部分设备发送数据有间隔,需调整时序
  • 数据乱码
    • 发送/接收端波特率必须一致,否则数据乱码。
  • 字符编码问题
    • 若接收中文字符乱码,需设置:
    • serialPort.Encoding = Encoding.GetEncoding("GB2312")。
    • 或 serialPort.Encoding = Encoding.UTF8;

六、扩展

1. 跨平台方案(RJCP.SerialPortStream)

对于需要跨平台(Linux/macOS)的场景,推荐使用 RJCP.SerialPortStream 库(通过 NuGet 安装 RJCP.SerialPortStream):

using RJCP.IO.Ports;
SerialPortStream serialPort = new SerialPortStream("COM3");
serialPort.BaudRate = 9600;
serialPort.Open();
// 事件处理与读写方式与 SerialPort 类似
serialPort.DataReceived += (sender, e) => 
{
    byte[] data = new byte[serialPort.BytesToRead];
    serialPort.Read(data, 0, data.Length);
    Console.WriteLine(Encoding.UTF8.GetString(data));
};

2. 编码

使用串口通信的时候,会涉及到数据的编码格式,如果我们使用ASCII编码格式,但是接收的时候采用的是其他的编码格式,可能就会导致数据解析错误,因此在一些特定场景下我们需要规定好发送和接收数据的编码格式,这个时候就需要用到System.Text.Encoding 类中的编码解码的功能。
详见:C# System.Text.Encoding 使用详解​

3. 清空缓冲区的方法

关于 DiscardInBuffer / DiscardOutBuffer 的使用,详见:C# SerialPort 类中清空缓存区的方法。

4. Handshake 设置

常用握手协议:

  • Handshake.None:无握手。
  • Handshake.RequestToSend:RTS/CTS(请求发送/清除发送)。
  • Handshake.XOnXOff:软件流控制(XON/XOFF 字符)。
serialPort.Handshake = Handshake.RequestToSend;

详见:C# SerialPort 类中 Handshake 属性的作用

转自​https://blog.csdn.net/qq_39847278/article/details/146395034



该文章在 2026/6/4 11:26:08 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-1  粤公网安备44030602007207号