📊 第30课:数字示波器

ADC采样 + 触发模式 + 波形显示 + 测量 — FPGA示波器!

🏆 成就:波形猎手 ✅ Verilator验证通过

📊
波形猎手
ADC采样+触发+波形VGA显示+自动测量

📊 数字示波器原理

示波器的核心是采样→触发→显示三步:高速ADC连续采样,触发电路找到波形起点,VGA显示波形。FPGA天然适合——并行采样+实时显示!

数字示波器架构 信号输入 ──→ [ADC] ──→ 采样缓冲(512点) │ [触发检测] ↑触发电平 │ [测量计算] 频率/Vpp/均值 │ VGA ──── [波形渲染] ←──────┘ 网格+波形+触发电平+测量值 触发模式: ● 自动: 无触发也刷新 ● 正常: 有触发才刷新 ● 单次: 触发一次后停止 时基: 控制采样率 快(1μs/div) → 慢(100ms/div)

📐 数字示波器核心

Verilogdigital_oscilloscope.v — 示波器核心
// 数字示波器核心 - 采样+触发+显示
module digital_oscilloscope (
    input  wire        clk,           // 50MHz
    input  wire        rst,
    // ADC接口(SPI)
    output wire        adc_clk,
    output wire        adc_cs_n,
    input  wire [7:0]  adc_data,
    input  wire        adc_valid,
    // 控制输入
    input  wire [1:0]  timebase,      // 时基选择
    input  wire [7:0]  trigger_level, // 触发电平
    input  wire        trigger_mode,  // 0=自动, 1=正常
    input  wire        trigger_edge,  // 0=上升, 1=下降
    // VGA输出
    input  wire [9:0]  hcount, vcount,
    input  wire        video_on,
    output wire [2:0]  rgb,
    // 测量输出
    output wire [7:0]  meas_freq,     // 频率(相对)
    output wire [7:0]  meas_vpp       // 峰峰值
);
    // 采样存储器(512点)
    reg [7:0] sample_buf [0:511];
    reg [8:0] wr_ptr;

    // 采样控制
    reg [15:0] sample_div;
    reg        sample_en;

    always @(posedge clk) begin
        if (rst) begin
            sample_div <= 0; wr_ptr <= 0;
        end else begin
            case(timebase)
                2'b00: sample_en <= (sample_div >= 4);     // 快
                2'b01: sample_en <= (sample_div >= 49);    // 中
                2'b10: sample_en <= (sample_div >= 499);   // 慢
                2'b11: sample_en <= (sample_div >= 4999);  // 最慢
            endcase
            if (sample_en) sample_div <= 0;
            else sample_div <= sample_div + 1;

            // 写入采样数据
            if (adc_valid && sample_en) begin
                sample_buf[wr_ptr] <= adc_data;
                wr_ptr <= wr_ptr + 1;
            end
        end
    end

    // 触发检测
    reg        triggered;
    reg [7:0]  prev_sample;
    reg [8:0]  trigger_pos;

    always @(posedge clk) begin
        if (rst) begin
            triggered <= 0; prev_sample <= 0;
            trigger_pos <= 0;
        end else if (adc_valid && sample_en) begin
            if (!triggered) begin
                if (trigger_edge == 0) begin
                    // 上升沿触发
                    if (prev_sample < trigger_level &&
                        adc_data >= trigger_level) begin
                        triggered <= 1;
                        trigger_pos <= wr_ptr;
                    end
                end else begin
                    // 下降沿触发
                    if (prev_sample >= trigger_level &&
                        adc_data < trigger_level) begin
                        triggered <= 1;
                        trigger_pos <= wr_ptr;
                    end
                end
            end
            prev_sample <= adc_data;
            // 自动模式: 无触发时也显示
            if (!trigger_mode && wr_ptr == 0)
                triggered <= 1;
        end
    end

    // 测量: 峰峰值
    reg [7:0] min_val, max_val;
    always @(posedge clk) begin
        if (rst) begin
            min_val <= 8'hFF; max_val <= 8'h00;
        end else if (wr_ptr == 0) begin
            min_val <= 8'hFF; max_val <= 8'h00;
        end else if (adc_valid) begin
            if (adc_data < min_val) min_val <= adc_data;
            if (adc_data > max_val) max_val <= adc_data;
        end
    end
    assign meas_vpp = max_val - min_val;

    // VGA波形显示
    reg [2:0] pixel_rgb;
    wire [8:0] disp_x = hcount[8:0]; // 0~511
    wire [7:0] disp_y = vcount[7:0]; // 0~255

    always @(*) begin
        pixel_rgb = 3'b000;
        if (video_on) begin
            // 网格线
            if (disp_x[4:0] == 0 || disp_y[4:0] == 0)
                pixel_rgb = 3'b011; // 青色网格
            // 波形
            else if (triggered) begin
                reg [7:0] wave_val;
                wave_val = sample_buf[disp_x];
                if (disp_y >= wave_val - 2 && disp_y <= wave_val + 2)
                    pixel_rgb = 3'b010; // 绿色波形
            end
            // 触发电平线
            if (disp_y == trigger_level)
                pixel_rgb = 3'b100; // 红色触发电平
        end
    end

    assign rgb = pixel_rgb;
    assign meas_freq = 0; // 简化

