
简介面向5G非正交多址技术NOMA研究场景这套MATLAB源码为无线通信方向学生、算法初学者及相关开发人员提供了可直接运行的接收机仿真实现核心聚焦迫零检测ZF与串行干扰消除SIC的联合运用。代码针对典型NOMA链路中的多用户检测环节进行模块化设计覆盖基础信号调制、QPSK软解调以及ZF-SIC主测试等关键功能另附Word版文档便于查阅与整理项目说明读者可对照脚本理解接收端从迫零检测到串行干扰消除的完整流程。资源压缩包共4个文件其中3个为.m脚本、1个为docx说明文档整体体积仅15KB结构轻量可直接放入MATLAB工作目录运行。目前已有596人浏览学习适合正开展课程设计、毕业设计或科研入门的同学使用这套源码由达摩老生校正发布经过测试可靠性较好可在其框架上继续扩展误码率对比、功率分配、用户配对等实验有效降低从零搭建NOMA仿真链路的门槛。1. 为什么 NOMA 仿真里接收端第一件事是按功率排队同一块时频资源上叠两个用户信号并不是「混在一起变乱」而是变成了一种可排序的叠加编码。区别在接收端直接做线性检测时弱用户的信号会被强用户完全淹没先做 ZF迫零再按信噪比顺序做 SIC串行干扰消除两个用户的误码率曲线就会明显拉开。「5G技术_NOMA_运用ZF SIC_matlab源码」这个标题的核心就是用 MATLAB 把这条链路完整仿真出来发射端做功率域叠加接收端用排序后的 ZF-SIC 逐级解码。它常见于 5G-Advanced 功率域 NOMA 的演示、研究生课程大作业和论文复现最终交付物通常是一张用户 1、用户 2 两条误码率曲线。适合已经知道 NOMA 概念、但打开 MATLAB 不知道第一行代码从哪里开始的读者。2. 叠加编码与 ZF-SIC 的解码顺序先让两个信号重叠再按信噪比排队2.1 功率域叠加两个用户的信号如何写成一个矢量NOMA 的发送端不做频分或时分而是在同一个资源块上做线性叠加。双用户场景下基站侧发送信号可以写成$$ x \sqrt{\alpha P}, s_1 \sqrt{(1-\alpha)P}, s_2 $$其中 $s_1$、$s_2$ 是两用户的调制符号例如 QPSK$P$ 是总发射功率$\alpha$ 是用户 1 的功率分配系数。NOMA 的基本约束是 $\alpha \ne 0.5$两个用户功率必须拉开差距接收端才有条件做串行干扰消除。这个公式看着简单仿真时最容易错的是功率归一化。QPSK 符号如果写成(1j)/sqrt(2)的形式单符号平均能量是 1叠加后总功率才是 $P$如果符号直接取1j叠加后总功率就不是 1后面信噪比全部对不上。参数含义本仿真取值$\alpha$用户 1 功率占比0.8$s_1, s_2$QPSK 符号单位功率$\pm 1/\sqrt{2} \pm j/\sqrt{2}$$H$2 发 2 收瑞利信道每元素均值为 0、方差为 1$P_1, P_2$两用户发射功率$\alpha P, (1-\alpha)P$$N_0$复噪声方差由目标 SNR 计算我一般在 MATLAB 里把用户发射幅度直接写成sqrt(alpha)和sqrt(1-alpha)这是工程上的默认做法也方便后面 SIC 抵消时直接复用同一个系数。2.2 SIC 串行检测谁先被解出来谁先被消掉SIC 的思路是「先解最强信号然后从接收信号里把它减掉再解剩下的」。在 NOMA 下所谓最强不是瞬时信道最强而是功率分配之后等效信干噪比最高。两用户的接收信号可以写为$$ y_k h_k \left( \sqrt{\alpha P}, s_1 \sqrt{(1-\alpha)P}, s_2 \right) n_k $$用户 1 被分配了更大功率所以 SIC 第一级通常解用户 1把 $s_2$ 当作干扰直接解 $s_1$得到判决 $\hat{s}_1$ 后用 $\hat{s}_1$ 重构用户 1 的贡献并从 $y_k$ 中减掉再解 $s_2$。关键点是重构时必须使用同一组信道系数和同一功率系数。很多初版仿真在「消除」一步用了重新生成的信道或者用了未经判决的软符号结果第二级性能不升反降。原因就是残余干扰没有真正消掉反而引入了误差传播。2.3 ZF 在 SIC 里的角色不是抗干扰而是保证端口独立性在双用户 MIMO-NOMA 中接收端有 2 根天线、用户是 2 个接收信号是一个 2 维向量。ZF 的作用是给这个 2 维向量做线性变换让每个输出端口只对应用户的符号从数学上解除用户间干扰$$ G (H^H H)^{-1} H^H, \quad z G y $$$G$ 是信道矩阵的伪逆。变换之后 $z_1$ 主要含 $s_1$$z_2$ 主要含 $s_2$。代价是噪声被放大尤其在信道矩阵接近奇异时$G$ 的某一行模值会非常大。为什么 SIC 需要 ZF 配合因为 SIC 在数学上要求每一步的消去操作作用在「解耦后的端口」上。先用 $G$ 把 MIMO 信道变成两个近似独立的标量信道再按信干噪比排序做逐级判决和抵消实现复杂度最低。MATLAB 中pinv(H)就是伪逆的稳定实现不建议直接用inv(H*H)*H后者在信道相关时数值不稳定。排序原则是等效信干噪比高者先解。等效信干噪比不是只看 $\alpha$还要把 ZF 的噪声放大算进去$$ \gamma_k \frac{P_k |\mathbf{g}_k \mathbf{h}_k|^2}{N_0 |\mathbf{g}_k|^2} $$$\mathbf{g}_k$ 是 $G$ 的第 $k$ 行。仿真里如果不做这一步直接用功率排序信道差异大时会出现近端用户误码率反超远端用户的荒谬曲线。3. 可复现的 NOMA-ZF-SIC MATLAB 链路从参数表到误码率曲线3.1 仿真场景与参数表我用的是双用户上行场景每个用户 1 根发射天线基站 2 根接收天线信道是瑞利平坦衰落。SSIC 排序和 ZF 都能体现。参数取值说明调制方式QPSK每个符号 2 bit格雷映射$\alpha$0.8用户 1 远端大功率每帧符号数2e4每用户每帧帧数20每信噪比点共 4e5 符号信噪比范围0:2:20 dB以 Eb/N0 计MATLAB 版本R2021a 及以上不依赖通信工具箱代码在 R2023b 上验证过只用了基本矩阵运算不需要安装额外工具箱。新建脚本直接贴主循环再补一个检测函数就可以跑。3.2 主脚本发射叠加与 Monte Carlo 循环%% 双用户上行 NOMA 2 接收天线 排序 ZF-SIC 检测 clear; clc; close all; rng(2026); % 固定随机种子保证可复现 alpha 0.8; % 用户1功率占比 numBits 2e4; % 每帧符号数 numFrames 20; % Monte Carlo 帧数 EbN0dB 0:2:20; % 比特信噪比扫描范围 ser1 zeros(size(EbN0dB)); ser2 zeros(size(EbN0dB)); for idx 1:length(EbN0dB) % QPSK 每符号 2 bit符号 SNR 比特 SNR 3dB snr EbN0dB(idx) 10*log10(2); noiseVar 10^(-snr/10); % 复噪声方差 err1 0; err2 0; for frame 1:numFrames % QPSK 符号单位能量 s1 (2*randi([0 1],1,numBits)-1 1j*(2*randi([0 1],1,numBits)-1))/sqrt(2); s2 (2*randi([0 1],1,numBits)-1 1j*(2*randi([0 1],1,numBits)-1))/sqrt(2); % 2x2 瑞利信道行是接收天线列是用户 H (randn(2,2) 1j*randn(2,2))/sqrt(2); % 功率域叠加后过信道 Tx H * [sqrt(alpha)*s1; sqrt(1-alpha)*s2]; % 复高斯噪声实部虚部各占 noiseVar/2 Y Tx sqrt(noiseVar/2)*(randn(2,numBits) 1j*randn(2,numBits)); % 排序 ZF-SIC 检测 [s1Hat, s2Hat] zf_sic_detect(Y, H, alpha, noiseVar); err1 err1 sum(s1Hat ~ s1); err2 err2 sum(s2Hat ~ s2); end ser1(idx) err1 / (numBits * numFrames); ser2(idx) err2 / (numBits * numFrames); end figure; semilogy(EbN0dB, ser1, o-, EbN0dB, ser2, s-, LineWidth, 1.5); grid on; xlabel(Eb/N0 (dB)); ylabel(Symbol Error Rate); legend(用户1 (\alpha0.8), 用户2 (\alpha0.2));两个关键点。第一noiseVar 10^(-snr/10)用的是符号信噪比因为发送端把发射功率归一化为 1如果改成别的功率基准这一行要同步调整。第二s1Hat ~ s1比较的是复数符号MATLAB 中直接比较两个复数向量是安全和可靠的不需要拆实部虚部。3.3 检测核心zf_sic_detect 的实现与排序逻辑function [s1Hat, s2Hat] zf_sic_detect(Y, H, alpha, noiseVar) % 排序 ZF-SIC 检测器 % Y: 2 x numBits 接收矩阵 % H: 2 x 2 信道矩阵H(:,k) 对应用户 k % alpha: 用户1功率占比 % noiseVar: 复噪声方差 P [alpha, 1-alpha]; % 第一步线性 ZF 加权矩阵 G pinv(H); % 第二步计算每个用户在 ZF 输出端的等效信噪比按大到小排序 gamma zeros(1,2); for k 1:2 gamma(k) P(k) * abs(G(k,:)*H(:,k))^2 / (noiseVar * norm(G(k,:))^2); end [~, order] sort(gamma, descend); % 第三级先解第一个用户硬判决后做干扰消除 k1 order(1); r1 G(k1,:) * Y; sHat1 qpsk_hard_demod(r1); Y Y - H(:,k1) * (sqrt(P(k1)) * sHat1); % 关键加上功率系数再消除 % 第四级剩余用户重新按单流最小二乘解码 k2 order(2); g2 H(:,k2) / (H(:,k2)*H(:,k2)); % 单列伪逆 r2 g2 * Y; sHat2 qpsk_hard_demod(r2); % 按用户编号还原 if order(1) 1 s1Hat sHat1; s2Hat sHat2; else s1Hat sHat2; s2Hat sHat1; end end function symHat qpsk_hard_demod(r) % QPSK 最小距离硬判决返回单位能量符号 symHat sign(real(r))/sqrt(2) 1j*sign(imag(r))/sqrt(2); endpinv(H)在这里同时完成了信道求逆和端口排序前的准备。第二级没有继续使用完整的 $G$而是对剩余信道向量单独算伪逆这是 ZF-SIC 和普通 ZF 的区分点每消除一个用户就重算一次加权向量而不是一劳永逸。如果第二级仍然用原来的 G会把已经被消掉的用户又重新带回来。qpsk_hard_demod返回单位能量符号因此消除步骤里必须再乘回sqrt(P(k1))。这是整个 SIC 最容易错的地方少乘或乘两次第二级用户误码率会直接掉一个数量级。3.4 仿真结果怎么读正常条件下用户 1 的曲线应当低且陡用户 2 的曲线高出 3 到 6 dB这是两个用户功率差带来的误码率差。如果两条曲线几乎重合或交叉优先怀疑 3.3 节的消除步骤和 2.3 节的排序逻辑。需要说明的是这里统计的是符号错误率不是比特误码率。QPSK 采用格雷映射时BER 约等于 SER 的一半用来做相对对比没有影响。如果想严格报 BER需要把符号映射成 bit 再统计链路结构不变。4. 参数调整、调试边界与 MATLAB 仿真的五个常见坑4.1 α 从 0.8 调到 0.6 会发生什么功率分配系数直接决定两条曲线的间距。α 越接近 0.5两用户越接近等功率SIC 第一级的残余干扰越大错误传播越明显α 接近 1用户 2 的接收条件变得极差曲线出现高底噪平台。α 取值用户 1用户 20.9 / 0.1曲线最好接近单用户下界高 SNR 下 SER 停在 1e-2 附近0.8 / 0.2两条曲线间距约 5 dB无明显底噪平台0.6 / 0.4低 SNR 时两条都变差高 SNR 时受错误传播拖累想验证功率分配对系统公平性影响时把 α 做成一个循环变量放在主脚本外面每轮只改 α其余完全不动是最干净的做法。4.2 信道相关性对 ZF 的伤害怎么量化ZF 的噪声增强取决于信道矩阵的条件数。生成相关瑞利信道时常见做法是对独立信道做线性变换% 生成 2x2 相关信道相关系数为 rho rho 0.9; R [1 rho; rho 1]; % 信道相关矩阵 C sqrtm(R); % 矩阵平方根 H_corr H * C; % H 是独立瑞利信道 % 量化 ZF 噪声增强程度 cond(H_corr) cond(H)条件数越大4.3 节中gamma里的norm(G(k,:))就越大等价于该端口的噪声被放大越厉害。这个量化比肉眼看误码率快得多。我一般会在脚本里把cond(H)的值打印出来和 SER 曲线放在一起看条件数超过 10 时ZF-SIC 的曲线会明显偏离理想曲线。4.3 排序方向、噪声拆分与 awgn 重复加噪三个坑按出现频率排序。第一排序方向写反。sort(gamma, descend)决定先解强用户如果误写成ascend第一级就先去解弱信号硬判决错误率升高消除步骤把错误符号重构并减掉第二级用户被二次污染。表现就是用户 1 和用户 2 的 SER 曲线交叉。第二复噪声方差拆分错误。randn生成的是实噪声一个复数符号由实部和虚部两个独立维度组成总方差为noiseVar时需要写成% 正确实部虚部各占一半 Y Tx sqrt(noiseVar/2)*(randn(2,numBits) 1j*randn(2,numBits)); % 错误总噪声功率翻倍 Y2 Tx sqrt(noiseVar)*randn(2,numBits);第三个坑是混用awgn函数。awgn(Y, snr, measured)会根据输入信号实测功率自动加噪声但如果你在生成Tx时已经手动归一化了功率再用awgn就会导致实际 SNR 和理论值偏差而且很难排查。我的习惯是全程手写噪声生成不混用两种加噪方式。5. 三个验证技巧证明你的 ZF-SIC 没写错5.1 退化测试把 α 设成 1 再跑一次把alpha改为 1用户 2 的发射功率变为 0系统退化为单用户 2 天线接收。此时用户 1 的 SER 应该和理论上的 2 阶接收分集曲线一致。如果退化场景下曲线仍然偏离理论值说明问题在信道建模或噪声功率定义而不是 SIC 逻辑。退化测试是成本最低的排错手段因为单用户场景有解析结果可对照。5.2 理想 SIC 对比量化错误传播把zf_sic_detect中第一级硬判决替换成真实发送符号再做消除就得到理想 SIC。两条曲线的差值就是硬判决错误传播的代价。% 理想 SIC用真实 s1 代替 sHat1 Y_ideal Y - H(:, k1) * (sqrt(P(k1)) * s1_true);在 NOMA 中这个差值通常在高 SNR 下缩小因为第一级符号错误率已经很低。如果差值始终很大说明第一级判决本身有问题多半是 3.3 节消除步骤的功率系数乘错了。5.3 记录条件数和排序顺序在 Monte Carlo 循环里加几行日志输出把每帧的cond(H)、排序结果和两用户的判决错误数写下来比事后画曲线高效得多。% 每帧记录一次条件数和排序 fprintf(Frame %d: cond%.3f, order[%d %d]\n, ... frame, cond(H), order(1), order(2));如果所有错误帧都对应cond 20那这个系统的瓶颈在信道相关性而不是 SIC 实现。这个习惯在把代码从理想信道换成 3GPP 相关信道模型时尤其有用它能直接告诉你 ZF 噪声增强在哪一帧开始吞掉 SIC 的增益。本文还有配套的精品资源点击获取