基于C# WinForm实现串口数据读取与实时折线图显示

一、系统架构设计

graph TD A[串口设备] -->|RS232/USB| B{串口通信模块} B --> C[数据解析器] C --> D[实时数据缓冲区] D --> E[Chart控件更新] E --> F[用户界面]

二、核心代码实现

1. 串口通信模块(SerialPortManager.cs)

using System.IO.Ports;
using System.Threading.Tasks;

public class SerialPortManager : IDisposable
{
    private SerialPort _port;
    private readonly BlockingCollection<byte[]> _buffer = new(100);
    private CancellationTokenSource _cts;

    public event Action<byte[]> DataReceived;

    public void Connect(string portName, int baudRate = 115200)
    {
        _port = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
        {
            ReadTimeout = 1000,
            WriteTimeout = 1000
        };
        
        _cts = new CancellationTokenSource();
        _ = Task.Run(() => ReadDataAsync(_cts.Token));
    }

    private async Task ReadDataAsync(CancellationToken token)
    {
        try
        {
            while (!token.IsCancellationRequested)
            {
                int bytesAvailable = _port.BytesToRead;
                if (bytesAvailable > 0)
                {
                    byte[] chunk = new byte[bytesAvailable];
                    _port.Read(chunk, 0, bytesAvailable);
                    _buffer.Add(chunk, token);
                    DataReceived?.Invoke(chunk);
                }
                await Task.Delay(10, token);
            }
        }
        catch (OperationCanceledException) { }
        catch (Exception ex)
        {
            HandleError($"串口读取错误: {ex.Message}");
        }
    }

    public void Disconnect() => _port?.Close();
    public void Dispose() => _port?.Dispose();

    private void HandleError(string message)
    {
        // 实现错误处理逻辑
    }
}

2. 数据解析与图表更新(MainForm.cs)

using System.Windows.Forms.DataVisualization.Charting;

public partial class MainForm : Form
{
    private readonly SerialPortManager _serialManager;
    private readonly CircularBuffer<float> _dataBuffer = new(1000);
    private Chart _chart;
    private Series _series;

    public MainForm()
    {
        InitializeComponent();
        InitializeChart();
        _serialManager = new SerialPortManager();
        _serialManager.DataReceived += OnDataReceived;
    }

    private void InitializeChart()
    {
        _chart = new Chart { Dock = DockStyle.Fill };
        _series = new Series("传感器数据")
        {
            ChartType = SeriesChartType.Spline,
            BorderWidth = 2,
            Color = Color.Blue
        };

        _chart.Series.Add(_series);
        _chart.ChartAreas[0].AxisX.Title = "时间";
        _chart.ChartAreas[0].AxisY.Title = "数值";
        _chart.ChartAreas[0].AxisX.Interval = 1;
        _chart.ChartAreas[0].AxisY.Interval = 5;
        
        this.Controls.Add(_chart);
    }

    private void OnDataReceived(byte[] data)
    {
        if (InvokeRequired)
        {
            Invoke(new Action(() => OnDataReceived(data)));
            return;
        }

        // 假设数据格式:温度高字节+温度低字节(16位)
        if (data.Length >= 2)
        {
            short raw = (short)(data[0] << 8 | data[1]);
            float value = raw / 10.0f;
            _dataBuffer.Enqueue(value);
            UpdateChart();
        }
    }

    private void UpdateChart()
    {
        if (_dataBuffer.Count > _chart.Series[0].Points.Count)
        {
            _chart.Series[0].Points.AddY(_dataBuffer.Last());
            if (_chart.Series[0].Points.Count > 1000)
            {
                _chart.Series[0].Points.RemoveAt(0);
            }
        }
        
        // 启用双缓冲减少闪烁
        _chart.DoubleBuffered = true;
        _chart.Invalidate();
    }

    private void btnConnect_Click(object sender, EventArgs e)
    {
        _serialManager.Connect("COM3");
    }

    private void btnDisconnect_Click(object sender, EventArgs e)
    {
        _serialManager.Disconnect();
    }
}

三、关键功能实现

1. 数据缓冲区(CircularBuffer.cs)

public class CircularBuffer<T>
{
    private readonly T[] _buffer;
    private int _head;
    private int _tail;
    private int _count;

    public CircularBuffer(int capacity)
    {
        _buffer = new T[capacity];
    }

    public void Enqueue(T item)
    {
        _buffer[_head] = item;
        _head = (_head + 1) % _buffer.Length;
        if (_count < _buffer.Length) _count++;
        else _tail = (_tail + 1) % _buffer.Length;
    }

    public T Dequeue() => _buffer[_tail];

    public T Last() => _buffer[(_tail + _count - 1) % _buffer.Length];

    public int Count => _count;
}

2. 性能优化配置

// 在Form_Load事件中配置
private void MainForm_Load(object sender, EventArgs e)
{
    // 启用双缓冲
    this.DoubleBuffered = true;
    
    // 设置控件更新模式
    _chart.Series[0].ChartType = SeriesChartType.FastLine;
    _chart.ChartAreas[0].AxisX.ScrollBar.Enabled = true;
    _chart.ChartAreas[0].AxisX.ScaleView.Size = 60; // 显示最近60秒数据
}

// 定时清理旧数据
private void timerCleanup_Tick(object sender, EventArgs e)
{
    while (_dataBuffer.Count > 1000)
    {
        _dataBuffer.Dequeue();
        _chart.Series[0].Points.RemoveAt(0);
    }
}

四、界面设计(MainForm.Designer.cs)

