☰
混合波束成形在多用户MIMO-OFDM中的MATLAB仿真与性能优化
2026/10/22 3:02:38 网站建设 项目流程

简介:本资源面向通信工程、无线系统仿真方向的学习者与研究人员,聚焦大规模MIMO场景下混合波束成形在多用户与单用户MIMO-OFDM系统中的实现方法,帮助读者理解如何将预编码拆分为数字基带与模拟射频两部分,并借助全通道探测获取发射端信道状态信息。压缩包共11个文件,以8个m脚本文件和3个png示意图为主,整体约137KB,脚本涵盖信道生成、导频设计、信道估计、JSDM发射权值计算等核心环节,图片则直观展示系统架构与探测流程。资源采用基于散射的空间信道模型,兼顾收发空间位置与天线方向图,并附带简化静态平坦MIMO信道用于链路验证,接收端通过EVM与BER等指标评估通信质量。目前已有681人学习下载,适合希望快速搭建混合波束成形仿真链路、对比多用户与单用户方案差异的读者参考。

1. 混合波束成形在多用户MIMO-OFDM里到底解决什么问题

如果你最近在调毫米波大规模阵列的链路预算,大概率会遇到一个很拧巴的局面:全数字波束成形性能天花板高,但每根天线配一条射频链,64×64 的阵面光射频前端就能把功耗和成本顶穿;纯模拟波束成形倒是便宜,可一个射频链只能压一个波束,多用户场景下用户之间抢波束,调度器直接摆烂。混合波束成形就是在这两个极端之间找平衡——用少量射频链驱动大量天线,数字域做多用户分离,模拟域做波束增益。

把这件事放到多用户 MIMO-OFDM 里,复杂度又上一层:OFDM 把宽带拆成一堆窄带子载波,每个子载波上的信道响应都不一样,模拟移相器却是宽带器件,所有子载波共用一套相位。这个「频率选择性信道 vs 频率平坦模拟预编码」的矛盾,是混合波束成形仿真里最容易翻车的地方。这篇笔记就围绕 MATLAB 仿真,把系统模型、混合预编码设计、多用户调度和性能验证串成一条能复现的路径,适合正在做阵列信号处理、通感一体化或 5G/6G 物理层链路仿真的工程师。

2. 系统模型与信道:先把仿真骨架搭对

2.1 天线阵列与射频链的映射关系

混合波束成形的核心结构是「数字预编码器 + 射频链 + 模拟移相网络 + 天线阵」。假设基站有 Nt 根发射天线、Nrf 条射频链、K 个单天线用户,且满足 K ≤ Nrf ≪ Nt。数字预编码器维度是 Nrf×K,模拟预编码器维度是 Nt×Nrf,两者级联后等效预编码矩阵 F = F_rf × F_bb。

