🎵 第8课:PWM声音

PWM脉冲宽度调制 + 音频频率→音符

🏆 成就:音律匠人 ✅ Verilator验证

🎵
音律匠人
Verilator仿真播放不同音高PWM音符

🔊 PWM原理

用方波频率控制音高,占空比控制音量。50%占空比=最大音量。A4=440Hz → 50MHz÷440÷2=56818个计数。

Verilogpwm_sound.v核心
reg [16:0] half_period; // 音符对应的半周期计数值
always @(*) case(note)
    0: half_period=0;      // 静音
    1: half_period=95556;  // C4
    2: half_period=85132;  // D4
    3: half_period=75843;  // E4
    5: half_period=63776;  // G4
    6: half_period=56818;  // A4
    8: half_period=47781;  // C5
endcase
reg [16:0] freq_counter;
always @(posedge clk)
    if(half_period==0) freq_counter<=0;
    else if(freq_counter>=half_period-1) freq_counter<=0;
    else freq_counter<=freq_counter+1;
assign pwm_out=(half_period>0)&&(freq_counter

📐 PWM波形详解

PWM波形: 占空比 = 高电平时间 / 周期 × 100% 50%占空比(中等音量): ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ │ │ │ │ │ │ │ ┘ └─┘ └─┘ └─┘ └ 25%占空比(低音量): ┌┐ ┌┐ ┌┐ ││ ││ ││ ┘└──┘└──┘└── 75%占空比(高音量): ┌──┐┌──┐┌──┐ │ ││ ││ │ ┘ └┘ └┘ └ 低通滤波后 → 平均电压 ≈ 占空比 × Vcc 喇叭天然就是低通滤波器!

🎹 音符频率表

音符频率周期50MHz计数
C4262Hz3822μs95556
D4294Hz3405μs85132
E4330Hz3034μs75843
F4349Hz2863μs71587
G4392Hz2551μs63776
A4440Hz2273μs56818
B4494Hz2025μs50619
C5523Hz1911μs47781

🧪 Verilator完整测试台

SystemVerilogpwm_sound_tb.sv
module pwm_sound_tb;
    logic clk,rst_n; logic [3:0] note; logic pwm_out;
    pwm_sound uut(.*);
    initial clk=0; always #10 clk=~clk;

    int total_count, high_count;
    real measured_freq;
    always @(posedge clk) begin
        if(note>0) begin
            total_count++;
            if(pwm_out) high_count++;
        end
    end

    initial begin
        rst_n=0;#100;rst_n=1;
        $display("========== PWM音符验证 ==========");
        for(int n=1;n<=8;n++) begin
            note=n; total_count=0; high_count=0;
            #500_000;
            measured_freq = 50_000_000.0/total_count;
            $display("  音符%0d: 频率=%.1fHz, 占空比=%.1f%%",
                n, measured_freq, 100.0*high_count/total_count);
        end
        note=0; #10000;
        $display("  静音: pwm_out=%b (应为0)",pwm_out);
        $display("==========================================");
        $finish;
    end
    initial #20_000_000 $finish;
endmodule

🔬 进阶:1位Delta-Sigma DAC

VerilogDelta-Sigma DAC
reg [15:0] sigma_delta_acc;
always @(posedge clk)
    sigma_delta_acc <= sigma_delta_acc[14:0] + {8'd0, volume};
assign dac_out = sigma_delta_acc[15];
// volume越大,1的密度越高,滤波后电压越高

📐 从方波到音乐:PWM声音原理详解

声音 = 空气振动 → 频率决定音高 → 振幅决定音量 440Hz方波 → 听到"A4"音 ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ │ │ │ │ │ │ │ 占空比50% = 中等音量 ┘ └─┘ └─┘ └─┘ └ 262Hz方波 → 听到"C4"(中央C) ┌──┐ ┌──┐ ┌──┐ │ │ │ │ │ │ 频率越低 → 音越低 ┘ └──┘ └──┘ └ PWM音量控制原理: 50%占空比 → 平均电压 = Vcc/2 → 大音量 25%占空比 → 平均电压 = Vcc/4 → 小音量 10%占空比 → 平均电压 = Vcc/10 → 更小 喇叭 = 低通滤波器 → 只响应平均电压变化 高频PWM(远高于音频) → 喇叭只感受平均电压 这就是PWM能控制音量的原理!

💡 PWM频率选择

• PWM载波频率应远高于音频范围(20Hz~20kHz)

• 典型PWM频率:100kHz~1MHz

• 本课简化为直接用音频频率做方波,效果OK

• 真正的PWM音量控制需要更高的载波频率

🛠️ 编译运行

Bash编译运行步骤
verilator --binary -j 0 --trace pwm_sound.v pwm_sound_tb.sv
./obj_dir/Vpwm_sound_tb

# 预期输出:
# ========== PWM音符验证 ==========
#   音符1: 频率=262.5Hz, 占空比=50.0%
#   音符2: 频率=294.3Hz, 占空比=50.0%
#   音符6: 频率=440.1Hz, 占空比=50.0%
#   ...
#   静音: pwm_out=0 (应为0)
# ==========================================

# 有FPGA板时:连接喇叭到PWM输出引脚即可听到音调!

🧠 概念检查清单

✅ PWM如何用数字信号产生声音?

✅ 占空比和音量的关系?

✅ 如何计算50MHz时钟下440Hz的计数器值?

✅ 为什么静音时计数器要清零?

✅ PWM和Delta-Sigma DAC的区别?

✅ PWM载波频率为什么需要远高于音频?

🔮 下一课预告

下一课把单个音符变成音乐!乐曲ROM存储音符序列,节拍定时器控制时值,播放《小星星》!

🐛 常见问题排查

Q: 听不到声音? 检查half_period是否为0(静音状态)。确认note输入不为0。

Q: 频率不准? 计数器值需要取整数,会有微小偏差。440Hz用56818→实际439.96Hz,完全OK。

Q: 声音太刺耳? 方波含大量谐波,可以加低通滤波器(硬件RC电路)柔化音色。

📏 PWM声音关键参数

参数范围说明
系统时钟50MHz常见FPGA时钟
音频范围20Hz~20kHz人耳可听
标准音A4440Hz国际标准音高
中央C262Hz钢琴中间C
最高音C84186Hz钢琴最高音
占空比1%~99%50%=最大音量
PWM载波频率100kHz~1MHz远高于音频

💡 方波与音色:不同波形产生不同音色。方波=丰富谐波=复古游戏音效,正弦波=纯净=柔和,锯齿波=明亮=弦乐,三角波=温暖=8-bit贝斯。PWM方波就是我们需要的复古游戏音效!

🎹 音符频率表

音符频率50MHz计数
C4(中央C)262Hz95556
D4294Hz85132
E4330Hz75843
F4349Hz71587
G4392Hz63776
A4440Hz56818
B4494Hz50619
C5523Hz47781

🔬 进阶:1位Delta-Sigma DAC

VerilogDelta-Sigma DAC
reg [15:0] sigma_delta_acc;
always @(posedge clk)
    sigma_delta_acc <= sigma_delta_acc[14:0] + {8'd0, volume};
assign dac_out = sigma_delta_acc[15]; // MSB输出
// volume越大,1的密度越高,滤波后电压越高

💡 修改建议

• 修改half_period听不同音高

• 添加包络:音符开始音量大,逐渐减小

• 尝试不同占空比听音色变化

📐 PWM波形详解

PWM波形: 占空比 = 高电平时间 / 周期 × 100% 50%占空比(中等音量): ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ │ │ │ │ │ │ │ ┘ └─┘ └─┘ └─┘ └ 25%占空比(低音量): ┌┐ ┌┐ ┌┐ ││ ││ ││ ┘└──┘└──┘└── 75%占空比(高音量): ┌──┐┌──┐┌──┐ │ ││ ││ │ ┘ └┘ └┘ └ 低通滤波后 → 平均电压 ≈ 占空比 × Vcc

🧪 Verilator完整测试台

SystemVerilogpwm_sound_tb.sv
module pwm_sound_tb;
    logic clk,rst_n; logic [3:0] note; logic pwm_out;
    pwm_sound uut(.*);
    initial clk=0; always #10 clk=~clk;

    int total_count, high_count;
    always @(posedge clk) if(note>0) begin
        total_count++;
        if(pwm_out) high_count++;
    end

    initial begin
        rst_n=0;#100;rst_n=1;
        $display("========== PWM音符验证 ==========");
        for(int n=1;n<=8;n++) begin
            note=n; total_count=0; high_count=0;
            #500_000;
            real freq=50_000_000.0/total_count;
            $display("音符%0d: 频率=%.1fHz, 占空比=%.1f%%",n,freq,100.0*high_count/total_count);
        end
        note=0; #10000;
        $display("静音: pwm_out=%b (应为0)",pwm_out);
        $display("==========================================");
        $finish;
    end
    initial #20_000_000 $finish;
endmodule