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

C# WinForms串口通信实时绘图多问题求助

C# WinForms串口通信实时绘图问题解决方案

问题列表

  • 当前代码仅支持单数据绘图,而Arduino串口输出以制表符分隔的两组数据(格式如"xxxx yyyy"),如何在实时图表中同时绘制这两组数据?
  • 每次关闭程序都会卡顿,需通过任务管理器强制关闭,如何解决?
  • 绘图几秒后变得卡顿,需实现至少60秒的流畅绘图,原因及解决方法是什么?
  • Arduino串口最后一行输出为三组计算结果(格式如"aaaa bbbb cccc"),无需绘制到图表,需显示到文本框,如何实现?
  • 希望X轴以ss:ffff格式显示时间,参考资料修改代码后出现"'CharValueType' does not exist in the current context"错误,且X轴固定不随时间扩展,图表数据左移消失,如何修复?

原始代码

namespace Trial
{
    public partial class Form9 : Form
    {
        public Form9(string str_value)
        {
            InitializeComponent();
            label1.Text = str_value;
            this.chart1.ChartAreas[0].AxisX.LabelStyle.Format = "ss.fff";
            chart1.Series[0].XValueType = ChartValueType.DateTime;
            timer1.Start();
        }
        kayChart SerialDataChart;

        int i = 0;
        List<DateTime> TimeList = new List<DateTime>();

        private void label1_Click(object sender, EventArgs e)
        {

        }

