别再死记硬背ASK/FSK/PSK了!用Python+Matplotlib手把手画星座图,5分钟搞懂数字调制
2026/5/8 19:49:58 网站建设 项目流程

用Python绘制数字调制星座图:从ASK到QAM的实战解析

通信工程师们常说:"星座图是数字调制的DNA图谱。"但翻开教科书,满页的数学公式和抽象描述总让人望而生畏。今天我们将用Python代码这把"手术刀",解剖ASK、FSK、PSK和QAM的技术本质——不是通过死记硬背,而是亲手绘制它们的时域波形和星座图。当你看到Matplotlib画布上跳动的信号点时,那些困扰多年的调制概念会突然变得清晰可见。

1. 环境准备与基础概念

在开始编码之前,我们需要配置Python环境并理解几个核心概念。打开你的Jupyter Notebook或PyCharm,安装以下必备库:

pip install numpy matplotlib scipy

数字调制的本质,是通过改变载波信号的振幅、频率或相位来传递信息。就像用不同颜色的灯光传递信号:

  • ASK(幅移键控):类似调节灯泡亮度(1=亮,0=灭)
  • FSK(频移键控):类似切换不同颜色的灯泡(红/蓝交替)
  • PSK(相移键控):类似让灯泡按不同节奏闪烁(快闪/慢闪)
  • QAM(正交幅度调制):综合运用亮度和闪烁模式(既调亮度又调节奏)

让我们先定义一个通用的载波生成函数,后续所有调制方式都将基于此:

import numpy as np import matplotlib.pyplot as plt def generate_carrier(freq, duration=1, sample_rate=1000): """生成基准载波信号""" t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) return np.cos(2 * np.pi * freq * t), t

2. ASK调制:最直观的开关控制

2.1 二进制ASK实现

二进制ASK就像摩斯电码中的"点"和"划",我们用振幅A表示1,振幅0表示0。下面代码生成数字序列[1,0,1,1,0]的ASK信号:

