资讯动态

Winner II信道仿真:GSCM参数配置与链路级应用解析

发布时间:2026/9/11 20:50:03 来源:尧图企业网站定制
简介Winner II信道模型的MATLAB实现面向无线通信领域的科研人员、工程师和高年级学生可用于信道仿真、链路性能评估与通信算法验证。代码包为RAR压缩包共47个文件大小约639KB以41个m脚本为核心涵盖信道参数配置、路径损耗、多径生成、时变效应、MIMO空间相关性等模块另含4个p格式封装函数、1份PDF说明文档和1个txt版本说明便于查看模型原理与调用方式。已有628人学习下载。配套文档和示例脚本如信道矩阵生成示例能帮助使用者快速上手按需调整场景类型UMi、UMa、InH、Rural、移动速度、天线配置等参数观察不同信道条件下的系统性能为预编码、均衡、资源调度等算法提供贴近实际的仿真环境。整体结构清晰适合作为Winner II信道建模入门、课题复现与二次开发的基础工具。1. 为什么还在用 Winner II 做信道仿真做物理层算法评估的人手里多半有过这么一版winner2代码里面一堆.m和.p文件名字像wim.m、wimparset.m、layoutparset.m一眼看去像老古董但真到了写专利、投期刊或者给领导汇报「信道模型是否 realistic」的时候它又比 3GPP TR 38.901 的查表公式更接近实测统计。原因在于 Winner II 把几何随机模型GSCM的完整链路——路径损耗、大尺度参数、小尺度衰落、极化、天线阵列响应——都固化成了一套可执行的 MATLAB 实现你只需要改参数结构体就能在 UMi、UMa、InH、Rural 之间切换而不必自己去拟合每一张经验表。这套代码对两类人最有价值一是做 MIMO 预编码、波束管理、信道估计的算法工程师需要一组有空间相关性的信道系数而不是白噪声二是做系统级仿真的平台开发者需要把标准里散落在文档各处的查表逻辑落成可重复的函数。本文不打算复述手册而是从wimparset到wim.m拆出调用链然后用一个最小例子跑通再聊天线和排错。我会把踩过的坑和参数边界一并写出来尽量让新手照着按步执行熟手也能看到某些.p文件的真实行为。2. 拆解 winner2 包从 wimparset 到 wim.m 的仿真链路2.1 目录里的关键文件与调用关系拿到winner2.rar解压后不要急着运行wim.m先看Contents.m它会列出每个文件的一句话说明。这个版本默认是 WIM2 Ver 0.6.4WIM2_ver064_notes.txt里有变更记录。我把文件按职责分成四组参数与布局wimparset.m信道参数、layoutparset.m链路布局、ScenarioMapping.m场景到模型参数映射、ScenParTables.m查表数据核心链路wim.m主入口、wim_core.m逐链路计算、generate_bulk_par.m大尺度参数、interpbp.p插值、pathloss.m、LScorrelation.m天线与极化antenna_pol_vect.m、AntennaArray.m、AntennaResponse.m、ArrayPreprocess.m、BP2Aperture.m、BP2Aperture1D.m、G_Calc3D_simple.p、G_Calc1D_simple.p工具函数rotate_vector.m、mysph2cart.m、mycart2sph.m、StationDistanceXY.m、StationVelocityXY.m、fixedAods.m、fixedAoas.m、fixedPdp.m典型调用顺序是wimparset定义信道参数 →layoutparset定义基站/终端位置、移动方向 →wim(layout, cfg)输出信道系数H。而wim.m内部会先调wim_core再在需要时调用generate_bulk_par生成大尺度随机数并用LScorrelation做空间相关。注意fixedPdp.m和fixedAods.m这些是给「固定抽头」模式使用的对应cfg.useFixedPdp 1的情况默认是随机模式。2.2 参数集初始化wimparset 与 layoutparsetwimparset返回一个结构体里面的字段决定了仿真带宽、场景、极化、收发天线数目。我一般这样看默认值cfg wimparset; disp(cfg);字段很多重点看这几个字段名默认值作用cfg.scenarioUMa场景可选UMi,InH,Rural等cfg.centerFrequency5.0e9中心频率影响路径损耗和多普勒cfg.velocity[0 0 0; 0 0 0]收发端速度向量单位 m/scfg.numMS1终端数量cfg.numBS1基站数量cfg.useFixedPdp0是否使用固定抽头1 表示用fixedPdpcfg.useFixedAoD/cfg.useFixedAoA0是否固定离开/到达角注意wimparset里很多字段是给wim_core内部使用的不要乱改名字否则.p文件读取时会报错。比如cfg.numPaths控制每链路径数默认值实际上是 20Winner II 默认但你直接cfg.numPaths 12是有效的因为wim_core会按这个数分配矩阵。layoutparset定义的是链路拓扑不是天线阵列。它至少包含Pairing哪对收发天线关联、Stations的位置坐标、Scatterers等信息。最小例子如下layout layoutparset; layout.StationPositions(1).Pos [0 0 32]; % 基站位置单位米 layout.StationPositions(2).Pos [30 40 1.5]; % 终端位置这里layout.StationPositions是一个结构数组索引 1 对应第一个站点通常是 BS索引 2 对应 MS。Pos必须是一个 1x3 的向量顺序是 x, y, z。如果只给二维坐标wim.m会在某些.p函数里因mysph2cart转换报错。2.3 核心仿真函数 wim.m 的输入输出wim.m的调用方式有两种H wim(layout, cfg); % 主接口 [H, out] wim(layout, cfg); % 返回额外信息H的维度是[numTx*numRx, numPaths, numTimeSamples]还是其他取决于cfg里的numTimeSamples和sampleDensity。默认情况下wim.m输出的是一个三维复数数组第一维是收发天线对的索引第二维是抽头簇索引第三维是时间采样点。而out里会带有out.pathData这类结构包含每条路径的时延、功率、角度等用于二次处理。注意wim.m并不直接给出频域信道它输出的是时域抽头。你要做 OFDM需要自己对抽头做 FFT或者用example_channel_matrix.m里的脚本直接生成频域矩阵。这个区别很多新手会混淆——把H当频域响应直接乘在 OFDM 符号上结果整个星座图都是错的。2.4 手写一个最小仿真例子为了验证环境我建议先不要跑example_syntetic_arrays.m因为那个例子带了天线阵列预处理出问题不好定位。先跑最小脚本% 最小 Winner II 信道仿真脚本 cfg wimparset; cfg.scenario UMi; % 微蜂窝场景 cfg.centerFrequency 3.5e9; % 5G 常用频段 cfg.velocity [0 0 0; 20 0 0]; % 终端以 20 m/s 沿 x 轴移动 cfg.numTimeSamples 100; % 时间采样点数 cfg.sampleDensity 1; % 每波长采样密度 layout layoutparset; layout.StationPositions(1).Pos [0 0 25]; % BS layout.StationPositions(2).Pos [50 0 1.5]; % MS H wim(layout, cfg); size(H) % 观察输出维度这段代码做了四件事设置场景和频段、让终端移动以产生多普勒、固定收发位置、调用核心函数。cfg.velocity是一个 2x3 矩阵第一行是 BS 速度第二行是 MS 速度如果设为[0 0 0; 0 0 0]则信道时不变。sampleDensity与numTimeSamples共同决定时间轴长度实际采样间隔 波长 / (sampleDensity * 光速 / 频率)。跑完后如果H是空的或维度异常先检查layout.StationPositions的字段名对应版本可能是StationPositions而不是StationPos在layoutparset返回后先用fieldnames(layout)确认。3. 场景映射与信道系数生成ScenarioMapping、LSP 与 3D 角度3.1 场景识别ScenarioMapping.m 怎么工作wimparset里只给了cfg.scenario UMa这种字符串真正把字符串转成具体参数表索引的是ScenarioMapping.m。它内部维护一个函数式表每个场景对应一组延迟比例、角度扩展、阴影衰落标准差等。这些参数已经按照 Winner II 最终报告做成了查表结构ScenParTables.m。为什么要单独做一层映射因为同一种环境在不同频段、不同站高下大尺度参数可能不同。例如UMa在 2 GHz 和 28 GHz 下路径损耗指数和时延扩展的取值不一样。ScenarioMapping.m的输入不仅有场景名还有cfg.centerFrequency和cfg.bsAntHeight这类辅助量。所以在改频段时不要只改centerFrequency还要确认ScenarioMapping是否支持该频段的参数表如果不支持它会回退到最近似的频段并打印一行提示有时不打印需要看wim.m里的verbose开关。调用ScenarioMapping.m是隐式的你在wim.m内部看不到直接调用但可以通过dbstop if error回溯。如果你想自己单独测试场景映射可以这样cfg wimparset; cfg.scenario InH; [params, err] ScenarioMapping(cfg);其中params是结构体包含params.delaySpread、params.as等。这里要注意ScenarioMapping的第二个输出是错误信息如果场景名非法err非空而params是空。所以校验用户输入时别只看cfg.scenario是否在列表里还要调一次映射确认。3.2 大尺度参数ds.m、fixedPdp.m 与 LScorrelation.mWinner II 的 GSCM 模型把大尺度参数延迟扩展 DS、角度扩展 AS、阴影衰落 SF建模为对数正态分布且 DS、AS、SF 之间存在相关性。LScorrelation.m就是干这个的它接收一组独立的服从 N(0,1) 的随机数通过相关矩阵和均值/方差生成一组相关的对数正态变量。ds.m只是计算延迟扩展的辅助函数实际仿真里你通常不需要直接调用它。真正影响信道的是fixedPdp.m当cfg.useFixedPdp 1时信道抽头功率不再由每次随机生成而是用fixedPdp返回的一组固定功率/时延便于复现实验。比如你想做 BER 曲线对比希望每次仿真的信道统计特性一致就打开这个开关。我自己常用的做法是先跑一次随机模式记录out.bulk里的参数这些是每次信道实现的大尺度参数然后固定种子复现。generate_bulk_par.m内部实际使用了randn所以控制随机数种子即可rng(42, twister); H1 wim(layout, cfg); rng(42, twister); H2 wim(layout, cfg); isequal(H1, H2) % 返回 1 表示完全复现注意wim.m内部可能使用rand和randn两种随机源只设置一种种子不够。稳妥做法是同时rng(42)和rng(42,twister)等价实际上 MATLAB 的rng同时重置所有生成器。如果仍不能复现检查cfg.useFixedPdp和cfg.useFixedAoD是否为 1这些开关会绕过随机生成。3.3 角度生成fixedAods/fixedAoas 与 rotate_vector每条路径的出发角AoD和到达角AoA是 GSCM 的核心。fixedAods.m与fixedAoas.m分别在固定角度模式下提供一组预先定义的角度值。而rotate_vector.m负责把角度旋转到绝对坐标系。因为 Winner II 中每个簇有 20 条子径子径之间有小角度偏移这些偏移由offset_matrix_generation.m生成。该矩阵是预先计算好的不同簇之间不要互相覆盖。这三个函数配合的过程是generate_bulk_par生成每簇的平均角度 →offset_matrix_generation生成子径偏移 →rotate_vector将相对偏移叠加到平均角度上。如果你要仿真车联网中的快速角度变化可以考虑每次调用后手动修改layout中站点朝向再重新生成角度。但注意rotate_vector对输入参数格式有要求角度必须是弧度制方向向量的模长任意函数输出是单位向量。举个例子把基站到终端的视距方向向量旋转 30 度% 原始方向向量 v [50 0 0] - [0 0 25]; % 由 BS 指向 MS v v / norm(v); % 绕 z 轴旋转 30 度的旋转矩阵 theta deg2rad(30); Rz [cos(theta) -sin(theta) 0; sin(theta) cos(theta) 0; 0 0 1]; v_rot (Rz * v.).;而rotate_vector本身支持更一般的任意轴旋转但调用时你需要提供圆心、半径、初始角度等参数直接查看其帮助信息即可。实际业务中除非要模拟车辆转向否则不需要手动干预角度生成。3.4 从路径参数到信道冲激响应wim_core.m是真正把角度、时延、功率、极化组装成信道系数的模块。它的流程是从bulk中取出每簇的时延tau和功率P用interpbp.p对时间轴进行插值得到每个采样时刻的随机相位根据 AoD/AoA 计算发射和接收端的阵列响应向量用antenna_pol_vect计算极化响应累加所有簇的贡献得到每对收发天线间的冲激响应这里最容易出错的是维度对齐。interpbp.p是一个 Fortran 编译的p文件它要求输入的时间轴必须是均匀采样的否则会报Input arguments must be scalar这类信息。我踩过这个坑自定义cfg.timeVector时用了非均匀间隔结果在interpbp处直接卡死。解决办法是让cfg.numTimeSamples和cfg.sampleDensity决定时间轴而不是自己传入。另一个坑是cfg.numClusters与cfg.numSubPaths的乘积要等于cfg.numPaths。默认是 20 个簇 × 20 条子径 400 条径但输出H的第二维是簇数而不是总径数。所以当你想减少计算量设cfg.numClusters 10时一定要同时检查wim_core是否仍然能分配矩阵有些版本要求这两个字段同时修改否则后续reshape报错。4. 天线阵列与极化处理从 AntennaArray 到 BP2Aperture4.1 天线阵列描述ArrayPreprocess 与 AntennaResponseWinner II 的标准输出是针对理想天线单元的全向方向图但实际仿真常需要引入阵列方向性。layoutparset里layout.StationPositions(i).AntennaArray可以指定天线类型。默认是单天线即增益为 1、方向图全向。若要用均匀线阵ULA需要自己构建天线阵列结构体。ArrayPreprocess.m负责把用户提供的阵列描述预处理成内部格式。它的输入是一个结构体至少包含array struct(); array.type ULA; array.numElements 4; array.elementSpacing 0.5; % 单位波长 array.orientation [0 0 0]; % 阵列法向的欧拉角然后调用arrayparset文件列表里有arrayparset.m生成layout.StationPositions(i).AntennaArraylayout.StationPositions(1).AntennaArray arrayparset(array.type, ... array.numElements, array.elementSpacing, array.orientation);注意arrayparset的返回值不是直接给wim用的ArrayPreprocess会在wim_core内被自动调用。如果你看到AntennaArray字段为空那么默认按单天线处理。4.2 极化矢量antenna_pol_vect.m 与 G_Calc 系列Winner II 模型支持双极化天线。发射端每个单元有 V 和 H 两个极化分量接收端同样。极化响应由antenna_pol_vect.m生成它根据天线单元在全局坐标中的朝向计算该单元在 V/H 基下的辐射矢量。这个矢量会与G_Calc3D_simple.p生成的角度相关极化矩阵相乘得到最终的极化传输矩阵。G_Calc3D_simple.p是一个三维极化耦合系数计算函数输入是出发角和到达角输出是 2x2 的极化矩阵。G_Calc1D_simple.p则是简化版本适用于方位角为主、俯仰角被忽略的场景。如果你想仿真 UPA均匀平面阵列和 3D 波束成型要确保cfg中开启 3D 极化选项否则即使你的阵列是二维的极化矩阵也只会按 1D 计算。这个类的.p文件没法打开源码但你可以通过传入不同角度观察输出G G_Calc3D_simple.p(deg2rad([30 45 60]), deg2rad([10 20 30]), 0.5, 0.5, 1);其中前两个参数是发射和接收角度弧度后面参数是极化倾斜角等。实际业务中如果只是做波束成型算法通常不会去改极化矩阵而是把cfg.polarized 0关闭极化让信道变成标量信道运算更快。但当你做交叉极化鉴别率XPD研究或 dual-polarized MIMO 时必须保持打开。4.3 孔径耦合BP2Aperture1D 的使用边界BP2Aperture.m和BP2Aperture1D.m用于把天线方向图从球面基函数表示转换为孔径场表示。这是为近场或特殊天线自定义准备的工具普通仿真用不到。BP2Aperture1D.m是一维版本的孔径耦合它接收天线方向图采样值和角度网格返回孔径场分布。如果你想把自定义方向图导入 Winner II常见做法不是改写BP2Aperture而是在AntennaResponse.m里直接返回你需要的响应。AntennaResponse的接口是resp AntennaResponse(theta, phi, array)其中theta是俯仰角phi是方位角array是天线结构体。你可以仿照默认实现写一个自己的版本function resp AntennaResponse(theta, phi, array) % 自定义方向图全向但带 3 dB 损耗 resp repmat(10^(-3/20), size(theta)); % 如果想模拟定向天线可按 theta/phi 修改resp end然后把这个函数路径加入MATLAB搜索路径并把layout.StationPositions(1).AntennaArray.customResponse设为AntennaResponse。wim_core在计算阵列响应时会优先调用该自定义函数。4.4 常见自定义天线方式在文件列表里dipole.m是偶极子天线方向图示例。example_EADF_approx.m展示了如何用 EADF有效孔径分布函数近似实际天线方向图并将近似结果用于信道仿真。EADF 的好处是能压缩方向图数据量同时保证插值平滑。如果你的天线是实测方向图我建议先跑一遍example_EADF_approx.m它会生成一个 EADF 系数矩阵然后你把这个矩阵存成.mat在AntennaResponse中查表即可。需要注意的是example_syntetic_arrays.m演示了合成阵列synthetic array的使用。所谓合成阵列不是物理上存在多个天线单元而是通过移动一个天线在不同空间位置采样模拟出虚拟天线阵列。这种方式适合测量场景下的信道探测但在纯仿真中不建议用因为移动采样会引入时间上的伪相关。5. 让仿真跑起来的排错清单与提速技巧5.1 常见错误p 文件兼容、路径问题、维数不匹配拿到代码后最常遇见的三个问题分别是.p文件打不开、路径缺失和维度错误。.p文件是加密的 MATLAB 函数在 R2020b 之后依然能运行但需要注意interpbp.p是 32 位还是 64 位取决于编译时的 MATLAB 版本。如果你的 MATLAB 是 R2023b 且从老版本拷贝来interpbp.p可能报Invalid MEX-file。解决办法是重新找对应的源码版本编译或者用interp1替代但替代后性能会下降。路径问题比较隐蔽。wim.m内部会调用ScenParTables而ScenParTables.m依赖同目录下的.mat数据文件。如果你只拷贝了.m文件而没拷数据表运行时会提示找不到变量。建议解压后不要移动任何文件直接在根目录运行addpath(genpath(winner2))。维度不匹配的典型报错是Matrix dimensions must agree位置通常在wim_core.m或LScorrelation.m。这多半是因为cfg.numMS和layout.StationPositions的站点数没对应。记住layout中的站点数量必须是cfg.numBS cfg.numMS且排序默认前numBS个是基站、后numMS个是终端不要混排。5.2 如何把信道系数接入自己的链路级仿真wim输出的是时域冲激响应抽头接入 OFDM 链路时要做频域转化。我在实际项目中写了一个转换函数直接把抽头变成 OFDM 频域信道矩阵function Hf taps2ofdm(H, nfft, cpLen, snr) % H: [numTx*numRx, numTaps, numSamples] % 输出: [numSubcarriers, numSamples, numTx*numRx] [numLinks, numTaps, numSamples] size(H); Hf zeros(nfft, numSamples, numLinks); for l 1:numLinks h squeeze(H(l,:,:)); % numTaps x numSamples % 对每个时刻的冲激响应做 FFT Hf(:,:,l) fft(h., nfft, 1).; % 转置后按时间轴方向 FFT end % 加入噪声的功率归一化在外部做 end这个函数的思路是把每个时刻的抽头向量补零到nfft长度并做 FFT。参数说明nfft是 OFDM 子载波数cpLen通常用来检查最大时延是否超过循环前缀超过会产生符号间干扰可以打印警告。实际使用时建议把H的第三维时间采样点与 OFDM 符号数对齐如果不够可以用插值扩展。注意H中的功率归一化已经考虑了路径损耗所以你在做链路仿真时不需要额外乘距离衰减。但如果你用的是cfg.scenario InH其路径损耗模型与UMa差异很大不同场景输出的信噪比绝对值不适合直接对比。我在对比算法时会固定cfg.normalizeChannel 1或手动把H的能量归一化到 1。5.3 二次开发修改 LOSprobability、多普勒扩展最后分享一个常用技巧如何调整视距概率。LOSprobability.m根据距离和场景返回视距概率。它默认实现是查表但你可以直接替换函数内容比如在室外 UMi 下自定义两段式概率function pLOS LOSprobability(d, scenario) % d 是三维距离scenario 是字符串 if strcmp(scenario, UMi) pLOS min(18./d, 1) .* (1 - exp(-d/36)) exp(-d/36); else % 默认查表逻辑 pLOS min(18./d, 1); end end改了之后需要让generate_bulk_par.m使用新的函数。这两个函数都放在 winner2 目录下你可以直接编辑保存但建议复制一份到自己的工程目录再按上面方式addpath覆盖。修改完成后跑一个蒙特卡洛仿真统计 1000 次信道实现中第一簇功率占总功率的比例看是否符合你的预期——第一簇功率高代表视距。这个方法也常用于验证你的 LOS/NLOS 切换逻辑是否生效。多普勒扩展的调整则更简单直接改cfg.velocity。但要记住velocity是线性速度多普勒频移等于v * cos(夹角) * fc / c。当终端沿随机方向移动时StationVelocityXY.m会生成均匀分布的随机速度方向。你可以在调用wim前手动设置layout.StationPositions(2).VelocityVector [5 0 0]从而精确控制运动方向。对做 V2X 仿真的同学建议把速度向量拆成 x/y 分量并配合cfg.sampleDensity调节采样频率避免频率混叠。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价