别再死记硬背了!用Python+Matplotlib手把手教你画BPSK/QPSK/16-QAM星座图(附源码)
用Python实战解析通信星座图:从理论到可视化实现
通信工程中那些看似神秘的星座图,其实就像夜空中的导航星图——每个点都精确对应着特定的信息状态。但传统教学中,学生往往被要求死记硬背各种调制方式的星座点分布,却少有机会亲手"点亮"这些星辰。本文将用Python带你走进通信调制的核心地带,通过代码生成BPSK、QPSK到16-QAM的星座图,让抽象概念变成可交互的视觉呈现。
1. 环境配置与基础准备
在开始绘制星座图之前,我们需要搭建合适的Python环境。推荐使用Anaconda创建独立环境,避免库版本冲突:
conda create -n constellation python=3.9
conda activate constellation
pip install numpy matplotlib
关键库的作用说明:
- NumPy :生成和处理调制信号的数值数据
- Matplotlib :可视化展示IQ平面上的星座点
理解星座图的核心是掌握IQ平面的概念。我们可以将任何调制信号分解为:
- I分量(In-phase) :信号的实部,对应余弦波
- Q分量(Quadrature) :信号的虚部,对应正弦波
这两个分量构成了二维的IQ平面,每个调制符号都对应平面上的一个坐标点。下面表格展示了不同调制方式的基本参数对比:
| 调制类型 | 符号数 | 每符号比特数 | 星座形状 |
|---|---|---|---|
| BPSK | 2 | 1 | 线性排列 |
| QPSK | 4 | 2 | 单位圆上 |
| 16-QAM | 16 | 4 | 4×4方阵 |
2. BPSK调制与星座实现
BPSK(Binary Phase Shift Keying)是最简单的数字调制方式,只使用两种相位状态来表示二进制数据。让我们用Python生成BPSK信号并绘制星座图:
import numpy as np
import matplotlib.pyplot as plt
# 生成BPSK符号
bits = np.random.randint(0, 2, 1000) # 1000个随机比特
bpsk_symbols = 2 * bits - 1 # 映射到[-1, 1]
# 绘制星座图
plt.figure(figsize=(8, 6))
plt.scatter(bpsk_symbols, np.zeros_like(bpsk_symbols), alpha=0.3)
plt.title('BPSK Constellation Diagram')
plt.xlabel('I Component')
plt.ylabel('Q Component')
plt.grid(True)
plt.axhline(0, color='black', lw=1)
plt.axvline(0, color='black', lw=1)
plt.xlim(-1.5, 1.5)
plt.ylim(-0.1, 0.1)
plt.show()
这段代码实现了:
- 生成随机二进制序列(0和1)
- 将比特映射到BPSK符号(-1和1)
- 在IQ平面上绘制结果(Q分量全为0)
注意:实际通信系统中会加入脉冲成形滤波器,这里为简化展示直接使用矩形脉冲
BPSK星座图的特点:
- 所有点都落在I轴上
- 两点间距离为2,抗噪声能力强
- 理论误码率公式:$P_e = Q(\sqrt{\frac{2E_b}{N_0}})$
3. QPSK调制与可视化进阶
QPSK(Quadrature Phase Shift Keying)通过利用IQ两个维度,将频谱效率提高了一倍。每个符号携带2比特信息,对应四个相位状态:
# 生成QPSK符号
bits = np.random.randint(0, 2, 2000) # 2000个随机比特
symbol_indices = 2 * bits[::2] + bits[1::2] # 每两比特组合
qpsk_symbols = np.exp(1j * (np.pi/4 + symbol_indices * np.pi/2))
qpsk_symbols = np.stack((qpsk_symbols.real, qpsk_symbols.imag), axis=1)
# 绘制星座图
plt.figure(figsize=(8, 8))
plt.scatter(qpsk_symbols[:, 0], qpsk_symbols[:, 1], alpha=0.3)
plt.title('QPSK Constellation Diagram')
plt.xlabel('I Component')
plt.ylabel('Q Component')
plt.grid(True)
plt.axhline(0, color='black', lw=1)
plt.axvline(0, color='black', lw=1)
plt.xlim(-1.5, 1.5)
plt.ylim(-1.5, 1.5)
plt.show()
QPSK的关键特性:
- 星座点均匀分布在单位圆上
- 相邻点间角度差为90°
- 最小欧氏距离为$\sqrt{2}$
- 格雷编码确保相邻符号只有1比特差异
实际工程中常使用偏移QPSK(OQPSK)来减小包络波动。我们可以通过添加噪声来观察星座点的扩散:
# 添加高斯白噪声
noise_power = 0.05
noise = np.random.normal(0, np.sqrt(noise_power/2), qpsk_symbols.shape)
noisy_qpsk = qpsk_symbols + noise
# 绘制带噪声的星座图
plt.scatter(noisy_qpsk[:, 0], noisy_qpsk[:, 1], alpha=0.3, color='red')
4. 16-QAM调制实现与性能分析
16-QAM通过组合幅度和相位调制,进一步提高了频谱效率。每个符号携带4比特信息,对应16个星座点:
# 生成16-QAM符号
bits = np.random.randint(0, 2, 4000) # 4000个随机比特
symbol_indices = np.reshape(bits, (-1, 4)) # 每4比特一组
# 格雷编码映射
mapping = {
(0,0,0,0): (-3, -3), (0,0,0,1): (-3, -1),
(0,0,1,1): (-3, 1), (0,0,1,0): (-3, 3),
(0,1,1,0): (-1, 3), (0,1,1,1): (-1, 1),
(0,1,0,1): (-1, -1), (0,1,0,0): (-1, -3),
(1,1,0,0): (1, -3), (1,1,0,1): (1, -1),
(1,1,1,1): (1, 1), (1,1,1,0): (1, 3),
(1,0,1,0): (3, 3), (1,0,1,1): (3, 1),
(1,0,0,1): (3, -1), (1,0,0,0): (3, -3)
}
qam16_symbols = np.array([mapping[tuple(b)] for b in symbol_indices])
# 绘制星座图
plt.figure(figsize=(8, 8))
plt.scatter(qam16_symbols[:, 0], qam16_symbols[:, 1], alpha=0.3)
plt.title('16-QAM Constellation Diagram')
plt.xlabel('I Component')
plt.ylabel('Q Component')
plt.grid(True)
plt.axhline(0, color='black', lw=1)
plt.axvline(0, color='black', lw=1)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
16-QAM的重要特点:
- 星座点呈4×4方阵排列
- 存在三种不同的幅度等级
- 最小欧氏距离为2(相邻点间)
- 平均符号能量为10(归一化前)
我们可以计算不同调制方式的理论误码率,并进行比较:
# 误码率计算函数
def qfunc(x):
return 0.5 * erfc(x / np.sqrt(2))
snr_db = np.arange(0, 20, 0.1)
snr_linear = 10 ** (snr_db / 10)
# BPSK理论BER
ber_bpsk = qfunc(np.sqrt(2 * snr_linear))
# QPSK理论BER (与BPSK相同)
ber_qpsk = ber_bpsk
# 16-QAM近似理论BER
ber_16qam = 3/8 * qfunc(np.sqrt(snr_linear / 5))
# 绘制比较曲线
plt.figure(figsize=(10, 6))
plt.semilogy(snr_db, ber_bpsk, label='BPSK/QPSK')
plt.semilogy(snr_db, ber_16qam, label='16-QAM')
plt.xlabel('SNR (dB)')
plt.ylabel('Bit Error Rate')
plt.grid(True)
plt.legend()
plt.title('Theoretical BER Comparison')
plt.show()
5. 星座图分析进阶技巧
掌握了基本调制方式的星座图绘制后,我们可以进一步探索更专业的分析技术:
星座图眼图 :通过叠加多个符号周期的轨迹,可以观察符号间干扰情况:
# 生成带滚降滤波器的QPSK信号
sps = 8 # 每符号采样数
alpha = 0.35 # 滚降因子
h = rrcosfilter(30, alpha, 1/sps, sps)[1]
# 上采样并滤波
up_symbols = np.zeros(len(qpsk_symbols) * sps)
up_symbols[::sps] = qpsk_symbols[:, 0] # I分量
i_wave = np.convolve(up_symbols, h, mode='same')
# 绘制眼图
plt.figure(figsize=(12, 6))
for i in range(10, 50):
plt.plot(i_wave[i*sps:i*sps+2*sps], 'b-', alpha=0.3)
plt.title('QPSK Eye Diagram (I Component)')
plt.grid(True)
plt.show()
误差矢量分析 :计算EVM(Error Vector Magnitude)量化信号质量:
def calculate_evm(ideal, measured):
error = ideal - measured
evm_rms = np.sqrt(np.mean(np.abs(error)**2))
evm_percent = 100 * evm_rms / np.sqrt(np.mean(np.abs(ideal)**2))
return evm_percent
# 计算16-QAM的EVM
evm = calculate_evm(qam16_symbols, noisy_qam16)
print(f"EVM: {evm:.2f}%")
载波恢复可视化 :展示频偏对星座图的影响及恢复过程:
# 添加频偏
fo = 0.01 # 归一化频偏
t = np.arange(len(qpsk_symbols))
rotated = qpsk_symbols * np.exp(2j * np.pi * fo * t)
# 绘制旋转的星座图
plt.scatter(rotated[:, 0], rotated[:, 1], alpha=0.3, color='green')
plt.title('QPSK with Frequency Offset')
plt.show()
# 简单的频偏估计与校正
delta_phi = np.angle(np.mean(rotated[1:] * np.conj(rotated[:-1])))
fo_est = delta_phi / (2 * np.pi)
corrected = rotated * np.exp(-2j * np.pi * fo_est * t)
plt.scatter(corrected[:, 0], corrected[:, 1], alpha=0.3, color='purple')
plt.title('After Frequency Correction')
plt.show()
在实际项目中,星座图分析常常需要结合其他工具和技术。例如,使用USRP等软件无线电设备捕获真实信号,或者结合机器学习算法自动识别调制类型和信号质量。
更多推荐


所有评论(0)