这里有个容易忽略的约束:模拟移相器只能调相位不能调幅度,所以 F_rf 每个元素必须满足恒模约束,即 |F_rf(i,j)| = 1/√Nt。这个约束直接决定了后续优化问题的非凸性,也是很多论文里用交替最小化或者码本搜索的原因。在 MATLAB 里建模时,我一般先把阵列响应向量写成一个函数,均匀线阵的响应是 exp(1j×π×(0:Nt-1)'×sin(θ)),注意这里的相位参考点选在阵列中心还是边缘会影响后续波束指向的标定,建议统一选阵列第一个阵元为参考。

% 均匀线阵响应向量,Nt 阵元,间距半波长 function a = ula_response(Nt, theta) idx = (0:Nt-1).'; a = exp(1j * pi * idx * sind(theta)); % sind 用角度制,避免弧度换算错误 end

这段代码里sind而不是sin是个小习惯,因为波束扫描角度通常用度数描述,直接写sind能省掉deg2rad的嵌套。idx用列向量保证输出是 Nt×1,后续和信道矩阵做 Kronecker 积时维度不会错乱。

2.2 多用户 MIMO-OFDM 信道生成

宽带信道我习惯用几何随机信道模型(GBSM),每个用户对应若干条多径,每条径有独立的时延、角度和增益。OFDM 调制后,第 k 个用户在第 n 个子载波上的频域信道写成:

H_k[n] = Σ_l α_l × a(θ_l) × exp(-j2πnΔf τ_l)

其中 α_l 是复增益,θ_l 是出发角,τ_l 是时延,Δf 是子载波间隔。MATLAB 里可以用循环生成,但更高效的做法是把时延和角度做成矩阵运算。

% 生成多用户宽带信道:K 用户,Nc 子载波,L 条多径 Nt = 64; Nc = 64; K = 4; L = 6; Delta_f = 60e3; % 子载波间隔 60 kHz H = zeros(Nt, K, Nc); for k = 1:K theta = -60 + 120*rand(1, L); % 出发角在 ±60 度内随机 tau = rand(1, L) / Delta_f; % 时延不超过一个符号周期 alpha = (randn(1,L)+1j*randn(1,L))/sqrt(2); for n = 1:Nc phase = exp(-1j*2*pi*(n-1)*Delta_f*tau); H(:,k,n) = ula_response(Nt, theta) * (alpha .* phase).'; end end

参数说明:Delta_f取 60 kHz 是毫米波 OFDM 的常见配置,子载波数 64 对应约 3.84 MHz 带宽。tau限制在一个符号周期内是为了避免符号间干扰,实际仿真里如果要做循环前缀验证,可以把时延放大到 CP 长度附近观察 ISI。alpha做归一化保证每径平均功率为 1,方便后续 SNR 定义。

2.3 混合预编码的等效基带模型

接收端第 k 个用户在第 n 个子载波上的信号是:

y_k[n] = h_k[n]^H × F_rf × f_bb,k[n] × s_k[n] + 干扰 + 噪声

这里 f_bb,k[n] 是数字预编码矩阵的第 k 列。注意模拟预编码 F_rf 对所有子载波共用,数字预编码可以逐子载波设计。这个结构决定了混合波束成形的设计要分两步走:先根据信道统计信息设计 F_rf,再在给定 F_rf 下优化每个子载波的 F_bb。

提示:仿真时先把 F_rf 固定成基于阵列响应的码本,再跑数字域,能快速验证链路是否通,别一上来就联合优化,否则调试时根本分不清是模拟相位错了还是数字权重错了。

3. 混合预编码设计:从码本搜索到交替优化

3.1 模拟预编码的码本构造与搜索

模拟预编码最常用的做法是波束码本搜索。码本里每个码字对应一个波束指向,从离散傅里叶变换(DFT)码本里取。对于 Nt 阵元,DFT 码本的码字是:

w_m = (1/√Nt) × [1, exp(jπ sin(θ_m)), ..., exp(jπ(Nt-1)sin(θ_m))]^T

其中 θ_m 在 [-π/2, π/2] 均匀采样。MATLAB 里可以直接用dftmtx生成,但要注意dftmtx生成的是方阵,取前 Nrf 列作为码本。

% DFT 码本构造,Nt 阵元,Nrf 个波束 Nt = 64; Nrf = 8; codebook = dftmtx(Nt) / sqrt(Nt); % 每列是一个正交波束 codebook = codebook(:, 1:Nrf); % 取前 Nrf 列作为候选 % 验证恒模约束 assert(all(abs(abs(codebook(:)) - 1/sqrt(Nt)) < 1e-10), '码本不满足恒模');

这段代码的关键是dftmtx(Nt)/sqrt(Nt)的归一化,保证每列功率为 1。assert那行是我自己加的检查,因为一旦码本不满足恒模,后续射频端功率放大器会工作在非线性区,仿真结果直接失真。搜索准则一般用最大化每个用户的主径增益,或者最大化系统和速率下界。

3.2 数字预编码:从迫零到正则化迫零

给定 F_rf 后,等效信道是 H_eff[k,n] = H[k,n]^H × F_rf,维度 K×Nrf。数字预编码最直接的是迫零(ZF):

F_bb[n] = H_eff[n]^H × (H_eff[n] × H_eff[n]^H)^(-1)

但 ZF 在信道条件数大时噪声放大严重,实际仿真里我一般用正则化迫零(MMSE 类):

F_bb[n] = H_eff[n]^H × (H_eff[n] × H_eff[n]^H + αI)^(-1)

α 取噪声功率与发射功率的比值。MATLAB 实现时注意矩阵求逆用\而不是inv,数值稳定性更好。

% 逐子载波数字预编码,正则化迫零 alpha = 0.01; % 正则化因子,按噪声功率/发射功率调整 F_bb = zeros(Nrf, K, Nc); for n = 1:Nc Heff = squeeze(H(:,:,n))' * F_rf; % K×Nrf F_bb(:,:,n) = Heff' / (Heff*Heff' + alpha*eye(K)); end % 功率归一化,保证总发射功率恒定 for n = 1:Nc F_bb(:,:,n) = F_bb(:,:,n) / norm(F_rf*F_bb(:,:,n), 'fro') * sqrt(K); end

参数说明:alpha的取值直接影响误码率曲线,太小则噪声放大,太大则干扰抑制不足,一般扫 0.001 到 0.1 之间找拐点。功率归一化那步不能省,否则不同子载波发射功率不一致,OFDM 的 PAPR 会异常。

3.3 交替优化:模拟与数字的联合迭代

如果不想用码本,可以做交替最小化:固定 F_bb 优化 F_rf 的相位,再固定 F_rf 优化 F_bb,迭代到收敛。F_rf 的相位更新有闭式解,令 C = F_bb × F_bb^H,则 F_rf 的第 i 行第 j 列相位取 ∠(Σ_k C(j,k) × H_eff(i,k)) 的共轭。这个推导在经典论文里有,MATLAB 实现时注意每次迭代后都要做恒模投影。

% 交替优化主循环,max_iter 次 max_iter = 20; for iter = 1:max_iter % 固定 F_rf,更新 F_bb(正则化迫零) for n = 1:Nc Heff = squeeze(H(:,:,n))' * F_rf; F_bb(:,:,n) = Heff' / (Heff*Heff' + alpha*eye(K)); end % 固定 F_bb,更新 F_rf 相位 C = zeros(Nrf, Nrf); for n = 1:Nc C = C + F_bb(:,:,n) * F_bb(:,:,n)'; end for i = 1:Nt for j = 1:Nrf tmp = 0; for k = 1:K tmp = tmp + C(j,k) * conj(H(i,k,1)); end F_rf(i,j) = exp(1j * angle(tmp)) / sqrt(Nt); end end end

这段代码里H(i,k,1)只用了第一个子载波的信道,这是简化处理,严格来说应该对所有子载波求和。迭代次数 20 是我在 64×8 配置下试出来的经验值,再大收益很小。每次更新 F_rf 后必须重新归一化,否则恒模约束会被破坏。

4. 多用户调度与 OFDM 资源分配

4.1 用户分组与波束分配

多用户场景下,射频链数量有限,不能同时服务所有用户。常见做法是按信道相关性分组:相关性高的用户分到同一组,用数字预编码做空分复用;相关性低的用户分到不同组,用模拟波束做空分。MATLAB 里可以用信道矩阵的 Gram 矩阵来度量相关性。

% 用户分组:基于信道相关性贪心分组 corr_matrix = zeros(K, K); for i = 1:K for j = 1:K hi = squeeze(H(:,i,1)); hj = squeeze(H(:,j,1)); corr_matrix(i,j) = abs(hi'*hj) / (norm(hi)*norm(hj)); end end % 贪心分组,每组最多 Nrf 个用户 group = cell(1, ceil(K/Nrf)); available = 1:K; for g = 1:length(group) if isempty(available), break; end seed = available(1); group{g} = seed; available(available == seed) = []; while length(group{g}) < Nrf && ~isempty(available) [~, idx] = max(corr_matrix(seed, available)); group{g} = [group{g}, available(idx)]; available(idx) = []; end end

参数说明:corr_matrix用第一个子载波计算是简化,实际可以用宽带平均相关性。分组数ceil(K/Nrf)是理论上限,实际调度时还要考虑用户公平性,可以引入比例公平因子。

4.2 子载波功率分配

OFDM 各子载波信道增益不同,等功率分配不是最优。注水算法是经典解,但计算量大,仿真里我一般用简化的按增益比例分配:

P_n = P_total × |h_eff,n|^2 / Σ|h_eff,n|^2

其中 h_eff,n 是第 n 个子载波的等效信道增益。这个分配在低 SNR 下接近注水,高 SNR 下差距也不大,胜在实现简单。

% 按等效信道增益比例分配功率 P_total = 1; % 归一化总功率 gain = zeros(1, Nc); for n = 1:Nc Heff = squeeze(H(:,:,n))' * F_rf * F_bb(:,:,n); gain(n) = norm(Heff, 'fro')^2; end P_alloc = P_total * gain / sum(gain); % 验证功率约束 assert(abs(sum(P_alloc) - P_total) < 1e-10, '功率分配不满足约束');

这段代码里gain用 Frobenius 范数平方作为等效增益,物理意义是等效信道矩阵的总能量。assert检查功率守恒,避免数值误差累积。

4.3 和速率计算与性能评估

仿真最后要落到和速率曲线。第 k 个用户在第 n 个子载波上的信干噪比是:

SINR_k,n = |h_eff,k,n × f_bb,k,n|^2 / (Σ_{j≠k} |h_eff,k,n × f_bb,j,n|^2 + σ^2)

和速率 R = Σ_n Σ_k log2(1 + SINR_k,n)。MATLAB 里逐用户逐子载波算,注意干扰项要排除自身。

% 和速率计算 SNR_dB = 0:2:20; R_sum = zeros(size(SNR_dB)); for s = 1:length(SNR_dB) sigma2 = 10^(-SNR_dB(s)/10); R = 0; for n = 1:Nc Heff = squeeze(H(:,:,n))' * F_rf * F_bb(:,:,n); % K×K for k = 1:K signal = abs(Heff(k,k))^2 * P_alloc(n); interference = sum(abs(Heff(k,:)).^2) * P_alloc(n) - signal; SINR = signal / (interference + sigma2); R = R + log2(1 + SINR); end end R_sum(s) = R; end plot(SNR_dB, R_sum, 'b-o', 'LineWidth', 1.5); xlabel('SNR (dB)'); ylabel('Sum Rate (bps/Hz)'); grid on;

参数说明:SNR_dB范围 0 到 20 dB 是毫米波仿真的典型区间。sigma2按 SNR 定义反推噪声功率。干扰项计算时注意Heff(k,:)包含自身,要减掉。

5. 避坑与排查:仿真跑不通时先看这几处

5.1 恒模约束被破坏导致波束图畸变

现象:画出来的波束方向图旁瓣异常高,主瓣增益比理论值低 3 dB 以上。原因:F_rf 更新后没有做恒模投影,或者码本归一化因子用错。解决:每次更新 F_rf 后强制F_rf = exp(1j*angle(F_rf))/sqrt(Nt),并在码本生成后加assert检查。

5.2 子载波间干扰导致误码率平台

现象:误码率曲线在高 SNR 下不再下降,出现平台。原因:时延扩展超过循环前缀长度,或者数字预编码没有逐子载波更新。解决:检查tau最大值是否小于 CP 长度,确认F_bb的第三维是 Nc 而不是 1。

5.3 矩阵维度不匹配的隐性错误

现象:MATLAB 不报错但结果明显不对。原因:squeeze把 K×1 的向量压成 1×K,后续矩阵乘法维度悄悄变了。解决:在关键步骤后加size打印,或者用reshape显式指定维度,别依赖squeeze的自动压缩。

5.4 功率归一化遗漏导致 SNR 定义失效

现象:和速率曲线随 SNR 增长但绝对值偏高,对比理论曲线差一个常数因子。原因:F_bb 没有做功率归一化,等效发射功率随子载波数增长。解决:每个子载波预编码后除以norm(F_rf*F_bb(:,:,n),'fro'),再乘sqrt(K)保证总功率恒定。

5.5 用户分组后调度器死锁

现象:仿真跑一半卡住,available列表为空但循环没退出。原因:分组循环里while条件没考虑available为空的情况。解决:在while里加~isempty(available)判断,或者用for循环替代while。

6. 进阶技巧:把仿真从能跑变成可信

6.1 用蒙特卡洛验证统计特性

单次信道实现的结果波动很大,和速率曲线至少要跑 100 次独立信道取平均。我一般把信道生成和预编码设计包成一个函数,外层用parfor并行。

% 蒙特卡洛主循环 num_realizations = 100; R_avg = zeros(size(SNR_dB)); parfor r = 1:num_realizations H = generate_channel(Nt, K, Nc, L, Delta_f); [F_rf, F_bb] = hybrid_precoding(H, Nrf, alpha); R_avg = R_avg + compute_sum_rate(H, F_rf, F_bb, SNR_dB); end R_avg = R_avg / num_realizations;

parfor要求循环内变量不依赖迭代顺序,这里每次信道独立生成,满足条件。注意R_avg是累积变量,MATLAB 会自动处理归约。

6.2 和全数字波束成形对比看差距

混合波束成形的性能上界是全数字,仿真里加一条全数字曲线能直观看出损失。全数字预编码直接用 MMSE:

F_full[n] = H[n]^H × (H[n]×H[n]^H + αI)^(-1)

维度是 Nt×K,没有恒模约束。对比时注意两者发射功率要归一化到同一水平,否则比较没意义。

配置射频链数和速率 @10dB复杂度
全数字6428.5 bps/Hz高
混合(码本)822.1 bps/Hz低
混合(交替优化)824.3 bps/Hz中

这张表是我在 64 阵元、4 用户、64 子载波配置下跑出来的典型值,具体数字会随信道模型变,但相对关系稳定:交替优化比码本高约 2 bps/Hz,离全数字差 4 bps/Hz 左右。

6.3 用波束图直观检查模拟预编码

数字域的东西看不见摸不着,但模拟波束方向图可以画出来。用pattern函数或者自己算阵列因子:

% 画模拟波束方向图 theta_scan = -90:0.5:90; AF = zeros(size(theta_scan)); for i = 1:length(theta_scan) a = ula_response(Nt, theta_scan(i)); AF(i) = abs(a' * F_rf(:,1))^2; end AF_dB = 10*log10(AF / max(AF)); plot(theta_scan, AF_dB, 'LineWidth', 1.5); xlabel('Angle (deg)'); ylabel('Normalized Gain (dB)'); ylim([-40, 0]); grid on;

主瓣宽度应该约等于 2/Nt 弧度对应的角度,旁瓣低于主瓣 13 dB 左右(均匀加权)。如果旁瓣异常高,回去查恒模约束。

6.4 我踩过的最深的一个坑

早期做这个仿真时,我一直觉得和速率上不去,换了各种预编码算法都没用。后来发现是信道生成里tau的单位搞错了——Delta_f是 Hz,tau应该是秒,但我写成了毫秒,导致时延扩展大了 1000 倍,子载波间干扰把信号淹没了。这个 bug 隐蔽在于 MATLAB 不报错,波束图也正常,只有和速率曲线异常。从那以后我养成了一个习惯:任何物理量赋值后,立刻在旁边注释单位,并且用assert检查量级。比如assert(tau < 1/Delta_f, '时延超过符号周期')。这个习惯帮我省了至少几十次调试时间。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询