简介:这是一套面向无线通信研究者和高年级学生的MATLAB仿真资源,聚焦NOMA与OFDMA两种多址接入技术的对比与融合设计,可用于学习功率域非正交资源分配、子载波调度以及BER性能评估等核心问题。包体共13个文件,以m脚本为主,覆盖turbo编解码、OFDM收发、NOMA/OFDMA编码与检测、数据生成及误码率绘图流程,同时包含一个mat结果数据文件和zip压缩包,整体仅156KB,轻量便于部署。已有381人关注学习。资源主干可直接运行,能够直观输出不同方案下的误码率曲线;配套的turbo编码、NOMA解码、OFDMA解码等模块便于按需拆解研究,帮助读者对照理解NOMA在用户密集场景下的频谱效率优势和OFDMA在干扰抑制方面的特点,也为进一步探索NOMA-OFDMA联合优化提供可扩展的实验基底。
1. NOMA 与 OFDMA 的这场对比,缺的是完整链路
NOMA 与 OFDMA 看起来是两条相反的路线:一个往功率域叠用户,一个往频域划子载波。想公平对比这两种多址方式,必须有一条完整的编码、调制、收发链路顶着,否则 BER 出了问题都说不清是哪一级在拖后腿。NOMA-OFDMA-SIM 就是一套能直接跑通的 MATLAB 仿真工程,从数据生成、Turbo 编解码、OFDM 收发到 NOMA/OFDMA 多址编解码和 BER 统计全部串好。
代码包里的 data_gen.m、turbo_enc.m、ofdm_tx.m、noma_enc.m、ofdma_enc.m、main_v2_ber.m 等模块覆盖完整链路,课程设计、毕业设计可以直接改参数出对比图,做预研的也可以拿它验证功率分配策略。下面把主流程、SIC 模块和 OFDMA 子载波映射逐个拆开讲。
2. 功率域多址与正交子载波如何共存:NOMA-OFDMA 的底层逻辑
2.1 频谱效率差异:OFDMA 的隔离思想与 NOMA 的叠加思想
OFDMA 的设计逻辑是「隔离」:把频带切成互相正交的子载波,每个用户独占一部分,接收端用 FFT 天然分离各路信号,不需要做联合检测。这套思路在用户流量均衡时非常干净,但它有个隐性成本——当一个子载波上只有一个用户的数据要传时,频谱是闲着的;当用户数变多而带宽不足时,每个用户分到的子载波数量就得压缩,随之而来的是调制阶数被迫升高、对信噪比要求变苛刻。
NOMA 换了个策略:不隔离,而是让多路信号在同一个子载波上叠加,再靠功率差区分用户。核心依据是接收端可以用 SIC(串行干扰消除)逐层剥开叠加信号。只要两个用户的信道增益拉开差距,功率较强的那路信号就能先解码、重构、再抵消,剩余部分留给较弱用户。这两种方式在资源维度和接收端复杂度上有本质差别:
| 维度 | OFDMA | NOMA |
|---|---|---|
| 资源隔离方式 | 正交子载波独占 | 同一子载波功率域叠加 |
| 接收端复杂度 | FFT 后直接抽取子载波 | 需要 SIC 逐级解码 |
| 频谱效率瓶颈 | 用户增多时缺少额外自由度 | 用户功率差不够大时 SIC 失效 |
| 典型场景 | 用户分布均匀、信道较好 | 用户密集、信道增益差异大 |
NOMA 被反复提及的价值,不是把带宽变大,而是把「时频资源」和「功率域」两个自由度同时用起来。代价是接收端复杂度上去了,以及功率分配系数必须精确控制。简单说,OFDMA 靠的是“划干净”,NOMA 靠的是“叠起来再剥开”。
2.2 OFDMA 的灵活性边界:子载波级调度不是万能的
OFDMA 在 4G LTE 和 5G NR 系统里被广泛采用,不只是因为它抗多径能力强,还因为子载波级别的调度给了系统层很大的灵活性。一个用户可以被分配一段连续子载波,也可以被分配分散在频带各处的子载波,系统据此做频率选择性调度,让信道好的用户用到信道好的资源块。
这种灵活性有一个边界:它是排他性分配。如果一个用户只有几 KB 的突发数据,OFDMA 仍然要给它至少一个资源块来做完整帧;如果某子载波上所有用户的信道都很差,这个子载波上承载的数据就跟着差,系统只能靠链路自适应调整调制编码方案,无法在同一个子载波上引入第二份信息。换句话说,OFDMA 在用户数量多但总带宽固定的场景里,频谱效率是存在天花板的。
把 NOMA 和 OFDMA 放一起看就顺理成章了:OFDMA 负责把频带切成可控的资源块,NOMA 在资源块内部叠加多个用户。仿真工程里 ofdma_enc.m 和 noma_enc.m 同时存在,本质就是在模拟“先按 OFDMA 思路划频带,再在频带内按 NOMA 思路叠加”的双层接入方式。这也是标题里 NOMA-OFDMA 连字符的实际含义——不是二选一,而是嵌套关系。
2.3 融合成功与否,取决于 SIC 顺序与功率分配系数 α
融合链路里最关键的仿真参数不是信道矩阵,而是功率分配系数 α 和解码顺序。设近端用户(信道增益高)被分配 α 的功率占比,远端用户(信道增益低)分到 1-α。发射端发送的是两者在时频资源上的叠加信号,即 noma_enc.m 的核心叠加行:
function y = noma_enc(x_near, x_far, alpha) % x_near: 近端用户调制符号 % x_far : 远端用户调制符号 % alpha : 近端用户的功率占比 y = sqrt(alpha) * x_near + sqrt(1-alpha) * x_far; end这里有个反直觉点:远端用户功率大,是因为它在 SIC 中是先解的对象。接收端先把功率大的远端信号解出来,重构它的干扰,然后从叠加信号里减掉,剩下的才是近端信号。如果调反顺序,近端用户被远端干扰直接淹没,BER 曲线会非常难看。
参数上常见做法是把 α 设在 0.2 到 0.3 之间:近端用户功率占比不高但信道好,远端用户靠功率优势保证能被先解调。α 太小,两个用户功率拉不开,SIC 第一步就出错;α 太大,近端用户等于在给远端用户让路,自己容量被牺牲。这个平衡点需要在一套完整链路里用 BER 曲线去扫描,而不是拍脑袋定。另一个容易误用的点是用户配对:有人本能认为两用户信道增益接近更好,实际恰恰相反,NOMA 的增益来自信道增益差大的配对,差得越大,SIC 解开的成功率越高。
3. 把仿真链路跑通:从 data_gen.m 到 main_v2_ber.m 的主流程
3.1 主脚本的执行顺序与全局参数
main_v2_ber.m 是整个工程的入口,做的事可以归纳成三步:先定义仿真参数(信噪比范围、用户数、子载波数、功率分配系数),再在循环里跑完整链路,最后把两种多址方式的误码率汇给 plot_ber.m 画图。
%% 主仿真脚本 main_v2_ber.m clear; clc; % ---- 全局参数 ---- nUsers = 2; % 叠加用户数,NOMA 按 2 用户设置 nSubcarriers = 64; % OFDM 子载波总数 cpLen = 16; % 循环前缀长度,覆盖 64 点时延扩展 snrRange = 0:2:20; % 扫描信噪比,单位 dB alpha = 0.25; % 近端用户功率分配系数 nFrames = 2000; % 每个 SNR 下的帧数,用于统计 BER berNoma = zeros(size(snrRange)); berOfdma = zeros(size(snrRange)); for k = 1:length(snrRange) snr = snrRange(k); for frame = 1:nFrames % 数据生成 + Turbo 编码 + QPSK 调制 bitSeq = data_gen(nUsers, 128); codSeq = turbo_enc(bitSeq); modSym = qpsk_mod(codSeq); % NOMA 路线:OFDM 收发 + 功率域叠加 nomaSym = noma_enc(modSym(:,1), modSym(:,2), alpha); ofdmTx = ofdm_tx(nomaSym, nSubcarriers, cpLen); ofdmRx = awgn_channel(ofdmTx, snr); nomaDe = noma_dec(ofdm_rx(ofdmRx, nSubcarriers, cpLen), alpha); berNoma(k) = berNoma(k) + count_error(nomaDe, bitSeq); % OFDMA 路线:子载波划分 + 独立解调 ofdmaSym = ofdma_enc(modSym, nUsers, nSubcarriers); ofdmTx = ofdm_tx(ofdmaSym, nSubcarriers, cpLen); ofdmRx = awgn_channel(ofdmTx, snr); ofdmaDe = ofdma_dec(ofdm_rx(ofdmRx, nSubcarriers, cpLen), nUsers); berOfdma(k) = berOfdma(k) + count_error(ofdmaDe, bitSeq); end berNoma(k) = berNoma(k) / (nFrames * 128); berOfdma(k) = berOfdma(k) / (nFrames * 128); end plot_ber(snrRange, berNoma, berOfdma);主循环的重点是两条路线的对称性。NOMA 路线在 OFDM 基础上多了 noma_enc / noma_dec 一进一出,OFDMA 路线则是 ofdma_enc / ofdma_dec。两条路线共享同一套 Turbo 编码与 OFDM 收发,最后画出来的 BER 差异就归结到多址方式本身,这是这套工程最值得利用的对比设计。两个 BER 数组分别除以总比特数做归一化,避免帧数差异导致曲线整体平移。
参数说明:snrRange 从 0 dB 扫到 20 dB、步进 2 dB,覆盖了 Turbo 码纠错增益最明显的区间;nFrames=2000 是为了让低误码率区间(10 的负三次方附近)统计稳定,如果只看趋势,500 帧也能跑,但曲线尾部会抖;cpLen=16 与 nSubcarriers=64 的 1/4 比例是 OFDM 系统里的常用值,后面避坑章节会讲它不匹配时的症状。文件列表里没有单独的 qpsk_mod.m 和 awgn_channel.m,实际代码通常把调制合并进 data_gen.m、把信道合并进 ofdm_rx.m,我习惯把它们拆成独立函数便于逐级排查。
3.2 data_gen.m 与 Turbo 编解码参数
data_gen.m 负责生成发端比特序列。除了随机比特外,实际使用中最好让它把帧号也带上,便于出错后定位是从哪一帧开始错的。Turbo 编码器 turbo_enc.m 是两路递归卷积码的并联,交织器放在第二路之前:
function out = turbo_enc(inBits) % inBits: 1xN 原始信息比特 % 返回: 1x3N,依次为系统位、两路校验位 N = length(inBits); sys = inBits; % 系统位 p1 = rsc_encode(inBits); % 第一路校验 interleaved = interleave(inBits); % 交织器 p2 = rsc_encode(interleaved); % 第二路校验 out = [sys, p1, p2]; end关键参数是交织器深度 N 和迭代次数。交织器深度不足时,两个成员译码器交换的外部信息相关性变强,中期迭代收益消失;迭代次数不够时,低信噪比区间 BER 降不下来。配套的 turbo_dec.m 一般用 max-log-MAP 做迭代译码,迭代 4 到 6 次是个收尾平衡点:超过 6 次 BER 改善已经不明显,仿真时间却会拉高三四成。
data_gen.m 里还要注意编码速率的统计口径。Turbo 编码速率 1/3 意味着每比特多出两路校验,noma_dec.m 和 ofdma_dec.m 内部做软信息映射时,对数似然比的幅度要按 1/3 速率对应的能量折算。如果漏了这一步,两条链路都会出现 BER 在高信噪比下突然抬高的“小平台”。
3.3 noma_enc.m 与 noma_dec.m 的功率域编解码实现
noma_enc.m 在工程里的实现比原理示意多了一层维度对齐,它要保证两个用户的调制符号在同一个子载波位置上对齐,然后做叠加。noma_dec.m 处理两步 SIC:
function rxBits = noma_dec(rxSym, H, alpha) % rxSym: 接收端 OFDM 解调后的叠加符号 % H : 用户信道增益,H(1) 近端,H(2) 远端 % 第一步:解远端用户,远端功率大,先解 symFarEst = rxSym / sqrt(1-alpha) / H(2); bitsFar = qpsk_demod(symFarEst); symFarRe = qpsk_mod(bitsFar) * sqrt(1-alpha) * H(2); % 第二步:消去远端干扰,再解近端用户 symNearRem = rxSym - symFarRe; bitsNear = qpsk_demod(symNearRem / sqrt(alpha) / H(1)); rxBits = [bitsNear, bitsFar]; end这个顺序是 NOMA 仿真的核心考点。常见错误是把近端用户当成第一步解码对象,结果远端用户的强干扰直接让近端误码率崩掉。另一个细节是干扰重构必须用远端用户的硬判决符号重新映射,而不是直接拿接收符号相减,否则噪声被搬移两次,SIC 增益被吃掉一半。
提示:SIC 里的干扰重构符号必须来自硬判决再映射,直接拿接收符号相减会把噪声带进第二次解调。
功率分配上,我一般会把 α 设在 0.25 再扫描。α 一旦高于 0.4,近端用户和远端用户的功率差只有不到 4 dB,SIC 第一步几乎必错;α 低于 0.1,近端用户信噪比太低,即使 SIC 完全正确也没用。所以仿真报告里看到 α 扫描曲线,第一眼要盯两边的平衡点落在哪。
4. 从子载波分配到 BER 统计:OFDM 与 OFDMA 收发链路
4.1 ofdm_tx.m 与 ofdm_rx.m:IFFT 调制与循环前缀
ofdm_tx.m 的核心操作是把频域符号通过 IFFT 转成时域采样,再加循环前缀。这里有一个容易被忽视的数据形状问题:传入的符号矩阵维度必须跟 nSubcarriers 对齐。常见做法是接收一列频域符号,填满 nSub 个子载波:
function tx = ofdm_tx(symFrame, nSub, cpLen) % symFrame: nSub x 1 频域符号列向量 ifftOut = ifft(symFrame, nSub, 1); % 沿第一维做 IFFT tx = [ifftOut(end-cpLen+1:end, :); ifftOut]; % 循环前缀,取尾部 tx = tx(:); % 展成时域序列 endofdm_rx.m 是逆过程:把时域序列先 reshape 回 nSub+cpLen 行,丢掉开头 cpLen 行,再做 FFT,然后按发射端的列顺序取符号。接收端最容易漏的一步是 FFT 之前必须先按发射端的展列顺序做 reshape,直接用默认的列填充顺序常常把符号顺序对调,导致后面的 NOMA 解码拿到错位符号。这种错误表现很隐蔽,因为单条 BER 曲线看起来依然在下降,只是比正常情况差几个 dB,没有耐心的排查很容易误判成信道模型问题。
循环前缀长度 cpLen 要覆盖信道最大时延扩展,这句在不同教材里写法不一,在仿真里更现实的判断标准是:信道冲激响应长度超过 cpLen 时,前一个 OFDM 符号的尾部渗入后一个符号,产生符号间干扰。默认 cpLen=16 配上 64 个子载波,能容忍 16 个采样点多径展宽,如果信道模型的多径抽头超过这个数,第一时间把 cpLen 加上去。
4.2 ofdma_enc.m 与 ofdma_dec.m:子载波按块划分与独立解调
OFDMA 在代码里的角色是把两个用户的符号放到不重叠的子载波集合上。以 64 子载波、2 用户为例,常见做法是用户 1 占前 32 个子载波,用户 2 占后 32 个:
function txFrame = ofdma_enc(modSym, nUsers, nSub) % modSym: nSub/nUsers x nUsers,每列是一个用户的调制符号 txFrame = zeros(nSub, 1); blockLen = nSub / nUsers; for u = 1:nUsers idx = (u-1) * blockLen + (1:blockLen); txFrame(idx) = modSym(:, u); end endofdma_dec.m 只需要反向抽取对应子载波的符号。由于子载波间正交,接收端拿到频域符号后不需要联合检测,复杂度比 NOMA 的 SIC 低一大截。这个反差正好是两种多址方式接收复杂度的直观体现——OFDMA 解码是“按索引取数”,NOMA 解码是“解一层、消一层、再解下一层”,仿真报告里写对比分析时,接收机复杂度这一句可以从这里得到直接支撑。
OFDMA 路线的性能瓶颈不在这里,而在子载波调度策略本身。固定分块在信道频率选择性强的场景里不是最优分配,但作为 BER 对比基线是公平的,因为 NOMA 路线也在同样一组子载波上承载用户。如果想让 OFDMA 基线更强,可以把固定分块改成按信道增益动态分配,不过那属于系统级调度优化,跟多址本身无关。
4.3 plot_ber.m 与 ber.mat:两条曲线的统计口径
plot_ber.m 负责把 BER 随 SNR 的变化画成对比图,基本职责是在半对数坐标下画两条曲线,并标注不同 α 对应的 NOMA 结果。绘图部分的统计分母必须一致:NOMA 路线两个用户合起来算误码率,OFDMA 路线也是两个用户合起来算,否则两条曲线没有可比性。很多第一次上手的人会下意识“分别统计每个用户”,然后画四条线,结果近端用户曲线和远端用户曲线一高一低,跟 OFDMA 的合并曲线没法直接对比。
ber.mat 保存的是跑好的结果,里面至少应该有 snrRange、berNoma、berOfdma 三个变量。不想重跑整条链路时,可以直接load('ber.mat')再调用 plot_ber。加载后先whos看一下变量列表,确认变量名跟当前脚本一致,避免把主脚本里的同名变量覆盖掉。
在报告里怎么用这些数据?我习惯输出两句话:一是低信噪比区间(比如 4 dB 以下)NOMA 与 OFDMA 谁先跌下来,二是在目标误码率(比如 10 的负三次方)上两种方案差几个 dB。前一句说明编码增益与 SIC 的交互,后一句说明功率分配策略的价值,这两句话正好对应 NOMA-OFDMA 融合研究的两个核心问题:用户配对怎么选、功率分配怎么调。如果只看曲线形状而不做这两个数值提取,仿真结果很难在文档里形成明确结论。
5. NOMA-OFDMA 仿真常见问题排查:五个容易翻车的点
5.1 Turbo 译码不收敛,BER 在低信噪比区卡在 0.5 平台
现象:仿真跑完,NOMA 和 OFDMA 的 BER 在某个信噪比以上不再下降,呈现近乎水平的直线,数值在 0.5 附近。
原因:Turbo 编码器的移位寄存器状态没有逐帧复位。MATLAB 里如果 turbo_enc.m 的状态变量定义在函数外部或持久变量,上一帧的寄存器值会带入下一帧,相当于给每个帧头掺了一段随机错误。另一个常见原因是交织器与解交织器索引不一致,交织表生成规则在编码端和译码端各写了一份,两边没对齐。
解决:在 turbo_enc.m 每帧入口强制复位移位寄存器,交织表只生成一份,编码器和译码器引用同一份副本。改完后先跑 OFDMA 单路线验证,BER 曲线应当随 SNR 单调下降,再回来看 NOMA 曲线。
5.2 SIC 顺序搞反,近端用户 BER 反而比远端用户差
现象:α=0.25 时,近端用户 BER 明显高于远端用户,两条用户级曲线出现交叉,而理论上近端用户信道好,BER 应该更低。
原因:noma_dec.m 里第一步解的是近端用户。近端用户应是最后被解码的对象,因为它的信号混在远端强信号之下,先解它等于在没有消掉强干扰时做判决,符号错误率自然高。
解决:把解码顺序完整反过来——第一步解功率占比大的远端用户,信道估计 H(2) 对准它;重构远端符号后从接收信号里消去,第二步才解近端。改完后近端用户 BER 应回落到远端用户下面,两条曲线不再交叉。
5.3 叠加信号发射功率未归一化,星座图外扩削顶
现象:OFDM 时域信号幅度明显大于单用户场景,接收端 BER 在全部信噪比区间都比预期差,且 NOMA 路线的曲线形状跟 OFDMA 对不上。
原因:两个功率归一化的 QPSK 信号叠加后,瞬时功率取决于两个符号的相位差,峰值因子比单用户高。如果发射端做了硬限幅或削顶,SIC 重构用的符号幅度已经失真,后面的干扰消除全部建立在错误的重构上。
解决:发射端不做硬限幅。需要固定平均功率时,把叠加后的符号按 1/sqrt(nUsers) 缩放做能量归一化,同时把 α 的含义换算清楚——α 是叠加前两个用户之间的功率比,不是归一化之后的绝对功率。归一化放在叠加之后,功率比例放在叠加之前。
5.4 循环前缀长度不够,中等信噪比出现共平台
现象:OFDMA 和 NOMA 两条曲线在 8 到 12 dB 之间同时出现平台,超过这个区间又继续下降,看起来像两条曲线被“同时绊了一下”。
原因:cpLen=16 小于信道冲激响应抽头数,前一个 OFDM 符号的拖尾混入当前符号,子载波正交性被破坏。这种错误属于符号间干扰而不是加性噪声,它不会随 SNR 提高而消失,只会在某个信噪比区间把两条曲线同时压住。
解决:把信道模型的最大抽头数打印出来,设为 nTap,让 cpLen 至少大于 nTap。如果是多径抽头数超过 16 的仿真场景,直接把 cpLen 加到 32 或 64 重跑,平台就会消失。
5.5 BER 统计分母不一致,两条曲线整体平移
现象:NOMA 曲线看起来比 OFDMA 好了 3 dB,但仔细一算是两套数据的统计基数不同,曲线形状其实一样。
原因:NOMA 解码时把系统位和校验位都算进了误码分母,OFDMA 路线只统计了信息位;或者 Turbo 编码后某个用户存在打孔,两边的有效比特数不一致。这种问题最隐蔽,因为曲线形状正常、趋势也正常,只有数值整体平移。
解决:在 main_v2_ber.m 里只用一个变量 nTotalBits 做统计分母,两条路线统一用 Turbo 译码后的信息比特做误码判决,不要拿编码后的全序列直接比。每次改动统计代码后,先在同一信噪比下跑两遍,确认数值完全一致,再放开全区间扫描。
6. 进阶:把功率分配系数 α 做成扫描向量
前面的流程把 α 固定成单一值,但 NOMA-OFDMA 验证里最值得看的就是 α 从 0.1 到 0.4 变化时两条曲线的响应。把主脚本里的 α 从常量改成向量,外层加一圈循环:
alphaList = 0.1:0.05:0.4; berAlpha = zeros(length(alphaList), length(snrRange)); for a = 1:length(alphaList) alpha = alphaList(a); % ... 内部复用主循环的 NOMA 链路 ... berAlpha(a, :) = berNoma; end plot_ber_alpha(snrRange, berAlpha, alphaList);扫描完会得到一组 NOMA 曲线,同一 SNR 下不同 α 的 BER 呈现 U 形:α 太小时近端用户信噪比不足,α 太大时远端用户 SIC 第一步崩掉,中间某个 α 值附近两用户同时达到最低误码率。我习惯再补一张图——在目标误码率 10 的负三次方下,把所需信噪比按 α 画成横轴曲线,最低点对应的就是当前用户配对下的最佳功率分配。如果两张图的极值点不一样,优先看第二张,因为工程指标通常以目标 BER 为约束。
从那以后,我每次拿到一份新的多址仿真代码,都会强制走一遍固定顺序:先跑单用户 OFDM 链路确认无误码底噪,再开双用户 OFDMA 建立基线,最后才开 NOMA 的 SIC 与功率分配。链路本身的问题不排除干净,多址对比的结果就不可信。希望这套排查顺序能帮到你,省下对着曲线猜原因的时间。
本文还有配套的精品资源,点击获取