手把手用Python+Matplotlib仿真6种数字调制(ASK/FSK/PSK/QAM/MSK/OFDM)波形
用Python实现6种数字调制技术的可视化仿真在通信系统设计中数字调制技术扮演着至关重要的角色。无论是简单的无线遥控器还是复杂的5G网络都依赖于这些基础调制方式将数字信息转换为适合传输的模拟信号。对于工程师和学生而言仅仅理解理论公式是不够的通过代码实现和可视化才能真正掌握这些技术的精髓。本文将使用Python科学计算栈NumPyMatplotlib完整实现6种主流数字调制技术ASK/OOK、FSK、PSK、QAM、MSK和OFDM。每个实现都包含可运行的代码片段、参数调整建议以及多种可视化输出时域波形、星座图和频谱分析。这些代码可以直接复制到Jupyter Notebook中运行是通信原理学习的绝佳实践材料。1. 环境准备与基础信号生成1.1 安装必要的Python库在开始之前请确保已安装以下Python库。推荐使用Anaconda环境管理工具pip install numpy matplotlib scipy对于更复杂的信号处理我们还会用到pyqtgraph进行实时可视化可选安装pip install pyqtgraph1.2 生成基带数字信号所有数字调制技术都需要一个基带信号作为输入。我们先定义一个函数来生成随机的二进制序列import numpy as np import matplotlib.pyplot as plt def generate_bit_sequence(num_bits, samples_per_bit): 生成随机二进制序列并扩展为采样信号 bits np.random.randint(0, 2, num_bits) return np.repeat(bits, samples_per_bit) # 参数设置 num_bits 10 # 二进制位数 samples_per_bit 100 # 每比特采样点数 bit_rate 1000 # 比特率(Hz) sample_rate bit_rate * samples_per_bit # 采样率 # 生成信号 bits generate_bit_sequence(num_bits, samples_per_bit) t np.arange(len(bits)) / sample_rate # 时间轴 # 可视化 plt.figure(figsize(10, 3)) plt.plot(t, bits, b, linewidth2) plt.title(原始二进制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.ylim(-0.1, 1.1) plt.show()提示在实际通信系统中通常会先对原始二进制信号进行编码如曼彻斯特编码以提高传输可靠性。本文为简化流程直接使用原始二进制信号。2. 幅移键控(ASK/OOK)实现2.1 ASK调制原理幅移键控(ASK)是最简单的数字调制方式之一通过改变载波幅度来表示二进制数据。OOK(On-Off Keying)是ASK的特例用载波的有无表示1和0。数学表达式为s(t) A(t) * cos(2πf_c t)其中A(t)根据比特值变化比特1A(t) A比特0A(t) 0 (OOK) 或 A(t) A/2 (标准ASK)2.2 Python实现与可视化def ask_modulate(bits, fc, sample_rate, ask_typeOOK): ASK调制实现 t np.arange(len(bits)) / sample_rate carrier np.cos(2 * np.pi * fc * t) if ask_type OOK: modulated bits * carrier else: # 标准ASK modulated (0.5 0.5 * bits) * carrier return modulated # 参数设置 fc 2000 # 载波频率(Hz) # 调制信号 ook_signal ask_modulate(bits, fc, sample_rate, OOK) ask_signal ask_modulate(bits, fc, sample_rate, ASK) # 可视化 plt.figure(figsize(12, 6)) plt.subplot(2, 1, 1) plt.plot(t, ook_signal, b) plt.title(OOK调制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.subplot(2, 1, 2) plt.plot(t, ask_signal, r) plt.title(标准ASK调制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.tight_layout() plt.show()2.3 频谱分析了解调制信号的频谱特性对于通信系统设计至关重要from scipy.fft import fft, fftfreq def plot_spectrum(signal, sample_rate, title): 绘制信号频谱 n len(signal) yf fft(signal) xf fftfreq(n, 1 / sample_rate) plt.figure(figsize(10, 4)) plt.plot(xf[:n//2], np.abs(yf[:n//2])) plt.title(title) plt.xlabel(频率(Hz)) plt.ylabel(幅度) plt.grid(True) plt.show() plot_spectrum(ook_signal, sample_rate, OOK信号频谱) plot_spectrum(ask_signal, sample_rate, ASK信号频谱)3. 频移键控(FSK)实现3.1 FSK调制原理频移键控(FSK)通过改变载波频率来表示不同比特值。二进制FSK(BFSK)使用两个不同频率比特1频率f1比特0频率f0数学表达式为s(t) A * cos(2πf(t)t)其中f(t) f1 (比特1) 或 f0 (比特0)3.2 Python实现def fsk_modulate(bits, f0, f1, sample_rate): FSK调制实现 t np.arange(len(bits)) / sample_rate phase np.cumsum(np.where(bits, f1, f0)) / sample_rate return np.cos(2 * np.pi * phase) # 参数设置 f0 1000 # 比特0对应频率(Hz) f1 2000 # 比特1对应频率(Hz) # 调制信号 fsk_signal fsk_modulate(bits, f0, f1, sample_rate) # 可视化 plt.figure(figsize(10, 4)) plt.plot(t, fsk_signal, g) plt.title(FSK调制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.show() # 频谱分析 plot_spectrum(fsk_signal, sample_rate, FSK信号频谱)3.3 频率跳变优化实际FSK系统中频率跳变可能导致相位不连续产生高频分量。连续相位FSK(CPFSK)通过保持相位连续性来解决这个问题def cpfsk_modulate(bits, f0, f1, sample_rate): 连续相位FSK实现 t np.arange(len(bits)) / sample_rate freq_dev (f1 - f0) / 2 phase 2 * np.pi * f0 * t 2 * np.pi * freq_dev * np.cumsum(2*bits-1) / sample_rate return np.cos(phase) cpfsk_signal cpfsk_modulate(bits, f0, f1, sample_rate) plt.figure(figsize(10, 4)) plt.plot(t, cpfsk_signal, m) plt.title(CPFSK调制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.show()4. 相移键控(PSK)实现4.1 BPSK调制二进制相移键控(BPSK)是最简单的PSK形式使用0°和180°两种相位状态def bpsk_modulate(bits, fc, sample_rate): BPSK调制实现 t np.arange(len(bits)) / sample_rate phase np.pi * (1 - bits) # 0→π, 1→0 return np.cos(2 * np.pi * fc * t phase) # 参数设置 fc 2000 # 载波频率(Hz) # 调制信号 bpsk_signal bpsk_modulate(bits, fc, sample_rate) # 可视化 plt.figure(figsize(10, 4)) plt.plot(t, bpsk_signal, c) plt.title(BPSK调制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.show() # 星座图绘制 def plot_constellation(signal, bits, samples_per_bit): 绘制星座图 # 取每个符号中间点的采样 mid_samples signal[samples_per_bit//2::samples_per_bit] plt.figure(figsize(6, 6)) plt.scatter(np.real(mid_samples), np.imag(mid_samples), cbits[::samples_per_bit], cmapbwr) plt.title(星座图) plt.xlabel(同相分量(I)) plt.ylabel(正交分量(Q)) plt.grid(True) plt.show() # 由于BPSK只有实部我们添加零作为虚部 complex_bpsk bpsk_signal 0j plot_constellation(complex_bpsk, bits, samples_per_bit)4.2 QPSK调制正交相移键控(QPSK)通过同时调制两个比特来提高频谱效率使用四种相位状态def qpsk_modulate(bits, fc, sample_rate): QPSK调制实现 # 将比特流分为I路和Q路 i_bits bits[::2] q_bits bits[1::2] # 确保I路和Q路长度相同 min_len min(len(i_bits), len(q_bits)) i_bits i_bits[:min_len] q_bits q_bits[:min_len] # 映射到符号0→-1, 1→1 i_symbols 2 * i_bits - 1 q_symbols 2 * q_bits - 1 # 上采样 samples_per_symbol samples_per_bit * 2 i_signal np.repeat(i_symbols, samples_per_symbol) q_signal np.repeat(q_symbols, samples_per_symbol) # 生成时间轴 t np.arange(len(i_signal)) / (sample_rate / 2) # 调制 carrier_i np.cos(2 * np.pi * fc * t) carrier_q np.sin(2 * np.pi * fc * t) return i_signal * carrier_i q_signal * carrier_q # 调制信号 qpsk_signal qpsk_modulate(bits, fc, sample_rate) # 可视化 t_qpsk np.arange(len(qpsk_signal)) / (sample_rate / 2) plt.figure(figsize(10, 4)) plt.plot(t_qpsk, qpsk_signal, b) plt.title(QPSK调制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.show() # 星座图 complex_qpsk qpsk_signal 0j plot_constellation(complex_qpsk, bits, samples_per_bit * 2)5. 正交幅度调制(QAM)实现5.1 16-QAM调制QAM同时利用幅度和相位来编码信息频谱效率更高。以下是16-QAM的实现def qam16_modulate(bits, fc, sample_rate): 16-QAM调制实现 # 将比特流分组为4比特一组 num_symbols len(bits) // 4 bits bits[:num_symbols*4] # 截断为整数个符号 # 重新整形为(num_symbols, 4)数组 symbol_bits bits.reshape((num_symbols, 4)) # 映射到符号(I和Q各2比特) i_levels 2 * symbol_bits[:, 0] symbol_bits[:, 1] - 1.5 q_levels 2 * symbol_bits[:, 2] symbol_bits[:, 3] - 1.5 # 归一化因子(使平均功率为1) normalization np.sqrt(10) # 上采样 samples_per_symbol samples_per_bit * 4 i_signal np.repeat(i_levels / normalization, samples_per_symbol) q_signal np.repeat(q_levels / normalization, samples_per_symbol) # 生成时间轴 t np.arange(len(i_signal)) / (sample_rate / 4) # 调制 carrier_i np.cos(2 * np.pi * fc * t) carrier_q np.sin(2 * np.pi * fc * t) return i_signal * carrier_i q_signal * carrier_q # 调制信号 qam16_signal qam16_modulate(bits, fc, sample_rate) # 可视化 t_qam np.arange(len(qam16_signal)) / (sample_rate / 4) plt.figure(figsize(10, 4)) plt.plot(t_qam, qam16_signal, purple) plt.title(16-QAM调制信号) plt.xlabel(时间(s)) plt.ylabel(幅值) plt.grid(True) plt.show() # 星座图 complex_qam qam16_signal 0j plot_constellation(complex_qam, bits, samples_per_bit * 4)5.2 QAM与PSK性能比较QAM相比PSK在相同频带内可以传输更多信息但对抗噪声能力有所降低。下表比较了几种调制方式的特性调制方式符号率(比特/符号)抗噪声能力频谱效率实现复杂度BPSK1高低低QPSK2中中中16-QAM4低高高注意实际系统选择调制方式时需要权衡频谱效率、功率效率和实现复杂度。高密度调制(如64-QAM)需要更高的信噪比。6. 正交频分复用(OFDM)实现6.1 OFDM基本原理OFDM通过将高速数据流分配到多个正交子载波上传输有效对抗多径干扰def ofdm_modulate(bits, num_subcarriers, cp_length, sample_rate): OFDM调制简化实现 # 将比特流映射为QPSK符号 symbols 2 * bits - 1 # BPSK映射 # 确保符号数是子载波数的整数倍 num_symbols len(symbols) // num_subcarriers symbols symbols[:num_symbols * num_subcarriers] symbol_matrix symbols.reshape((num_symbols, num_subcarriers)) # IFFT变换 time_domain np.fft.ifft(symbol_matrix, axis1) # 添加循环前缀 time_domain_cp np.hstack([time_domain[:, -cp_length:], time_domain]) # 串行化 ofdm_signal time_domain_cp.ravel() return ofdm_signal # 参数设置 num_subcarriers 64 # 子载波数量 cp_length 16 # 循环前缀长度 # 生成更长的比特序列用于OFDM long_bits np.random.randint(0, 2, num_subcarriers * 10 * samples_per_bit) # 调制信号 ofdm_signal ofdm_modulate(long_bits, num_subcarriers, cp_length, sample_rate) # 可视化部分信号 plt.figure(figsize(12, 4)) plt.plot(np.real(ofdm_signal[:1000]), b, label实部) plt.plot(np.imag(ofdm_signal[:1000]), r, label虚部) plt.title(OFDM信号(前1000个采样点)) plt.xlabel(采样点) plt.ylabel(幅值) plt.legend() plt.grid(True) plt.show() # 频谱分析 plot_spectrum(np.real(ofdm_signal), sample_rate, OFDM信号频谱(实部))6.2 OFDM系统关键参数OFDM系统性能受多个参数影响下面是主要参数的典型设置和影响参数典型值影响子载波数量64-2048数量越多频谱效率越高但复杂度增加循环前缀长度1/4符号长度保护间隔对抗多径时延但降低频谱效率子载波间隔15kHz(4G)决定符号持续时间影响多普勒频移容忍度调制方式(每子载波)QPSK/16-QAM频谱效率与抗噪声能力的权衡在实际应用中OFDM还需要考虑同步、信道估计和功率分配等问题。Wi-Fi(802.11a/g/n/ac)和4G/5G移动通信都采用了OFDM技术。