ARTICLE DETAIL

资讯详情

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

NOMA仿真链路实战:QPSK调制、ZF检测与SIC解码的MATLAB实现

NOMA仿真链路实战:QPSK调制、ZF检测与SIC解码的MATLAB实现 简介这份资源面向无线通信方向的学生与研究人员聚焦5G及未来网络中的非正交多址接入NOMA技术提供一套可直接运行的MATLAB仿真代码用于理解功率域多址、QPSK调制与零强迫ZF解码的完整链路。压缩包共3个文件均为.m脚本体积约2KB分别承担主程序仿真、QPSK调制与软解调功能结构紧凑、便于快速上手。已有244人学习下载说明其在同类教学与科研场景中具备一定参考价值。读者可借助该代码复现NOMA系统在同一时频资源上叠加多用户信号的过程观察SIC逐级解码与ZF接收矩阵对多用户干扰的抑制效果并进一步调整功率分配、信噪比与用户数量等参数评估系统性能变化。对于需要完成课程设计、毕业课题或算法验证的读者这套脚本可作为理解NOMA与QPSK结合原理、对比线性接收策略的实用起点。1. NOMA 与 ZF 检测的 MATLAB 仿真从 QPSK 调制到 SIC 解码的完整链路如果你正在做 5G 多址接入相关的课程设计或论文复现大概率绕不开 NOMA 这个关键词。非正交多址接入的核心思路是在同一时频资源块上用功率域叠加多个用户的信号接收端再通过串行干扰消除逐级解码。这套源码包围绕 NOMA 技术展开包含 QPSK 调制、迫零检测和软解调三个核心模块适合通信工程专业的学生或刚接触 NOMA 的工程师用来搭建可运行的仿真链路。它解决的不是“NOMA 是什么”这种概念问题而是“怎么在 MATLAB 里把一条完整的 NOMA 链路跑通并看到误码率曲线”这个落地问题。你拿到手就能改参数、换信道、对比不同检测算法不用从零搭框架。2. 拆开源码包三个文件如何串起一条 NOMA 链路2.1 文件分工与调用关系拿到一个源码包我习惯先看文件命名和调用关系而不是急着跑主程序。这个包的结构很清晰三个 .m 文件各司其职文件名角色核心功能modulation.m发送端调制比特流到 QPSK 符号的映射test2zf.m主仿真程序信道生成、功率分配、ZF 检测、SIC 解码、误码率统计qpsk_demodulation_soft.m接收端软解调从含噪符号计算对数似然比输出软信息调用链是test2zf.m作为入口内部先调modulation.m完成每个用户的 QPSK 调制经过功率域叠加和信道传输后再用 ZF 矩阵做线性检测最后调qpsk_demodulation_soft.m输出软比特送入 SIC 循环逐用户解码。这个结构的好处是模块解耦——你想换调制方式只改modulation.m想换检测算法只改test2zf.m里的检测矩阵部分。常见做法是把这三个文件放在同一个工作目录下MATLAB 的当前文件夹指向该目录直接运行test2zf.m即可。不需要额外的工具箱依赖基础 MATLAB 环境就能跑。2.2 QPSK 调制模块的实现细节modulation.m做的事情本质上是格雷码映射。QPSK 每个符号携带 2 比特信息将比特对映射到四个相位点上。来看这个模块的典型实现逻辑function symbols modulation(bits) % modulation.m - QPSK 调制 % 输入: bits - 比特流 (列向量, 长度为偶数) % 输出: symbols - 复数符号向量 bits bits(:); % 强制列向量 N length(bits) / 2; % 符号数 b reshape(bits, 2, N).; % 每行一个比特对 % 格雷码映射: 00-11j, 01--11j, 11--1-1j, 10-1-1j real_part 1 - 2*b(:,1); % 第1比特决定实部 imag_part 1 - 2*b(:,2); % 第2比特决定虚部 symbols (real_part 1j*imag_part) / sqrt(2); % 归一化功率 end这段代码的关键在于归一化那一步。除以sqrt(2)是为了让平均符号功率为 1这样后面加噪声时信噪比的定义才准确。如果你忘了归一化SNR 的计算会偏 3dB误码率曲线整体左移看起来性能“变好”了实际上是标尺错了。格雷码映射的选择也不是随便定的——相邻相位点之间只差一个比特这样在信噪比不高时一个符号判错大概率只错一个比特而不是两个比特同时错。参数方面输入比特流长度必须是偶数否则reshape会报错。如果你从更上层传下来的比特流长度不确定建议在调用前加一句bits bits(1:2*floor(end/2));做截断保护。2.3 ZF 检测与 SIC 解码的主循环test2zf.m是整个仿真的骨架。它要完成的事情包括生成多用户比特流、QPSK 调制、功率分配、叠加、过信道、ZF 检测、SIC 逐级解码、统计误码率。核心流程如下% test2zf.m - NOMA ZF 检测主程序核心片段 clear; clc; close all; %% 参数设置 N_user 2; % 用户数 N_bits 100000; % 每用户比特数 SNR_dB 0:2:20; % 信噪比扫描范围 N_iter 100; % 每个 SNR 点的蒙特卡洛次数 %% 功率分配系数 (满足 p1 p2 1, p1 p2) p [0.8, 0.2]; % 近端用户功率大远端用户功率小 ber zeros(N_user, length(SNR_dB)); for s_idx 1:length(SNR_dB) snr_lin 10^(SNR_dB(s_idx)/10); err_count zeros(1, N_user); for iter 1:N_iter % 1. 生成比特并调制 bits cell(1, N_user); syms cell(1, N_user); for u 1:N_user bits{u} randi([0 1], N_bits, 1); syms{u} modulation(bits{u}); end % 2. 功率域叠加 x zeros(size(syms{1})); for u 1:N_user x x sqrt(p(u)) * syms{u}; end % 3. 过瑞利衰落信道 AWGN h (randn(N_user,1) 1j*randn(N_user,1)) / sqrt(2); noise (randn(size(x)) 1j*randn(size(x))) / sqrt(2*snr_lin); y zeros(size(x)); for u 1:N_user y y h(u) * sqrt(p(u)) * syms{u}; end y y noise; % 4. ZF 检测矩阵 H h .* sqrt(p).; % 等效信道矩阵 W pinv(H); % ZF 均衡矩阵 % 5. SIC 逐级解码 (按功率从大到小) [~, order] sort(p, descend); y_res y; for k 1:N_user u order(k); y_eq W(u,:) * y_res; % ZF 均衡 llr qpsk_demodulation_soft(y_eq, 1); % 软解调 bits_hat llr 0; % 硬判决 err_count(u) err_count(u) sum(bits_hat ~ bits{u}); % 重构已解码用户的信号并从接收信号中减去 if k N_user x_hat modulation(double(bits_hat)); y_res y_res - h(u) * sqrt(p(u)) * x_hat; end end end ber(:, s_idx) err_count / (N_bits * N_iter); end %% 绘图 figure; semilogy(SNR_dB, ber(1,:), b-o, SNR_dB, ber(2,:), r-s); xlabel(SNR (dB)); ylabel(BER); legend(近端用户 (高功率), 远端用户 (低功率)); grid on;这段代码有几个值得注意的设计点。第一功率分配系数p的选择直接影响两个用户的性能——高功率用户先解码信噪比条件好误码率低低功率用户后解码要承受残余干扰误码率相对高。这是 NOMA 的固有特性不是 bug。第二ZF 均衡矩阵用pinv而不是直接求逆因为当用户数接近天线数时信道矩阵可能接近奇异伪逆更稳健。第三SIC 循环里每解码一个用户就重构信号并减去这个“后悔药”机制是 NOMA 区别于正交多址的关键——先解的用户帮后面的用户清路。参数调整建议N_bits至少设到 10000 以上否则高 SNR 点误码率统计不收敛N_iter在 SNR 较低时可以少一些SNR 高时要多跑几次才能抓到足够的错误比特。如果你把N_user改成 3 或 4记得同步调整p的长度和h的维度。3. 软解调模块LLR 计算与 SIC 的配合3.1 软信息为什么比硬判决更值得用qpsk_demodulation_soft.m输出的是对数似然比而不是直接的 0/1 判决。这个区别在 NOMA 的 SIC 场景下特别重要。硬判决只告诉你“这个比特是 0 还是 1”软解调告诉你“这个比特是 0 的概率是 0.9是 1 的概率是 0.1”。在 SIC 逐级干扰消除时软信息能让重构信号更准确减少误差传播。来看软解调的实现function llr qpsk_demodulation_soft(y, noise_var) % qpsk_demodulation_soft.m - QPSK 软解调 % 输入: y - 接收符号向量 % noise_var - 噪声方差 % 输出: llr - 对数似然比向量 (长度 2 * 符号数) y y(:); N length(y); llr zeros(2*N, 1); % QPSK 星座点 (归一化后) % s00 (11j)/sqrt(2), s01 (-11j)/sqrt(2) % s11 (-1-1j)/sqrt(2), s10 (1-1j)/sqrt(2) for k 1:N r y(k); % 第1比特的 LLR: 比较实部 llr(2*k-1) 2 * sqrt(2) * real(r) / noise_var; % 第2比特的 LLR: 比较虚部 llr(2*k) 2 * sqrt(2) * imag(r) / noise_var; end end这里的 LLR 计算利用了 QPSK 星座的对称性——实部决定第一个比特虚部决定第二个比特两者独立。系数2*sqrt(2)/noise_var来自最大似然准则的推导noise_var越小信噪比越高LLR 的绝对值越大表示判决越“自信”。3.2 软解调与 SIC 的接口设计在test2zf.m的 SIC 循环里软解调的输出经过硬判决后用于重构信号。这里有一个容易翻车的地方重构时用的是硬判决后的比特而不是软信息本身。如果你直接把 LLR 值当信号幅度去重构量纲对不上减法会出错。正确的做法是bits_hat llr 0先做硬判决再用modulation重新映射成符号。另一个细节是noise_var的传递。在 SIC 的后续阶段由于已经减去了前面用户的信号残余干扰的方差不再是原始噪声方差而是噪声加上未完全消除的干扰。严格来说每一级 SIC 后noise_var应该更新。但在用户数较少2-3 个且功率分配合理的情况下直接用原始噪声方差也能跑出合理的误码率曲线。如果你发现远端用户的误码率曲线出现“地板效应”高 SNR 时不再下降大概率就是残余干扰没建模准确。4. 避坑与排查跑 NOMA 仿真时最容易翻车的五个地方4.1 误码率曲线不下降或出现平台现象SNR 增加到 20dB 以上远端用户的 BER 仍然在 10^-2 附近徘徊曲线出现明显的“地板”。原因最常见的原因是 SIC 解码顺序搞反了。NOMA 要求先解码高功率用户再解码低功率用户。如果你按用户编号顺序解码而用户 1 恰好是低功率用户那它的信号会被高功率用户淹没解码错误率极高后续重构和减法也会连带出错。解决在 SIC 循环前加一句[~, order] sort(p, descend);确保按功率从大到小解码。另外检查p向量是否满足sum(p) 1功率不归一化会导致 SNR 定义偏移。4.2 QPSK 星座图旋转或缩放异常现象接收端星座图不是标准的四个点而是被旋转了一个角度或者点与点之间的距离不对。原因信道相位没有补偿。在test2zf.m里如果 ZF 均衡矩阵没有正确使用信道估计值h或者h的维度与用户数不匹配均衡后的符号会残留相位旋转。解决检查H h .* sqrt(p).这一行确保h是列向量、sqrt(p)是行向量点乘后得到N_user × 1的等效信道。然后用W pinv(H)得到1 × N_user的均衡向量。如果h的维度搞错MATLAB 可能不报错但结果完全错误。4.3 软解调输出全为正或全为负现象qpsk_demodulation_soft返回的 LLR 向量所有值符号相同硬判决后全是 0 或全是 1。原因noise_var传参错误。如果noise_var传了一个负数或者零LLR 计算会出问题。另一个可能是接收符号y没有经过均衡就直接送进了软解调导致相位完全随机。解决在调用软解调前打印noise_var确认它是正数且量级与 SNR 匹配。SNR10dB 时noise_var大约是 0.1SNR0dB 时大约是 1。如果数量级差太多检查 SNR 到噪声方差的转换公式。4.4 蒙特卡洛仿真跑得太慢现象N_bits 100000、N_iter 100时跑完整个 SNR 扫描需要几十分钟甚至更久。原因MATLAB 的 for 循环在大量迭代时效率低尤其是内层还有逐符号的软解调循环。解决把qpsk_demodulation_soft.m里的逐符号循环改成向量化操作% 向量化版本 - 替换逐符号循环 llr zeros(2*N, 1); llr(1:2:end) 2 * sqrt(2) * real(y) / noise_var; llr(2:2:end) 2 * sqrt(2) * imag(y) / noise_var;这一改动能把软解调的速度提升一个数量级。另外N_iter在 SNR 较高时可以适当减少因为高 SNR 下误码事件本身就少不需要那么多次蒙特卡洛。4.5 用户数增加后 ZF 检测性能急剧恶化现象从 2 用户扩展到 4 用户后即使 SNR 很高误码率也降不下来。原因ZF 检测的核心是矩阵求逆当用户数增加时等效信道矩阵H的条件数变差求逆会放大噪声。这是 ZF 的固有缺陷——它只消除干扰不考虑噪声增强。解决如果必须用线性检测可以换成 MMSE 准则在消除干扰和抑制噪声之间做折中。把W pinv(H)改成W inv(H*H eye(N_user)/snr_lin) * H这就是 MMSE 均衡矩阵。代价是干扰不能完全消除但噪声放大问题会缓解很多。另一个方向是增加接收天线数让H变成矩阵而不是向量ZF 的性能会明显改善。5. 从能跑到好用参数扫描与性能对比的实操技巧把链路跑通只是第一步真正有价值的是用这套代码做参数对比。我一般会固定其他变量只扫一个参数看误码率曲线的变化趋势。比如功率分配系数p对两个用户性能的影响——当p [0.9, 0.1]时近端用户几乎不受干扰但远端用户的信噪比极低当p [0.6, 0.4]时两个用户的性能更均衡但近端用户的优势被削弱。这个折中关系是 NOMA 论文里经常讨论的“公平性 vs 总吞吐量”问题用仿真曲线说话比纯理论推导直观得多。具体操作上我会把test2zf.m里的p改成可扫描的向量外层再加一层循环p_list [0.9 0.1; 0.8 0.2; 0.7 0.3; 0.6 0.4]; snr_fixed 10; % 固定 SNR 看功率分配的影响 for p_idx 1:size(p_list, 1) p p_list(p_idx, :); % ... 运行仿真记录两个用户的 BER ... fprintf(p [%.1f, %.1f], BER [%.4e, %.4e]\n, ... p(1), p(2), ber(1), ber(2)); end跑完这个扫描你会得到一张表清楚看到功率分配怎么影响两个用户的误码率。这张表可以直接放进论文或报告里比一张模糊的曲线图更有说服力。另一个值得做的对比是 ZF 和 MMSE 的检测性能。在test2zf.m里加一个开关变量detector_type分别用pinv(H)和 MMSE 矩阵跑一遍把两条曲线画在同一张图上。你会发现在低 SNR 区域 MMSE 明显优于 ZF高 SNR 区域两者趋近——这个结论和教科书一致但自己跑出来的曲线会让你对“为什么 MMSE 更鲁棒”有肌肉记忆。还有一个容易被忽略的参数是信道模型。这套代码默认用的是瑞利衰落信道h (randn 1j*randn)/sqrt(2)。如果你想模拟视距场景可以把h改成固定值加小扰动比如h [1; 0.5] 0.01*(randn(2,1)1j*randn(2,1))。信道条件一变ZF 检测的性能排序可能完全不一样——这也是我每次改信道模型后都会重新跑一遍基线的原因。从那以后我每次拿到新的通信仿真代码都强制先跑一遍默认参数确认基线再改一个参数跑一遍对比确认改动方向符合预期后才开始大规模扫描。这个习惯帮我省了很多“改了三个参数不知道哪个出了问题”的后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表