模拟信号要进入数字世界,必须经过两个关键步骤:采样(Sampling)和量化(Quantization)。采样将时间连续的信号变为时间离散,量化将幅度连续的信号变为幅度离散。这两个操作的质量直接决定了数字通信系统的性能上限。
模拟信号 → 抗混叠滤波 → 采样 → 量化 → 编码 → 数字信号
每一步都至关重要:抗混叠滤波防止频谱混叠,采样保证信息不丢失,量化控制精度损失,编码输出最终比特流。
采样定理是数字通信的基石之一:
含义:采样频率必须大于信号最高频率的两倍,否则将发生混叠(Aliasing),无法无失真恢复原始信号。
设连续信号 x(t) 的频谱为 X(f),且当 |f| > B 时 X(f) = 0(带限信号)。
采样信号 x_s(t) = x(t) × Σ δ(t - nT_s),其中 T_s = 1/f_s。
频域上,采样等效于频谱的周期延拓:
当 f_s ≥ 2B 时,延拓的频谱互不重叠,可用低通滤波器恢复原始信号。
当 f_s < 2B 时,高频分量折叠到低频,造成不可逆的混叠失真。
f_s = (2.5~4) × f_max,留出过渡带给抗混叠滤波器。例如CD音频:f_max=20kHz,f_s=44.1kHz(2.205倍)。
量化将连续幅度映射到有限个离散级别。设量化位数为 N 位,则有 L = 2^N 个量化级别。
量化间隔 Δ = V_pp / L,其中 V_pp 为信号峰峰值。
量化噪声功率(均匀分布近似):
量化信噪比(SQNR):
这就是著名的"6dB/位"规则——每增加1位量化精度,SQNR提高约6dB。
| 位数N | 量化级数L | SQNR (dB) | 典型应用 |
|---|---|---|---|
| 8 | 256 | 49.92 | 语音PCM |
| 12 | 4096 | 74.00 | ADC采样 |
| 14 | 16384 | 86.04 | 通信接收机 |
| 16 | 65536 | 98.08 | CD音频 |
| 24 | 16777216 | 146.24 | 专业音频 |
语音信号幅度分布不均匀(小信号概率大),均匀量化导致小信号SQNR差。
μ律(北美/日本): y = sgn(x) × ln(1+μ|x|) / ln(1+μ),μ=255
A律(欧洲/中国): 分段线性近似,A=87.56
效果:8位非均匀量化≈12位均匀量化的动态范围。
// adc_sampler.v - 多通道ADC采样控制器
// 第02课:采样与量化
module adc_sampler #(
parameter ADC_BITS = 12, // ADC位数
parameter NUM_CHANNELS = 4, // 通道数
parameter SAMPLE_RATE = 16, // 采样率分频系数 (clk/16)
parameter FIFO_DEPTH = 256 // 采样FIFO深度
)(
input wire clk, // 系统时钟
input wire rst_n, // 异步复位
// ADC接口 (模拟前端)
output reg [2:0] adc_ch_sel, // 通道选择
output wire adc_conv_start, // 转换启动
input wire adc_conv_done, // 转换完成
input wire [ADC_BITS-1:0] adc_data, // ADC转换结果
// 数字输出接口
output wire [ADC_BITS-1:0] sample_data, // 采样数据
output wire [2:0] sample_ch, // 数据所属通道
output wire sample_valid,// 数据有效
input wire sample_ready,// 下游就绪
// 控制接口
input wire enable, // 采样使能
input wire [15:0] sample_div, // 采样分频比
output wire [NUM_CHANNELS-1:0] ch_active, // 通道活跃指示
// 状态
output wire [3:0] state_out, // 状态输出
output wire [15:0] sample_count // 采样计数
);
// ============================================================
// 状态机定义
// ============================================================
localparam S_IDLE = 4'd0;
localparam S_SELECT_CH = 4'd1;
localparam S_START_CONV = 4'd2;
localparam S_WAIT_CONV = 4'd3;
localparam S_READ_DATA = 4'd4;
localparam S_WRITE_FIFO = 4'd5;
reg [3:0] state;
reg [2:0] current_ch;
reg [15:0] div_cnt;
reg [15:0] total_samples;
reg [NUM_CHANNELS-1:0] ch_en;
// 采样保持寄存器
reg [ADC_BITS-1:0] sample_hold [0:NUM_CHANNELS-1];
// FIFO
reg [ADC_BITS+2:0] fifo_mem [0:FIFO_DEPTH-1]; // {ch[2:0], data[ADC_BITS-1:0]}
reg [$clog2(FIFO_DEPTH):0] fifo_wr_ptr, fifo_rd_ptr;
reg [$clog2(FIFO_DEPTH):0] fifo_count;
wire fifo_full = (fifo_count == FIFO_DEPTH);
wire fifo_empty = (fifo_count == 0);
assign state_out = state;
assign sample_count = total_samples;
assign adc_conv_start = (state == S_START_CONV);
// FIFO输出
assign sample_data = fifo_mem[fifo_rd_ptr][ADC_BITS-1:0];
assign sample_ch = fifo_mem[fifo_rd_ptr][ADC_BITS+2:ADC_BITS];
assign sample_valid = ~fifo_empty;
assign ch_active = ch_en;
// ============================================================
// 采样定时器
// ============================================================
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
div_cnt <= 16'd0;
else if (state == S_IDLE && enable)
div_cnt <= (div_cnt >= sample_div) ? 16'd0 : div_cnt + 1'b1;
end
// ============================================================
// 主状态机
// ============================================================
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
current_ch <= 3'd0;
adc_ch_sel <= 3'd0;
total_samples <= 16'd0;
fifo_wr_ptr <= 0;
fifo_rd_ptr <= 0;
fifo_count <= 0;
ch_en <= {NUM_CHANNELS{1'b1}};
end else begin
case (state)
S_IDLE: begin
if (enable && div_cnt >= sample_div) begin
state <= S_SELECT_CH;
current_ch <= 3'd0;
end
end
S_SELECT_CH: begin
// 跳过未使能的通道
if (ch_en[current_ch]) begin
adc_ch_sel <= current_ch;
state <= S_START_CONV;
end else begin
if (current_ch < NUM_CHANNELS - 1)
current_ch <= current_ch + 1'b1;
else
state <= S_IDLE; // 所有通道未使能
end
end
S_START_CONV: begin
state <= S_WAIT_CONV;
end
S_WAIT_CONV: begin
if (adc_conv_done) begin
state <= S_READ_DATA;
end
end
S_READ_DATA: begin
// 保存采样值
sample_hold[current_ch] <= adc_data;
state <= S_WRITE_FIFO;
end
S_WRITE_FIFO: begin
if (!fifo_full) begin
fifo_mem[fifo_wr_ptr] <= {current_ch, adc_data};
fifo_wr_ptr <= fifo_wr_ptr + 1'b1;
fifo_count <= fifo_count + 1'b1;
end
total_samples <= total_samples + 1'b1;
// 切换到下一个通道
if (current_ch < NUM_CHANNELS - 1) begin
current_ch <= current_ch + 1'b1;
state <= S_SELECT_CH;
end else begin
state <= S_IDLE;
end
end
default: state <= S_IDLE;
endcase
end
end
// FIFO读操作
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
fifo_rd_ptr <= 0;
end else if (sample_valid && sample_ready) begin
fifo_rd_ptr <= fifo_rd_ptr + 1'b1;
fifo_count <= fifo_count - 1'b1;
end
end
endmodule
// ============================================================
// 抗混叠滤波器 (FIR低通)
// ============================================================
module antialias_filter #(
parameter DATA_W = 12,
parameter COEFF_W = 16,
parameter TAP_NUM = 33, // 滤波器阶数
parameter CUTOFF_RATIO = 13 // 截止频率 = f_s / CUTOFF_RATIO
)(
input wire clk,
input wire rst_n,
input wire [DATA_W-1:0] din,
input wire din_valid,
output wire [DATA_W-1:0] dout,
output wire dout_valid
);
// 滤波器系数 (由Python生成)
// 截止频率: f_s/2 * (1/CUTOFF_RATIO)
// 窗函数: Hamming
reg signed [COEFF_W-1:0] coeffs [0:TAP_NUM-1];
initial begin
// 33阶FIR低通系数(归一化,需乘2^15)
coeffs[0] = 16'sd0; coeffs[1] = -16'sd2;
coeffs[2] = -16'sd5; coeffs[3] = -16'sd8;
coeffs[4] = -16'sd8; coeffs[5] = -16'sd3;
coeffs[6] = 16'sd8; coeffs[7] = 16'sd24;
coeffs[8] = 16'sd42; coeffs[9] = 16'sd56;
coeffs[10] = 16'sd58; coeffs[11] = 16'sd43;
coeffs[12] = 16'sd9; coeffs[13] = -16'sd40;
coeffs[14] = -16'sd94; coeffs[15] = -16'sd140;
coeffs[16] = -16'sd163; coeffs[17] = -16'sd140;
coeffs[18] = -16'sd94; coeffs[19] = -16'sd40;
coeffs[20] = 16'sd9; coeffs[21] = 16'sd43;
coeffs[22] = 16'sd58; coeffs[23] = 16'sd56;
coeffs[24] = 16'sd42; coeffs[25] = 16'sd24;
coeffs[26] = 16'sd8; coeffs[27] = -16'sd3;
coeffs[28] = -16'sd8; coeffs[29] = -16'sd8;
coeffs[30] = -16'sd5; coeffs[31] = -16'sd2;
coeffs[32] = 16'sd0;
end
// 延迟线
reg signed [DATA_W-1:0] shift_reg [0:TAP_NUM-1];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
integer i;
for (i = 0; i < TAP_NUM; i = i + 1)
shift_reg[i] <= 0;
end else if (din_valid) begin
shift_reg[0] <= $signed(din);
integer j;
for (j = 1; j < TAP_NUM; j = j + 1)
shift_reg[j] <= shift_reg[j-1];
end
end
// 乘累加 (MAC)
reg signed [DATA_W+COEFF_W:0] acc;
reg valid_d;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
acc <= 0;
valid_d <= 1'b0;
end else if (din_valid) begin
acc = 0;
integer k;
for (k = 0; k < TAP_NUM; k = k + 1)
acc = acc + shift_reg[k] * coeffs[k];
valid_d <= 1'b1;
end else begin
valid_d <= 1'b0;
end
end
assign dout = acc[DATA_W+COEFF_W-1:COEFF_W];
assign dout_valid = valid_d;
endmodule
#!/usr/bin/env python3
"""sampling_quantization.py - 采样与量化仿真
第02课:采样与量化
演示Nyquist采样定理、混叠现象、量化噪声
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
def demonstrate_aliasing():
"""演示混叠现象"""
# 原始信号: 5Hz正弦波
f_signal = 5 # 信号频率
f_s_ok = 50 # 满足Nyquist
f_s_low = 7 # 不满足Nyquist (2*5=10, 7<10)
t_continuous = np.linspace(0, 1, 1000)
x_continuous = np.sin(2 * np.pi * f_signal * t_continuous)
# 正常采样
t_sampled_ok = np.arange(0, 1, 1/f_s_ok)
x_sampled_ok = np.sin(2 * np.pi * f_signal * t_sampled_ok)
# 欠采样 (混叠)
t_sampled_low = np.arange(0, 1, 1/f_s_low)
x_sampled_low = np.sin(2 * np.pi * f_signal * t_sampled_low)
# 混叠后的"表观频率"
f_alias = abs(f_signal - f_s_low * round(f_signal / f_s_low))
print(f"信号频率: {f_signal} Hz")
print(f"采样率: {f_s_low} Hz (不满足Nyquist)")
print(f"混叠频率: {f_alias} Hz")
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
axes[0].plot(t_continuous, x_continuous, 'c-', alpha=0.5, label='原始信号')
axes[0].stem(t_sampled_ok, x_sampled_ok, linefmt='r-', markerfmt='ro', basefmt='k-')
axes[0].set_title(f'正常采样 (f_s={f_s_ok}Hz > 2×{f_signal}Hz)', fontsize=13)
axes[0].set_xlabel('时间 (s)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].plot(t_continuous, x_continuous, 'c-', alpha=0.5, label='原始信号')
axes[1].stem(t_sampled_low, x_sampled_low, linefmt='r-', markerfmt='ro', basefmt='k-')
x_reconstructed = np.sin(2 * np.pi * f_alias * t_continuous)
axes[1].plot(t_continuous, x_reconstructed, 'y--', alpha=0.7, label=f'混叠信号 ({f_alias}Hz)')
axes[1].set_title(f'欠采样混叠 (f_s={f_s_low}Hz < 2×{f_signal}Hz)', fontsize=13)
axes[1].set_xlabel('时间 (s)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/var/www/ttl/digital-comm/aliasing.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
print("混叠演示图已保存")
def simulate_quantization():
"""仿真量化过程和量化噪声"""
# 生成测试信号
fs = 48000
t = np.arange(0, 0.1, 1/fs)
f_test = 1000
x = 0.8 * np.sin(2 * np.pi * f_test * t)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 不同位数的量化
bit_depths = [2, 4, 8, 16]
sqnrs = []
for idx, bits in enumerate(bit_depths):
L = 2 ** bits
# 均匀量化
x_clipped = np.clip(x, -1, 1)
q_step = 2.0 / L
x_quantized = np.round((x_clipped + 1) / q_step) * q_step - 1
# 量化噪声
q_noise = x_quantized - x_clipped
noise_power = np.mean(q_noise ** 2)
signal_power = np.mean(x_clipped ** 2)
sqnr = 10 * np.log10(signal_power / noise_power) if noise_power > 0 else float('inf')
sqnrs.append(sqnr)
row, col = idx // 2, idx % 2
t_short = t[:500]
axes[row, col].plot(t_short * 1000, x_clipped[:500], 'c-', alpha=0.5, label='原始')
axes[row, col].plot(t_short * 1000, x_quantized[:500], 'r.', markersize=2, label='量化')
axes[row, col].set_title(f'{bits}位量化 (L={L}, SQNR={sqnr:.1f}dB)', fontsize=12)
axes[row, col].set_xlabel('时间 (ms)')
axes[row, col].legend(fontsize=9)
axes[row, col].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/var/www/ttl/digital-comm/quantization.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
# 验证6.02N + 1.76公式
print("\n量化SQNR验证:")
print(f"{'位数':>6} {'仿真SQNR':>12} {'理论SQNR':>12} {'误差':>8}")
for bits, sqnr_sim in zip(bit_depths, sqnrs):
sqnr_theory = 6.02 * bits + 1.76
print(f"{bits:>6} {sqnr_sim:>12.2f} {sqnr_theory:>12.2f} {sqnr_sim-sqnr_theory:>8.2f}")
print("量化仿真图已保存")
def design_fir_filter():
"""设计抗混叠FIR滤波器"""
fs = 48000 # 采样率
f_cutoff = 8000 # 截止频率
numtaps = 33 # 滤波器阶数
# Hamming窗FIR
coeffs = signal.firwin(numtaps, f_cutoff, fs=fs, window='hamming')
# 频率响应
w, h = signal.freqz(coeffs, fs=fs)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# 幅度响应
ax1.plot(w, 20 * np.log10(np.abs(h) + 1e-10), 'c-', linewidth=1.5)
ax1.axvline(f_cutoff, color='r', linestyle='--', alpha=0.7, label=f'截止频率 {f_cutoff}Hz')
ax1.axvline(fs/2, color='y', linestyle='--', alpha=0.7, label=f'Nyquist {fs/2}Hz')
ax1.set_xlabel('频率 (Hz)')
ax1.set_ylabel('幅度 (dB)')
ax1.set_title('抗混叠FIR滤波器频率响应', fontsize=13)
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.set_ylim(-80, 5)
# 冲激响应
ax2.stem(range(numtaps), coeffs, linefmt='c-', markerfmt='co', basefmt='k-')
ax2.set_xlabel('抽头序号')
ax2.set_ylabel('系数值')
ax2.set_title('FIR滤波器冲激响应', fontsize=13)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/var/www/ttl/digital-comm/fir_filter.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
# 输出Verilog系数
print("\nFIR系数 (Q1.15格式):")
for i, c in enumerate(coeffs):
c_fixed = int(round(c * 2**15))
print(f" coeffs[{i:2d}] = 16'sd{c_fixed};")
print("FIR滤波器设计图已保存")
return coeffs
def simulate_mu_law():
"""仿真μ律压扩"""
mu = 255
x = np.linspace(-1, 1, 1000)
# μ律压缩
y_compress = np.sign(x) * np.log(1 + mu * np.abs(x)) / np.log(1 + mu)
# μ律扩展
y_expand = np.sign(y_compress) * (1/mu) * ((1 + mu)**np.abs(y_compress) - 1)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
ax1.plot(x, y_compress, 'c-', linewidth=2, label=f'μ律压缩 (μ={mu})')
ax1.plot(x, x, 'r--', alpha=0.5, label='线性')
ax1.set_xlabel('输入幅度')
ax1.set_ylabel('压缩输出')
ax1.set_title('μ律压缩特性', fontsize=13)
ax1.legend()
ax1.grid(True, alpha=0.3)
# 8位μ律量化的SQNR vs 信号幅度
amplitudes = np.logspace(-3, 0, 100)
sqnr_linear = []
sqnr_mulaw = []
for amp in amplitudes:
t = np.arange(0, 1000) / 8000
sig = amp * np.sin(2 * np.pi * 1000 * t)
# 线性8位量化
L = 256
q_step = 2.0 / L
sig_lin = np.round((np.clip(sig, -1, 1) + 1) / q_step) * q_step - 1
n_lin = sig_lin - sig
sqnr_l = 10 * np.log10(np.mean(sig**2) / (np.mean(n_lin**2) + 1e-20))
sqnr_linear.append(sqnr_l)
# μ律8位量化
sig_comp = np.sign(sig) * np.log(1 + mu * np.abs(sig)) / np.log(1 + mu)
sig_comp_q = np.round((np.clip(sig_comp, -1, 1) + 1) / q_step) * q_step - 1
sig_exp = np.sign(sig_comp_q) * (1/mu) * ((1 + mu)**np.abs(sig_comp_q) - 1)
n_mu = sig_exp - sig
sqnr_m = 10 * np.log10(np.mean(sig**2) / (np.mean(n_mu**2) + 1e-20))
sqnr_mulaw.append(sqnr_m)
ax2.plot(20*np.log10(amplitudes), sqnr_linear, 'r-', label='线性8位')
ax2.plot(20*np.log10(amplitudes), sqnr_mulaw, 'c-', linewidth=2, label='μ律8位')
ax2.set_xlabel('信号幅度 (dB)')
ax2.set_ylabel('SQNR (dB)')
ax2.set_title('线性 vs μ律量化 SQNR对比', fontsize=13)
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/var/www/ttl/digital-comm/mulaw.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
print("μ律压扩仿真图已保存")
if __name__ == '__main__':
print("=" * 60)
print("采样与量化 - 全流程仿真")
print("=" * 60)
demonstrate_aliasing()
simulate_quantization()
design_fir_filter()
simulate_mu_law()
print("\n✅ 所有仿真完成!")
练习1:一个4kHz语音信号,分别用8kHz和6kHz采样,结果有何不同?用Python验证。
练习2:设计一个12位ADC的量化器,计算满量程2V时的量化间隔和SQNR。
练习3:修改FIR滤波器系数,设计一个截止频率为4kHz(f_s=48kHz)的低通滤波器。
练习4:在Verilog中添加FIFO溢出检测和中断机制。
练习5:对比μ律和A律在小信号(-40dB)时的SQNR差异。
你掌握了模拟世界与数字世界的桥梁!从Nyquist定理到量化噪声,从FIR滤波器到μ律压扩,你已经能够设计完整的ADC前端。
下一课预告:第03课将进入调制世界,学习ASK/FSK/PSK三种基本数字调制方式。