
简介这份资源是一套基于MATLAB实现的OFDMA收发仿真系统面向通信工程、电子信息类专业学生及无线通信算法入门研究者用于理解正交频分多址在4G LTE与5G NR中的复用机制。压缩包共2个文件包含1个m脚本与1个txt许可说明整体约4KB其中m文件承载核心仿真逻辑txt用于声明使用与分发条件。仿真内容覆盖子载波正交性、频域资源分配、QPSK/16QAM/64QAM调制与卷积或Turbo编码、IFFT/FFT变换、同步与信道均衡、接收端信道估计与解调解码以及误码率、符号错误率、吞吐量等性能评估和星座图、误码率曲线可视化。已有213人学习下载适合作为课程设计或算法验证的参考脚本帮助读者在MATLAB环境下搭建基本OFDMA链路并观察多用户传输效果。1. OFDMA仿真系统从“收发链路跑不通”到“误码率曲线能对齐”的那条路做无线通信基带仿真的工程师几乎都绕不开 OFDMA 这个词。它是 Wi-Fi 6、5G NR、电力线通信里都在用的多址方式核心思路是把一段宽带切成许多互相正交的子载波再按需分配给不同用户。听起来不复杂但真正动手写一套 OFDMA 收发 MATLAB 仿真时很多人会卡在同一个地方单用户链路明明能跑一加多用户资源映射星座图就散成一团误码率曲线怎么调都对不上理论值。这套仿真系统要解决的正是从“子载波分配、IFFT/FFT、循环前缀、信道估计到多用户解调”这一整条链路的可复现问题。它适合已经懂一点 OFDM、想进一步搞清多用户资源调度和接收端处理的同学也适合需要快速搭一个验证平台、用来测试自己信道估计算法或同步算法的从业者。下面我按自己搭这套东西的顺序把关键环节和踩过的坑讲清楚。2. 先把 OFDMA 收发链路的骨架搭对子载波分配与 IFFT 的先后关系2.1 为什么不能直接把 OFDM 代码改成多用户很多人上手的第一反应是拿一段单用户 OFDM 代码把数据分成几份分别做 IFFT再把结果加起来。这个做法在数学上等价于把多个用户的信号在时域叠加但接收端要分离它们就非常困难因为每个用户经历的 IFFT 是独立的子载波映射关系被打乱了。OFDMA 的正确做法是所有用户在频域上共享同一个 IFFT 块每个用户只占用分配给自己的那部分子载波其余子载波置零。这样一次 IFFT 就完成了多用户复用接收端做一次 FFT 后按同样的子载波分配表就能把各用户的数据取出来。这个顺序不能反反了后面所有同步和估计都会对不上。2.2 资源映射的最小可运行代码下面这段代码演示了 4 个用户、总共 64 个子载波、每个用户占 16 个连续子载波的资源映射过程。实际系统里可能是交织分配但连续分配更容易看清映射逻辑。% 参数设置 N_fft 64; % FFT 点数 N_cp 16; % 循环前缀长度 N_user 4; % 用户数 N_sc_per_user N_fft / N_user; % 每用户子载波数 mod_order 4; % QPSK bits_per_sym log2(mod_order); % 生成每个用户的随机比特并调制 user_data cell(1, N_user); user_sym cell(1, N_user); for u 1:N_user n_bits N_sc_per_user * bits_per_sym; user_data{u} randi([0 1], n_bits, 1); % 比特转整数再 QPSK 调制 int_data bi2de(reshape(user_data{u}, bits_per_sym, [])., left-msb); user_sym{u} pskmod(int_data, mod_order, pi/4); end % 频域资源映射每个用户占用一段连续子载波 freq_grid zeros(N_fft, 1); for u 1:N_user idx (u-1)*N_sc_per_user (1:N_sc_per_user); freq_grid(idx) user_sym{u}; end % 注意这里没有做 DC 置零和边缘保护实际系统要留 guard band % IFFT 变换到时域 time_signal ifft(freq_grid, N_fft) * sqrt(N_fft); % 加循环前缀 tx_signal [time_signal(end-N_cp1:end); time_signal];这段代码里最容易出错的是ifft之后的归一化系数。MATLAB 的ifft默认除以 N如果不乘sqrt(N_fft)发端功率会随 FFT 点数变化后面加噪声时信噪比就对不上了。另一个点是pskmod的相位偏移参数QPSK 用pi/4可以避免星座点落在坐标轴上但接收端解调时必须用同样的偏移否则星座图会整体旋转。资源映射的索引idx也要仔细检查连续分配时第 u 个用户的起始位置是(u-1)*N_sc_per_user 1如果写成(u-1)*N_sc_per_user就会从 0 开始MATLAB 会报索引错误。2.3 接收端 FFT 与用户数据提取接收端拿到的是经过信道和噪声的时域信号先去 CP再做 FFT 回到频域然后按发端相同的分配表取每个用户的符号。% 假设 rx_signal 是接收到的时域信号长度与 tx_signal 一致 rx_no_cp rx_signal(N_cp1:end); % 去循环前缀 rx_freq fft(rx_no_cp, N_fft) / sqrt(N_fft); % 归一化 % 按用户提取频域符号 rx_sym cell(1, N_user); for u 1:N_user idx (u-1)*N_sc_per_user (1:N_sc_per_user); rx_sym{u} rx_freq(idx); end % 信道均衡这里假设已知理想信道实际需要估计 % H_est 是每个子载波的信道响应长度 N_fft % rx_freq_eq rx_freq ./ H_est;去 CP 时索引从N_cp1开始是因为发端把 CP 加在了前面。如果 CP 长度设得比信道最大时延还短就会引入符号间干扰这时候误码率曲线会出现误码平台怎么加发射功率都降不下去。一般 CP 长度取信道最大时延扩展的 2 到 4 倍室内场景常用 16 或 32 个采样点。3. 信道估计与均衡让多用户解调不再靠“猜”3.1 块状导频和梳状导频怎么选OFDMA 仿真里信道估计的导频图案直接决定了估计精度和开销。块状导频是在某些 OFDM 符号的所有子载波上都放导频适合信道变化慢的场景梳状导频是在每个 OFDM 符号的固定间隔子载波上放导频适合信道变化快的场景。我一般先在时不变信道下用块状导频把链路跑通确认资源映射和同步没问题再换成梳状导频去测时变信道下的跟踪性能。如果一上来就用梳状导频同步稍微偏一点导频位置的信道估计就会错后面插值出来的整个信道响应都是歪的排查起来非常费劲。3.2 LS 估计与 MMSE 估计的代码实现最小二乘LS估计最简单直接用接收导频除以本地导频。最小均方误差MMSE估计需要知道信道统计特性但性能更好。下面给出 LS 估计的代码MMSE 在此基础上乘一个由信噪比和信道相关矩阵决定的滤波矩阵。% 假设导频位置索引为 pilot_idx本地导频符号为 pilot_sym % rx_pilot 是接收到的导频位置符号 H_ls rx_pilot ./ pilot_sym; % LS 估计 % 对数据子载波做插值这里用线性插值 H_est interp1(pilot_idx, H_ls, (1:N_fft)., linear, extrap); % 均衡 rx_freq_eq rx_freq ./ H_est;interp1的最后一个参数extrap很重要因为边缘子载波可能没有导频覆盖不 extrap 会返回 NaN后面解调直接崩掉。LS 估计在低信噪比下噪声放大明显如果发现均衡后星座图在低 SNR 时反而比高 SNR 还散大概率就是 LS 估计的噪声被除进去了。这时候要么改用 MMSE要么在 LS 之后加一个简单的滑动平均滤波。3.3 多用户之间的干扰怎么在仿真里体现OFDMA 的子载波是正交分配的理论上用户之间没有多址干扰。但仿真里如果资源映射的索引有重叠或者 IFFT 点数与子载波总数不匹配就会人为引入干扰。我习惯在资源映射之后加一句检查sum(freq_grid ~ 0)应该等于N_user * N_sc_per_user如果不等说明有子载波被重复写入或者漏写。另外接收端提取用户数据时如果索引和发端不一致会把别人的数据当成自己的解调误码率直接接近 0.5这时候看星座图会发现点均匀散布没有聚类。4. 避坑与排查OFDMA 仿真里最容易翻车的五个地方4.1 误码率曲线在高信噪比下不降反升现象SNR 超过 15 dB 后误码率曲线开始往上翘。原因通常是定时同步偏差导致的符号间干扰或者信道估计在低噪声下把导频处的噪声也估计进去了。解决方法是检查同步算法里的相关峰位置确保 FFT 窗口没有偏移同时在 LS 估计后加一个基于 DFT 的降噪把时域信道响应截断到 CP 长度以内。4.2 星座图整体旋转现象接收端解调前的星座图是一个旋转过的 QPSK点与点之间角度不对。原因是发端和收端的相位偏移参数不一致或者信道估计没有补偿公共相位误差。解决方法是确认pskmod和pskdemod的相位参数相同并在均衡后加一个基于导频的公共相位校正。4.3 资源映射后子载波功率忽大忽小现象不同用户的发射功率差异很大导致弱用户误码率很高。原因是 IFFT 前没有做功率归一化或者不同用户的调制阶数不同但符号功率没对齐。解决方法是在资源映射前把每个用户的符号功率归一化到 1或者在接收端按用户做自动增益控制。4.4 循环前缀长度不够导致误码平台现象无论怎么提高 SNR误码率都卡在 1e-2 左右降不下去。原因是 CP 长度小于信道最大时延符号间干扰无法消除。解决方法是把 CP 长度增加到信道时延扩展的 2 倍以上或者改用频域均衡加更长的 CP。4.5 多用户数据提取时索引越界现象MATLAB 报“索引超出矩阵维度”。原因是用户数乘以每用户子载波数不等于 FFT 点数或者索引计算时用了从 0 开始的编号。解决方法是在资源映射前加断言assert(N_user * N_sc_per_user N_fft)并统一用从 1 开始的索引。5. 把仿真跑成可复用的验证平台参数化与批量跑 SNR 点5.1 用结构体管理参数避免脚本里到处是魔法数字一套 OFDMA 仿真系统要反复调参数如果每个数字都硬编码在脚本里改一个 FFT 点数就要翻遍全文。我习惯把所有参数塞进一个结构体主脚本只负责调用。cfg.N_fft 64; cfg.N_cp 16; cfg.N_user 4; cfg.mod_order 4; cfg.SNR_dB 0:2:20; cfg.N_frame 1000; % 每个 SNR 点跑 1000 帧 ber zeros(size(cfg.SNR_dB)); for i 1:length(cfg.SNR_dB) err_cnt 0; bit_cnt 0; for f 1:cfg.N_frame [tx, tx_bits] ofdma_tx(cfg); rx awgn(tx, cfg.SNR_dB(i), measured); rx_bits ofdma_rx(rx, cfg); err_cnt err_cnt sum(rx_bits ~ tx_bits); bit_cnt bit_cnt length(tx_bits); end ber(i) err_cnt / bit_cnt; end semilogy(cfg.SNR_dB, ber, o-); grid on; xlabel(SNR (dB)); ylabel(BER);把收发封装成ofdma_tx和ofdma_rx两个函数主脚本只做循环和统计。这样换调制方式、换用户数、换信道模型都只改cfg不用动主逻辑。awgn的measured选项会先测量信号功率再按 SNR 加噪声比手动算噪声功率更省事但要注意它测量的是整个帧的功率如果帧内有静默期测出来的功率会偏低。5.2 用理论误码率曲线验证仿真是否正确QPSK 在 AWGN 下的理论误码率是ber_theory 0.5 * erfc(sqrt(10^(SNR_dB/10) / 2))。把仿真曲线和理论曲线画在一起如果差距在 1 dB 以内说明链路基本正确如果差 3 dB 以上优先检查归一化系数和信道估计。我一般会在仿真脚本最后加一段对比代码省得每次靠肉眼判断。snr_lin 10.^(cfg.SNR_dB/10); ber_theory 0.5 * erfc(sqrt(snr_lin / 2)); semilogy(cfg.SNR_dB, ber, o-, cfg.SNR_dB, ber_theory, k--); legend(仿真, 理论);5.3 一个容易被忽略的细节随机种子跑批量仿真时如果每次randi的种子都不同不同 SNR 点的比特流不一样误码率曲线会有抖动。我习惯在循环开始前固定随机种子rng(42)这样每次跑出来的曲线可复现调参数时也能排除随机性的干扰。等链路验证通过后再换几个不同的种子跑几遍确认曲线稳定。这套仿真系统本身不复杂难的是每个环节的归一化、索引和同步细节。我自己的习惯是每加一个模块就先在理想信道下跑通再加噪声再加信道最后加多用户。一次只改一个变量出了问题才能定位到具体哪一行。希望帮到你。本文还有配套的精品资源点击获取