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:双数据实时绘图解决方案
- 在WinForms设计器中给
chart1添加第二个Series(命名为SerialRead2),自定义颜色等样式。 - 修改串口数据接收事件,拆分数据并更新两个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); } }); } } }
- 删除原
kayChart相关代码,改用直接操作Chart控件的方式。
问题2:程序关闭卡顿解决方案
解决步骤:
- 修改
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(); } }
- 移除
Application.Exit()调用,Form关闭会自动终止程序。
问题3:绘图卡顿原因及解决方法
原因:
- 串口线程频繁跨线程更新UI,未限制数据点数量导致Chart渲染压力过大。
- 测试用的定时器代码额外增加Chart负载。
解决方法:
- 限制数据点数量:如问题1代码所示,超过阈值时移除最早的点。
- 开启Chart双缓冲:在Form构造函数中添加:
typeof(Chart).InvokeMember("DoubleBuffered", BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, chart1, new object[] { true });
- 删除无效代码:移除
TimeList和timer1_Tick中的正弦曲线测试代码。 - 优化日志更新:串口数据接收时,避免每次都调用
AppendText,可批量更新或限制更新频率。
问题4:串口计算结果显示到文本框
- 在设计器中添加新TextBox(命名为
txtCalculationResult)。 - 修改串口数据接收事件,判断并处理三组数据:
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轴自动滚动和动态范围更新。
解决步骤:
- 修正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(); }
- 修改定时器事件,动态更新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
相关产品推荐
相关产品推荐

