资讯动态

别再死记IFFT了!用Python手搓一个OFDM发射机,彻底搞懂5GNR的调制原理

发布时间:2026/9/10 18:47:50 来源:尧图企业网站定制
别再死记IFFT了用Python手搓一个OFDM发射机彻底搞懂5GNR的调制原理通信工程师面试时总被问为什么OFDM要用IFFT而教材里那些公式推导让人头大今天我们用Python从零构建一个迷你OFDM发射机用代码和图形告诉你答案。不需要死记硬背跟着做一遍你会发现自己突然看懂了那些天书般的协议文档。1. 准备工作理解OFDM的核心思想先忘掉公式想象你要把一批货物从A地运到B地。如果只用一辆大卡车单载波遇到坑洼路段整个运输就会延迟。但换成几十辆小货车子载波并行运输即使某条路况差其他货车也能准时到达——这就是OFDM的核心理念。关键组件准备import numpy as np import matplotlib.pyplot as plt from scipy.fft import fft, ifft注意现代Python推荐使用scipy.fft替代numpy.fft前者默认使用更高效的FFTW库2. 构建发射机流水线2.1 生成比特流信息的源头我们先模拟要发送的二进制数据。实际系统中这里可能是编码后的比特num_bits 1024 # 发送1024个比特 bits np.random.randint(0, 2, num_bits) # 随机生成0/1序列 print(f前10个比特: {bits[:10]})2.2 星座映射比特变符号把比特转换为复数符号这里用最简单的QPSK调制def bits_to_qpsk(bits): # 每2比特映射为1个QPSK符号 symbols np.zeros(len(bits)//2, dtypecomplex) for i in range(0, len(bits), 2): if bits[i] 0 and bits[i1] 0: # 00 → 1j symbols[i//2] 1 1j elif bits[i] 0 and bits[i1] 1: # 01 → 1-j symbols[i//2] 1 - 1j elif bits[i] 1 and bits[i1] 0: # 10 → -1j symbols[i//2] -1 1j else: # 11 → -1-j symbols[i//2] -1 - 1j return symbols / np.sqrt(2) # 归一化功率 qpsk_symbols bits_to_qpsk(bits)可视化星座图plt.scatter(qpsk_symbols.real, qpsk_symbols.imag) plt.title(QPSK星座图) plt.grid(True) plt.show()2.3 资源映射把符号放到正确位置OFDM的核心是把符号分配到不同子载波上。我们创建一个空的频域向量然后把QPSK符号填充到指定位置num_subcarriers 64 # 总子载波数 num_data_carriers 48 # 实际用于数据传输的子载波数 subcarrier_indices np.arange(-num_data_carriers//2, num_data_carriers//2) subcarrier_indices subcarrier_indices[subcarrier_indices ! 0] # 跳过直流载波 # 创建空的频域向量 frequency_domain np.zeros(num_subcarriers, dtypecomplex) # 把QPSK符号映射到子载波上跳过保护带和直流载波 symbols_per_ofdm len(subcarrier_indices) assert len(qpsk_symbols) symbols_per_ofdm frequency_domain[subcarrier_indices num_subcarriers//2] qpsk_symbols[:symbols_per_ofdm]2.4 魔法时刻IFFT变换终于到了关键环节——为什么用IFFT看代码就明白time_domain ifft(frequency_domain) * np.sqrt(num_subcarriers) # 能量归一化IFFT的本质把分散在不同子载波上的符号打包成一个时域波形。就像把分散在不同货架的商品频域符号集中到一个购物车时域信号里。可视化时域信号plt.plot(np.real(time_domain), label实部) plt.plot(np.imag(time_domain), label虚部) plt.title(OFDM时域信号) plt.legend() plt.show()3. 添加保护间隔对抗多径干扰实际系统必须加循环前缀CP我们模拟这个操作cp_length num_subcarriers // 4 # 循环前缀长度 time_domain_with_cp np.concatenate([ time_domain[-cp_length:], # 复制尾部作为前缀 time_domain ]) plt.plot(np.abs(time_domain_with_cp)) plt.title(带循环前缀的OFDM符号) plt.show()4. 完整发射机代码整合把上述步骤封装成函数方便重复使用def ofdm_transmitter(bits, num_subcarriers64, cp_ratio0.25): # 参数校验 assert num_subcarriers % 2 0, 子载波数必须是偶数 # 星座映射 symbols bits_to_qpsk(bits) # 计算可用数据子载波数跳过直流和保护带 num_data_carriers int(num_subcarriers * 0.75) # 75%利用率 subcarrier_indices np.arange(-num_data_carriers//2, num_data_carriers//2) subcarrier_indices subcarrier_indices[subcarrier_indices ! 0] # 初始化输出 ofdm_frames [] for i in range(0, len(symbols), len(subcarrier_indices)): # 资源映射 frame_symbols symbols[i:ilen(subcarrier_indices)] frequency_domain np.zeros(num_subcarriers, dtypecomplex) frequency_domain[subcarrier_indices num_subcarriers//2] frame_symbols # IFFT变换 time_domain ifft(frequency_domain) * np.sqrt(num_subcarriers) # 添加循环前缀 cp_length int(num_subcarriers * cp_ratio) time_domain_with_cp np.concatenate([ time_domain[-cp_length:], time_domain ]) ofdm_frames.append(time_domain_with_cp) return np.concatenate(ofdm_frames) # 测试完整发射机 tx_signal ofdm_transmitter(bits) plt.specgram(tx_signal, NFFT256, Fs1e6) plt.title(OFDM信号频谱) plt.xlabel(时间(ms)) plt.ylabel(频率(MHz)) plt.show()5. 为什么是IFFT而不是FFT这个历史选择其实很巧妙数学对称性IFFT和FFT本质是同一操作的两种视角实现便利发射端用IFFT接收端自然对应FFT物理意义IFFT把频域资源分配转换为时域波形用代码验证IFFT/FFT的对称性test_data np.random.randn(64) 1j*np.random.randn(64) print(重建误差:, np.max(np.abs(ifft(fft(test_data)) - test_data))) # 应该≈06. 实际工程中的优化技巧频域插零技巧# 在频域信号两侧补零可以提高IFFT分辨率 padded_freq np.pad(frequency_domain, (256,256), constant) high_res_time ifft(padded_freq) * np.sqrt(len(padded_freq)) plt.plot(np.linspace(0, 1, len(time_domain)), np.abs(time_domain), o-, label原始) plt.plot(np.linspace(0, 1, len(high_res_time)), np.abs(high_res_time), label插零后) plt.legend() plt.title(频域插零对时域分辨率的影响) plt.show()保护带配置对照表系统类型子载波总数数据子载波数保护带比例5G NR3300264020%WiFi 62562348.6%LTE2048120041.4%7. 从仿真到现实的挑战在实验室用NumPy玩OFDM是一回事实际部署又是另一回事。几个需要突破的关卡峰均比问题OFDM信号的动态范围太大peak_to_avg np.max(np.abs(tx_signal)**2) / np.mean(np.abs(tx_signal)**2) print(f峰均比: {10*np.log10(peak_to_avg):.1f} dB) # 通常10dB同步难题接收端如何找准符号起始位置相位噪声本地振荡器的微小抖动会导致子载波间干扰解决这些问题的技术包括预失真技术时域加窗高级信道编码8. 扩展实验破坏性测试故意修改代码观察现象这是最好的学习方式实验1去掉循环前缀broken_signal ofdm_transmitter(bits, cp_ratio0) # 接收端会出现符号间干扰(ISI)实验2错误映射子载波frequency_domain[1::2] 0 # 隔一个子载波置零 # 会看到频谱出现规律空洞实验3忘记能量归一化time_domain ifft(frequency_domain) # 去掉*sqrt(N) # 信号功率会异常波动这些实验带来的认知冲击比读十篇论文都管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价