本页说明在 .NET Framework 4.8 窗体(或控制台)项目中用 System.IO.Ports.SerialPort 枚举端口、开关串口、收发文本/字节。目标框架以 LCode 默认的 .NET 4.8 为准;示例写法面向 C# 5.0 与 VB.NET(勿用 C# 6+ 语法如字符串插值、nameof)。操作文案以 LCode 中文界面为准。UI 线程与后台回调见 异步、同步与多线程

选用哪种方式

方式 命名空间 / 程序集 适用
SerialPort(推荐) System.IO.Ports(程序集 System RS-232 / USB 转串口等常见串口设备。框架自带,无需第三方包
工具箱组件「串行端口」 同上;设计器组件名 SerialPort 拖到窗体后可在属性窗口设 PortNameBaudRate 等;底层仍是 SerialPort
第三方串口库 另引 DLL 需特殊协议栈时再考虑;本页不展开。LCode 主界面 NuGet暂不支持,待更新完善

在 LCode 中使用

窗体应用入门 建好「Windows 应用程序」后,任选其一:

  1. 代码创建(常用):窗体项目默认已引用 System,直接 using System.IO.Ports; / Imports System.IO.Ports 即可。一般不必再「添加引用」。
  2. 工具箱拖入:打开窗体设计器 → 工具箱组件区找到串行端口(英文类型名 SerialPort)→ 拖到窗体。组件出现在窗体下方托盘;在属性窗口设置端口名、波特率等。
  3. 若编译报找不到 System.IO.Ports:项目 → 添加引用.NET集合浏览器,确认勾选 System → 确定。详见 引用 DLL
  4. 菜单 编译 → 编译解决方案F8),确认无命名空间错误。
提示:本机须有可用 COM 口(物理串口或 USB 转串口驱动)。枚举结果因驱动与权限而异;虚拟串口对(如 com0com)便于无硬件联调。

常用属性

属性 类型 说明
PortName string 端口名,如 COM1COM3(须与设备管理器一致)
BaudRate int 波特率,常见 9600、115200;须与对端一致
DataBits int 数据位,多为 8
Parity Parity 校验:None / Odd / Even / Mark / Space
StopBits StopBits 停止位:One / Two / OnePointFive
Handshake Handshake 流控:NoneXOnXOffRequestToSend
Encoding Encoding 文本读写编码;默认常为 ASCII。中文设备可按协议设 Encoding.GetEncoding("GB2312")
ReadTimeout / WriteTimeout int 读写超时(毫秒);-1 表示无限等待(慎用,易卡住 UI)
IsOpen bool 是否已打开(只读)
BytesToRead / BytesToWrite int 接收/发送缓冲区中尚未处理的字节数
DtrEnable / RtsEnable bool 部分设备上电/握手依赖 DTR/RTS,按硬件手册设置
NewLine string ReadLine / WriteLine 使用的行结束符,默认 \n

常用方法

方法 说明
GetPortNames()(静态) 返回本机当前串口名字符串数组
Open() / Close() 打开 / 关闭端口。打开前须设好 PortName 与波特率等参数
Write(string) / WriteLine(string) Encoding 写文本;WriteLine 追加 NewLine
Write(byte[], int, int) 写原始字节(协议帧常用)
Read(byte[], int, int) / ReadExisting() / ReadLine() 读字节或已缓冲文本;无数据且设了超时会抛 TimeoutException
DiscardInBuffer() / DiscardOutBuffer() 清空收/发缓冲区
Dispose() 释放底层句柄;窗体关闭时务必关闭并释放,避免端口被占用

常用事件

事件 说明
DataReceived 接收缓冲区有数据时触发。回调不在 UI 线程,更新控件须 Invoke / BeginInvoke
ErrorReceived 帧错误、溢出等;可记日志或提示
PinChanged CTS/DSR/CD 等引脚状态变化(按需)
注意:DataReceived 中直接改 TextBox.Text 会引发跨线程异常。务必用 Control.Invoke(或先判断 InvokeRequired)。通则见异步专页。

枚举端口并打开 / 关闭

窗体上放 ComboBox cmbPortsButton btnRefreshButton btnOpenButton btnClose。端口名与波特率按设备手册修改。

C#
using System;
using System.IO.Ports;
using System.Windows.Forms;

public partial class Form1 : Form
{
    private SerialPort port;

    public Form1()
    {
        InitializeComponent();
        port = new SerialPort();
        port.BaudRate = 9600;
        port.DataBits = 8;
        port.Parity = Parity.None;
        port.StopBits = StopBits.One;
        port.Handshake = Handshake.None;
        port.ReadTimeout = 500;
        port.WriteTimeout = 500;
        RefreshPorts();
    }

    private void RefreshPorts()
    {
        cmbPorts.Items.Clear();
        string[] names = SerialPort.GetPortNames();
        Array.Sort(names);
        cmbPorts.Items.AddRange(names);
        if (cmbPorts.Items.Count > 0)
            cmbPorts.SelectedIndex = 0;
    }

    private void btnRefresh_Click(object sender, EventArgs e)
    {
        RefreshPorts();
    }

    private void btnOpen_Click(object sender, EventArgs e)
    {
        if (port.IsOpen)
            return;
        if (cmbPorts.SelectedItem == null)
        {
            MessageBox.Show("请选择端口。");
            return;
        }
        try
        {
            port.PortName = cmbPorts.SelectedItem.ToString();
            port.Open();
            MessageBox.Show("已打开 " + port.PortName);
        }
        catch (Exception ex)
        {
            MessageBox.Show("打开失败: " + ex.Message);
        }
    }

