You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForm与Arduino FTDI232双向串口通信问题求解

问题根因

所有异常的核心诱因是串口对象的错误使用逻辑:

  • 串口通信全程只需要初始化1次SerialPort实例、保持长连接即可,不需要每次发送指令都重新实例化对象、反复开关串口
  • 现有代码在Form_Load中已经初始化了绑定数据接收事件的串口实例,但点击LED控制按钮时又重新创建新的SerialPort对象,发完指令就关闭连接,直接冲掉了原有正常工作的串口实例,既会导致DataReceived接收事件失效,也会造成串口资源被异常占用无法释放。

对应修复方案

1. 应用退出自动释放串口

为窗体添加FormClosing生命周期事件,在窗口触发关闭动作时主动检测串口状态,关闭并释放串口资源,无需用户手动操作。

2. 收发状态切换异常修复

删除LED开/关按钮事件中重复创建SerialPort、执行Open/Close串口的逻辑,直接复用Form_Load阶段初始化完成的全局myPort实例发送数据即可。串口保持长连接状态下不存在收发状态切换的冲突,不会出现通信拒绝问题。

3. 发送LED指令后接收失效修复

该问题完全是发送指令时重复创建串口、关闭原有连接导致的,复用全局串口实例发送数据后,DataReceived事件会持续处于监听状态,不会丢失Arduino回传的数据。

修正后可直接运行的代码
public partial class Form1 : Form
{
    public SerialPort myPort;
    private DateTime dateTime;
    private string inData;
    const string com = "COM8";
       
    public Form1()
    {
        InitializeComponent();
        // 注册窗体关闭事件,程序退出时自动回收串口资源
        this.FormClosing += Form1_FormClosing;
    }

    private void start_btn_Click(object sender, EventArgs e) // 点亮LED
    {
        if (myPort == null || !myPort.IsOpen)
        {
            MessageBox.Show("串口未连接", "操作错误");
            return;
        }
        try
        {
            myPort.WriteLine("A");
            error_tb.Text = "";
        }
        catch (Exception exc)
        {
            MessageBox.Show(exc.Message, "发送失败");
        }
    }

    private void stop_btn_Click(object sender, EventArgs e) // 熄灭LED
    {
        if (myPort == null || !myPort.IsOpen)
        {
            MessageBox.Show("串口未连接", "操作错误");
            return;
        }
        try
        {
            myPort.WriteLine("B");
            error_tb.Text = "";
        }
        catch (Exception exc2)
        {
            MessageBox.Show(exc2.Message, "发送失败");
        }
    }

    void MyPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        try
        {
            inData = myPort.ReadLine();
            this.Invoke(new EventHandler(displayData_event));
        }
        catch (Exception ex)
        {
            // 跨线程更新错误提示,避免接收异常导致程序崩溃
            this.Invoke((Action)(() => { error_tb.Text = $"接收异常: {ex.Message}"; }));
        }
    }

    private void displayData_event(object sender, EventArgs e)
    {
        dateTime = DateTime.Now;
        string time = $"{dateTime.Hour}:{dateTime.Minute}:{dateTime.Second}";
        // 追加日志而非覆盖,保留所有历史接收记录
        values_tb.AppendText($"{time}\t\t\t\t\t{inData}\r\n");
    }

    private void Form1_Load(object sender, EventArgs e) // 程序启动初始化串口
    {
        myPort = new SerialPort();
        myPort.BaudRate = 9600;
        myPort.PortName = com;
        myPort.Parity = Parity.None;
        myPort.DataBits = 8;
        myPort.StopBits = StopBits.One;
        // 配置读写超时,避免ReadLine永久阻塞线程
        myPort.ReadTimeout = 500;
        myPort.WriteTimeout = 500;
        myPort.DataReceived += MyPort_DataReceived;

        try
        {
            myPort.Open();
            values_tb.Text = "";
            error_tb.Text = "串口连接成功";
        }
        catch (Exception exc3)
        {
            MessageBox.Show(exc3.Message, "串口打开失败");
        }
    }

    // 窗口关闭时自动释放串口资源
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (myPort == null) return;
        try
        {
            if (myPort.IsOpen) myPort.Close();
            myPort.Dispose();
        }
        catch
        {
            // 退出阶段的串口关闭异常直接忽略,不阻塞程序退出流程
        }
    }

    private void exit_btn_Click(object sender, EventArgs e) // 退出程序
    {
        Application.Exit();
    }

    private void button2_Click(object sender, EventArgs e) // 手动关闭串口(可选保留)
    {
        try
        {
            if (myPort != null && myPort.IsOpen)
            {
                myPort.Close();
                error_tb.Text = "串口已手动断开";
            }
        }
        catch (Exception exc4)
        {
            MessageBox.Show(exc4.Message, "关闭失败");
        }
    }
}

配套配置注意事项
  • 代码中新增了串口读写超时配置,避免ReadLine()方法因为等不到行结束标志永久卡死后台线程
  • 数据显示区改用AppendText方法追加日志,原有直接赋值Text的写法会覆盖之前的接收记录,只能看到最后一条数据
  • Arduino端回传数据请使用Serial.println()方法发送,该方法会自动在数据末尾添加\r\n换行标志,匹配C#端ReadLine()的读取规则,避免超时。

内容的提问来源于stack exchange,提问作者KilerKazanu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 23:00:57