partial class MainForm
{
    private System.ComponentModel.IContainer components = null;
    private Button btnConnect;
    private Button btnDisconnect;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
        this.btnConnect = new System.Windows.Forms.Button();
        this.btnDisconnect = new System.Windows.Forms.Button();
        
        this.SuspendLayout();
        
        // 连接按钮
        this.btnConnect.Location = new System.Drawing.Point(20, 20);
        this.btnConnect.Size = new System.Drawing.Size(75, 23);
        this.btnConnect.Text = "连接";
        this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
        
        // 断开按钮
        this.btnDisconnect.Location = new System.Drawing.Point(101, 20);
        this.btnDisconnect.Size = new System.Drawing.Size(75, 23);
        this.btnDisconnect.Text = "断开";
        this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click);
        
        // 主布局
        this.Controls.Add(this.btnDisconnect);
        this.Controls.Add(this.btnConnect);
        this.ClientSize = new System.Drawing.Size(800, 600);
        this.Name = "MainForm";
        this.Text = "串口实时监控";
        this.ResumeLayout(false);
    }
}

五、扩展功能实现

1. 多通道支持

// 修改数据解析逻辑
private void OnDataReceived(byte[] data)
{
    if (data.Length >= 4)
    {
        short tempRaw = (short)(data[0] << 8 | data[1]);
        short humiRaw = (short)(data[2] << 8 | data[3]);
        
        float temp = tempRaw / 10.0f;
        float humi = humiRaw / 10.0f;
        
        _tempBuffer.Enqueue(temp);
        _humiBuffer.Enqueue(humi);
        
        UpdateCharts();
    }
}

private void UpdateCharts()
{
    if (_chart.Series.Count < 2)
    {
        _chart.Series.Add(new Series("湿度") { ChartType = SeriesChartType.Spline, Color = Color.Red });
    }
    
    // 更新温度曲线
    if (_tempBuffer.Count > _chart.Series[0].Points.Count)
    {
        _chart.Series[0].Points.AddY(_tempBuffer.Last());
        _chart.Series[0].Points.RemoveAt(0);
    }
    
    // 更新湿度曲线
    if (_humiBuffer.Count > _chart.Series[1].Points.Count)
    {
        _chart.Series[1].Points.AddY(_humiBuffer.Last());
        _chart.Series[1].Points.RemoveAt(0);
    }
}

2. 数据存储集成

// 添加SQLite存储
public class DataStorage
{
    private readonly string _dbPath = "data.sqlite";
    
    public void SaveData(float temp, float humi)
    {
        using (var conn = new SQLiteConnection($"Data Source={_dbPath};Version=3;"))
        {
            conn.Open();
            var cmd = new SQLiteCommand(
                "INSERT INTO sensor_data (timestamp, temperature, humidity) " +
                "VALUES (datetime('now'), @temp, @humi)", conn);
                
            cmd.Parameters.AddWithValue("@temp", temp);
            cmd.Parameters.AddWithValue("@humi", humi);
            cmd.ExecuteNonQuery();
        }
    }
}

// 在OnDataReceived中调用
private void OnDataReceived(byte[] data)
{
    // ...解析数据...
    _storage.SaveData(temp, humi);
}

六、调试与优化建议

1. 性能监控

// 在状态栏显示性能指标
private void UpdatePerformanceMetrics()
{
    var cpuUsage = new PerformanceCounter("Processor", "% Processor Time", "_Total");
    var ramUsage = new PerformanceCounter("Memory", "Available MBytes");
    
    lblStatus.Text = $"CPU: {cpuUsage.NextValue()}% | 内存: {ramUsage.NextValue()}MB";
}

2. 异常处理

// 添加串口状态监控
private void CheckSerialPortStatus()
{
    if (!_serialManager.IsOpen)
    {
        btnConnect.Enabled = true;
        btnDisconnect.Enabled = false;
        lblStatus.Text = "串口未连接";
    }
    else
    {
        btnConnect.Enabled = false;
        btnDisconnect.Enabled = true;
        lblStatus.Text = "数据接收中...";
    }
}

七、部署方案

1. 安装包配置(Inno Setup脚本)

[Files]
Source: "YourApp.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "zlgcan.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "data.sqlite"; DestDir: "{app}"; Flags: ignoreversion

[Icons]
Name: "{group}\实时监控"; Filename: "{app}\YourApp.exe"

2. 系统服务化

public class BackgroundService : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        _serialManager.Connect("COM3");
        _timer.Start();
    }

    protected override void OnStop()
    {
        _serialManager.Disconnect();
        _timer.Stop();
    }
}

参考代码 使用C#的winform读取串口,并用chart控件显示实时数据折线图 www.youwenfan.com/contentcnr/112224.html

八、测试用例

1. 模拟数据测试

// 生成测试数据
private void GenerateTestData()
{
    var rand = new Random();
    var timer = new Timer(100);
    timer.Elapsed += (s, e) => {
        var temp = 20 + rand.NextDouble() * 10;
        var humi = 40 + rand.NextDouble() * 20;
        _serialManager.RaiseDataReceived(new byte[] 
        { (byte)(temp >> 8), (byte)temp, (byte)(humi >> 8), (byte)humi });
    };
    timer.Start();
}

2. 压力测试

// 高负载数据测试
private void HighLoadTest()
{
    var buffer = new byte[1024 * 1024]; // 1MB数据块
    Parallel.For(0, 1000, i => {
        _serialManager.RaiseDataReceived(buffer);
    });
}
posted @ 2026-02-10 17:09  晃悠人生  阅读(15)  评论(0)    收藏  举报