 全流程:5步实现信号生成与解调)
Python 仿真数字调制 (PSK/QAM) 全流程5步实现信号生成与解调数字调制技术是现代通信系统的基石从5G到WiFi从卫星通信到物联网设备都离不开PSK和QAM等数字调制技术的支持。对于通信工程师和学生而言理解这些技术的原理固然重要但能够通过代码实现完整的调制解调流程才是真正掌握这门技术的关键。本文将带你用Python从零开始一步步构建完整的数字调制仿真系统。在开始之前我们需要明确几个核心概念。数字调制本质上是一种将数字比特流转换为适合在信道中传输的模拟信号的过程。与模拟调制不同数字调制处理的是离散的数字信息这使得它具有更好的抗噪声性能和更高的频谱效率。PSK相移键控和QAM正交幅度调制是两种最常用的数字调制技术广泛应用于现代通信系统中。1. 环境准备与基础设置在开始编码之前我们需要准备好Python环境并安装必要的库。推荐使用Python 3.8或更高版本以及以下核心库NumPy用于数值计算和数组操作Matplotlib用于数据可视化SciPy提供信号处理相关函数可以通过以下命令安装这些依赖pip install numpy matplotlib scipy为了确保我们的仿真结果可重复最好设置固定的随机种子import numpy as np import matplotlib.pyplot as plt from scipy import signal np.random.seed(42) # 固定随机种子在数字调制仿真中我们需要定义一些基本参数# 基本参数设置 bit_rate 1000 # 比特率 (bps) sample_rate 44100 # 采样率 (Hz) carrier_freq 2000 # 载波频率 (Hz) num_bits 100 # 要传输的比特数 duration num_bits / bit_rate # 信号持续时间这些参数中比特率决定了数据传输的速度采样率应符合奈奎斯特采样定理至少是信号最高频率的两倍载波频率则是我们调制使用的中心频率。2. 比特流生成与星座图映射数字调制的第一步是将要传输的信息转换为二进制比特流然后将这些比特映射到星座图上的特定点。对于不同的调制方式映射方式也有所不同。2.1 生成随机比特流我们首先生成一个随机的比特序列作为我们的原始数据# 生成随机比特流 bits np.random.randint(0, 2, num_bits) print(f原始比特流: {bits[:10]}...) # 打印前10位2.2 PSK调制映射对于PSK调制我们只需要关注信号的相位。以QPSK4-PSK为例每两个比特组合映射到一个符号def bits_to_psk(bits, modulation_order): if modulation_order 4: # QPSK # 将比特流重组为每两个比特一组 symbol_bits bits.reshape(-1, 2) # 将二进制转换为十进制 symbols_decimal np.packbits(symbol_bits, axis1, bitorderlittle) 0b11 # 映射到相位0-45°, 1-135°, 2-225°, 3-315° phases np.pi/4 symbols_decimal * np.pi/2 return phases.flatten()2.3 QAM调制映射QAM调制则同时利用了信号的幅度和相位。以16-QAM为例每4个比特映射到一个符号def bits_to_qam(bits, modulation_order): if modulation_order 16: # 16-QAM # 将比特流重组为每4个比特一组 symbol_bits bits.reshape(-1, 4) # 将二进制转换为十进制 symbols_decimal np.packbits(symbol_bits, axis1, bitorderlittle) 0b1111 # 16-QAM星座图映射 amplitude_levels np.array([1, 3]) / np.sqrt(10) phase_levels np.array([1, 3]) / np.sqrt(10) # 创建查找表 constellation np.zeros(16, dtypecomplex) for i in range(16): # 前两位决定实部(I)后两位决定虚部(Q) i_bits (i 2) 0b11 q_bits i 0b11 constellation[i] amplitude_levels[i_bits % 2] * (1 if i_bits 2 else -1) \ 1j * amplitude_levels[q_bits % 2] * (1 if q_bits 2 else -1) return constellation[symbols_decimal.flatten()]2.4 星座图可视化理解星座图对于数字调制至关重要。我们可以绘制PSK和QAM的星座图def plot_constellation(symbols, title): plt.figure(figsize(6, 6)) plt.scatter(np.real(symbols), np.imag(symbols), markero) plt.title(title) plt.xlabel(In-phase (I)) plt.ylabel(Quadrature (Q)) plt.grid(True) plt.axhline(0, colorblack, linewidth0.5) plt.axvline(0, colorblack, linewidth0.5) plt.show() # QPSK星座图示例 qpsk_symbols np.exp(1j * (np.pi/4 np.arange(4) * np.pi/2)) plot_constellation(qpsk_symbols, QPSK Constellation) # 16-QAM星座图示例 qam16_symbols bits_to_qam(np.random.randint(0, 2, 16), 16) plot_constellation(qam16_symbols, 16-QAM Constellation)星座图直观地展示了不同调制方式中符号在复平面上的分布情况。对于PSK所有符号点都位于单位圆上只有相位不同而对于QAM符号点则分布在复平面的网格点上既有幅度变化也有相位变化。3. 载波调制与信号生成有了映射后的符号下一步就是将这些符号调制到载波上。数字调制通常采用正交调制IQ调制的方式可以同时利用正弦和余弦两个正交分量。3.1 生成时间序列首先我们需要创建对应的时间序列t np.linspace(0, duration, int(sample_rate * duration), endpointFalse)3.2 PSK调制实现对于PSK调制我们只需要考虑相位变化def psk_modulate(phases, t, carrier_freq): # 为每个符号创建时间序列 samples_per_symbol len(t) // len(phases) modulated_signal np.zeros(len(t)) for i, phase in enumerate(phases): start i * samples_per_symbol end (i 1) * samples_per_symbol if end len(t): end len(t) time_segment t[start:end] modulated_signal[start:end] np.cos(2 * np.pi * carrier_freq * time_segment phase) return modulated_signal3.3 QAM调制实现QAM调制则需要同时处理I路和Q路信号def qam_modulate(symbols, t, carrier_freq): # 为每个符号创建时间序列 samples_per_symbol len(t) // len(symbols) modulated_signal np.zeros(len(t)) for i, symbol in enumerate(symbols): start i * samples_per_symbol end (i 1) * samples_per_symbol if end len(t): end len(t) time_segment t[start:end] # I路和Q路调制 i_component np.real(symbol) * np.cos(2 * np.pi * carrier_freq * time_segment) q_component np.imag(symbol) * np.sin(2 * np.pi * carrier_freq * time_segment) modulated_signal[start:end] i_component - q_component return modulated_signal3.4 信号可视化我们可以绘制调制前后的信号对比图def plot_signals(t, original, modulated, title): plt.figure(figsize(12, 6)) plt.subplot(2, 1, 1) plt.plot(t[:1000], original[:1000]) plt.title(Original Signal) plt.xlabel(Time (s)) plt.ylabel(Amplitude) plt.subplot(2, 1, 2) plt.plot(t[:1000], modulated[:1000]) plt.title(fModulated Signal ({title})) plt.xlabel(Time (s)) plt.ylabel(Amplitude) plt.tight_layout() plt.show() # 示例QPSK调制 phases bits_to_psk(bits, 4) psk_signal psk_modulate(phases, t, carrier_freq) plot_signals(t, bits.repeat(int(sample_rate/bit_rate))[:len(t)], psk_signal, QPSK) # 示例16-QAM调制 qam_symbols bits_to_qam(bits, 16) qam_signal qam_modulate(qam_symbols, t, carrier_freq) plot_signals(t, bits.repeat(int(sample_rate/bit_rate))[:len(t)], qam_signal, 16-QAM)从时域信号图中我们可以看到原始的数字信号经过调制后变成了高频的模拟信号。虽然PSK和QAM在时域上的波形看起来相似但它们的星座图结构和解调方式有很大不同。4. 信道模拟与噪声添加真实的通信信道会引入各种干扰和噪声。为了模拟真实环境我们需要在信号中添加噪声。4.1 添加高斯白噪声最常用的噪声模型是加性高斯白噪声AWGNdef add_noise(signal, snr_db): # 计算信号功率 signal_power np.mean(np.abs(signal)**2) # 根据SNR计算噪声功率 noise_power signal_power / (10 ** (snr_db / 10)) # 生成高斯白噪声 noise np.random.normal(0, np.sqrt(noise_power), len(signal)) return signal noise # 添加噪声示例 snr_db 10 # 信噪比(dB) noisy_psk_signal add_noise(psk_signal, snr_db) noisy_qam_signal add_noise(qam_signal, snr_db)4.2 多径效应模拟除了噪声多径效应也是无线信道中的重要影响因素def add_multipath(signal, delays, attenuations): result signal.copy() for delay, atten in zip(delays, attenuations): if delay 0: result[delay:] atten * signal[:-delay] return result # 多径效应示例 delays [int(0.001 * sample_rate)] # 1ms延迟 attenuations [0.3] # 衰减系数 multipath_psk_signal add_multipath(noisy_psk_signal, delays, attenuations) multipath_qam_signal add_multipath(noisy_qam_signal, delays, attenuations)4.3 信号频谱分析观察信号的频谱可以帮助我们理解调制后的信号特性def plot_spectrum(signal, sample_rate, title): n len(signal) freq np.fft.fftfreq(n, d1/sample_rate) spectrum np.abs(np.fft.fft(signal)) / n plt.figure(figsize(10, 4)) plt.plot(freq[:n//2], spectrum[:n//2]) plt.title(fFrequency Spectrum ({title})) plt.xlabel(Frequency (Hz)) plt.ylabel(Magnitude) plt.grid(True) plt.show() plot_spectrum(psk_signal, sample_rate, QPSK) plot_spectrum(qam_signal, sample_rate, 16-QAM)从频谱图中可以看到调制后的信号能量集中在载波频率附近。QAM信号的频谱效率通常比PSK更高这也是为什么现代通信系统广泛采用QAM调制的原因。5. 信号解调与比特恢复解调是调制的逆过程目标是从接收到的信号中恢复出原始的比特流。解调的质量直接决定了通信系统的性能。5.1 PSK解调实现PSK解调需要检测信号的相位def psk_demodulate(signal, t, carrier_freq, modulation_order): samples_per_symbol int(sample_rate / bit_rate) * int(np.log2(modulation_order)) num_symbols len(signal) // samples_per_symbol recovered_bits [] # 正交解调 i_component signal * np.cos(2 * np.pi * carrier_freq * t) q_component -signal * np.sin(2 * np.pi * carrier_freq * t) # 低通滤波 b, a signal.butter(5, carrier_freq/sample_rate, low) i_filtered signal.filtfilt(b, a, i_component) q_filtered signal.filtfilt(b, a, q_component) for i in range(num_symbols): start i * samples_per_symbol end (i 1) * samples_per_symbol if end len(signal): end len(signal) # 采样中点 sample_point start samples_per_symbol // 2 if sample_point len(i_filtered): break # 计算相位 phase np.arctan2(q_filtered[sample_point], i_filtered[sample_point]) phase (phase 2*np.pi) % (2*np.pi) # 归一化到0-2π # 相位到比特的映射 if modulation_order 4: # QPSK symbol int(round(phase / (np.pi/2))) % 4 recovered_bits.extend([(symbol 1) 1, symbol 1]) return np.array(recovered_bits)5.2 QAM解调实现QAM解调需要同时考虑幅度和相位def qam_demodulate(signal, t, carrier_freq, modulation_order): samples_per_symbol int(sample_rate / bit_rate) * int(np.log2(modulation_order)) num_symbols len(signal) // samples_per_symbol recovered_bits [] # 正交解调 i_component signal * np.cos(2 * np.pi * carrier_freq * t) q_component -signal * np.sin(2 * np.pi * carrier_freq * t) # 低通滤波 b, a signal.butter(5, carrier_freq/sample_rate, low) i_filtered signal.filtfilt(b, a, i_component) q_filtered signal.filtfilt(b, a, q_component) # 16-QAM星座点 if modulation_order 16: constellation np.array([ 11j, 13j, 31j, 33j, -11j, -13j, -31j, -33j, 1-1j, 1-3j, 3-1j, 3-3j, -1-1j, -1-3j, -3-1j, -3-3j ]) / np.sqrt(10) decision_points np.array([-2, 0, 2]) / np.sqrt(10) for i in range(num_symbols): start i * samples_per_symbol end (i 1) * samples_per_symbol if end len(signal): end len(signal) # 采样中点 sample_point start samples_per_symbol // 2 if sample_point len(i_filtered): break # 获取IQ值 i_val i_filtered[sample_point] q_val q_filtered[sample_point] received_symbol i_val 1j * q_val # 找到最近的星座点 if modulation_order 16: # 对I和Q分别做硬判决 i_decision np.digitize(i_val, decision_points) - 2 q_decision np.digitize(q_val, decision_points) - 2 symbol i_decision 1j * q_decision # 将符号转换为比特 symbol_idx np.argmin(np.abs(constellation - symbol)) recovered_bits.extend([(symbol_idx 3) 1, (symbol_idx 2) 1, (symbol_idx 1) 1, symbol_idx 1]) return np.array(recovered_bits)5.3 误码率计算与性能评估评估解调性能的关键指标是误码率BERdef calculate_ber(original_bits, recovered_bits): min_len min(len(original_bits), len(recovered_bits)) if min_len 0: return 1.0 errors np.sum(original_bits[:min_len] ! recovered_bits[:min_len]) return errors / min_len # 示例QPSK性能评估 recovered_psk_bits psk_demodulate(multipath_psk_signal, t, carrier_freq, 4) psk_ber calculate_ber(bits, recovered_psk_bits) print(fQPSK误码率: {psk_ber:.4f}) # 示例16-QAM性能评估 recovered_qam_bits qam_demodulate(multipath_qam_signal, t, carrier_freq, 16) qam_ber calculate_ber(bits, recovered_qam_bits[:len(bits)]) # 确保长度一致 print(f16-QAM误码率: {qam_ber:.4f})在实际测试中你会发现QPSK的误码率通常低于16-QAM这是因为QPSK的星座点间距更大抗噪声能力更强。然而16-QAM的频谱效率是QPSK的两倍这就是通信系统中常见的权衡更高的数据速率通常以更高的误码率为代价。5.4 眼图分析眼图是评估数字通信系统性能的重要工具def plot_eye_diagram(signal, samples_per_symbol, title): symbol_length samples_per_symbol num_eyes 2 # 每个符号周期显示两个眼图 samples_per_eye symbol_length // num_eyes # 确保有足够的数据 if len(signal) 10 * symbol_length: print(数据不足无法生成眼图) return plt.figure(figsize(12, 6)) for i in range(0, min(100, len(signal) - samples_per_eye), samples_per_symbol): segment signal[i:i samples_per_eye] plt.plot(np.linspace(0, 1, len(segment)), segment, b-, alpha0.1) plt.title(fEye Diagram ({title})) plt.xlabel(Normalized Time) plt.ylabel(Amplitude) plt.grid(True) plt.show() # 示例QPSK眼图 plot_eye_diagram(psk_signal, int(sample_rate / bit_rate) * 2, QPSK) # 示例16-QAM I路眼图 i_component qam_signal * np.cos(2 * np.pi * carrier_freq * t) b, a signal.butter(5, carrier_freq/sample_rate, low) i_filtered signal.filtfilt(b, a, i_component) plot_eye_diagram(i_filtered, int(sample_rate / bit_rate) * 4, 16-QAM (I component))眼图的张开程度直观反映了系统的抗噪声和抗干扰能力。在理想情况下眼图应该完全张开而随着噪声和干扰的增加眼图会逐渐闭合导致误码率上升。