def ask_modulate(bits, carrier_freq=10, bit_duration=1): """二进制ASK调制""" carrier, t = generate_carrier(carrier_freq, len(bits)*bit_duration) modulated = np.array([]) for i, bit in enumerate(bits): segment = carrier[i*len(carrier)//len(bits) : (i+1)*len(carrier)//len(bits)] modulated = np.append(modulated, segment * bit) return modulated, t bits = [1, 0, 1, 1, 0] ask_signal, t = ask_modulate(bits) plt.figure(figsize=(10,4)) plt.plot(t, ask_signal) plt.title('二进制ASK信号波形') plt.xlabel('时间(s)') plt.ylabel('振幅') plt.grid() plt.show()

运行后会看到明显的"有/无"载波交替现象。ASK的星座图简单到令人惊讶——只是坐标轴上的两个点:

plt.figure(figsize=(6,6)) plt.scatter([0,1], [0,0], s=100) plt.xlim(-0.5,1.5) plt.ylim(-0.5,0.5) plt.title('ASK星座图') plt.xlabel('同相分量(I)') plt.ylabel('正交分量(Q)') plt.grid() plt.show()

2.2 ASK的致命缺陷

ASK在实际应用中面临三个主要问题:

  1. 能量效率低下:0比特不发送能量,浪费信道容量
  2. 抗噪能力弱:任何幅度干扰都会导致误判
  3. 同步困难:难以区分"0"和"无信号"

提示:改进方案是采用非归零编码(NRZ),用两种不同振幅表示0和1,但这仍然无法解决根本性的抗干扰问题。

3. FSK调制:频率的舞蹈

3.1 基础FSK实现

FSK通过切换频率来传递信息,就像在无线电的两个频道间快速切换。以下是生成FSK信号的代码:

def fsk_modulate(bits, freq0=5, freq1=20, bit_duration=1): """二进制FSK调制""" t = np.linspace(0, len(bits)*bit_duration, 1000*len(bits), endpoint=False) modulated = np.array([]) for i, bit in enumerate(bits): if bit == 0: segment = np.cos(2 * np.pi * freq0 * t[i*1000 : (i+1)*1000]) else: segment = np.cos(2 * np.pi * freq1 * t[i*1000 : (i+1)*1000]) modulated = np.append(modulated, segment) return modulated, t fsk_signal, t = fsk_modulate(bits)

FSK的星座图表现为两个不同频率对应的点,在频域上分离:

from scipy.fft import fft, fftfreq N = len(fsk_signal) yf = fft(fsk_signal) xf = fftfreq(N, 1/1000)[:N//2] plt.figure(figsize=(10,4)) plt.plot(xf, 2/N * np.abs(yf[0:N//2])) plt.title('FSK信号频谱') plt.xlabel('频率(Hz)') plt.ylabel('幅度') plt.grid() plt.show()

3.2 FSK的优缺点分析

优势对比ASK

  • 更好的抗幅度噪声能力
  • 更容易实现时钟同步

仍然存在的问题

  • 需要更宽的频带
  • 频率切换可能产生相位不连续
  • 频谱效率仍然较低

4. PSK调制:相位的艺术

4.1 BPSK与QPSK实现

PSK通过改变相位传递信息,就像用不同方向的箭头表示数据。我们先实现最简单的BPSK(二进制PSK):

def bpsk_modulate(bits, carrier_freq=10, bit_duration=1): """二进制PSK调制""" carrier, t = generate_carrier(carrier_freq, len(bits)*bit_duration) modulated = np.array([]) for i, bit in enumerate(bits): phase = 0 if bit == 1 else np.pi segment = np.cos(2 * np.pi * carrier_freq * t[i*len(carrier)//len(bits) : (i+1)*len(carrier)//len(bits)] + phase) modulated = np.append(modulated, segment) return modulated, t bpsk_signal, t = bpsk_modulate(bits)

BPSK的星座图有两个对称的点:

plt.figure(figsize=(6,6)) plt.scatter([-1,1], [0,0], s=100) plt.xlim(-1.5,1.5) plt.ylim(-1.5,1.5) plt.title('BPSK星座图') plt.xlabel('同相分量(I)') plt.ylabel('正交分量(Q)') plt.grid() plt.show()

升级到QPSK(四相PSK),每个符号可以携带2比特信息:

def qpsk_modulate(symbols, carrier_freq=10, symbol_duration=1): """QPSK调制""" carrier, t = generate_carrier(carrier_freq, len(symbols)*symbol_duration) modulated = np.array([]) phases = [np.pi/4, 3*np.pi/4, -3*np.pi/4, -np.pi/4] # 格雷编码 for i, sym in enumerate(symbols): phase = phases[sym] segment = np.cos(2 * np.pi * carrier_freq * t[i*len(carrier)//len(symbols) : (i+1)*len(carrier)//len(symbols)] + phase) modulated = np.append(modulated, segment) return modulated, t symbols = [0, 2, 3, 1] # 对应00,10,11,01 qpsk_signal, t = qpsk_modulate(symbols)

4.2 PSK的抗干扰优势

PSK星座点的最小距离公式为:

d_min = 2 * sqrt(E_s) * sin(π/M)

其中M是相位数量。这意味着:

  • BPSK(M=2):d_min = 2√E_s
  • QPSK(M=4):d_min = √(2E_s)
  • 8PSK(M=8):d_min ≈ 0.765√E_s

随着M增加,星座点间距减小,抗噪能力下降。这就是为什么PSK通常不超过8相位。

5. QAM调制:振幅与相位的交响乐

5.1 16QAM实现

QAM结合了ASK和PSK的优点,在星座图上形成规则的网格。以下是16QAM的生成代码:

def qam16_modulate(symbols, carrier_freq=10, symbol_duration=1): """16QAM调制""" carrier, t = generate_carrier(carrier_freq, len(symbols)*symbol_duration) modulated = np.array([]) # 16QAM星座点坐标(归一化) constellation = [ (-3, -3), (-3, -1), (-3, 3), (-3, 1), (-1, -3), (-1, -1), (-1, 3), (-1, 1), (3, -3), (3, -1), (3, 3), (3, 1), (1, -3), (1, -1), (1, 3), (1, 1) ] for i, sym in enumerate(symbols): I, Q = constellation[sym] segment = I * np.cos(2 * np.pi * carrier_freq * t[i*len(carrier)//len(symbols) : (i+1)*len(carrier)//len(symbols)]) - \ Q * np.sin(2 * np.pi * carrier_freq * t[i*len(carrier)//len(symbols) : (i+1)*len(carrier)//len(symbols)]) modulated = np.append(modulated, segment) return modulated, t symbols = [0, 5, 15, 10] # 示例符号序列 qam16_signal, t = qam16_modulate(symbols)

16QAM星座图的绘制:

constellation = [ (-3, -3), (-3, -1), (-3, 3), (-3, 1), (-1, -3), (-1, -1), (-1, 3), (-1, 1), (3, -3), (3, -1), (3, 3), (3, 1), (1, -3), (1, -1), (1, 3), (1, 1) ] I = [point[0] for point in constellation] Q = [point[1] for point in constellation] plt.figure(figsize=(6,6)) plt.scatter(I, Q, s=100) plt.title('16QAM星座图') plt.xlabel('同相分量(I)') plt.ylabel('正交分量(Q)') plt.grid() plt.show()

5.2 QAM与PSK的性能对比

在相同平均功率下,16QAM比16PSK具有更大的最小星座点距离:

调制方式最小距离频谱效率抗噪能力
16PSK0.39√E_s4bit/s/Hz较弱
16QAM0.63√E_s4bit/s/Hz较强

这个差异源于星座点的分布方式——QAM充分利用了二维平面,而PSK局限在圆周上。实际测试中,16QAM在相同误码率下可比16PSK节省约4dB的信噪比。

6. 调制方式综合对比与选择指南

6.1 关键参数对比表

特性ASKFSKPSKQAM
频谱效率最高
抗噪能力最弱中等最强
实现复杂度最简单简单中等最复杂
功率效率最好中等
典型应用光通信无线传感卫星通信宽带接入

6.2 选择建议

  1. 追求简单可靠:选择BPSK(如深空通信)
  2. 平衡效率与复杂度:QPSK是经典选择(如卫星电视)
  3. 高频谱效率需求:16QAM或更高(如5G通信)
  4. 低成本低功耗场景:FSK(如物联网传感器)
  5. 特殊应用场景:ASK(如红外遥控)

在真实项目中,我通常会先用Python模拟不同调制方案在目标信噪比下的表现。以下是一个简单的性能比较函数:

def simulate_ber(mod_type, snr_db, num_bits=10000): """模拟不同调制在给定SNR下的误码率""" # 生成随机比特序列 if mod_type == '16QAM': symbols = np.random.randint(0, 16, num_bits//4) tx_signal, _ = qam16_modulate(symbols) elif mod_type == 'QPSK': symbols = np.random.randint(0, 4, num_bits//2) tx_signal, _ = qpsk_modulate(symbols) # 其他调制方式的模拟... # 添加高斯白噪声 snr_linear = 10 ** (snr_db / 10) noise_power = 1 / snr_linear noise = np.sqrt(noise_power) * np.random.randn(len(tx_signal)) rx_signal = tx_signal + noise # 解调并计算误码率... return ber # 返回误码率

绘制不同调制方式的误码率曲线时,你会发现一个有趣的现象:在低信噪比区域,简单的BPSK可能 outperform 复杂的16QAM,但随着信噪比提高,16QAM的频谱效率优势会越来越明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询