        private void button4_Click(object sender, EventArgs e)
        {
            try
            {
                comboBox1.Items.Clear();
                using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE Caption like '%(COM%'"))
                {
                    var portnames = SerialPort.GetPortNames();
                    var ports = searcher.Get().Cast<ManagementBaseObject>().ToList().Select(p => p["Caption"].ToString());

                    var portList = portnames.Select(n => n + " - " + ports.FirstOrDefault(s => s.Contains(n))).ToList();

                    foreach (string s in portList)
                    {
                        comboBox1.Items.Add(s);
                        if (s.Contains("Arduino") == true)
                        {
                            int index = comboBox1.Items.IndexOf(s);
                            comboBox1.SelectedIndex = index;
                        }
                        else
                        {
                            comboBox1.SelectedIndex = 0;
                        }

                    }

                }

            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error E1!", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            PortOpening();
        }
        private void PortOpening()
        {
            try
            {
                Cursor.Current = Cursors.WaitCursor;
                if (serialPort1.IsOpen)
                {
                    serialPort1.Close();
                }

                string ComportDeviceID = comboBox1.Text.Split().First();
                serialPort1.PortName = ComportDeviceID;
                serialPort1.Open();

                if (serialPort1.IsOpen)
                {
                    button1.Enabled = false;
                    button2.Enabled = true;
                    DateTime d = DateTime.Now;
                    textBox1.Text = d.ToString("dd.MM.yyyy");
                }

            } 
            catch
            {
                MessageBox.Show("Either hardware is disconnected or wrong port has been selected from drop down menu. " +
                "\n\nTry selecting correct option from drop down menu or reconnect the hardware." +
                "\n\nIf problem persists, restart the software.", "Error : Unable to connect to port.",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
                PortClosing();

            }
            finally
            {
                Cursor.Current = Cursors.Default;
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            PortClosing();
        }
        private void PortClosing()
        {
            try
            {
                serialPort1.Close();
            
                button1.Enabled = true;
                button2.Enabled = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            try
            {
                if (serialPort1.IsOpen)
                {
                    serialPort1.BaudRate = 115200;
                    serialPort1.Parity = Parity.None;
                    serialPort1.StopBits = StopBits.One;
                    serialPort1.DataBits = 8;

                    serialPort1.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceivedEventHandler);

                }
                else
                {
                    MessageBox.Show("Error : Port needs to be open or wrong port selected!");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void SerialDataReceivedEventHandler(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort sData = sender as SerialPort;
            string recvData = sData.ReadLine();

            SerialData1.Invoke((MethodInvoker)delegate { SerialData1.AppendText("Received: " +recvData); });

            double data;
            bool result = Double.TryParse(recvData, out data);
            if(result)
            {
                SerialDataChart.TriggeredUpdate(data);
            }

        }

        private void Form9_Load(object sender, EventArgs e)
        {
            SerialDataChart = new kayChart(chart1, 60);
        
            SerialDataChart.serieName = "SerialRead";
        }

        private void Form9_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                serialPort1.Close();
            }
            Application.Exit();
        }

        private void SerialData1_TextChanged(object sender, EventArgs e)
        {
            SerialData1.SelectionStart = SerialData1.Text.Length;
            SerialData1.ScrollToCaret();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            DateTime now = DateTime.Now;
            TimeList.Add(now);

            chart1.Series[0].Points.AddXY(now, Math.Sin(i / 60.0));
            i += 2;
        }
    }
}

问题1:双数据实时绘图解决方案

  1. 在WinForms设计器中给chart1添加第二个Series(命名为SerialRead2),自定义颜色等样式。
  2. 修改串口数据接收事件,拆分数据并更新两个Series:
private void SerialDataReceivedEventHandler(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sData = sender as SerialPort;
    string recvData = sData.ReadLine().Trim();

    SerialData1.Invoke((MethodInvoker)delegate { SerialData1.AppendText("Received: " + recvData + Environment.NewLine); });

    string[] dataParts = recvData.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
    if (dataParts.Length == 2)
    {
        double data1, data2;
        if (double.TryParse(dataParts[0], out data1) && double.TryParse(dataParts[1], out data2))
        {
            DateTime now = DateTime.Now;
            chart1.Invoke((MethodInvoker)delegate
            {
                chart1.Series["SerialRead"].Points.AddXY(now, data1);
                chart1.Series["SerialRead2"].Points.AddXY(now, data2);
                // 限制数据点数量,避免内存过载
                if (chart1.Series["SerialRead"].Points.Count > 600)
                {
                    chart1.Series["SerialRead"].Points.RemoveAt(0);
                    chart1.Series["SerialRead2"].Points.RemoveAt(0);
                }
            });
        }
    }
}
  1. 删除原kayChart相关代码,改用直接操作Chart控件的方式。

问题2:程序关闭卡顿解决方案

解决步骤:

  1. 修改Form9_FormClosing事件,优雅释放资源:
private void Form9_FormClosing(object sender, FormClosingEventArgs e)
{
    // 停止定时器
    timer1.Stop();
    
    // 移除串口事件绑定并关闭串口
    if (serialPort1 != null)
    {
        serialPort1.DataReceived -= SerialDataReceivedEventHandler;
        if (serialPort1.IsOpen)
        {
            serialPort1.DiscardInBuffer();
            serialPort1.DiscardOutBuffer();
            serialPort1.Close();
        }
        serialPort1.Dispose();
    }
}
  1. 移除Application.Exit()调用,Form关闭会自动终止程序。

问题3:绘图卡顿原因及解决方法

原因:

  • 串口线程频繁跨线程更新UI,未限制数据点数量导致Chart渲染压力过大。
  • 测试用的定时器代码额外增加Chart负载。

解决方法:

  1. 限制数据点数量:如问题1代码所示,超过阈值时移除最早的点。
  2. 开启Chart双缓冲:在Form构造函数中添加:
typeof(Chart).InvokeMember("DoubleBuffered", BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, chart1, new object[] { true });
  1. 删除无效代码:移除TimeList和timer1_Tick中的正弦曲线测试代码。
  2. 优化日志更新:串口数据接收时,避免每次都调用AppendText,可批量更新或限制更新频率。

问题4:串口计算结果显示到文本框

  1. 在设计器中添加新TextBox(命名为txtCalculationResult)。
  2. 修改串口数据接收事件,判断并处理三组数据:
private void SerialDataReceivedEventHandler(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sData = sender as SerialPort;
    string recvData = sData.ReadLine().Trim();

    SerialData1.Invoke((MethodInvoker)delegate { SerialData1.AppendText("Received: " + recvData + Environment.NewLine); });

    string[] dataParts = recvData.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
    if (dataParts.Length == 2)
    {
        // 双数据绘图逻辑(同问题1)
        double data1, data2;
        if (double.TryParse(dataParts[0], out data1) && double.TryParse(dataParts[1], out data2))
        {
            DateTime now = DateTime.Now;
            chart1.Invoke((MethodInvoker)delegate
            {
                chart1.Series["SerialRead"].Points.AddXY(now, data1);
                chart1.Series["SerialRead2"].Points.AddXY(now, data2);
                if (chart1.Series["SerialRead"].Points.Count > 600)
                {
                    chart1.Series["SerialRead"].Points.RemoveAt(0);
                    chart1.Series["SerialRead2"].Points.RemoveAt(0);
                }
            });
        }
    }
    else if (dataParts.Length == 3)
    {
        txtCalculationResult.Invoke((MethodInvoker)delegate
        {
            txtCalculationResult.Text = $"计算结果:{dataParts[0]}, {dataParts[1]}, {dataParts[2]}";
        });
    }
}

问题5:X轴格式及动态扩展修复

错误原因:

  • CharValueType是拼写错误,正确为ChartValueType。
  • 未设置X轴自动滚动和动态范围更新。

解决步骤:

  1. 修正X轴设置,开启自动滚动:
public Form9(string str_value)
{
    InitializeComponent();
    label1.Text = str_value;
    var chartArea = chart1.ChartAreas[0];
    // 设置X轴格式为秒:毫秒
    chartArea.AxisX.LabelStyle.Format = "ss:fff";
    chartArea.AxisX.XValueType = ChartValueType.DateTime;
    // 开启自动滚动和缩放
    chartArea.AxisX.ScaleView.Zoomable = true;
    chartArea.CursorX.AutoScroll = true;
    // 初始显示最近60秒数据
    chartArea.AxisX.Minimum = DateTime.Now.AddSeconds(-60).ToOADate();
    chartArea.AxisX.Maximum = DateTime.Now.ToOADate();
    
    chart1.Series[0].XValueType = ChartValueType.DateTime;
    chart1.Series[1].XValueType = ChartValueType.DateTime;
    
    typeof(Chart).InvokeMember("DoubleBuffered", BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, chart1, new object[] { true });
    timer1.Start();
}
  1. 修改定时器事件,动态更新X轴范围:
private void timer1_Tick(object sender, EventArgs e)
{
    var chartArea = chart1.ChartAreas[0];
    DateTime now = DateTime.Now;
    // 保持显示最近60秒数据
    chartArea.AxisX.Minimum = now.AddSeconds(-60).ToOADate();
    chartArea.AxisX.Maximum = now.ToOADate();
    chartArea.AxisX.ScaleView.Position = now.AddSeconds(-60).ToOADate();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 03:19:54