C#手写串口助手

实现思路

  1. 界面设计:包含串口参数选择(端口、波特率等)、连接控制按钮、数据收发区域、发送按钮等核心控件。
  2. 串口操作 :使用 System.IO.Ports.SerialPort 类实现串口的打开 / 关闭、数据发送、数据接收(异步接收避免界面卡死)。
  3. 功能完善:支持十六进制 / 文本模式收发、清空数据、自动添加换行符等常用功能。
  4. 异常处理:捕获串口操作中的常见异常(如端口被占用、参数错误等),提升程序稳定性。

完整代码实现

第一步:创建 WinForms 项目
  1. 打开 Visual Studio → 创建新项目 → 选择「Windows 窗体应用 (.NET Framework)」。
  2. 按以下布局添加控件(控件名称对应代码中的变量名):
控件类型 名称 作用
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(); // 释放资源
        }
    }
}

关键代码解释

  1. 串口初始化

    • SerialPort 是 C# 操作串口的核心类,需引用 System.IO.Ports 命名空间。
    • DataReceived 事件是异步触发的,用于接收串口数据,避免阻塞 UI 线程。
  2. 跨线程更新 UI

    • 串口数据接收事件在非 UI 线程执行,直接修改 TextBox 会报错,必须使用 Invoke 方法切换到 UI 线程。
  3. 十六进制收发处理

    • 接收:将字节数组转换为 XX XX XX 格式的字符串。
    • 发送:清理空格 / 短横线,将十六进制字符串转换为字节数组(需确保长度为偶数)。
  4. 异常处理

    • 捕获串口打开、数据收发中的异常,通过 MessageBox 提示用户,避免程序崩溃。
    • 窗体关闭时主动关闭串口并释放资源,防止端口被占用。

使用说明

  1. 编译运行项目后,程序会自动加载系统中可用的串口和默认波特率(9600)。
  2. 选择需要连接的串口和波特率,点击「打开串口」按钮。
  3. 发送数据:
    • 文本模式:直接输入文字,勾选「自动加换行符」可在发送时添加 \r\n
    • 十六进制模式:输入如 01 02 03010203 格式的十六进制字符串。
  4. 接收数据:勾选「十六进制接收」可按十六进制格式显示,否则按文本显示。
  5. 点击「清空接收」可清空接收区域,点击「关闭串口」可断开连接。

总结

  1. 核心类SerialPort 是 C# 操作串口的核心,需掌握其 Open/Close 方法、DataReceived 事件、Write/Read 方法。
  2. UI 线程安全 :串口事件在非 UI 线程执行,更新界面必须使用 Invoke 方法,否则会抛出跨线程异常。
  3. 功能扩展方向:可在此基础上添加「自动发送」「保存接收数据」「串口参数自定义(校验位 / 停止位)」等功能,满足更多场景需求。
相关推荐
Kyln.Wu2 小时前
【python实用小脚本-292】[HR揭秘]手工党点名10分钟的终结者|Python版Zoom自动签到+名单导出加速器(建议收藏)
开发语言·python·swift
普通网友2 小时前
PictureSelector 相册全白不显示问题
java·开发语言
普通网友2 小时前
用 Next.js 15 做图片查看网站:图片双击放大的交互坑与修复
开发语言·javascript·交互
独自破碎E2 小时前
kafka中的时间轮实现
java·开发语言
向宇it2 小时前
2025年技术总结 | 在Unity游戏开发路上的持续探索与沉淀
游戏·unity·c#·游戏引擎
程序员阿鹏2 小时前
如何保证写入Redis的数据不重复
java·开发语言·数据结构·数据库·redis·缓存
JAY_LIN——82 小时前
字符串函数(strncpy/cat/cmp、strstr、strtok、strerror)
c语言·开发语言
lly2024062 小时前
C# 数据类型
开发语言
树欲静而风不止慢一点吧2 小时前
Qt5/6版本对应的Emscripten版本
开发语言·qt