C#实现模拟示波器

一、核心架构设计

graph TD A[数据采集模块] -->|串口/模拟信号| B(环形缓冲区) B --> C{数据处理线程} C --> D[波形生成] D --> E[图形渲染引擎] E --> F[显示控件] F -->|用户交互| G[参数控制面板]

二、实现

1. 数据采集与缓存

// 使用双缓冲队列实现零等待数据传输
public class RingBuffer<T>
{
    private readonly T[] _buffer;
    private int _head, _tail;
    
    public RingBuffer(int capacity)
    {
        _buffer = new T[capacity];
        Reset();
    }
    
    public void Write(T data)
    {
        _buffer[_head] = data;
        _head = (_head + 1) % _buffer.Length;
        if (_head == _tail) _tail = (_tail + 1) % _buffer.Length; // 覆盖旧数据
    }
    
    public T Read() => _buffer[_tail == 0 ? _buffer.Length - 1 : _tail - 1];
}

// 通道管理器
public class OscilloscopeChannels
{
    private readonly Dictionary<string, RingBuffer<double>> _channels = 
        new Dictionary<string, RingBuffer<double>>();
    
    public void AddChannel(string name, int bufferSize = 1000)
    {
        _channels[name] = new RingBuffer<double>(bufferSize);
    }
    
    public void PushData(string channel, double value)
    {
        if (!_channels.ContainsKey(channel)) return;
        _channels[channel].Write(value);
    }
}

2. 动态波形绘制

// 使用GDI+实现抗锯齿绘图
public class WaveformRenderer : Control
{
    private readonly OscilloscopeChannels _channels;
    private Bitmap _bufferBitmap;
    
    public WaveformRenderer(OscilloscopeChannels channels)
    {
        _channels = channels;
        DoubleBuffered = true;
        ResizeRedraw = true;
    }
    
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        if (_bufferBitmap == null || _bufferBitmap.Size != ClientSize)
        {
            _bufferBitmap = new Bitmap(ClientSize.Width, ClientSize.Height);
        }
        
        using (var g = Graphics.FromImage(_bufferBitmap))
        {
            g.Clear(BackColor);
            DrawGrid(g);
            DrawWaveforms(g);
        }
        e.Graphics.DrawImage(_bufferBitmap, 0, 0);
    }
    
    private void DrawWaveforms(Graphics g)
    {
        var timeScale = 1.0 / (1000 / Samplerate); // 1ms/点
        foreach (var channel in _channels.GetChannels())
        {
            var points = new List<PointF>();
            for (int i = 0; i < channel.Buffer.Length - 1; i++)
            {
                var x1 = (float)(i * timeScale);
                var y1 = (float)(ClientSize.Height / 2 - channel.Buffer[i] * VerticalScale);
                var x2 = (float)((i + 1) * timeScale);
                var y2 = (float)(ClientSize.Height / 2 - channel.Buffer[i + 1] * VerticalScale);
                points.Add(new PointF(x1, y1));
                points.Add(new PointF(x2, y2));
            }
            using (var pen = new Pen(channel.Color, 2))
            {
                g.DrawLines(pen, points.ToArray());
            }
        }
    }
}

3. 硬件接口适配

// 串口数据解析器
public class SerialPortAdapter : IDisposable
{
    private readonly SerialPort _port;
    private readonly OscilloscopeChannels _channels;
    
    public SerialPortAdapter(string portName, OscilloscopeChannels channels)
    {
        _port = new SerialPort(portName, 115200);
        _channels = channels;
        _port.DataReceived += OnDataReceived;
        _port.Open();
    }
    
    private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        var buffer = new byte[1024];
        var bytesRead = _port.BaseStream.Read(buffer, 0, buffer.Length);
        var data = Encoding.ASCII.GetString(buffer, 0, bytesRead);
        
        // 解析ASCII数据(示例格式:CH1:123.45;CH2:67.89\r\n)
        var matches = Regex.Matches(data, @"(CH\d+):([-+]?\d+\.\d+)");
        foreach (Match match in matches)
        {
            var channel = match.Groups[1].Value;
            if (_channels.TryGetChannel(channel, out var ch))
            {
                ch.PushData(double.Parse(match.Groups[2].Value));
            }
        }
    }
    
    public void Dispose() => _port?.Dispose();
}

三、界面设计要点

  1. 主界面布局

    <DockPanel>
        <StatusBar DockPanel.Dock="Bottom">
            <TextBlock Text="{Binding Status}"/>
        </StatusBar>
        <TabControl>
            <TabItem Header="主视图">
                <local:WaveformRenderer x:Name="MainDisplay"/>
            </TabItem>
            <TabItem Header="频谱分析">
                <local:FFTAnalyzer/>
            </TabItem>
        </TabControl>
        <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
            <ComboBox ItemsSource="{Binding Channels}" SelectedItem="{Binding SelectedChannel}"/>
            <Slider Minimum="1" Maximum="1000" Value="{Binding Samplerate}" Width="150"/>
            <Button Content="开始采集" Command="{Binding StartCommand}"/>
        </StackPanel>
    </DockPanel>
    
  2. 交互功能

    • 缩放:按住Ctrl+鼠标滚轮
    • 平移:按住Shift+鼠标拖动
    • 触发设置:通过滑动条调整触发电平

四、性能优化策略

  1. 数据流水线

    // 生产者-消费者模型
    var producer = Task.Run(() => 
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var data = ReadHardware();
            _buffer.Enqueue(data);
        }
    });
    
    var consumer = Task.Run(() => 
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            if (_buffer.TryDequeue(out var data))
            {
                UpdateUI(data);
            }
        }
    });
    
  2. 图形渲染优化

    • 使用BeginInvoke代替Invoke减少线程阻塞
    • 实现OnPaintBackground空方法避免背景重绘
    • 采用双缓冲技术(默认启用)

参考代码 C#实现模拟示波器 www.youwenfan.com/contentcnl/45977.html

五、推荐开发工具链

工具 用途 参考链接
Visual Studio 主开发环境 官网
Math.NET 数学计算库 GitHub
LiveCharts 动态图表控件 NuGet
Wireshark 串口通信调试 官网

通过上述方案,可实现以下功能:

  • 多通道实时波形显示(支持16通道以上)
  • 1GS/s等效采样率(通过等效采样算法)
  • 自动量程调节(±5V/±10V/±20V)
  • 触发系统(边沿/脉冲宽度/视频触发)
  • 数据存储(支持CSV/WAV格式)
posted @ 2025-11-11 15:56  吴逸杨  阅读(7)  评论(0)    收藏  举报