实现思路
- 界面设计:包含串口参数选择(端口、波特率等)、连接控制按钮、数据收发区域、发送按钮等核心控件。
- 串口操作 :使用
System.IO.Ports.SerialPort类实现串口的打开 / 关闭、数据发送、数据接收(异步接收避免界面卡死)。 - 功能完善:支持十六进制 / 文本模式收发、清空数据、自动添加换行符等常用功能。
- 异常处理:捕获串口操作中的常见异常(如端口被占用、参数错误等),提升程序稳定性。
完整代码实现
第一步:创建 WinForms 项目
- 打开 Visual Studio → 创建新项目 → 选择「Windows 窗体应用 (.NET Framework)」。
- 按以下布局添加控件(控件名称对应代码中的变量名):
| 控件类型 | 名称 | 作用 |
|---|---|---|
| ComboBox | cboPortName | 选择串口名称(COM1/COM2) |
| ComboBox | cboBaudRate | 选择波特率(9600/115200) |
| Button | btnOpenClose | 打开 / 关闭串口 |
| TextBox | txtReceive | 接收数据显示区域(多行) |
| TextBox | txtSend | 发送数据输入框 |
| Button | btnSend | 发送数据按钮 |
| CheckBox | chkHexReceive | 十六进制接收 |
| CheckBox | chkHexSend | 十六进制发送 |
| CheckBox | chkAutoNewLine | 发送时自动加换行符 |
| Button | btnClearReceive | 清空接收区域 |
第二步:完整代码(Form1.cs)
cs
using System;
using System.IO.Ports;
using System.Text;
using System.Windows.Forms;
namespace SerialPortAssistant
{
public partial class Form1 : Form
{
// 声明串口对象
private SerialPort _serialPort;
public Form1()
{
InitializeComponent();
// 初始化串口助手
InitSerialPortAssistant();
}
/// <summary>
/// 初始化串口助手(加载参数、绑定事件)
/// </summary>
private void InitSerialPortAssistant()
{
// 1. 初始化串口对象
_serialPort = new SerialPort();
_serialPort.DataReceived += SerialPort_DataReceived; // 绑定数据接收事件
// 2. 加载常用波特率
cboBaudRate.Items.AddRange(new string[] { "9600", "19200", "38400", "115200" });
cboBaudRate.SelectedIndex = 0; // 默认选中9600
// 3. 加载可用串口列表
LoadAvailablePorts();
// 4. 设置接收框样式(多行、滚动条、只读)
txtReceive.Multiline = true;
txtReceive.ScrollBars = ScrollBars.Vertical;
txtReceive.ReadOnly = true;
txtReceive.WordWrap = false;
// 5. 设置发送框多行
txtSend.Multiline = true;
txtSend.ScrollBars = ScrollBars.Vertical;
// 6. 按钮初始状态
btnOpenClose.Text = "打开串口";
btnSend.Enabled = false;
}
/// <summary>
/// 加载可用串口列表
/// </summary>
private void LoadAvailablePorts()
{
cboPortName.Items.Clear();
// 获取当前系统可用的串口名称
string[] ports = SerialPort.GetPortNames();
if (ports.Length > 0)
{
cboPortName.Items.AddRange(ports);
cboPortName.SelectedIndex = 0; // 默认选中第一个
}
else
{
cboPortName.Items.Add("无可用串口");
cboPortName.SelectedIndex = 0;
btnOpenClose.Enabled = false; // 无串口时禁用打开按钮
}
}
/// <summary>
/// 打开/关闭串口按钮点击事件
/// </summary>
private void btnOpenClose_Click(object sender, EventArgs e)
{
try
{
if (!_serialPort.IsOpen)
{
// 配置串口参数
_serialPort.PortName = cboPortName.SelectedItem.ToString();
_serialPort.BaudRate = int.Parse(cboBaudRate.SelectedItem.ToString());
_serialPort.Parity = Parity.None; // 校验位:无
_serialPort.DataBits = 8; // 数据位:8
_serialPort.StopBits = StopBits.One; // 停止位:1
_serialPort.Handshake = Handshake.None; // 流控:无
_serialPort.ReadTimeout = 500; // 读取超时
// 打开串口
_serialPort.Open();
// 更新界面状态
btnOpenClose.Text = "关闭串口";
btnSend.Enabled = true;
cboPortName.Enabled = false;
cboBaudRate.Enabled = false;
MessageBox.Show($"成功打开串口:{_serialPort.PortName}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
// 关闭串口
_serialPort.Close();
// 更新界面状态
btnOpenClose.Text = "打开串口";
btnSend.Enabled = false;
cboPortName.Enabled = true;
cboBaudRate.Enabled = true;
}
}
catch (Exception ex)
{
MessageBox.Show($"串口操作失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 串口数据接收事件(异步触发,需跨线程更新UI)
/// </summary>
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
if (_serialPort.IsOpen)
{
// 读取接收缓冲区的数据
byte[] receiveBytes = new byte[_serialPort.BytesToRead];
_serialPort.Read(receiveBytes, 0, receiveBytes.Length);
string receiveStr = string.Empty;
if (chkHexReceive.Checked)
{
// 十六进制模式:转换为 00-00-00 格式
receiveStr = BitConverter.ToString(receiveBytes).Replace("-", " ") + " ";
}
else
{
// 文本模式:转换为UTF8字符串
receiveStr = Encoding.UTF8.GetString(receiveBytes);
}
// 跨线程更新UI(必须使用Invoke)
if (txtReceive.InvokeRequired)
{
txtReceive.Invoke(new Action(() =>
{
txtReceive.AppendText(receiveStr);
// 滚动到最后一行
txtReceive.SelectionStart = txtReceive.Text.Length;
txtReceive.ScrollToCaret();
}));
}
else
{
txtReceive.AppendText(receiveStr);
txtReceive.SelectionStart = txtReceive.Text.Length;
txtReceive.ScrollToCaret();
}
}
}
catch (Exception ex)
{
MessageBox.Show($"数据接收失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 发送数据按钮点击事件
/// </summary>
private void btnSend_Click(object sender, EventArgs e)
{
try
{
if (!_serialPort.IsOpen)
{
MessageBox.Show("串口未打开,请先打开串口!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string sendContent = txtSend.Text.Trim();
if (string.IsNullOrEmpty(sendContent))
{
MessageBox.Show("发送内容不能为空!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
byte[] sendBytes;
if (chkHexSend.Checked)
{
// 十六进制发送:先清理非十六进制字符,再转换为字节数组
sendContent = sendContent.Replace(" ", "").Replace("-", "");
if (sendContent.Length % 2 != 0)
{
MessageBox.Show("十六进制数据长度必须为偶数!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
sendBytes = new byte[sendContent.Length / 2];
for (int i = 0; i < sendContent.Length; i += 2)
{
sendBytes[i / 2] = Convert.ToByte(sendContent.Substring(i, 2), 16);
}
}
else
{
// 文本发送:转换为UTF8字节数组
sendBytes = Encoding.UTF8.GetBytes(sendContent);
// 自动添加换行符
if (chkAutoNewLine.Checked)
{
sendBytes = CombineBytes(sendBytes, Encoding.UTF8.GetBytes(Environment.NewLine));
}
}
// 发送数据
_serialPort.Write(sendBytes, 0, sendBytes.Length);
}
catch (Exception ex)
{
MessageBox.Show($"数据发送失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 合并两个字节数组(用于添加换行符)
/// </summary>
private byte[] CombineBytes(byte[] a, byte[] b)
{
byte[] result = new byte[a.Length + b.Length];
Array.Copy(a, 0, result, 0, a.Length);
Array.Copy(b, 0, result, a.Length, b.Length);
return result;
}
/// <summary>
/// 清空接收区域按钮点击事件
/// </summary>
private void btnClearReceive_Click(object sender, EventArgs e)
{
txtReceive.Clear();
}
/// <summary>
/// 窗体关闭时确保串口关闭
/// </summary>
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
_serialPort.Dispose(); // 释放资源
}
}
}
关键代码解释
-
串口初始化:
SerialPort是 C# 操作串口的核心类,需引用System.IO.Ports命名空间。DataReceived事件是异步触发的,用于接收串口数据,避免阻塞 UI 线程。
-
跨线程更新 UI:
- 串口数据接收事件在非 UI 线程执行,直接修改
TextBox会报错,必须使用Invoke方法切换到 UI 线程。
- 串口数据接收事件在非 UI 线程执行,直接修改
-
十六进制收发处理:
- 接收:将字节数组转换为
XX XX XX格式的字符串。 - 发送:清理空格 / 短横线,将十六进制字符串转换为字节数组(需确保长度为偶数)。
- 接收:将字节数组转换为
-
异常处理:
- 捕获串口打开、数据收发中的异常,通过
MessageBox提示用户,避免程序崩溃。 - 窗体关闭时主动关闭串口并释放资源,防止端口被占用。
- 捕获串口打开、数据收发中的异常,通过
使用说明
- 编译运行项目后,程序会自动加载系统中可用的串口和默认波特率(9600)。
- 选择需要连接的串口和波特率,点击「打开串口」按钮。
- 发送数据:
- 文本模式:直接输入文字,勾选「自动加换行符」可在发送时添加
\r\n。 - 十六进制模式:输入如
01 02 03或010203格式的十六进制字符串。
- 文本模式:直接输入文字,勾选「自动加换行符」可在发送时添加
- 接收数据:勾选「十六进制接收」可按十六进制格式显示,否则按文本显示。
- 点击「清空接收」可清空接收区域,点击「关闭串口」可断开连接。
总结
- 核心类 :
SerialPort是 C# 操作串口的核心,需掌握其Open/Close方法、DataReceived事件、Write/Read方法。 - UI 线程安全 :串口事件在非 UI 线程执行,更新界面必须使用
Invoke方法,否则会抛出跨线程异常。 - 功能扩展方向:可在此基础上添加「自动发送」「保存接收数据」「串口参数自定义(校验位 / 停止位)」等功能,满足更多场景需求。