
简介面向无线通信研究者提供非正交多址NOMA与正交频分多址OFDMA的联合仿真资源对比两种接入技术在功率分配、用户配对、干扰消除等方面的性能差异并探讨融合设计以提升频谱效率的可行路径。资源包共十三份文件以十一份脚本为主要内容另含一份数据文件与一份备份压缩包整体大小仅一百五十六千字节脚本覆盖发射接收链路、Turbo编解码、非正交多址与正交频分多址的编解码以及误码率统计等核心模块。目前已有三百八十一人学习适合通信专业学生与研究人员快速上手。借助该仿真包可直观复现两种多址方案的误码率表现理解功率域复用与正交子载波分配的实现细节也可为课程设计、算法改进及非正交多址与正交频分多址融合研究提供可扩展的实验平台。1. NOMA-OFDMA 这对组合为什么值得跑一遍仿真做无线物理层接入方案的人大概率都纠结过同一个问题OFDMA 正交资源干净利落但频谱效率在用户密集时上不去NOMA 功率域叠加能把频谱效率拉满却把接收机的复杂度顶到了天花板上。NOMA-OFDMA 恰好是这两条路线的折中地带——在 OFDMA 的子载波格子上叠加 NOMA 用户把正交与功率域复用放在同一个系统里评估。这份 NOMA-OFDMA-SIM 代码包干的就是这件事从数据生成、Turbo 编码到 OFDM 收发信机和两种多址方式的编解码最后统一落到 BER 曲线上。对做 5G/6G 接入预研的研究生或者想快速拿到性能对照曲线的算法工程师来说这套工程文件的参考价值都在于不用自己从零搭链路直接改参数就能复现对比、验证想法。2. 先把代码包拆清楚main_v2_ber.m 跑的是哪条链路拿到一个 MATLAB 仿真包我一般不会先点开某个函数看细节而是先找主脚本把数据流捋顺。这个包的核心是 main_v2_ber.m从文件名也能看出来v2 说明作者迭代过一版ber 说明它的输出目标就是误码率。整个仿真的链路顺序在大多数 OFDM 收发信机工程里是固定的只是这个包额外插入了两个并行的编码模块noma_enc.m/noma_dec.m 和 ofdma_enc.m/ofdma_dec.m分别对应两套多址接入方式的发射端和接收端。2.1 主脚本的执行顺序与数据流先看发送方向data_gen.m 生成随机源比特然后送进 turbo_enc.m 做信道编码编码后的比特经过调制映射变成符号再交给多址接入编码模块。这里的多址编码有两种路径一种是 noma_enc.m把多个用户的符号在功率域上叠加另一种是 ofdma_enc.m把不同用户的符号映射到不同的子载波索引上。这两条路径输出结果都会经过 ofdm_tx.m 做 IFFT 和加循环前缀然后送入信道模型加噪声。接收方向就是镜像ofdm_rx.m 去掉循环前缀做 FFT之后 if 分支根据当前仿真的是 NOMA 还是 OFDMA分别调用 noma_dec.m 或 ofdma_dec.m 做多用户分离再进 turbo_dec.m 译码最后统计 BER。% main_v2_ber.m 主循环的典型骨架结构示意 for snr_idx 1:length(snr_range) for frame_idx 1:num_frames tx_bits data_gen(num_users, frame_len); % 随机源比特 enc_bits turbo_enc(tx_bits, code_rate); % Turbo 编码 tx_sym qam_mod(enc_bits, modulation_order); % QAM 映射 if strcmp(multiple_access, NOMA) tx_signal noma_enc(tx_sym, user_power_alloc); % 功率域叠加 else tx_signal ofdma_enc(tx_sym, subcarrier_map); % 频域分配 end ofdm_waveform ofdm_tx(tx_signal, fft_size, cp_len, pilot_idx); rx_waveform awgn_channel(ofdm_waveform, snr_dB); rx_sym ofdm_rx(rx_waveform, fft_size, cp_len, pilot_idx); if strcmp(multiple_access, NOMA) rx_bits noma_dec(rx_sym, user_power_alloc, modulation_order); else rx_bits ofdma_dec(rx_sym, subcarrier_map, modulation_order); end dec_bits turbo_dec(rx_bits, code_rate, turbo_iter); ber_all_users(snr_idx, :) cal_ber(tx_bits, dec_bits); end end这段代码的逻辑说明主循环外层扫信噪比内层跑多帧统计取平均这符合 BER 仿真对帧数的基本要求——每帧都重新随机生成数据避免单帧偶然性把误码率带偏。参数说明fft_size 是 IFFT/FFT 点数cp_len 是循环前缀长度modulation_order 对应 QPSK 取 4、16QAM 取 16snr_range 是扫描区间user_power_alloc 在 NOMA 模式下决定各用户的功率权重。实际包里的代码可能把 AWGN 信道放在一个单独文件里或者直接内联但骨架逻辑和这个是一致的。2.2 仿真入口参数表改哪里直接影响 BER 结果主脚本里值得关注的参数集中在文件头部或者 config 结构体里。我把这套代码里最常调的参数整理成一张表照着改就可以不会破坏链路结构。参数名常见默认值作用改它之后的影响num_users2~4NOMA 或 OFDMA 的用户数超过 2 时 NOMA 叠加层数增加SIC 复杂度上升OFDMA 需重新分配子载波fft_size64 或 128OFDM 的 FFT 点数决定子载波总数必须大于所有用户占用的子载波之和cp_len16循环前缀长度必须大于信道最大时延扩展否则产生符号间干扰modulation_order4 或 16QPSK / 16QAM 映射高阶调制对 SNR 更敏感BER 曲线整体右移code_rate1/2 或 1/3Turbo 编码码率码率越低纠错越强代价是有效数据速率下降turbo_iter4~8Turbo 译码迭代次数迭代太少 BER 偏高太多仿真时间暴涨power_alloc0.8/0.2NOMA 用户功率分配功率差越小SIC 越难收敛误码平台越明显snr_range0:2:20 dB信噪比扫描范围太低 BER 接近 0.5太高耗时且无信息量一个容易被忽略的点是 fft_size 和 num_users 的联动关系。在 OFDMA 模式下用户数增加意味着每个用户分到的子载波数减少如果子载波映射索引没有相应调整就会覆盖到别的用户或者超出 FFT 点数导致内存访问越界。在 NOMA 模式下num_users 和 user_power_alloc 的长度必须匹配否则脚本在矩阵逐元素相加时会直接报维度错误。2.3 配套文件各自扮演什么角色data_gen.m 不只是生成随机比特它还负责保证输出比特数能被 Turbo 交织器和 QAM 调制整除。Turbo 编码的输入长度必须满足码率缩放关系比如输入 1000 比特、码率 1/2输出就得是 2000 比特如果 data_gen.m 的长度设置和 turbo_enc.m 内部交织器尺寸不一致编码过程不会直接报错但译码出来的数据长度对不上最终 BER 计算那一步全是 NaN。这属于跑仿真最容易碰到的隐性翻车点。turbo_enc.m 和 turbo_dec.m 是成对出现的解码端要复用编码端的交织表。有的 MATLAB 实现里交织器是每次随机生成的这对短帧影响不大但帧长一长随机交织器的统计特性差异就会反映在 BER 上。检查交织器是否固定的方法是跑两次一帧的仿真对比解码后的比特有没有出现随机抖动。我不止一次因为这个原因排查了几个小时最后发现是交织表没有固定同一帧数据的 BER 在不同运行时不一样。ofdm_tx.m 和 ofdm_rx.m 在 OFDMA 和 NOMA 两条路径下都会被复用所以它们是独立稳定的模块。接收端 FFT 的缩放因子经常是个隐患MATLAB 的 fft 函数默认不归一化如果 ofdm_tx 端做了 IFFT 但 ofdm_rx 端没有除以 fft_size信号幅值就整体放大了 fft_size 倍等效 SNR 凭空抬高BER 曲线看起来比理论值好一大截。真碰到这种结果先怀疑缩放而不是怀疑新的多址算法。ber.mat 是作者预跑好的结果缓存plot_ber.m 会引导你直接加载这个文件绘制两组 BER 曲线。这个设计对快速看图很方便但要注意如果你改了参数重跑了 main_v2_ber.mber.mat 必须被新的变量覆盖或者换个名字保存否则画出来的还是老结果新旧参数对比就成了自欺欺人。3. NOMA 的功率域叠加noma_enc.m 与 noma_dec.m 的实现逻辑NOMA 的核心就一句话让多个用户的信号在同一个时间-频率资源块上同时传输靠功率分配制造可分离性。这句话翻译成 MATLAB 代码发射端只是几行矩阵乘法接收端才是真正体现算法深度的地方。noma_enc.m 负责把不同用户的符号按功率权重叠加成一路信号noma_dec.m 再做逐级干扰消除也就是 SIC。3.1 发射端不同功率等级的信号怎么叠在同一个子载波上功率域 NOMA 的发射端不复杂。假设有两个用户用户的调制符号分别是 s1 和 s2功率分配因子是 a 和 b满足 a 平方加 b 平方等于 1那么叠加后的符号就是 x a * s1 b * s2。这里谁是近用户谁是远用户直接决定功率权重怎么给。% noma_enc.m 的典型实现双用户功率域叠加示意 function tx_signal noma_enc(tx_sym, power_alloc) % tx_sym: [num_users, num_symbols] 每个用户已调制的符号矩阵 % power_alloc: [1, num_users] 功率归一化权重例如 [0.8, 0.2] num_users size(tx_sym, 1); power_weight sqrt(power_alloc); % 幅度权重 功率权重的平方根 tx_signal zeros(1, size(tx_sym, 2)); for u 1:num_users tx_signal tx_signal power_weight(u) * tx_sym(u, :); end % 归一化保证叠加符号的平均功率接近 1 tx_signal tx_signal / sqrt(sum(power_alloc)); end这段代码的逻辑说明叠加过程是纯线性的每个用户符号乘上自己的幅度权重再累加。功率分配向量是功率意义上的权重所以转成幅度时要开根号。比如 [0.8, 0.2] 的功率分配幅度上是 [0.894, 0.447]大权重用户比小权重用户高 6 dB 左右。注意最后一步归一化是必要的否则叠加后信号功率会随用户数增长导致仿真 SNR 虚高。参数说明power_alloc 的长度必须等于 num_users且元素之和严格为 1如果追求更精细的分配可以用注水算法按信道增益动态计算但包里的默认实现一般是固定比例。3.2 接收端SIC 逐级解码的顺序与判决门限SIC 的思路是「先解强的再剥掉它去解弱的」。在功率域 NOMA 里强信号指的是功率分配更大的用户通常是信道条件差的远端用户给它大功率是为了保证它也能被正确解码。解码顺序反过来就全错这是 NOMA 仿真里最典型的翻车点。% noma_dec.m 的 SIC 核心循环双用户示意 function dec_bits noma_dec(rx_sym, power_alloc, modulation_order) % rx_sym: 经过 OFDM 解调后取出来的接收符号 % power_alloc: 与发射端一致的功率分配向量 [~, sort_idx] sort(power_alloc, descend); % 功率大者优先解码 residual rx_sym; for u 1:length(sort_idx) user_idx sort_idx(u); % 把当前用户当作目标信号其余未解用户视为噪声 sym_est qam_demod(residual, modulation_order); bits_est qam_demod_to_bits(sym_est, modulation_order); % 重构已解用户的发射符号并减去 recon qam_mod(bits_est, modulation_order); % 重新映射 weight sqrt(power_alloc(user_idx)); residual residual - weight * recon; end dec_bits bits_est; % 实际实现中会按用户分别保存后合并 end这段代码的逻辑说明SIC 的第一步不是直接解调而是先按功率从大到小排序这是整个算法的前提。每一次循环解出一个用户立刻做 QAM 映射重构出它的发射符号再从残留信号里减掉。减掉之后剩余信号里就只剩下功率第二大的用户加上噪声依此类推。参数说明一个容易被忽视的细节是重构时必须使用解码后重新映射的符号而不是理想情况下发射端的原始符号。如果直接用原始符号去减就相当于给了接收端作弊器BER 会显得比实际可达性能好很多真实系统里解码错误会扩散到下一个用户这是 SIC 误差传播的本质。3.3 功率分配、误码平台与折中区间NOMA 的性能对功率分配极其敏感「玄学」成分不少。两个用户的功率差如果太小比如 0.6/0.4远端用户解调时会受到近端用户很大的残留干扰即便 SIC 顺序正确第一级判决的误码率也不理想误码会顺着减法操作传播给第二级用户。反过来功率差拉大到 0.9/0.1远端用户性能很好但近端用户几乎被噪声淹没它自己的 BER 会极其难看。仿真里常见的做法是把近端用户放在高阶调制上、远端用户放在低阶调制上用调制阶数的差异配合功率分配让两端的误码量级尽量接近。实际跑的时候可以先固定调制阶数扫 power_alloc 从 0.7/0.3 到 0.9/0.1观察两个用户的 BER 交汇区间在哪这个区间就是系统的最优工作点。需要注意的是功率差拉大后叠加符号的峰均比会上升OFDM 本身对高峰均比敏感配合起来波形更复杂这是 NOMA-OFDMA 融合方案在生产系统中落地慢的一个重要原因。4. OFDMA 正交链路ofdma_enc.m、ofdma_dec.m 与 OFDM 收发信的配合OFDMA 这条路径的代码要比 NOMA 直观得多因为它的核心思想是「不打架」——每个用户拿自己的子载波频率上天然正交。但正交意味着资源分配要提前规划好子载波映射的索引表就是 OFDMA 的命根子。4.1 OFDMA 编码和解码子载波分配矩阵怎么建ofdma_enc.m 做的事情本质上是一个频域资源分配操作把每个用户的调制符号放到指定的子载波位置上其余位置置零。这个过程的输入输出都是频域符号块IFFT 之前就已经欠定好了关系。% ofdma_enc.m 的子载波分配示意 function tx_freq_grid ofdma_enc(tx_sym, subcarrier_map) % tx_sym: [num_users, num_data_symbols] 每用户待发送的数据符号 % subcarrier_map: [num_users, per_user_sc] 每个用户占据的子载波索引 fft_size max(subcarrier_map(:)) 1; % 满足 FFT 点数要求 tx_freq_grid zeros(fft_size, size(tx_sym, 2)); for u 1:size(tx_sym, 1) sc_idx subcarrier_map(u, :); % 第 u 个用户的子载波集合 tx_freq_grid(sc_idx, :) tx_sym(u, :); end end这段代码的逻辑说明每个用户的数据符号被直接赋值到自己的子载波索引上用户与用户之间没有符号重叠。解码端 ofdma_dec.m 做的事情完全对称——按同一个 subcarrier_map 从接收符号矩阵里把对应索引的数据取出来再做 QAM 解调。参数说明subcarrier_map 的设计有一些隐含约束比如每个用户的子载波数应该等于它一帧里的数据符号数且索引号必须小于 fft_size当 fft_size 取 64、num_users 取 4最好的分配是一个用户分 16 个子载波但如果数据和导频混在一起实际可用的数据子载波会少一部分。这里常碰到的坑是旁边导频和直流子载波的预留如果忘记把这些位置从分配表里剔除接收端解出来的符号就会混入导频能量星座图整体偏移。还有一点OFDMA 的正交性只在理想信道下成立一旦有多径时延扩展子载波间的正交性会被破坏这个包如果加了卷积信道模型你会在 BER 曲线上看到所谓的地板效应。4.2 OFDM 收发信机IFFT/FFT、循环前缀与信噪比换算ofdma_enc 的输出是频域符号块它还不能直接上信道需要过 OFDM 调制IFFT 把频域符号变成时域波形加上循环前缀作为保护间隔再把多用户的帧拼接起来串行发送。接收端先去掉循环前缀做 FFT 回到频域再进行子载波解映射和符号判决。% OFDM 收发端的核心操作ofdm_tx.m 与 ofdm_rx.m 的关键行 % 发射端 ifft_out ifft(tx_freq_grid, fft_size, 1); % 频域转时域 tx_waveform [ifft_out(end-cp_len1:end, :); ifft_out]; % 加循环前缀 % 接收端 rx_no_cp rx_waveform(cp_len1:cp_lenfft_size, :); % 去循环前缀 rx_freq fft(rx_no_cp, fft_size, 1); % 时域转频域 % SNR 映射已知噪声功率时把 dB 值转换成复数噪声幅度 noise_power 10^(-snr_dB/10); noise sqrt(noise_power / 2) * (randn(size(tx_waveform)) 1j*randn(size(tx_waveform)));这段代码的逻辑说明IFFT 之前是频域符号块每一列代表一个 OFDM 符号周期内所有子载波上的取值IFFT 后得到的每一列就是一个 OFDM 符号的时域采样。循环前缀的做法是把每个符号最后 cp_len 个采样点复制到符号开头这样做的好处是抵抗多径时延让上一符号的拖尾不影响当前符号的解调窗口。接收端去掉循环前缀后再做 FFT就能把信道在时域上的卷积效应近似转换成频域上的逐子载波乘法这也是 OFDM 能简化均衡的根本原因。参数说明噪声生成时除以 2 是因为复噪声的实部和虚部各占一半功率randn 生成的实噪声方差为 1所以要给两个维度各乘 sqrt(noise_power/2)。一个常见的错误是只关心信号功率而忘记把导频的额外能量算进去导致实际 SNR 与设定值偏了一两个 dB。4.3 正交接收为什么比 SIC 简单性能与复杂度的权衡OFDMA 的优势在这条链路里体现得非常直接接收端拿到频域符号后不同用户的符号已经在不同子载波上分开了不存在跨用户干扰也不需要迭代减法一次解调就结束。这个正交性不仅简化了接收机还意味着某个用户的深度衰落不会吃掉另一个用户的性能。但代价是频谱效率的浪费比如 fft_size 是 64一个用户只分到了 16 个子载波那剩下 48 个子载波要么空闲要么分配给其他用户而空闲就意味着资源没利用起来。NOMA 恰恰相反它让所有用户都在全部子载波上叠加资源利用率高但接收机复杂度随用户数指数级上升。两种方式的性能对比需要放到同一个 SNR 区间、同一个频谱效率目标下才有意义这也是这套包把两条链路跑进同一个主脚本、用同一套 BER 计算函数的原因——当你把两条 BER 曲线画在一张图里复杂度换频谱效率的取舍关系就直观了。仿真里可以固定总用户数和总数据比特数分别跑两种方案比较达到某个目标 BER 所需的 SNR如果 NOMA 在低 SNR 区能省 2~3 dB 但代价是接收时间翻倍这个交换是否值得取决于应用场景对时延和功耗的容忍程度。5. 避坑排查跑不通的五个常见原因与定位方法这个包我从下载到跑通中间也折腾了不少时间。MATLAB 仿真不像 FPGA 那样有硬件报错约束它更多是结果不符合预期、曲线形状诡异。下面这几条是我实际遇到过的现象和排查过程按「现象 → 原因 → 解决」写能帮你少走几天弯路。5.1 BER 曲线完全是一条直线或 0.5 横贯现象跑出来的 BER 从低 SNR 到高 SNR 一直没有下降趋势打印输出看到误码率接近 0.5也就是译码输出和原始比特完全不相关。原因排查顺序是先看数据生成和比特对齐再看 Turbo 状态。最常见的原因是 Turbo 译码器的输出比特和源比特之间存在交织顺序错位发射端交织过的比特在接收端没有解交织就送了比较器。另一个可能是 data_gen.m 生成的数据长度和 turbo_enc.m 的可编码长度不整除多出来的比特被静默丢弃导致收发两端的帧结构不一致。解决方法是在主脚本里插入一个中间变量检查编码前和译码后各打印一次数据长度确认收发两侧对同一帧的比特位置确实是对应的。还有一个快速验证方法是把 SNR 调到 30 dB 以上物理层如果一切正常BER 应该降到 1e-4 量级以下如果 30 dB 还是 0.5基本可以断定问题在编码链路而不是信道噪声。% 快速定位打印每级处理的比特长度与维度信息 fprintf(tx_bits: %d bits\n, length(tx_bits)); fprintf(enc_bits: %d bits\n, length(enc_bits)); fprintf(dec_bits: %d bits\n, length(dec_bits)); fprintf(BER calculation length match: %d\n, length(tx_bits) length(dec_bits));这段代码的作用是在主循环里加三个打印点把数据流每一级后的比特数显式打出来。任何一处的长度不一致都会让后面的 BER 统计失去意义。实际排查中我还遇到过 MATLAB 因为矩阵维度不匹配直接 Index exceeds array bounds 崩溃这种硬错误反而是好事至少知道去哪修最怕的是维度都匹配但数据顺序错乱这种只能靠对齐检查或者在发射端固定一组已知比特序列做端到端验证。5.2 NOMA 的 BER 曲线出现平台高 SNR 下不下降现象NOMA 那条 BER 曲线在 SNR 到 10 dB 左右开始变平继续增加 SNR 也不改善。原因是 SIC 解码时第一级用户判决错误没有被正确消除误差传播给后续用户但更深层原因是功率分配矩阵和排序逻辑不匹配。如果 noma_dec.m 的排序是按功率从小到大而发射端功率分配是从大到小设计的那么 SIC 第一个解出来的其实是功率最小的近端用户远端用户的大功率信号被当成干扰整个系统就废了。解决方法是统一功率分配的定义方向确保发射端和接收端共用同一个 power_alloc 顺序而不是各自维护一套。另外如果调制阶数太高而功率差不够大即使 SIC 顺序正确第一级解调的误码率本身就高残留干扰降不下来也会出现平台。我一般会先降一档调制阶数跑一次看平台是否消失以此确认是调制阶数的问题还是功率分配的问题。5.3 子载波索引越界OFDMA 模式直接报错现象ofdma_enc.m 里往 tx_freq_grid(sc_idx, :) 赋值时报 Index exceeds array bounds或者 FFT 出来的幅度谱在视觉上明显不对称。原因很直接subcarrier_map 里设置的某个子载波索引大于等于 fft_size。比如 fft_size 配了 64但 subcarrier_map 里写了索引 64MATLAB 索引从 1 开始合法范围是 1~64索引 64 实际上是直流子载波这位置通常被预留或者置零。解决方法是把 subcarrier_map 的生成逻辑改成先算最大可用子载波数再分配同时避开直流和导频位置。另一个容易被忽略的坑是用户间分配的子载波重叠这通常发生在手工修改 subcarrier_map 时建议写一个小断言函数在编码前检查所有用户占用的子载波索引集合两两交集为空重叠即报错。把资源分配的正确性交给断言比依赖人工检查靠谱得多。5.4 仿真时间异常长帧数和迭代次数被低估了现象main_v2_ber.m 跑一次要几个小时尤其是 SNR 扫描点数多的时候。原因是 SNR 区间内有多个工作点每个工作点要求的帧数不一样低 SNR 时 BER 较高统计 100 帧足够高 SNR 时误码率低需要跑几千帧才能数出足够多的错误比特来形成有效统计。如果代码里对所有 SNR 点用固定帧数要么低 SNR 白跑要么高 SNR 统计量不足。此外 Turbo 译码的迭代次数直接乘到计算量上迭代从 4 拉到 8时间不止翻倍因为每次迭代都要做两轮 BCJR 前向后向递推。解决方法是让帧数随目标错误比特数动态调整比如设定至少收集 100 个错误比特再停止当前 SNR 点turbo_iter 从 4 开始看 BER 是否收敛到平台如果 BER 没明显变化就不必再加迭代。一个小技巧是在 plot_ber.m 能看到曲线的阶段先跑稀疏的 SNR 网格比如 0:4:20把曲线轮廓摸清楚再用 1 dB 间隔细化省下的时间相当可观。5.5 ber.mat 缓存文件导致画图结果与实际不符现象改了参数重跑仿真但 plot_ber.m 画出来的曲线和修改前的完全一样。原因是 ber.mat 是旧的仿真结果plot_ber.m 直接用 load 把它读进来而新的 main_v2_ber.m 运行结果没有覆盖同名变量或者脚本里只保存到工作区没有写回文件。解决方法是养成每次跑完主脚本立即用 save 覆盖 ber.mat 的习惯或者在 plot_ber.m 里加一个文件修改时间戳的判断只要 ber.mat 早于本次仿真结束时间就重新加载变量。更稳妥的做法是把每次仿真参数连同 BER 结果一起存成结构体文件名带上时间戳比如 ber_snr0to20_noma_user2.mat这样对比新旧参数时不会误用旧数据。这套包本身的设计思路是让你能快速看曲线但缓存文件也最容易造成误导我当时在这个坑里花了一个晚上最后才发现新仿真结果根本没保存成功。6. 扩展成 NOMA-OFDMA 叠加模式一个可复现的验证流程前面两条链路都是独立跑通的但它们各有边界纯 NOMA 复杂度高、故障风险大纯 OFDMA 频谱效率受限。把两者融合才是这个资源真正的进阶价值所在——在 OFDMA 的子载波子集上叠加两个 NOMA 用户既保留子载波间的正交性又在每个子载波组内部利用功率域复用。以 fft_size 为 64、4 个用户的场景为例可以这样设计前两个用户以功率分配 [0.75, 0.25] 叠加到第 1~32 号子载波上后两个用户以同样的功率分配叠到第 33~64 号子载波上每组内部走 SIC组间天然正交。实现上不需要改 ofdm_tx.m 和 ofdm_rx.m只需要在 noma_enc.m 和 noma_dec.m 的外面包一个子载波分组循环。验证方案是新写一个 main_hybrid_ber.m把纯 NOMA、纯 OFDMA、NOMA-OFDMA 三条 BER 曲线画在同一坐标系下横轴是总频谱效率归一化后的等效 SNR这样三套方案才可比较。方案资源占用方式接收机结构频谱效率复杂度纯 NOMA全部用户叠加在全部子载波SIC逐级消除干扰高高用户数指数影响纯 OFDMA用户独占子载波单符号判决低有空闲子载波低NOMA-OFDMA组内功率叠加组间频分组内 SIC 组间正交分离介于两者之间介于两者之间跑这个扩展时我踩到过一个很有代表性的坑子载波分组后 OFDM 符号的功率均衡变了如果直接在原有 ofdm_tx.m 上叠加而不重新归一化整条混合方案的 BER 曲线会比单独 OFDMA 好原因不是算法变强了而是等效发射功率被抬高了几分贝。从那以后我每次跑多方案对比都会先固定总发射功率并且把每个方案的 SNR 定义统一到每比特能量上而不是简单用符号功率这个检查步骤看起来基础却是对比结论站不脚的唯一保障。这套包的常规代码先跑通一遍再把混合模式的效果对比出来算是对 NOMA 和 OFDMA 的性能边界有了亲手验证过的直观判断希望帮到你。本文还有配套的精品资源点击获取