C# SerialPort 使用详解
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
前言在工业控制、物联网、嵌入式开发等领域,串口通信(Serial Port Communication)是连接串行设备(如条码扫描器、GPS接收器等)与计算机的重要手段。C# 提供了内置的 一、什么是 SerialPort?1. 定义
2. 串行通信简介串口通信(Serial Communication)是通过单条数据线按顺序传输数据的通信方式,具有接线简单、成本低廉的特点。在工业控制、物联网设备、传感器等领域广泛应用,波特率范围常见为 9600 9600 9600 到 115200 115200 115200。
3.
|
| 属性名 | 类型/说明 |
|---|---|
| PortName | string 串口名称(如 COM1、COM3)。 |
| BaudRate | int 波特率,表示数据传输速率。常用的有 9600、19200、38400、57600以及115200等 |
| Parity | Parity 奇偶校验类型如 None无奇偶校验、Even偶检验、Odd奇检验。 |
| DataBits | int 数据位数。常见数据位为8位,如需要特别设置,还可设置位5位,6位,7位 |
| StopBits | StopBits 停止位数如 None 不使用停止位One 1个停止位、Two2个停止位、OnePointFive1.5个停止位)。 |
| Handshake | Handshake 握手协议(如 None、RequestToSend、XOnXOff)。 |
| Encoding | Encoding 数据编码方式(如 Encoding.UTF8)。 |
| ReadTimeout | int 读取操作超时时间(毫秒),默认为 InfiniteTimeout。 |
| WriteTimeout | int 写入操作超时时间(毫秒),默认为 InfiniteTimeout。 |
| IsOpen | bool 表示串口是否已打开。 |
| NewLine | string 定义行结束符(如 "\r\n"),用于 ReadLine() 和 WriteLine()。 |
| DtrEnable | bool 控制数据终端就绪(DTR)信号状态。 |
| RtsEnable | bool 控制请求发送(RTS)信号状态。 |
| ReceivedBytesThreshold | int 触发 DataReceived 事件的最小字节数。 |
| 方法名 | 说明 |
|---|---|
| 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() | 清空输出缓冲区中的数据。 |
| 事件名 | 说明 |
|---|---|
| DataReceived | 当接收到数据且字节数达到 ReceivedBytesThreshold 时触发。 |
| ErrorReceived | 当串口发生错误(如奇偶校验错误)时触发。 |
| 属性名 | 类型/说明 |
|---|---|
| BytesToRead | int 接收缓冲区中已接收的字节数。 |
| BytesToWrite | int 输出缓冲区中待发送的字节数。 |
| ReadBufferSize | int 设置输入缓冲区大小(默认 4096)。 |
| WriteBufferSize | int 设置输出缓冲区大小(默认 2048)。 |
在使用SerialPort类之前,需要先添加对System.IO.Ports命名空间的引用:
using System.IO.Ports;
串口通信的核心是正确配置参数,确保与硬件设备匹配,否则通信失败。
using System.IO.Ports;
// 创建对象并指定端口名称(如COM3)
SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
// 可选:手动设置参数(适用于动态配置)
SerialPort serialPort = new SerialPort();
serialPort.PortName = "COM3"; // 端口号
serialPort.BaudRate = 9600; // 波特率
serialPort.Parity = Parity.None; // 校验位
serialPort.DataBits = 8; // 数据位
serialPort.StopBits = StopBits.One; // 停止位
在进行数据传输之前,需要打开串行端口:
serialPort.Open(); // 打开串口
优化一下:加上try catch 做异常处理
try
{
if (!serialPort.IsOpen)
{
serialPort.Open();
Console.WriteLine("端口已打开");
}
}
catch (Exception ex)
{
Console.WriteLine($"打开失败:{ex.Message}");
}
数据传输完成后,应及时关闭串行端口。务必在程序退出前调用Close
serialPort.Close(); // 关闭端口
优化一下:加上try catch 做异常处理
try
{
if (serialPort.IsOpen)
{
// 关闭端口
serialPort.Close();
Console.WriteLine("端口已关闭");
// 销毁serialPort对象
serialPort.Dispose();
}
}
catch (Exception ex)
{
Console.WriteLine($"关闭失败:{ex.Message}");
}
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); // 发送字节
可以通过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();
通过 DataReceived 事件实时接收串口数据。当有数据到达时,DataReceived事件会被触发。可以通过注册该事件来实时处理接收到的数据:
// 注册 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}");
}
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);
}
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}");
}
};
以上三种方式,基本原理一致,只是部分的读取数据方式和事件定义的方式稍有不同而已。
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();
}
以下是一个完整的示例,展示如何在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("串口已关闭");
}
}
}
}
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);
}
};
防止长时间阻塞(单位:毫秒)。
// 设置读取超时为 1000ms
serialPort.ReadTimeout = 1000;
// 设置写入超时
serialPort.WriteTimeout = 500;
ReceivedBytesThreshold:指定触发 DataReceived 事件的字节阈值,即触发 DataReceived 事件的最小字节数。
serialPort.ReceivedBytesThreshold = 2; //默认值 为 1
sp.Encoding = Encoding.ASCII; // 编码格式
sp.NewLine = "\r\n"; // 换行符定义
可以通过SerialPort.GetPortNames()方法获取系统中所有可用的串行端口名称:
// 获取所有可用 COM 端口
string[] ports = SerialPort.GetPortNames();
foreach (var port in ports)
{
Console.WriteLine(port);
}
当串行通信中发生错误(如帧错误/缓冲区溢出)时,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}");
};
使用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错误");
}
为了能够实时响应串口接收到的数据,可以使用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 ;
}));
}
推荐使用生产者-消费者模式:
BlockingCollection<string> dataQueue = new BlockingCollection<string>();
// 生产者线程
void DataReceivedHandler(...)
{
dataQueue.Add(receivedData);
}
// 消费者线程
Task.Run(() =>
{
foreach (var data in dataQueue.GetConsumingEnumerable())
{
// 处理数据
}
});
DataReceived事件中处理数据时,由于它是在单独的线程中触发的,若要更新UI控件,必须使用Invoke方法来确保线程安全try-catch 处理 Open()、Read()、Write() 可能抛出的异常。DiscardInBuffer 和 DiscardOutBuffer 清空缓冲区,避免数据残留。
sp.DiscardInBuffer() 清空缓存。Handshake.RequestToSend,避免数据丢失。try...catch(IOException)捕获端口不存在错误BytesToRead确保读取全部缓存数据Thread.Sleep(50))等待完整帧serialPort.Encoding = Encoding.GetEncoding("GB2312")。serialPort.Encoding = Encoding.UTF8;对于需要跨平台(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));
};
使用串口通信的时候,会涉及到数据的编码格式,如果我们使用ASCII编码格式,但是接收的时候采用的是其他的编码格式,可能就会导致数据解析错误,因此在一些特定场景下我们需要规定好发送和接收数据的编码格式,这个时候就需要用到System.Text.Encoding 类中的编码解码的功能。
详见:C# System.Text.Encoding 使用详解
关于 DiscardInBuffer / DiscardOutBuffer 的使用,详见:C# SerialPort 类中清空缓存区的方法。
常用握手协议:
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