资讯详情

多用户MIMO系统级仿真:信道建模、预编码与容量计算实战

📅 2026/10/31 21:59:36 | 华诺云谱 👁 阅读
多用户MIMO系统级仿真:信道建模、预编码与容量计算实战
简介MU-MIMO_sim.rar 是一套面向无线通信专业学生与科研人员的多用户MIMO系统仿真MATLAB程序集用于研究MIMO信道建模、预编码与多用户调度等核心问题。资源围绕瑞利衰落、多径衰落等信道模型展开涵盖MMSE、MRT、零强迫等预编码算法以及用户选择策略、吞吐量与误码率性能分析等模块适合具备一定通信原理基础、希望从理论走向实现的读者。压缩包共229个文件约1.65MB以94个mat数据文件、51个m脚本、38个asv备份、14个c源码及12个mexw32与2个mexw64编译文件为主另含少量log、h、cpp与fig图形文件结构完整、便于逐模块研读。目前已有390人学习下载。通过运行与调试这些代码读者可直观理解多用户MIMO的信道差异、用户间干扰抑制及系统容量评估方法在理论与工程实践之间建立有效连接。1. 从一份 MU-MIMO_sim.rar 说起多用户 MIMO 到底在仿真什么如果你手上正好有一个叫MU-MIMO_sim.rar的压缩包或者你正在用 MATLAB 做多用户 MIMO 信道建模那你大概率卡在同一个地方单用户 MIMO 的代码满地都是多用户一上来就懵——用户之间怎么区分预编码矩阵怎么塞进链路信道容量曲线为什么和论文对不上这个标题背后其实是一整套「多用户 MIMO 系统级仿真」的活儿从信道建模、预编码、功率分配到容量计算和误码率验证。它适合通信方向的研究生、做基带算法的工程师以及需要快速复现论文曲线的从业者。下面我按自己搭这套仿真的顺序把能跑通的路径和踩过的坑一次讲清楚。2. 多用户 MIMO 信道建模从 i.i.d. 到相关信道怎么选2.1 为什么不能直接套单用户瑞利信道单用户 MIMO 里信道矩阵H就是一个Nr×Nt的复高斯矩阵每个元素独立同分布生成一行代码就完事。但多用户场景下H变成了K个用户的堆叠第k个用户的信道是H_k维度Nr_k×Nt。这时候如果还按 i.i.d. 生成用户之间完全独立仿真出来的和速率会偏乐观因为现实中基站侧天线相关性、用户空间相关性都会让性能掉一截。我一般会分三层来建模大尺度衰落路径损耗阴影、小尺度衰落瑞利或莱斯、空间相关性Kronecker 模型。大尺度决定用户信噪比的差异小尺度决定快衰落相关性决定 MIMO 增益能不能吃满。少了任何一层容量曲线在低 SNR 和高 SNR 段的斜率都会不对。2.2 用 Kronecker 模型生成相关信道矩阵Kronecker 模型的核心是把发射端相关矩阵R_t和接收端相关矩阵R_r做克罗内克积再乘上 i.i.d. 矩阵。下面这段是我常用的生成函数支持每个用户独立配置天线数和相关性系数function H generate_correlated_channel(Nt, Nr, K, rho_t, rho_r) % Nt: 基站发射天线数 % Nr: 每个用户的接收天线数标量假设所有用户相同 % K: 用户数 % rho_t, rho_r: 发射/接收端相关系数 (0~1) % 返回 H: Nr*K x Nt 的堆叠信道矩阵 H zeros(Nr*K, Nt); for k 1:K % 生成发射端相关矩阵指数相关模型 Rt zeros(Nt, Nt); for i 1:Nt for j 1:Nt Rt(i,j) rho_t^abs(i-j); end end % 生成接收端相关矩阵 Rr zeros(Nr, Nr); for i 1:Nr for j 1:Nr Rr(i,j) rho_r^abs(i-j); end end % Kronecker 模型H_k Rr^(1/2) * G * Rt^(1/2) G (randn(Nr, Nt) 1j*randn(Nr, Nt)) / sqrt(2); Hk sqrtm(Rr) * G * sqrtm(Rt); H((k-1)*Nr1:k*Nr, :) Hk; end end逻辑说明sqrtm是矩阵平方根保证Rr^(1/2)和Rt^(1/2)的乘积再乘自身转置等于原相关矩阵。rho_t和rho_r取 0 时退化成 i.i.d. 瑞利信道取 0.5 以上时相关性明显容量会下降 15% 到 30%。参数上Nt常见取 4、8、16Nr取 1 或 2K取 4 到 10。注意sqrtm对大规模矩阵较慢如果Nt超过 64建议改用 Cholesky 分解加滤波速度能快一个数量级。2.3 路径损耗和阴影衰落的叠加方式大尺度部分我习惯用简化模型PL(d) 128.1 37.6*log10(d)d单位公里这是 3GPP 常见的宏站路损公式。阴影衰落用对数正态标准差 8 dB。每个用户独立生成然后折算成线性信噪比缩放因子乘到小尺度信道上。d 0.1 0.4*rand(K,1); % 用户距离 100~500 米 PL 128.1 37.6*log10(d); % 路径损耗 dB shadow 8*randn(K,1); % 阴影衰落 dB gain_dB -PL - shadow; % 总大尺度增益 gain_lin 10.^(gain_dB/10); % 线性增益 % 对每个用户的信道块乘以 sqrt(gain_lin(k)) for k 1:K H((k-1)*Nr1:k*Nr, :) sqrt(gain_lin(k)) * H((k-1)*Nr1:k*Nr, :); end这里gain_lin直接乘在信道上等价于接收功率的缩放。注意别把 dB 和线性值搞混我见过有人把gain_dB直接乘上去结果容量曲线直接飞到 10^12 量级血泪经验。3. 预编码与用户调度把多用户干扰压下去3.1 线性预编码选 ZF 还是 MMSE多用户 MIMO 下行最常用的两种线性预编码迫零ZF和最小均方误差MMSE。ZF 完全消除用户间干扰但会放大噪声在低 SNR 时性能差MMSE 在干扰和噪声之间折中低 SNR 更稳。我一般两个都实现对比着看。预编码矩阵W的维度是Nt×K每个用户单流。ZF 的公式是W H^H (H H^H)^{-1}然后按列归一化。MMSE 是W H^H (H H^H σ²/ P * I)^{-1}其中σ²是噪声功率P是总发射功率。function W precoding_zf(H, P) % H: Nr*K x Nt 堆叠信道 % P: 总发射功率 [~, Nt] size(H); K size(H,1) / (size(H,1)/size(H,1)); % 这里假设每个用户单天线 % 更稳妥的写法外部传入 K W H * inv(H*H); % 列归一化保证每列功率为 P/K for i 1:size(W,2) W(:,i) W(:,i) / norm(W(:,i)) * sqrt(P/size(W,2)); end end上面代码里K的计算我写了个占位实际用时建议把K作为参数传进来避免用户天线数不为 1 时出错。归一化那一步很关键不归一化的话预编码后总功率会随信道条件数剧烈波动容量曲线会出现不合理的尖峰。MMSE 版本只需把inv(H*H)改成inv(H*H noise_var/P * eye(size(H,1)))noise_var是噪声方差。3.2 用户调度选谁一起服务多用户 MIMO 不是用户越多越好。基站天线Nt4时同时服务超过 4 个用户ZF 的零空间不够用干扰压不干净。我一般用贪心调度先算所有用户的信道范数选最大的那个然后每次选一个和已选用户信道相关性最低的直到选满Nt个或达到用户数上限。function selected greedy_scheduling(H, K, Nr, Nt) % H: Nr*K x Nt % 返回选中的用户索引 selected []; candidates 1:K; % 第一个选信道增益最大的 gains zeros(K,1); for k 1:K Hk H((k-1)*Nr1:k*Nr, :); gains(k) norm(Hk, fro)^2; end [~, first] max(gains); selected [selected, first]; candidates(candidates first) []; while length(selected) min(Nt, K) ~isempty(candidates) best_corr inf; best_idx -1; for c candidates Hc H((c-1)*Nr1:c*Nr, :); % 计算与已选用户的最大相关性 max_corr 0; for s selected Hs H((s-1)*Nr1:s*Nr, :); corr abs(trace(Hc * Hs)) / (norm(Hc,fro)*norm(Hs,fro)); max_corr max(max_corr, corr); end if max_corr best_corr best_corr max_corr; best_idx c; end end selected [selected, best_idx]; candidates(candidates best_idx) []; end end调度带来的增益在用户数多的时候非常明显K10、Nt4时贪心调度比随机选 4 个用户和速率能高 40% 以上。注意相关性用trace(Hc*Hs)是一种简化更严格的做法是用子空间夹角但计算量大实际仿真里上面这种够用。3.3 功率分配注水还是等功率如果做的是容量仿真注水算法是理论最优。但多用户下注水要在预编码之后做而且每个用户等效信道增益不同。我一般先等功率跑通再换注水对比。等功率就是每个用户分P/K简单不容易错。注水需要先算等效信道H_eff H*W然后对H_eff的奇异值做注水。function p waterfilling(lambda, P) % lambda: 等效信道增益奇异值平方 % P: 总功率 lambda lambda(:); N length(lambda); % 二分法找水位 mu_low 0; mu_high max(lambda) P; for iter 1:100 mu (mu_low mu_high) / 2; p max(mu - 1./lambda, 0); if sum(p) P mu_high mu; else mu_low mu; end end p max(mu - 1./lambda, 0); endlambda是等效信道矩阵奇异值的平方1./lambda是等效噪声放大因子。注水在低 SNR 时会把功率集中到强子信道高 SNR 时接近等功率。实际仿真里如果lambda有零元素要加个极小值保护否则除零报错。4. 容量计算与误码率验证曲线对不上的排查思路4.1 和速率容量的两种算法多用户 MIMO 下行和速率容量在完美 CSI 和线性预编码下常用公式是R Σ log2(1 SINR_k)其中SINR_k是第 k 个用户预编码后的信干噪比。另一种是脏纸编码DPC容量上界作为理论天花板对比。function R sum_rate(H, W, P, noise_var, K, Nr) R 0; for k 1:K Hk H((k-1)*Nr1:k*Nr, :); wk W(:, k); % 有用信号功率 sig abs(Hk * wk)^2; % 干扰功率 interf 0; for j 1:K if j ~ k interf interf abs(Hk * W(:,j))^2; end end sinr sig / (interf noise_var); R R log2(1 sinr); end end注意noise_var要和预编码归一化时的功率假设一致。我见过有人预编码归一化用了P/K但容量计算时noise_var没对应缩放结果曲线整体平移。排查方法先跑K1单用户看容量是否等于log2(1SNR*|h|^2)对上了再上多用户。4.2 误码率仿真ML 检测和线性检测的差距容量是理论值误码率才是能验证系统能不能用的指标。多用户下行用户端做检测。线性检测ZF/MMSE复杂度低但性能差ML 检测最优但复杂度指数级。我一般先跑 MMSE 检测再跑 ML 做对比。function ber simulate_ber(H, W, P, noise_var, K, Nr, mod_order, nbits) % 简化每个用户单流QPSK 或 16QAM % 生成发送符号 M 2^mod_order; tx randi([0 M-1], K, nbits); tx_sym qammod(tx, M, UnitAveragePower, true); % 预编码 x W * tx_sym; % 过信道 y H * x sqrt(noise_var/2)*(randn(size(H,1), nbits) 1j*randn(size(H,1), nbits)); % MMSE 检测 G (W*H*H*W noise_var*eye(K)) \ (W*H); rx_sym G * y; rx qamdemod(rx_sym, M, UnitAveragePower, true); ber sum(rx(:) ~ tx(:)) / numel(tx); endmod_order取 2 是 QPSK取 4 是 16QAM。UnitAveragePower保证符号平均功率为 1和预编码功率假设一致。MMSE 检测矩阵G里的noise_var*eye(K)是正则项noise_var估计不准时性能会掉实际系统里需要信道估计辅助。5. 避坑与排查多用户 MIMO 仿真里最容易翻车的 5 个点5.1 容量曲线随 SNR 增长但斜率不对现象和速率曲线在低 SNR 正常高 SNR 时斜率明显小于min(Nt, Nr*K)。原因预编码矩阵没有按列归一化或者归一化时用了总功率P而不是P/K导致高 SNR 时功率分配错误。解决检查W每列的norm是否等于sqrt(P/K)并确认容量计算里的noise_var和归一化功率匹配。5.2 ZF 预编码在用户数等于天线数时噪声爆炸现象KNt时ZF 预编码后某些用户 SINR 极低误码率接近 0.5。原因H*H接近奇异求逆放大噪声。解决改用 MMSE 预编码或加正则化项H*H ε*Iε取0.01*P量级。也可以减少调度用户数留一个天线余量。5.3 信道相关矩阵sqrtm报错或结果非正定现象sqrtm返回复数或报「矩阵必须为正定」。原因相关系数rho设置过大或矩阵维度不匹配导致Rt或Rr非正定。解决确保rho在[0, 1)之间且Rt和Rr用指数相关模型时对角线为 1。如果仍报错改用chol分解加chol的转置效果等价且更稳。5.4 用户调度后容量反而下降现象加了贪心调度和速率比随机选用户还低。原因调度算法里相关性计算用了trace但没取模或者信道增益排序时把大尺度衰落和 small-scale 混在一起。解决调度前先对大尺度增益做归一化或者用等效信道H_eff H*W的范数排序。另外检查selected索引是否和H的块对应(k-1)*Nr1:k*Nr这个切片很容易写错。5.5 误码率曲线在高 SNR 出现错误地板现象SNR 超过 20 dB 后 BER 不再下降卡在 10^-3 左右。原因检测端用了 ZF 但没做干扰消除或者调制阶数太高而预编码残留干扰没压干净。解决换 MMSE 检测或降低mod_order到 2 先验证链路。如果必须用高阶调制考虑非线性预编码如 THP但复杂度会上去。6. 进阶技巧用蒙特卡洛加速和并行计算把仿真时间压下来多用户 MIMO 仿真最耗时的不是单次容量计算而是蒙特卡洛循环——每个 SNR 点要跑几千次信道实现。我一开始用for循环硬跑Nt8, K8, 1000 次要等十几分钟。后来改成parfor并行时间直接砍到原来的 1/4 到 1/6。snr_list 0:2:20; n_iter 2000; R_avg zeros(length(snr_list), 1); parfor i 1:length(snr_list) snr snr_list(i); noise_var 1 / (10^(snr/10)); R_tmp 0; for iter 1:n_iter H generate_correlated_channel(8, 1, 8, 0.3, 0.3); % 加路径损耗 d 0.1 0.4*rand(8,1); PL 128.1 37.6*log10(d); gain_lin 10.^((-PL - 8*randn(8,1))/10); for k 1:8 H(k,:) sqrt(gain_lin(k)) * H(k,:); end selected greedy_scheduling(H, 8, 1, 8); H_sel H(selected, :); W precoding_zf(H_sel, 1); R_tmp R_tmp sum_rate(H_sel, W, 1, noise_var, length(selected), 1); end R_avg(i) R_tmp / n_iter; endparfor要求循环内变量不互相依赖这里每个 SNR 点独立正好适合。注意parfor里不能用rand的全局流否则每次结果不一样建议在循环内用rng(iter)固定种子或者用RandStream独立流。另外n_iter取 2000 时曲线已经比较平滑取 5000 以上会更稳但时间线性增长。还有一个技巧把信道生成和容量计算拆成两个函数用profile看哪个占大头。我实测下来sqrtm和inv占 60% 以上时间后来把sqrtm换成chol的转置inv换成左除\整体又快了 30%。这些细节在论文里不会写但跑大规模仿真时能省不少电费。最后说个习惯每次改完参数先跑K1单用户验证链路再跑K2看干扰最后上KNt。跳过中间步骤直接上满用户出了问题根本不知道是哪一层。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