ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MATLAB实现5G NOMA功率域仿真:SIC解码误码率分析

MATLAB实现5G NOMA功率域仿真:SIC解码误码率分析 简介这是一套MATLAB编写的5G通信系统NOMA误码率仿真源码面向通信工程学生、5G算法工程师及研究非正交多址接入的开发者可用于快速搭建功率域NOMA的误码率测试环境验证不同参数对系统性能的影响。资源共2个m文件其中主程序负责参数配置、仿真流程控制和误码率曲线绘制函数文件实现信道建模与SIC串行干扰消除解码等核心功能压缩包仅2KB轻量精炼便于阅读和二次开发。已有302人浏览学习。源码完整覆盖高斯白噪声信道下的调制映射、功率分配、SIC逐层解调和误码率统计流程可通过修改信噪比、用户数量及功率分配策略清晰展示NOMA与OMA在频谱效率与误码性能上的差异同时为理解5G非正交多址接入机制、完成课程设计或科研论文仿真提供了可直接运行的参考实现。1. 从一次误码率实验看5G NOMA为什么值得自己动手仿把两个用户的信号完全叠加在同一份时频资源上接收端靠功率差一层层剥开这就是NOMA最核心的设定。很多人第一反应是“SIC一步判错就全盘崩掉”但实际把MATLAB仿真跑起来会发现在合适的功率分配和信噪比区间内NOMA的误码率并不必然比正交多址OMA更差某些场景下反而更稳。这份源码的入口很干净核心就两个文件Runme.m负责蒙特卡洛主流程Channel.m负责生成信道系数。跑通它等于把功率域NOMA从叠加编码、信道传播到SIC解码的整条链路亲手搭了一遍。对刚接触5G物理层的学生来说这是一份能直接改参数的实验台对做系统仿真的工程师而言它也是一个可以替换信道模型、扩展用户数的基线框架。下面几章我会按仿真执行顺序把容易改错和容易理解偏的位置逐一拆开。2. NOMA功率域复用与SIC解码Runme.m 与 Channel.m 的配合逻辑第一次打开这份源码不要急着从头读到尾。先确认两个文件的边界Runme.m做的是比特生成、调制、功率分配、过信道、SIC解码和误码统计Channel.m只返回信道系数不涉及任何比特处理。这个分离很关键后面换信道模型时主流程一行都不用动只要改Channel.m的分支。2.1 发送端多用户信号如何叠加到同一份资源上功率域NOMA的发送端本质是复数向量相加。两个用户分别调制到符号级再按不同的功率权重叠加成一个发射符号。下面这段代码是这套源码里最常见的主流程开头% 两个用户共用同一段时频资源符号数相同 numSymbols 1e5; % 每个用户的QPSK符号数 M 4; % QPSK每个符号携带2 bit P_total 1; % 总发射功率归一化为1 alpha 0.8; % 用户1获得80%功率通常给信道差的用户 dataSym1 randi([0 M-1], numSymbols, 1); dataSym2 randi([0 M-1], numSymbols, 1); symUser1 pskmod(dataSym1, M, 0); % QPSK调制 symUser2 pskmod(dataSym2, M, 0); % 功率域叠加注意这里是幅度加权开根号才对应功率 txSymbols sqrt(P_total * alpha) * symUser1 ... sqrt(P_total * (1 - alpha)) * symUser2;这段代码里最容易看漏的是sqrt()。alpha定义的是功率比例调制符号是幅度域的量所以叠加前必须开根号。alpha 0.8时用户1幅度权重是sqrt(0.8)≈0.8944用户2是sqrt(0.2)≈0.4472两者幅度比约为2:1功率比是4:1。这个幅度层级就是SIC能工作的前提。pskmod(dataSym, M, 0)的第三个参数是初始相位0表示星座点从相位0开始旋转。这里两个用户必须用完全相同的调制参数否则接收端解调时还要额外做相位补偿徒增代码复杂度。P_total1是归一化避免后续叠加产生过大的浮点数值尤其在高阶调制或多用户场景下很实用。2.2 SIC接收机怎样把叠加信号一层层“剥”出来接收端拿到的是两个用户信号经过各自信道后的混合再加上噪声。理想信道估计下SIC分三步先解高功率用户再重构它的信号并从接收信号中减去最后在残余信号里解低功率用户。对应代码片段如下% 假设外层循环已经给出SNRdB noiseVar P_total / (10^(SNRdB / 10)); noise sqrt(noiseVar/2) * (randn(numSymbols,1) 1j*randn(numSymbols,1)); % Channel.m 返回每个符号对应的信道系数 h1 Channel(rayleigh, numSymbols); h2 Channel(rayleigh, numSymbols); rxSymbols sqrt(P_total*alpha) * h1 .* symUser1 ... sqrt(P_total*(1-alpha)) * h2 .* symUser2 noise; % SIC第1步直接解高功率用户1 estUser1 rxSymbols ./ h1; symRx1 pskdemod(estUser1, M, 0); % SIC第2步重构用户1对接收信号的贡献并减去 interference sqrt(P_total*alpha) * h1 .* pskmod(symRx1, M, 0); rxRemain rxSymbols - interference; % SIC第3步在残余信号中解用户2 estUser2 rxRemain ./ h2; symRx2 pskdemod(estUser2, M, 0); % 符号索引转回比特统计误码数 bits1 de2bi(dataSym1, log2(M)); bits2 de2bi(dataSym2, log2(M)); bitsRx1 de2bi(symRx1, log2(M)); bitsRx2 de2bi(symRx2, log2(M)); ber1 sum(bits1(:) ~ bitsRx1(:)) / numel(bits1); ber2 sum(bits2(:) ~ bitsRx2(:)) / numel(bits2);这里的核心操作是除以h1和h2对应的是最简单的单抽头信道均衡。SIC的成败关键在于第2步的重构质量如果用户1的符号判决错了重构出来的“干扰信号”就是错的减完之后不只用户2的噪声没变还额外多了一个错误残留项这就是SIC错误传播的由来。SIC步骤操作内容对应MATLAB片段失败风险1解高功率用户1estUser1 rxSymbols ./ h1信道估计误差2重构用户1信号并消除rxRemain rxSymbols - interference判决错误传播3解低功率用户2estUser2 rxRemain ./ h2残余干扰叠加噪声在实际调试中如果用户2的BER曲线在高信噪比段出现平底error floor优先检查步骤2的重构增益是否用了正确功率权重。常见误用是重构时丢掉sqrt(P_total*alpha)导致减掉的信号幅度比真实干扰小剩下的残余干扰直接变成用户2底噪。3. 信道模型与调制方式参数化AWGN、Rayleigh与高阶映射Channel.m在源码里的定位很单纯给出一组与发送符号长度相同的信道系数。但不同信道模型对BER曲线形态和SIC行为的影响差异极大值得单独拆开看。3.1 三种信道模型下的SNR语义在AWGN信道下h恒等于1叠加信号经受的只有加性噪声BER曲线随SNR提高均匀下降。Rayleigh信道下每个符号乘以一个复高斯随机变量符号能量随机起伏深衰落位置上的用户1很容易判错错误会顺着SIC传播给用户2。Rician信道介于两者之间适合模拟存在视距路径的微蜂窝场景。模式h特性BER曲线特征适用场景awgnh 1曲线平滑便于验证SIC逻辑高斯噪声下的基线对照rayleigh每符号独立复高斯低SNR时BER下降变慢SIC错误传播更明显多径环境的简化模型rician视距分量 散射分量介于AWGN与Rayleigh之间室外微蜂窝、室内热点注意“每符号独立”这个设定。真实的移动信道相邻符号之间有时间相关性直接用randn生成的独立系数会丢掉多普勒效应严格讲只适用于静态快照或符号间隔足够长的场景。作为教学和基线验证这已经足够工程上要再换用相关信道模型。3.2 Channel.m的完整实现与切换方法上面代码里调用的Channel函数最紧凑的版本可以写成这样function h Channel(mode, numSymbols, K) % mode: awgn平直信道, rayleigh独立瑞利, rician莱斯信道 % numSymbols: 符号个数 % K: 可选莱斯因子默认10 if nargin 3 K 10; end switch mode case awgn h ones(numSymbols, 1); case rayleigh % 复高斯归一化使得E[|h|^2]1 h (randn(numSymbols,1) 1j*randn(numSymbols,1)) / sqrt(2); case rician los sqrt(K/(K1)); % 视距分量幅度 nlos sqrt(1/(K1)) * ... (randn(numSymbols,1) 1j*randn(numSymbols,1)) / sqrt(2); h los * ones(numSymbols,1) nlos; otherwise error(Channel: unknown mode %s, mode); end end在Runme.m里只要在顶层定义一个channelMode变量然后统一调用Channel(channelMode, numSymbols)即可。rayleigh分支里的/sqrt(2)是为了让实部、虚部方差之和等于1这样噪声功率定义不会被信道放大还是缩小干扰。如果去掉这个归一化SNR计算会整体偏移不同信道模型之间的对比就没有意义了。噪声功率的计算要与发射功率保持一致。接收端信号功率近似为P_total因此noiseVar P_total / 10^(SNRdB/10)。这个关系在发送端做了功率归一化之后才是准确的否则需要把实际发射功率代入。3.3 调制阶数扩展从QPSK到16QAM想观察更高阶调制下的SIC错误传播把M4改成M16还不够因为pskdemod只适用于PSK不适用于QAM。常规做法是换成qammod和qamdemodM 16; dataSym randi([0 M-1], numSymbols, 1); symUser qammod(dataSym, M, UnitAveragePower, true); % 接收端 rxSym qamdemod(est, M, UnitAveragePower, true);UnitAveragePower,true会让调制后的星座平均功率归一化为1这样发送端的功率分配仍然用alpha控制不需要再额外做能量归一化。如果当前MATLAB版本不支持该参数手动归一化可以用sqrt(mean(abs(qammod(0:M-1,M)).^2))作为除数。16QAM的星座点间距比QPSK小很多用户1在Rayleigh衰落下的判决错误率会快速上升SIC错误传播对用户2的影响会比QPSK大得多这正是高阶调制NOMA仿真中最值得看的现象。4. 功率分配策略对BER的影响固定分配与动态分配NOMA的功率分配不是简单把总功率分成两份它决定了SIC每一层能拿到多少信噪比余量。源码里的alpha是影响两条BER曲线形状最直接的参数。4.1 固定功率分配因子alpha怎么选固定功率分配是最容易上手的策略但alpha不能随便拍脑袋。alpha给远用户即信道条件差、需要更多功率的用户。当alpha0.5时两个用户功率相同SIC第一步几乎分不清谁是谁BER在低SNR区域直接不可用。当alpha0.8时用户1功率占优能够稳定解出但用户2的发射功率只有0.2受噪声和残余干扰双重影响。alpha0.95则反过来用户2功率过低BER可能一直在0.1量级徘徊。在固定分配下用户2的BER主要受三个因素限制噪声、SIC消除后的残余干扰、以及用户1判决错误的影响。其中第三个因素在高SNR下会消失所以如果你看到用户2的BER曲线在高SNR段斜率变小多半是功率分配没给够用户2余量或者用户1在深衰落处出现了零星误码。4.2 按信道状态动态分配功率的仿真实现更贴近5G调度的做法是先获取各用户的信道增益再按增益反比分配功率。信道差的用户拿大功率信道好的用户用小功率但SIC仍然能正确消除。这就是动态功率分配。% 假设已经通过Channel函数得到每个符号的信道系数 gain1 mean(abs(h1).^2); gain2 mean(abs(h2).^2); % 简单分数功率分配信道增益小的用户获得更大功率 alpha_dynamic gain2 / (gain1 gain2); P1 P_total * alpha_dynamic; P2 P_total * (1 - alpha_dynamic); % 叠加时使用动态功率 txSymbols sqrt(P1) * symUser1 sqrt(P2) * symUser2;这里的alpha_dynamic含义与固定模式相同都是用户1的功率占比。如果h2的平均增益小于h1gain2/(gain1gain2)就会小于0.5用户1拿到超过一半的功率。这种方案在静态信道下效果很好但在快衰落信道里每个符号块都要重新统计平均增益开销不小。调试时还可以用一个两层循环扫描alpha观察BER曲面alphaList 0.5:0.05:0.95; for aIdx 1:length(alphaList) ber1 zeros(size(SNRdB)); ber2 zeros(size(SNRdB)); for k 1:length(SNRdB) % 用alphaList(aIdx)替换固定alpha重复第2章SIC流程 [ber1(k), ber2(k)] simOneFrame(noiseVar, P_total, alphaList(aIdx), rayleigh); end plot(SNRdB, ber2); hold on; end这段扫描会让你直观看到用户2的最佳alpha和用户1的最佳alpha永远不在同一点。实际NOMA系统选的alpha本质是吞吐量公平性约束下的折中而不是单条BER最优。另外要注意不要把alpha直接乘在复数符号上。有些刚上手的人写成rx alpha * h1 .* symUser1 (1-alpha) * h2 .* symUser2这虽然也能跑但实际功率占比变成了alpha^2和(1-alpha)^2与论文里定义的功率分配因子对不上曲线没有可比性。5. 误码率统计原理与BER曲线绘制从计数到画图误码率统计看起来只是“错的除以总数”但在这套NOMA仿真里有两处容易算错一是把符号错误率当误码率二是在低SNR区域统计的帧数不足导致曲线抖动。5.1 误码率统计的拆解SER与BER不能混用QPSK一个符号携带2比特pskdemod返回的是符号索引不能直接和发送比特比。上面代码里用de2bi把符号索引展开成比特序列numel(bits1)才是参与统计的总比特数。如果误写成以符号数量作分母得到的其实是符号错误率SER。在格雷映射下QPSK的SER约等于2倍BER曲线会整体上移约3dB这个差异在对照论文结果时非常致命。蒙特卡洛统计还有一个置信度问题单个SNR点至少要累计到一定数量的错误比特BER曲线在高信噪比下才平滑。经验上误差比特数至少要几十到一百个。想显示到1e-5该SNR点就要跑接近1e6比特。所以源码里的numBits或numSymbols不能盲目调小。5.2 Runme.m主循环、噪声加载与semilogy绘图把第2章的SIC流程封装成simOneFrame可以让主循环非常干净。函数内部完整结构如下function [ber1, ber2] simOneFrame(noiseVar, P_total, alpha, channelMode) M 4; numSymbols 25000; dataSym1 randi([0 M-1], numSymbols, 1); dataSym2 randi([0 M-1], numSymbols, 1); sym1 pskmod(dataSym1, M, 0); sym2 pskmod(dataSym2, M, 0); h1 Channel(channelMode, numSymbols); h2 Channel(channelMode, numSymbols); noise sqrt(noiseVar/2) * (randn(numSymbols,1) 1j*randn(numSymbols,1)); rx sqrt(P_total*alpha) * h1 .* sym1 sqrt(P_total*(1-alpha)) * h2 .* sym2 noise; est1 rx ./ h1; symRx1 pskdemod(est1, M, 0); interference sqrt(P_total*alpha) * h1 .* pskmod(symRx1, M, 0); est2 (rx - interference) ./ h2; symRx2 pskdemod(est2, M, 0); bits1 de2bi(dataSym1, log2(M)); bits2 de2bi(dataSym2, log2(M)); bitsRx1 de2bi(symRx1, log2(M)); bitsRx2 de2bi(symRx2, log2(M)); ber1 sum(bits1(:) ~ bitsRx1(:)) / numel(bits1); ber2 sum(bits2(:) ~ bitsRx2(:)) / numel(bits2); end主循环里逐SNR、逐帧调用这个函数最后用semilogy绘制BER曲线SNRdB 0:2:20; numFrames 20; ber1All zeros(length(SNRdB), numFrames); ber2All zeros(length(SNRdB), numFrames); for s 1:length(SNRdB) noiseVar P_total / (10^(SNRdB(s) / 10)); for frame 1:numFrames [ber1All(s, frame), ber2All(s, frame)] ... simOneFrame(noiseVar, P_total, alpha, rayleigh); end end ber1 mean(ber1All, 2); ber2 mean(ber2All, 2); % semilogy不接受0值临时用1e-6占位实际统计时避免出现0帧 ber1(ber1 0) 1e-6; ber2(ber2 0) 1e-6; figure; semilogy(SNRdB, ber1, -o, LineWidth, 1.5); hold on; semilogy(SNRdB, ber2, -s, LineWidth, 1.5); grid on; xlabel(SNR (dB)); ylabel(BER); legend(User 1 (high power), User 2 (low power));ber1 0的占位处理只影响显示不代表真实误码率。如果某个SNR点BER为0说明这个点的蒙特卡洛样本量不够应该加大numSymbols或numFrames而不是直接用占位值交差。5.3 用parfor加速多SNR点仿真把外层for s 1:length(SNRdB)替换成parfor是这套源码最简单的提速方式。每个SNR点本身相互独立天然适合并行。写法上注意每个worker内部要使用独立临时变量parfor s 1:length(SNRdB) noiseVar P_total / (10^(SNRdB(s) / 10)); berTmp1 zeros(numFrames, 1); berTmp2 zeros(numFrames, 1); for frame 1:numFrames [berTmp1(frame), berTmp2(frame)] ... simOneFrame(noiseVar, P_total, alpha, rayleigh); end ber1All(s, :) berTmp1; ber2All(s, :) berTmp2; end并行前先用rng(s, philox)为每个SNR点设置独立随机种子这样多次运行结果可复现。需要注意parfor下不要修改共享的大矩阵ber1All的任意行否则会报透明度警告上面的写法已经把单SNR点的结果放进独立临时数组再回写是稳妥的方式。6. 把这套NOMA仿真接到5G NR接入网评估中跑通BER曲线只是第一步。这套源码的价值在于接口留得简洁可以往5G NR方向做三处扩展。6.1 让叠加符号落到NR资源块上把第2章的numSymbols对应到NR的物理资源概念上比如15kHz子载波间隔下一个资源块RB有12个子载波。若两个用户被调度到同一个RB用户1和用户2的符号就按NOMA方式叠加在相同RE上。基站侧做的功率分配对应源码里的alpha接收端做SIC对应pskdemod和干扰消除部分。可以进一步把两个用户信道改成5G NR的PDSCH传输方案在Runme.m外层包一个RB调度循环就能评估“同一RB配对不同用户”的误码表现。6.2 用5G Toolbox的TDL信道替换Channel.mChannel.m里的独立Rayleigh系数在低速移动场景下够用但站到5G基站侧评估时通常需要TR 38.901的TDL信道。如果安装了5G Toolbox可以替换成nrTDLChanneltdl nrTDLChannel; tdl.DelayProfile TDL-E; tdl.DelaySpread 100e-9; tdl.MaximumDopplerShift 30; tdl.SampleRate 30.72e6; [pathGains, sampleTimes] tdl();替换后要注意nrTDLChannel输出的是多径路径增益不再是逐符号的复数系数接收端需要先做OFDM解调、频域信道估计再做SIC。这比Channel.m复杂但能体现出多径时延对NOMA误码的真实影响。6.3 用“理想重构”探针量化SIC错误传播代价想单独观察SIC错误传播的影响一个很实用的技巧是做一个对比实验在消除用户1干扰时分别使用“真实发送符号”和“判决符号”来做重构两者得到的用户2 BER之差就是错误传播代价。% 理想消除传入真实的sym1 rxRemain_ideal rx - sqrt(P_total*alpha) * h1 .* sym1; % 真实SIC消除用判决后的symRx1 rxRemain_real rx - sqrt(P_total*alpha) * h1 .* pskmod(symRx1, M, 0);ber2_ideal反映的是没有错误传播时用户2能获得的性能上限ber2_real是系统实际性能。两者的差随SNR增大应该逐步缩小如果高SNR下仍然保持明显差距说明用户1的判决错误率没有被SNR压制住问题多半出在功率分配因子alpha过小或者信道深衰落导致estUser1的信噪比不足。这个探针比单纯看用户2的BER曲线更容易定位SIC实现里的短板。本文还有配套的精品资源点击获取
返回列表