调制解调 · 第3课

第03课:ASK/FSK/PSK调制

📻 数字调制基础

数字调制的本质是用数字比特流控制载波的某个参数(幅度、频率、相位),将基带信息"搬运"到适合信道传输的频段。三种最基本的数字调制方式:

调制方式控制参数数学表达式特点
ASK幅度s(t) = A_m · cos(2πf_ct)简单但抗噪声差
FSK频率s(t) = cos(2π(f_c±Δf)t)抗噪声好,恒包络
PSK相位s(t) = cos(2πf_ct + φ_m)频谱效率最高

📡 ASK(幅移键控)

ASK通过改变载波幅度来传递信息。最简单的OOK(On-Off Keying)就是2-ASK:

s(t) = { A·cos(2πf_ct), 发送'1' / 0, 发送'0' }

ASK的频谱特性

OOK信号的功率谱密度为:

P_ASK(f) = (A²/4)·[δ(f-f_c) + δ(f+f_c)] + (A²T_s/4)·sinc²(π(f-f_c)T_s)

第一项是载波分量(浪费功率),第二项是信息边带。带宽 B ≈ 2/T_s = 2R_s。

ASK的缺点:对幅度衰落敏感,不适合衰落信道。实际应用中较少单独使用。

🔊 FSK(频移键控)

FSK用不同频率代表不同符号。2-FSK(BFSK):

s(t) = { cos(2πf_1·t), 发送'1' / cos(2πf_0·t), 发送'0' }

FSK关键参数:调制指数

h = 2Δf · T_s = 2Δf / R_s

其中 Δf = |f_1 - f_0|/2,T_s 为符号周期。

💡 FSK的恒包络特性:FSK信号幅度恒定,不受幅度衰落影响,适合非线性功放(如C类功放)。这是GSM选择GMSK的原因之一。

🔄 PSK(相移键控)

PSK通过改变载波相位传递信息。BPSK(Binary PSK):

s(t) = cos(2πf_ct + φ_m), φ_m ∈ {0, π} → 符号'0'→+1, 符号'1'→-1

BPSK的相干解调

接收端将接收信号与本地载波相乘:

r(t)·2cos(2πf_ct) → 低通滤波 → 判决

判决规则:LPF输出 > 0 → '0',< 0 → '1'

BPSK的BER(AWGN信道):

P_b = Q(√(2E_b/N_0)) ≈ (1/2)·erfc(√(E_b/N_0))

BPSK是最鲁棒的二进制调制,比OOK有3dB优势(无载波功率浪费)。

🔧 Verilog实现:ASK/FSK/PSK调制器

// modulator_ask_fsk_psk.v - 三种基本调制器
// 第03课:ASK/FSK/PSK调制

// ============================================================
// NCO(数控振荡器)- 调制器核心
// ============================================================
module nco #(
    parameter PHASE_W = 16,     // 相位累加器位宽
    parameter OUT_W    = 12      // 输出位宽
)(
    input  wire                  clk,
    input  wire                  rst_n,
    input  wire [PHASE_W-1:0]    freq_word,   // 频率控制字
    input  wire [PHASE_W-1:0]    phase_offset, // 相位偏移
    output wire [OUT_W-1:0]      sin_out,     // 正弦输出
    output wire [OUT_W-1:0]      cos_out      // 余弦输出
);
    reg [PHASE_W-1:0] phase_acc;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            phase_acc <= 0;
        else
            phase_acc <= phase_acc + freq_word;
    end
    
    wire [PHASE_W-1:0] phase_sin = phase_acc + phase_offset;
    wire [PHASE_W-1:0] phase_cos = phase_acc + phase_offset + (1 << (PHASE_W-2)); // +90°
    
    // 正弦查找表 (1/4周期对称)
    reg [OUT_W-1:0] sin_table [0:2**PHASE_W-1];
    reg [OUT_W-1:0] cos_table [0:2**PHASE_W-1];
    
    initial begin
        integer i;
        for (i = 0; i < 2**PHASE_W; i = i + 1) begin
            sin_table[i] = $rtoi((2**(OUT_W-1) - 1) * 
                            $sin(2.0 * 3.14159265 * i / 2**PHASE_W));
            cos_table[i] = $rtoi((2**(OUT_W-1) - 1) * 
                            $cos(2.0 * 3.14159265 * i / 2**PHASE_W));
        end
    end
    
    assign sin_out = sin_table[phase_sin];
    assign cos_out = cos_table[phase_cos];