    private void btnClose_Click(object sender, EventArgs e)
    {
        if (port.IsOpen)
            port.Close();
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        if (port != null)
        {
            if (port.IsOpen)
                port.Close();
            port.Dispose();
            port = null;
        }
        base.OnFormClosing(e);
    }
}
VB.NET
Imports System.IO.Ports
Imports System.Windows.Forms

Public Class Form1
    Private port As SerialPort

    Public Sub New()
        InitializeComponent()
        port = New SerialPort()
        port.BaudRate = 9600
        port.DataBits = 8
        port.Parity = Parity.None
        port.StopBits = StopBits.One
        port.Handshake = Handshake.None
        port.ReadTimeout = 500
        port.WriteTimeout = 500
        RefreshPorts()
    End Sub

    Private Sub RefreshPorts()
        cmbPorts.Items.Clear()
        Dim names As String() = SerialPort.GetPortNames()
        Array.Sort(names)
        cmbPorts.Items.AddRange(names)
        If cmbPorts.Items.Count > 0 Then
            cmbPorts.SelectedIndex = 0
        End If
    End Sub

    Private Sub btnRefresh_Click(sender As Object, e As EventArgs) Handles btnRefresh.Click
        RefreshPorts()
    End Sub

    Private Sub btnOpen_Click(sender As Object, e As EventArgs) Handles btnOpen.Click
        If port.IsOpen Then Return
        If cmbPorts.SelectedItem Is Nothing Then
            MessageBox.Show("请选择端口。")
            Return
        End If
        Try
            port.PortName = cmbPorts.SelectedItem.ToString()
            port.Open()
            MessageBox.Show("已打开 " & port.PortName)
        Catch ex As Exception
            MessageBox.Show("打开失败: " & ex.Message)
        End Try
    End Sub

    Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click
        If port.IsOpen Then port.Close()
    End Sub

    Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
        If port IsNot Nothing Then
            If port.IsOpen Then port.Close()
            port.Dispose()
            port = Nothing
        End If
        MyBase.OnFormClosing(e)
    End Sub
End Class

发送与同步读取

按钮 btnSendTextBox txtSend / txtRecv。同步 ReadExisting 适合「发完立刻读」的短应答;长时间监听请用下一节 DataReceived

C#
private void btnSend_Click(object sender, EventArgs e)
{
    if (!port.IsOpen)
    {
        MessageBox.Show("请先打开串口。");
        return;
    }
    try
    {
        // 文本
        port.Write(txtSend.Text);

        // 或写字节帧:
        // byte[] frame = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01 };
        // port.Write(frame, 0, frame.Length);

        System.Threading.Thread.Sleep(50); // 给对端一点应答时间(按协议调整)
        string reply = port.ReadExisting();
        txtRecv.AppendText(reply);
    }
    catch (TimeoutException)
    {
        MessageBox.Show("读取超时。");
    }
    catch (Exception ex)
    {
        MessageBox.Show("收发失败: " + ex.Message);
    }
}
VB.NET
Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
    If Not port.IsOpen Then
        MessageBox.Show("请先打开串口。")
        Return
    End If
    Try
        ' 文本
        port.Write(txtSend.Text)

        ' 或写字节帧:
        ' Dim frame As Byte() = New Byte() {&H1, &H3, &H0, &H0, &H0, &H1}
        ' port.Write(frame, 0, frame.Length)

        System.Threading.Thread.Sleep(50) ' 给对端一点应答时间(按协议调整)
        Dim reply As String = port.ReadExisting()
        txtRecv.AppendText(reply)
    Catch ex As TimeoutException
        MessageBox.Show("读取超时。")
    Catch ex As Exception
        MessageBox.Show("收发失败: " & ex.Message)
    End Try
End Sub

DataReceived:异步收数并刷新界面

在构造函数或打开前订阅事件。收到数据后用 Invoke 回到 UI 线程追加文本。

C#
// 在构造函数中:
port.DataReceived += port_DataReceived;

private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        string data = port.ReadExisting();
        if (string.IsNullOrEmpty(data))
            return;
        if (txtRecv.InvokeRequired)
        {
            txtRecv.BeginInvoke(new Action(delegate
            {
                txtRecv.AppendText(data);
            }));
        }
        else
        {
            txtRecv.AppendText(data);
        }
    }
    catch (Exception)
    {
        // 关闭过程中可能抛异常,按需记录
    }
}
VB.NET
' 在构造函数中:
AddHandler port.DataReceived, AddressOf port_DataReceived

Private Sub port_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
    Try
        Dim data As String = port.ReadExisting()
        If String.IsNullOrEmpty(data) Then Return
        If txtRecv.InvokeRequired Then
            txtRecv.BeginInvoke(New Action(Sub()
                                               txtRecv.AppendText(data)
                                           End Sub))
        Else
            txtRecv.AppendText(data)
        End If
    Catch ex As Exception
        ' 关闭过程中可能抛异常,按需记录
    End Try
End Sub
提示:DataReceived 不保证「一帧完整协议」一次到齐。二进制协议应自行按长度/校验拼包;文本协议可缓冲到遇到分隔符再处理。

注意与易错点

说明
端口占用 同一 COM 口同时只能被一个进程打开;关闭应用或调用 Close/Dispose 后再用其他工具访问。
参数不一致 波特率、数据位、校验、停止位任一与对端不符会导致乱码或无应答。
UI 阻塞 在按钮事件里长时间 Read/Sleep 会假死窗体;监听场景优先 DataReceived,或把阻塞读放到后台线程。
跨线程 DataReceived → 更新控件必须 Invoke/BeginInvoke
打开后再改参数 多数属性须在 Open 之前设置;已打开时改 PortName 等会抛异常。
C# 语言版本 LCode / .NET 4.8 按 C# 5.0:勿用 $"…"nameof?. 等。

相关阅读