QPSk与16QAM调制的信号涌现的差异
通过一个动态信号波形模拟 + 星座图演变的方式,直观展示 QPSK 与 16QAM 在调制过程中信号的“涌现”差异。
🎯 目标:
用 Python 模拟以下过程:
- 生成一段随机比特流;
- 分别用 QPSK 和 16QAM 调制;
- 展示:
- 比特 → 符号映射过程(星座点跳跃);
- 实际调制后的时域信号波形(载波调制);
- 星座图对比(静态+动态演化);
- 直观看出两者的信号复杂度差异。
✅ 完整代码如下(可运行):
import numpy as np
import matplotlib.pyplot as plt
# 设置随机种子便于复现
np.random.seed(42)
def generate_bits(n):
return np.random.randint(0, 2, n)
def qpsk_modulate(bits):
# 每 2 bit 映射为一个 QPSK 符号
assert len(bits) % 2 == 0, "QPSK 要求比特数为偶数"
symbols = []
for i in range(0, len(bits), 2):
b1, b2 = bits[i], bits[i+1]
# 映射规则:00->(1,1), 01->(-1,1), 10->(-1,-1), 11->(1,-1)
if b1 == 0 and b2 == 0:
sym = complex(1, 1)
elif b1 == 0 and b2 == 1:
sym = complex(-1, 1)
elif b1 == 1 and b2 == 0:
sym = complex(-1, -1)
else:
sym = complex(1, -1)
symbols.append(sym)
return np.array(symbols)
def qam16_modulate(bits):
# 每 4 bit 映射为一个 16QAM 符号
assert len(bits) % 4 == 0, "16QAM 要求比特数为4的倍数"
symbols = []
levels = [-3, -1, 1, 3] # 16QAM 的实/虚部取值
# 归一化因子(使平均能量为1)
scale = np.sqrt(10)
for i in range(0, len(bits), 4):
b1, b2, b3, b4 = bits[i:i+4]
# 二进制转索引:b1 b2 -> I, b3 b4 -> Q
idx_i = b1 * 2 + b2
idx_q = b3 * 2 + b4
i_val = levels[idx_i]
q_val = levels[idx_q]
sym = complex(i_val, q_val) / scale
symbols.append(sym)
return np.array(symbols)
def add_noise(signal, snr_db):
power = np.mean(np.abs(signal)**2)
noise_power = power / (10**(snr_db/10))
noise = np.sqrt(noise_power/2) * (np.random.randn(len(signal)) + 1j*np.random.randn(len(signal)))
return signal + noise
def plot_signals_and_constellations(bits_qpsk, bits_16qam, snr_db=20):
# 1. 调制
qpsk_symbols = qpsk_modulate(bits_qpsk)
qam16_symbols = qam16_modulate(bits_16qam)
# 2. 添加噪声(模拟信道)
noisy_qpsk = add_noise(qpsk_symbols, snr_db)
noisy_16qam = add_noise(qam16_symbols, snr_db)
# 3. 画图:星座图 + 信号波形(简化版)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# --- 子图 1:QPSK 星座图 ---
axes[0,0].scatter(qpsk_symbols.real, qpsk_symbols.imag, c='blue', s=60, label='Original', marker='o')
axes[0,0].scatter(noisy_qpsk.real, noisy_qpsk.imag, c='red', s=30, label='Noisy', alpha=0.7)
axes[0,0].set_title(f'QPSK Constellation (SNR={snr_db}dB)', fontsize=12)
axes[0,0].set_xlabel('I')
axes[0,0].set_ylabel('Q')
axes[0,0].grid(True, alpha=0.3)
axes[0,0].legend()
axes[0,0].axis('equal')
# --- 子图 2:16QAM 星座图 ---
axes[0,1].scatter(qam16_symbols.real, qam16_symbols.imag, c='blue', s=60, label='Original', marker='o')
axes[0,1].scatter(noisy_16qam.real, noisy_16qam.imag, c='red', s=30, label='Noisy', alpha=0.7)
axes[0,1].set_title(f'16QAM Constellation (SNR={snr_db}dB)', fontsize=12)
axes[0,1].set_xlabel('I')
axes[0,1].set_ylabel('Q')
axes[0,1].grid(True, alpha=0.3)
axes[0,1].legend()
axes[0,1].axis('equal')
# --- 子图 3:QPSK 信号波形(简化)---
t = np.linspace(0, 0.01, 1000) # 时间轴
carrier_freq = 1000 # Hz
carrier = np.exp(1j * 2 * np.pi * carrier_freq * t)
# 取前几个符号做波形演示
symbol_duration = 0.001
symbols_to_plot = qpsk_symbols[:4]
signal_wave = np.zeros_like(t, dtype=complex)
for i, sym in enumerate(symbols_to_plot):
start_idx = int(i * symbol_duration * len(t))
end_idx = int((i+1) * symbol_duration * len(t))
signal_wave[start_idx:end_idx] = sym * carrier[start_idx:end_idx]
axes[1,0].plot(t, signal_wave.real, label="I component", lw=1.5)
axes[1,0].plot(t, signal_wave.imag, label="Q component", lw=1.5, linestyle="--")
axes[1,0].set_title("QPSK Time-Domain Signal (I & Q)", fontsize=12)
axes[1,0].set_xlabel("Time (s)")
axes[1,0].set_ylabel("Amplitude")
axes[1,0].legend()
axes[1,0].grid(True, alpha=0.3)
# --- 子图 4:16QAM 信号波形 ---
symbol_duration = 0.001
symbols_to_plot = qam16_symbols[:4]
signal_wave_16 = np.zeros_like(t, dtype=complex)
for i, sym in enumerate(symbols_to_plot):
start_idx = int(i * symbol_duration * len(t))
end_idx = int((i+1) * symbol_duration * len(t))
signal_wave_16[start_idx:end_idx] = sym * carrier[start_idx:end_idx]
axes[1,1].plot(t, signal_wave_16.real, label="I component", lw=1.5)
axes[1,1].plot(t, signal_wave_16.imag, label="Q component", lw=1.5, linestyle="--")
axes[1,1].set_title("16QAM Time-Domain Signal (I & Q)", fontsize=12)
axes[1,1].set_xlabel("Time (s)")
axes[1,1].set_ylabel("Amplitude")
axes[1,1].legend()
axes[1,1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# === 主程序:生成数据并绘图 ===
if __name__ == "__main__":
# 生成足够多的比特
total_bits = 1000
bits_qpsk = generate_bits(total_bits)
bits_16qam = generate_bits(total_bits)
# 只取前 1000 位用于 QPSK(每 2 位一组)
num_qpsk_symbols = len(bits_qpsk) // 2
bits_qpsk = bits_qpsk[:num_qpsk_symbols * 2]
# 只取前 1000 位用于 16QAM(每 4 位一组)
num_16qam_symbols = len(bits_16qam) // 4
bits_16qam = bits_16qam[:num_16qam_symbols * 4]
# 绘制信号与星座图
plot_signals_and_constellations(bits_qpsk, bits_16qam, snr_db=20)
📈 运行后你会看到什么?
图像 | 内容说明 |
---|---|
✅ 左上:QPSK 星座图 | 4 个清晰点,分布均匀,点间距离大 |
✅ 右上:16QAM 星座图 | 16 个点呈 4×4 网格,中心密集,边缘稀疏 |
✅ 左下:QPSK 时域波形 | 波形跳变少,信号平滑,相位变化较慢 |
✅ 右下:16QAM 时域波形 | 波形跳变频繁,幅度波动更大,更“剧烈” |
🔍 关键差异总结:
方面 | QPSK | 16QAM |
---|---|---|
星座点数 | 4 | 16 |
每符号比特数 | 2 | 4 |
信号复杂度 | 低(平稳) | 高(剧烈跳变) |
抗干扰能力 | 强(点远) | 弱(点密) |
数据速率 | 较低 | 较高 |
适用场景 | 无线、弱信号 | 光纤、高信噪比 |
💡 一句话理解“信号涌现差异”:
QPSK 的信号像“稳重的散步”,一步一停;
16QAM 的信号像“快速跑动的舞步”,步伐多变、节奏快,但容易踩空。
需要我再加一个“实时动画”版本(用 matplotlib.animation
动态展示星座点跳跃)吗?或者想看看误码率对比曲线?😊