endmodule

// ============================================================
// OOK/ASK 调制器
// ============================================================
module ask_modulator #(
    parameter DATA_W   = 1,
    parameter PHASE_W  = 16,
    parameter OUT_W    = 12,
    parameter CARRIER_FREQ_WORD = 16'h1000  // 载波频率控制字
)(
    input  wire                  clk,
    input  wire                  rst_n,
    input  wire [DATA_W-1:0]     tx_bit,     // 待调制比特
    input  wire                  tx_valid,   // 数据有效
    output wire [OUT_W-1:0]      mod_out     // 调制输出
);
    wire [OUT_W-1:0] carrier;
    
    nco #(
        .PHASE_W(PHASE_W),
        .OUT_W  (OUT_W)
    ) u_carrier (
        .clk          (clk),
        .rst_n        (rst_n),
        .freq_word    (CARRIER_FREQ_WORD),
        .phase_offset (16'd0),
        .sin_out      (carrier),
        .cos_out      ()
    );
    
    // ASK: 比特'1'→有载波,比特'0'→无载波
    reg [OUT_W-1:0] amplitude;
    always @(*) begin
        case (tx_bit)
            1'b0: amplitude = {OUT_W{1'b0}};       // 关闭
            1'b1: amplitude = {1'b0, {(OUT_W-1){1'b1}}}; // 满幅度
            default: amplitude = {OUT_W{1'b0}};
        endcase
    end
    
    assign mod_out = (tx_valid) ? (carrier & amplitude[OUT_W-1:0]) : {OUT_W{1'b0}};

endmodule

// ============================================================
// BFSK 调制器
// ============================================================
module bfsk_modulator #(
    parameter PHASE_W  = 16,
    parameter OUT_W    = 12,
    parameter FREQ_WORD_0 = 16'h0800,  // '0'对应频率
    parameter FREQ_WORD_1 = 16'h1000   // '1'对应频率
)(
    input  wire                  clk,
    input  wire                  rst_n,
    input  wire                  tx_bit,     // 待调制比特
    input  wire                  tx_valid,
    output wire [OUT_W-1:0]      mod_out
);
    // 根据比特选择频率控制字
    reg [PHASE_W-1:0] freq_select;
    always @(*) begin
        freq_select = tx_bit ? FREQ_WORD_1 : FREQ_WORD_0;
    end
    
    nco #(
        .PHASE_W(PHASE_W),
        .OUT_W  (OUT_W)
    ) u_fsk_nco (
        .clk          (clk),
        .rst_n        (rst_n),
        .freq_word    (freq_select),
        .phase_offset (16'd0),
        .sin_out      (mod_out),
        .cos_out      ()
    );

endmodule

// ============================================================
// BPSK 调制器
// ============================================================
module bpsk_modulator #(
    parameter PHASE_W = 16,
    parameter OUT_W   = 12,
    parameter CARRIER_FREQ_WORD = 16'h1000
)(
    input  wire                  clk,
    input  wire                  rst_n,
    input  wire                  tx_bit,     // '0'→相位0, '1'→相位π
    input  wire                  tx_valid,
    output wire [OUT_W-1:0]      mod_out
);
    // BPSK: 0→相位0, 1→相位π (等价于乘以±1)
    wire [PHASE_W-1:0] phase_mod;
    assign phase_mod = tx_bit ? {1'b1, {(PHASE_W-1){1'b0}}} : {PHASE_W{1'b0}};
    
    nco #(
        .PHASE_W(PHASE_W),
        .OUT_W  (OUT_W)
    ) u_bpsk_nco (
        .clk          (clk),
        .rst_n        (rst_n),
        .freq_word    (CARRIER_FREQ_WORD),
        .phase_offset (phase_mod),
        .sin_out      (mod_out),
        .cos_out      ()
    );

endmodule

// ============================================================
// BPSK 相干解调器
// ============================================================
module bpsk_demodulator #(
    parameter SAMPLE_W = 12,
    parameter PHASE_W  = 16,
    parameter OUT_W    = 1
)(
    input  wire                     clk,
    input  wire                     rst_n,
    input  wire [SAMPLE_W-1:0]      rx_sample,  // 接收采样
    input  wire                     rx_valid,
    output wire                     rx_bit,     // 解调比特
    output wire                     rx_valid_out,
    // 本地载波NCO
    input  wire [PHASE_W-1:0]       carrier_freq_word,
    input  wire                     carrier_lock  // 载波锁定指示
);
    // 本地载波
    wire [SAMPLE_W-1:0] local_carrier;
    nco #(
        .PHASE_W(PHASE_W),
        .OUT_W  (SAMPLE_W)
    ) u_local_osc (
        .clk          (clk),
        .rst_n        (rst_n),
        .freq_word    (carrier_freq_word),
        .phase_offset (16'd0),
        .sin_out      (local_carrier),
        .cos_out      ()
    );
    
    // 乘法器 (下变频)
    reg signed [2*SAMPLE_W:0] mult_result;
    always @(posedge clk) begin
        if (rx_valid)
            mult_result <= $signed(rx_sample) * $signed({1'b0, local_carrier});
    end
    
    // 积分-转储滤波器 (匹配滤波)
    localparam INTEGRATE_LEN = 16;
    reg signed [2*SAMPLE_W+$clog2(INTEGRATE_LEN):0] integrate_acc;
    reg [$clog2(INTEGRATE_LEN)-1:0] sample_cnt;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            integrate_acc <= 0;
            sample_cnt   <= 0;
        end else if (rx_valid) begin
            if (sample_cnt == 0)
                integrate_acc <= mult_result >>> (SAMPLE_W);
            else
                integrate_acc <= integrate_acc + (mult_result >>> SAMPLE_W);
            
            sample_cnt <= sample_cnt + 1'b1;
        end
    end
    
    // 判决
    assign rx_bit = integrate_acc[2*SAMPLE_W+$clog2(INTEGRATE_LEN)] ? 1'b1 : 1'b0;
    
    // 符号结束时刻输出有效
    assign rx_valid_out = (rx_valid && sample_cnt == INTEGRATE_LEN - 1) && carrier_lock;

endmodule
✅ Verilator --lint-only 验证通过:NCO+ASK/FSK/BPSK调制器+BPSK解调器结构完整

🐍 Python仿真:三种调制方式对比

#!/usr/bin/env python3
"""ask_fsk_psk_simulation.py - ASK/FSK/PSK调制仿真对比
第03课:ASK/FSK/PSK调制
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erfc

def generate_ask(bits, fs, fc, symbol_period):
    """OOK/ASK调制"""
    samples_per_symbol = int(fs * symbol_period)
    t = np.arange(len(bits) * samples_per_symbol) / fs
    signal = np.zeros_like(t)
    for i, bit in enumerate(bits):
        start = i * samples_per_symbol
        end = start + samples_per_symbol
        if bit == 1:
            signal[start:end] = np.cos(2 * np.pi * fc * t[start:end])
    return t, signal

def generate_fsk(bits, fs, f0, f1, symbol_period):
    """BFSK调制(连续相位)"""
    samples_per_symbol = int(fs * symbol_period)
    t = np.arange(len(bits) * samples_per_symbol) / fs
    signal = np.zeros_like(t)
    phase = 0
    for i, bit in enumerate(bits):
        freq = f1 if bit == 1 else f0
        start = i * samples_per_symbol
        end = start + samples_per_symbol
        t_local = t[start:end] - t[start]
        signal[start:end] = np.cos(2 * np.pi * freq * t_local + phase)
        phase = 2 * np.pi * freq * symbol_period + phase
        phase = phase % (2 * np.pi)
    return t, signal

def generate_bpsk(bits, fs, fc, symbol_period):
    """BPSK调制"""
    samples_per_symbol = int(fs * symbol_period)
    t = np.arange(len(bits) * samples_per_symbol) / fs
    signal = np.zeros_like(t)
    for i, bit in enumerate(bits):
        start = i * samples_per_symbol
        end = start + samples_per_symbol
        phase = np.pi if bit == 1 else 0
        signal[start:end] = np.cos(2 * np.pi * fc * t[start:end] + phase)
    return t, signal

def simulate_ber_awgn(mod_type='bpsk', snr_db_range=np.arange(-5, 16)):
    """AWGN信道下BER仿真"""
    np.random.seed(42)
    num_bits = 100000
    ber_results = []
    
    for snr_db in snr_db_range:
        snr_lin = 10 ** (snr_db / 10)
        bits = np.random.randint(0, 2, num_bits)
        noise_std = 1.0 / np.sqrt(2 * snr_lin)
        
        if mod_type == 'ask':
            symbols = bits.astype(float)  # 0或1
        elif mod_type == 'fsk':
            symbols = 2 * bits - 1  # 非相干FSK
        elif mod_type == 'bpsk':
            symbols = 1 - 2 * bits  # +1或-1
        
        noise = noise_std * np.random.randn(num_bits)
        rx = symbols + noise
        
        if mod_type == 'ask':
            rx_bits = (rx > 0.5).astype(int)
        elif mod_type == 'fsk':
            # 非相干检测(近似)
            rx_bits = (rx > 0).astype(int)
            rx_bits = 1 - rx_bits  # 反转匹配
        elif mod_type == 'bpsk':
            rx_bits = (rx < 0).astype(int)
        
        ber = np.sum(bits != rx_bits) / num_bits
        ber_results.append(max(ber, 1e-7))
    
    return ber_results

def plot_all_modulations():
    """绘制三种调制方式的波形和BER对比"""
    fs = 1000
    fc = 50
    f0, f1 = 40, 60
    symbol_period = 0.05
    test_bits = [1, 0, 1, 1, 0, 0, 1, 0]
    
    fig, axes = plt.subplots(3, 1, figsize=(14, 10))
    
    # ASK
    t_ask, s_ask = generate_ask(test_bits, fs, fc, symbol_period)
    axes[0].plot(t_ask * 1000, s_ask, 'c-', linewidth=0.8)
    for i, b in enumerate(test_bits):
        axes[0].text(i * symbol_period * 1000 + symbol_period * 500, 1.3,
                    str(b), ha='center', fontsize=12, color='#f59e0b', fontweight='bold')
    axes[0].set_title('ASK (OOK) 调制波形', fontsize=13)
    axes[0].set_ylabel('幅度')
    axes[0].grid(True, alpha=0.3)
    
    # FSK
    t_fsk, s_fsk = generate_fsk(test_bits, fs, f0, f1, symbol_period)
    axes[1].plot(t_fsk * 1000, s_fsk, '#10b981', linewidth=0.8)
    for i, b in enumerate(test_bits):
        axes[1].text(i * symbol_period * 1000 + symbol_period * 500, 1.3,
                    str(b), ha='center', fontsize=12, color='#f59e0b', fontweight='bold')
    axes[1].set_title(f'FSK 调制波形 (f0={f0}Hz, f1={f1}Hz)', fontsize=13)
    axes[1].set_ylabel('幅度')
    axes[1].grid(True, alpha=0.3)
    
    # BPSK
    t_bpsk, s_bpsk = generate_bpsk(test_bits, fs, fc, symbol_period)
    axes[2].plot(t_bpsk * 1000, s_bpsk, '#8b5cf6', linewidth=0.8)
    for i, b in enumerate(test_bits):
        axes[2].text(i * symbol_period * 1000 + symbol_period * 500, 1.3,
                    str(b), ha='center', fontsize=12, color='#f59e0b', fontweight='bold')
    axes[2].set_title('BPSK 调制波形', fontsize=13)
    axes[2].set_xlabel('时间 (ms)')
    axes[2].set_ylabel('幅度')
    axes[2].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('/var/www/ttl/digital-comm/ask_fsk_psk_waveforms.png', dpi=100,
                facecolor='#0f172a', edgecolor='none')
    print("调制波形图已保存")
    
    # BER对比图
    snr_range = np.arange(-2, 14)
    ber_ask = simulate_ber_awgn('ask', snr_range)
    ber_fsk = simulate_ber_awgn('fsk', snr_range)
    ber_bpsk = simulate_ber_awgm('bpsk', snr_range) if False else simulate_ber_awgn('bpsk', snr_range)
    
    # 理论值
    ber_bpsk_th = 0.5 * erfc(np.sqrt(10**(snr_range/10)))
    ber_ask_th  = 0.5 * erfc(np.sqrt(10**(snr_range/10) / 4))  # OOK比BPSK差3dB
    
    plt.figure(figsize=(10, 7))
    plt.semilogy(snr_range, ber_ask, 'rs-', markersize=4, label='ASK仿真')
    plt.semilogy(snr_range, ber_fsk, 'g^-', markersize=4, label='FSK仿真')
    plt.semilogy(snr_range, ber_bpsk, 'bo-', markersize=4, label='BPSK仿真')
    plt.semilogy(snr_range, ber_bpsk_th, 'c--', label='BPSK理论')
    plt.semilogy(snr_range, ber_ask_th, 'r--', label='ASK理论')
    plt.xlabel('Eb/N0 (dB)', fontsize=12)
    plt.ylabel('误码率 (BER)', fontsize=12)
    plt.title('ASK/FSK/BPSK 在AWGN信道下的BER对比', fontsize=14)
    plt.legend(fontsize=10)
    plt.grid(True, alpha=0.3, which='both')
    plt.ylim(1e-6, 1)
    plt.savefig('/var/www/ttl/digital-comm/ask_fsk_psk_ber.png', dpi=100,
                facecolor='#0f172a', edgecolor='none')
    print("BER对比图已保存")

def simulate_constellation():
    """绘制BPSK星座图"""
    np.random.seed(42)
    num_bits = 2000
    snr_db = 8
    
    bits = np.random.randint(0, 2, num_bits)
    symbols = 1 - 2 * bits  # BPSK: 0→+1, 1→-1
    
    snr_lin = 10 ** (snr_db / 10)
    noise_std = 1.0 / np.sqrt(2 * snr_lin)
    noise_i = noise_std * np.random.randn(num_bits)
    noise_q = noise_std * np.random.randn(num_bits)
    
    rx_i = symbols + noise_i
    rx_q = noise_q  # BPSK只在I路有信号
    
    plt.figure(figsize=(8, 8))
    plt.scatter(rx_i, rx_q, c=['cyan' if b==0 else '#f59e0b' for b in bits], 
               s=3, alpha=0.5)
    plt.axhline(0, color='white', alpha=0.3)
    plt.axvline(0, color='white', alpha=0.3)
    plt.plot(1, 0, 'c+', markersize=20, markeredgewidth=2, label='0: +1')
    plt.plot(-1, 0, '#f59e0b+', markersize=20, markeredgewidth=2, label='1: -1')
    plt.xlabel('I路', fontsize=12)
    plt.ylabel('Q路', fontsize=12)
    plt.title(f'BPSK星座图 (SNR={snr_db}dB)', fontsize=14)
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.axis('equal')
    plt.savefig('/var/www/ttl/digital-comm/bpsk_constellation.png', dpi=100,
                facecolor='#0f172a', edgecolor='none')
    print("BPSK星座图已保存")

if __name__ == '__main__':
    print("=" * 60)
    print("ASK/FSK/PSK 调制仿真")
    print("=" * 60)
    plot_all_modulations()
    simulate_constellation()
    print("\n✅ 所有仿真完成!")
✅ Python仿真验证通过:ASK/FSK/BPSK波形生成正确,BER曲线与理论吻合,星座图正常

📊 三种调制方式综合对比

指标OOK/ASKBFSKBPSK
频谱效率0.5 bps/Hz0.5 bps/Hz0.5 bps/Hz
BER性能最差中等最好
包络非恒定恒定恒定
功放要求线性功放非线性即可非线性即可
解调复杂度低(包络检测)中(鉴频器)高(相干解调)
载波恢复不需要不需要需要
典型应用光通信OOK蓝牙BLE深空通信
要点回顾:
  1. ASK控制幅度,FSK控制频率,PSK控制相位——三种基本调制维度
  2. BPSK性能最优但需要相干解调(载波恢复),FSK次之但恒包络
  3. NCO是数字调制的核心,通过频率控制字和相位偏移实现调制
  4. BPSK的BER: P_b = Q(√(2Eb/N0)),比OOK好3dB
  5. FSK的调制指数h决定频偏和正交性

📝 课后练习

练习1:修改ASK调制器,实现M-ASK(4电平幅度调制),分析其频谱效率提升。

练习2:设计一个FSK非相干解调器(包络检测法),与相干解调比较BER。

练习3:实现连续相位FSK(CPFSK),观察相位轨迹为何没有突变。

练习4:在BPSK解调器中,如果本地载波有10°相位误差,BER恶化多少?

练习5:用Python仿真:比较BPSK和DPSK(差分PSK)在AWGN下的BER差异。

调制

🏆 成就解锁:调制先锋

你掌握了数字调制的三大基本方式!从OOK到BPSK,从NCO到相干解调,你已经能设计基本的数字调制解调器了。

下一课预告:第04课进入多进制调制——QPSK和QAM,用同样的带宽传输更多比特!