endmodule

🧪 示波器测试

SystemVerilogdigital_oscilloscope_tb.sv — 示波器测试台
module digital_oscilloscope_tb;
    logic clk=0, rst=1;
    logic adc_clk, adc_cs_n;
    logic [7:0] adc_data=0;
    logic adc_valid=0;
    logic [1:0] timebase=0;
    logic [7:0] trigger_level=8'd128;
    logic trigger_mode=0, trigger_edge=0;
    logic [9:0] hcount=0, vcount=0;
    logic video_on=1;
    logic [2:0] rgb;
    logic [7:0] meas_freq, meas_vpp;

    digital_oscilloscope uut(.*);
    always #10 clk = ~clk;

    initial begin
        rst=1; #50; rst=0;
        $display("--- 数字示波器测试 ---");
        // 模拟正弦波ADC数据
        for(int i=0; i<512; i=i+1) begin
            adc_data = i[7:0]; // 简化锯齿波
            adc_valid = 1;
            #20;
        end
        adc_valid = 0;
        $display("  Vpp=%0d (应为~255)", meas_vpp);
        $display("数字示波器测试完成 ✓");
        #100; $finish;
    end
endmodule

📊 示波器参数设计

参数设计值说明
采样率1M~50Msps时基决定
缓冲深度512点BRAM存储
ADC分辨率8位0~255映射VGA
显示分辨率512×256VGA区域
触发类型上升/下降沿可配置
测量Vpp/频率硬件计算

💡 ADC选择:推荐ADC08100(8位100Msps)或TLC5540(8位20Msps)。SPI接口的MCP3008(8通道10位200ksps)更简单,适合低频信号。FPGA直接产生ADC时钟,采样率精确可控。

练习1:连接ADC芯片,采样真实信号

练习2:实现上升沿/下降沿触发切换

练习3:添加FFT频谱分析(简化的8点DFT)

练习4:实现游标测量:两条竖线测时间差

练习5:添加暂停功能:冻结波形供分析

📊
波形猎手
完成本课练习,打造你的FPGA示波器!

🔧 Verilator验证步骤

步骤1verilator --lint-only digital_oscilloscope.v

步骤2verilator --binary -j 0 digital_oscilloscope.v digital_oscilloscope_tb.sv

步骤3./obj_dir/Vdigital_oscilloscope_tb

🔬 示波器高级功能

1. XY模式:两个通道分别驱动X/Y偏转,显示李萨如图形

2. FFT模式:8点简化DFT→频谱柱状图显示

3. 自动测量:频率、Vpp、均值、占空比自动计算

4. 波形数学:CH1+CH2、CH1-CH2、CH1×CH2运算

5. 录制回放:保存多帧波形,慢速回放观察

💡 示波器设计优化

等效采样:对重复信号,每次采样偏移一点,等效更高采样率

插值:采样点之间线性/Sinc插值,波形更平滑

数字触发:软件触发比硬件模拟触发更灵活精确

峰值检测:记录每个采样区间的最大最小值,不漏窄脉冲

📊 示波器深入:采样理论

数字示波器的精度取决于采样率触发稳定性。根据奈奎斯特定理,采样率需≥2倍信号最高频率,但实际需要5~10倍才能良好重建波形。

等效采样:对重复信号,每次采样偏移Δt,N次后等效N×fs采样率

实时采样:单次事件需要高实时采样率,FPGA+高速ADC

触发抖动:触发不稳定→波形左右晃动,用滞后比较器减小

内存深度:采样率×时间窗口=所需内存,1Msps×10ms=10K点