宏小区覆盖仿真避坑指南:定向天线增益与下倾角扫描实践
简介面向北京邮电大学《移动通信仿真与规划》课程设计实验资源完整覆盖“宏小区覆盖”实验2所需的MATLAB源程序与实验报告适合通信工程、电子信息类专业学生参考学习移动通信仿真。压缩包内共8个文件其中7个.m源文件分别承担定向天线增益仿真、路径损耗计算、最佳基站选址以及宏小区覆盖结果输出等关键环节另有1份docx格式的《移动网络报告》记录实验过程与结论可直接对照实验要求核验仿真逻辑并补充拓展实验分析。资源总大小约2.92MB代码结构紧凑、层次清晰便于理解宏小区覆盖场景下的建模思路与参数调节方法。当前已有511人学习下载可作为完成课程设计、构建仿真链路及扩展覆盖场景的实用素材。1. 宏小区覆盖仿真为什么难定向天线增益才是那个最容易翻车的环节做过宏小区覆盖仿真的人应该都有这种感觉路径损耗算得再细只要天线方向图增益算错最终覆盖图照样面目全非。这个实验2的核心并不是基站选址算法本身而是定向天线增益仿真——GetBinGain 这类函数决定了每个栅格点到底能拿到多少增益直接左右了覆盖半径和小区边界形态。某邮电类高校《移动网络仿真与规划》课程设计里的实验2“宏小区覆盖”就是围绕这条主线展开的先完成定向天线增益仿真再用下发开源代码跑通宏小区覆盖最后写实验报告并补一个拓展实验。这篇笔记不打算重复教材推导就讲清楚这套源码怎么读、参数怎么调、报告怎么把拓展实验写得有说服力。2. 读懂 GetBinGain 与 GetBinGainmultiple定向天线增益仿真的两个关键入口2.1 先从文件职责看这套程序的设计思路拿到“实验2宏小区覆盖.zip”之后第一件事不是直接跑 main.m而是先分清几个 .m 文件各自的角色。main.m 负责整体流程调度main2.m 通常对应拓展实验GetPathLoss.m 算路径损耗GetBestSite.m 做基站选址而 GetBinGain.m 和 GetBinGainmultiple.m 是天线增益计算的核心。它们之间的关系可以理解为GetBinGain 计算单个扇区在某个方向上的增益GetBinGainmultiple 把多个扇区合起来输出一个覆盖每个栅格点的增益矩阵 G。宏小区覆盖里天线一般是三扇区或六扇区配置所以 GetBinGainmultiple 才是真正影响覆盖图形态的函数。函数文件输入侧关注点输出侧关注点GetBinGain.m方位角矢量、下倾角、天线方向图数据单扇区增益列矢量GetBinGainmultiple.m扇区数量、各扇区指向角、下倾角全栅格增益矩阵 G 或 GmaxGetPathLoss.m基站坐标、栅格坐标、频段、站高路径损耗矩阵 PLGetBestSite.m候选站址、覆盖门限、栅格掩模最优站址坐标与覆盖指标main.m地图大小、栅格步长、基站初始位置覆盖图与中间变量存档先说一个容易误判的点GetBinGain 返回的增益是相对值还是绝对值。常见实现里它读入天线厂商的方向图文件把水平面和垂直面的方向图数据做插值输出的是相对于全向天线的增益。所以后续所有链路预算都要在同一基准下做否则会出现“所有栅格点增益同时偏大几个 dB”的假象覆盖半径被整体放大。我一般会在读完函数后的第一时间打印一列增益值肉眼扫一遍最大最小值再确定它到底是 dBi 还是 dBd 基准这一步只要两分钟却能在后面省下两小时。2.2 单扇区增益计算的插值与归一化逻辑GetBinGain 这类函数的基本套路很固定输入一个方位角范围、一个俯仰角范围以及天线的方向图数据表然后通过插值得到任意方向上的增益值。宏小区场景中天线架设高度一般在 25 到 50 米之间机械下倾角通常设 2 到 6 度俯仰角范围不需要覆盖整个半球取 -10 度到 10 度就够用。以下是这类函数最常见的实现形态function G GetBinGain(azimuthVec, elevationVec, antennaPattern, tiltAngle) % 输入 % azimuthVec - 水平角采样矢量单位度例如 -180:1:180 % elevationVec - 垂直角采样矢量单位度例如 -10:1:10 % antennaPattern - 天线方向图结构体含 azimuth_pattern / elevation_pattern % tiltAngle - 机械下倾角单位度正值表示波束下压 % % 输出 % G - 与 azimuthVec 等长的增益列矢量单位 dBi % 第一步将下倾角转换为垂直方向的偏转补偿 tiltRad deg2rad(tiltAngle); % 第二步对天线方向图做线性插值得到当前俯仰采样下的增益 G interp1(antennaPattern.elevationAngles, ... antennaPattern.elevationGain, ... elevationVec - tiltAngle, linear, extrap); % 第三步叠加水平面方向图的贡献这里用简单相加近似 3D 方向图 G G interp1(antennaPattern.azimuthAngles, ... antennaPattern.azimuthGain, ... azimuthVec, linear, extrap); % 第四步减去方向图最大值确保全向分量不重复计入 G G - max(antennaPattern.azimuthGain); end这里每一步都有讲究。第一步把机械下倾角折算进垂直角采样是因为实际天线的主瓣方向是“安装角 下倾角”共同决定的不做这一步下倾角参数就形同虚设。第三步把水平面与垂直面增益相乘才是严格做法但很多课程设计代码为了简化直接用相加最后再整体归一化。这个简化对覆盖图的相对形态影响不大但绝对数值会略有偏差。参数说明azimuthVec 的采样间隔建议设为 1 度小于 1 度会显著增加计算量大于 5 度会让栅格边缘出现锯齿状覆盖边界。tiltAngle 的取值范围需要结合小区半径估计站间距 1 公里左右的宏小区下倾角 3 度上下通常能覆盖到 500 米开外超过 8 度时主瓣会压到塔下覆盖半径断崖式缩小。2.3 多扇区合并为什么 GetBinGainmultiple 输出的是包络而不是求和宏小区站点上不止一面天线。典型配置是三扇区各覆盖 120 度或者六扇区各覆盖 60 度。GetBinGainmultiple 要做的事就是把每个扇区单独算出来的增益按照各自指向角对齐后合并成一个矩阵。这个合并操作有讲究它是“取最大”不是“求和”。为什么不能求和因为终端在同一时刻只对接一个扇区的主瓣信号其他扇区方向上的增益再高也不会叠加到同一路信号上。如果对三扇区增益逐栅格求和覆盖交叠区会出现一个虚假的“增益凸起”看起来信号很强实际是数学假象。取最大值的物理含义是每个栅格点优先归属到增益最高的那个扇区这与小区选择的“最强小区”原则是一致的。以下示意代码展示了六扇区配置下的合并过程function Gmax GetBinGainmultiple(sectorAzimuths, tiltAngle, gridAzimuth, gridElevation, antennaPattern) % sectorAzimuths - 各扇区天线指向角例如 [0 60 120 180 240 300] % gridAzimuth - 栅格点相对于基站的方位角矩阵与栅格同尺寸 % gridElevation - 栅格点相对于基站的俯仰角矩阵与栅格同尺寸 % antennaPattern - 天线方向图结构体 numSectors length(sectorAzimuths); Gmax zeros(size(gridAzimuth)); % 初始化包络增益矩阵 for k 1:numSectors % 关键步骤将栅格方位角换算到以该扇区指向角为基准的本地角 localAzimuth wrapTo180(gridAzimuth - sectorAzimuths(k)); % 调用单扇区增益函数 Gk GetBinGain(localAzimuth(:), gridElevation(:), antennaPattern, tiltAngle); Gk reshape(Gk, size(gridAzimuth)); % 多扇区合并取包络 Gmax max(Gmax, Gk); end end这段代码里的 wrapTo180 是很多人第一次看会忽略的细节。扇区指向角是 60 度栅格点方位角是 300 度如果不做角度回绕差值是 -240 度直接插值会落到方向图后瓣上结果完全错误。回绕后差值为 60 度才对应到主瓣区域。我在复现时专门打印过回绕前后的本地角矩阵差异非常大建议你也跑一遍确认。再说下倾角如何影响 Gmax下倾角增大时远点栅格的俯仰角相对基站天线主瓣下沿偏差变大增益下降覆盖半径收缩近点栅格反而进入主瓣范围塔下覆盖增强。这个特性是后面拓展实验最值得做的方向扫描不同下倾角看覆盖面积如何变化是一个非常干净的量化对比。2.4 增益矩阵的归一化陷阱决定覆盖半径的最后一根稻草把天线方向图插值出来后最常遇到的情况是整套程序跑通但覆盖半径比课件上的结果大了 20%。这时十有八九是增益归一化出了问题。宏小区链路预算里接收功率等于发射功率加天线增益减路径损耗天线增益如果是相对值比如相对半波偶极子的 dBd和发射功率里隐含的 dBi 基准不一致叠加结果就会系统性偏移。常见做法是把所有增益统一到 dBi。dBd 与 dBi 相差约 2.15 dB方向图数据表里如果是 dBd要整体加上 2.15 再做后续计算。另一个更隐蔽的坑是方向图数据里已经包含了天线自身的最大增益链路预算里又额外加了一次标称最大增益等于重复计算覆盖面积直接被抬高一个档次。判断方法很简单看看 GetPathLoss.m 里有没有类似 G_total GainPattern maxGain 的代码如果有说明方向图是归一化过的不能再加一次。3. 从 GetPathLoss 到 GetBestSite宏小区覆盖的完整计算链路3.1 宏小区路径损耗模型为什么不能只用自由空间GetPathLoss.m 是整套仿真里距离语义最强的一个函数。宏小区场景里基站与终端之间隔着建筑物、树木、地面反射自由空间模型只在视距环境下成立用在宏小区里会把覆盖半径算得过于乐观。课程设计里常用的做法是采用 Hata 模型或者 COST231-Hata 修正模型把城市环境因子、基站高度、终端高度都塞进公式。以市区宏小区为例COST231-Hata 的典型形态如下function PL GetPathLoss(fc, hBS, hMS, d, envType) % 输入 % fc - 载波频率单位 MHz常见取值 1800 / 2100 / 2600 % hBS - 基站天线高度单位 m宏小区常见 25 ~ 50 % hMS - 终端天线高度单位 m常见 1.5 % d - 距离矢量单位 km % envType - 环境类型urban 或 suburban % % 输出 % PL - 路径损耗值单位 dB % 城市环境修正因子 aHR 3.2 * (log10(11.75 * hMS))^2 - 4.97; if strcmp(envType, urban) CM 0; else CM -2 * (log10(fc / 28))^2 - 5.4; % 郊区修正项 end % COST231-Hata 公式 PL 46.3 33.9 * log10(fc) ... - 13.82 * log10(hBS) ... - aHR ... (44.9 - 6.55 * log10(hBS)) * log10(d) ... CM; end这里有个容易忽略的点d 的单位是 km 而不是 m。如果栅格坐标以米为单位必须先做距离换算再喂给这个函数否则路径损耗曲线整体右移或左移覆盖半径差出几倍。我在某次复现里就是把 500 米直接当 0.5 km 算边界差了差不多 1 km 才发现问题。参数选择上频率选 1800 MHz 还是 2600 MHz 对路径损耗影响很大。以 900 MHz 为基准频率每翻一倍路径损耗增加约 10 dB反映在覆盖距离上差不多缩水三分之一。课程设计场景一般默认 2000 MHz 左右如果报告里自己改了频率务必同步更新发射功率和天线增益参数保持链路预算自洽。3.2 栅格化地图与距离矩阵的向量化计算覆盖仿真要把目标区域划分成栅格逐个栅格点计算接收功率。最直观的写法是双重 for 循环遍历所有栅格点但栅格一旦加密计算量会迅速失控。比如一个 10 km 乘 10 km 的区域步长 50 m栅格数就是 4 万个乘以基站数量、扇区数量、角度采样数量跑一轮要好几分钟。向量化的思路是把 x 与 y 坐标网格化然后用 meshgrid 直接生成距离矩阵避免 Python 或 MATLAB 里的标量循环。% 生成栅格坐标 xGrid 0:step:mapSize; yGrid 0:step:mapSize; [X, Y] meshgrid(xGrid, yGrid); % 基站坐标 bsX 5000; bsY 5000; % 计算每个栅格点到基站的水平距离矩阵 dHoriz hypot(X - bsX, Y - bsY); % 单位 m % 三维距离考虑基站天线高度与终端高度差 hDiff hBS - hMS; d3D sqrt(dHoriz.^2 hDiff^2);hypot 函数比 sqrt(X.^2 Y.^2) 更快也更稳直接返回水平距离矩阵。三维距离才是路径损耗真正该用的距离尤其在栅格点位于基站正下方时dHoriz 趋近于 0如果直接用水平距离路径损耗会变成负无穷大只有加上高度差才能算出一个合理下限。网格步长的选择也需要权衡步长 100 m 能快速出轮廓适合参数扫描步长 20 m 能看清小区边缘细节适合最终成图。日常调试先用大步长确认无误后再加密出图。3.3 GetBestSite 背后的代价函数本质是一次栅格打分GetBestSite.m 的输入是候选站点位置输出是最优站址。它内部做的事情可以理解为一个打分循环把候选站点逐一放到地图上用 GetPathLoss 和 GetBinGainmultiple 算出每个栅格点的接收功率然后统计超过门限的栅格点数量这个数量就是该候选位置的分数。分数最高的位置被选中。function [bestSite, bestScore] GetBestSite(candidateSites, bsParams, gridMask, threshold) % candidateSites - 候选站点坐标矩阵每行一个 [x, y] % bsParams - 基站参数含频率、天线高度、发射功率、下倾角 % gridMask - 目标区域掩模逻辑矩阵1 表示需要覆盖 % threshold - 覆盖门限单位 dBm常见 -95 或 -100 numCandidates size(candidateSites, 1); scores zeros(numCandidates, 1); for i 1:numCandidates % 计算该候选站点的路径损耗矩阵 PL GetPathLoss(bsParams.fc, bsParams.hBS, bsParams.hMS, d3D, bsParams.envType); % 计算增益包络矩阵 G GetBinGainmultiple(bsParams.sectorAzimuths, bsParams.tiltAngle, ... gridAzimuth, gridElevation, antennaPattern); % 链路预算接收功率 发射功率 增益 - 损耗 rxPower bsParams.EIRP G - PL; % 打分覆盖像素占比 scores(i) sum((rxPower threshold) gridMask) / sum(gridMask); end [bestScore, bestIdx] max(scores); bestSite candidateSites(bestIdx, :); end这段代码里 threshold 的选择直接影响选址结果。门限设 -95 dBm覆盖范围大评分容易趋同门限设 -105 dBm边缘覆盖要求严分数差距拉开选址结果更有区分度。实际课程设计里我一般用 -95 dBm 做主仿真的门限再用 -100 dBm 做敏感性分析报告中放两张图的对比即可。还有一个细节rxPower 的单位是 dBmG 和 PL 的单位是 dB三者直接相加不需要额外转换。但如果发射功率脚本里用的是 W必须先做 10log10(P1000) 换算成 dBm否则整个矩阵全部偏移 30 dB覆盖图几乎全白或全黑。3.4 单站贪心选址的边界为什么报告要写清“本文采用贪心策略”不少人在读 GetBestSite 时会想当然地认为它是全局最优选址其实课程设计版的 GetBestSite 通常是单站贪心。它的局限在于一次只优化一个站点其他站点位置固定不变候选站址集合也预先给定并不会在连续空间里做真正的最优化搜索。复数个站点的联合覆盖优化是一个组合爆炸问题单站贪心是课程设计时间约束下最合适的简化。这个简化在报告中写成“本文采用逐站贪心部署策略每次固定已部署站点为新增站点选择覆盖增益最大的候选位置”即可既符合源码行为又显得你理解到了算法边界。拓展实验如果想往前多走一步可以改成“禁忌搜索”或“模拟退火”但这就涉及到自己写优化器超出了本资源覆盖范围属于加分项而非必要项。4. 宏小区覆盖实验避坑四个容易翻车的地方与排查顺序4.1 运行 main.m 报错“矩阵维度必须一致”现象程序跑到 GetBinGainmultiple 或 GetBestSite 时MATLAB 直接抛维度不匹配错误断点停在某个乘法或加法上。原因栅格矩阵、增益矩阵、路径损耗矩阵三者尺寸没对齐。最常见的错法是在脚本里改了地图尺寸或栅格步长但增益矩阵还是按旧的地图尺寸生成的或者方位角采样数量与栅格行数不一致。解决在所有核心函数的第一行加一条断言检查三个矩阵的 size 是否一致。比如 assert(isequal(size(G), size(PL)), G and PL must be same size)。每次改完参数后先跑一遍断言再跑主流程能省掉大量排查时间。4.2 覆盖图出现大量孤立碎块主干不连续现象覆盖图上覆盖像素像斑点一样散落在各地没有形成连续的小区覆盖瓣或者边缘出现一圈网格线状的伪覆盖。原因增益计算用“求和合并”代替了“取最大合并”或者方向图插值时角度越界后用的外推值不合理。外推会将方向图后瓣的低增益值放大形成栅格状伪影。解决回到 GetBinGainmultiple确认合并方式为 max同时把插值方法从 linear 配合 extrap 改成 nearest避免在水平角超出方向图范围时外推出离谱的数值。改完后重新打印增益矩阵的最大值和最小值正常应该落在 -20 dB 到 20 dB 这个区间。4.3 结果看着合理但跑一轮要半小时调参成本太高现象main.m 能跑完覆盖图也对但每次改一个参数就是半小时起步根本没法做多组参数对比。原因脚本里用了嵌套 for 循环遍历每个栅格点去算损耗和增益而不是向量化计算。在 10 km 乘 10 km、步长 50 m 的场景里双重循环就是 4 万次迭代乘以扇区数和角度采样数计算量被放大了几个数量级。解决先把距离矩阵、方位角矩阵、俯仰角矩阵全部用 meshgrid 预计算再进函数其次把函数里逐栅格插值改成矩阵化插值。改完后同样场景通常能跑进 2 分钟以内。时间降下来之后才有机会做参数扫描否则拓展实验根本没素材可写。4.4 报告里的“拓展实验”只有一张最终覆盖图被导师指出没有对比现象报告写了“完成拓展实验”但正文只有一张最终覆盖图没有参数变化过程的量化分析看起来像换了张图交差。原因做拓展实验时只跑了一组参数没有设计对照。导师想看的是你理解了哪些参数影响覆盖而不是你多跑了一个场景。解决设计参数扫描时必须包含对照组。最省力的方案是固定其他参数只扫描下倾角依次取 0 度、2 度、4 度、6 度记录覆盖像素占比的变化画一张横轴为下倾角、纵轴为覆盖率的曲线图。这张图一张顶十张既有量化指标又有趋势分析直接就能撑起报告里“拓展实验与分析”一节。5. 从复现到拓展把下倾角扫描做成报告里最有说服力的一张图拓展实验不需要做得多高深关键在“控制变量”和“量化对比”。我个人最推荐的做法是固定天线方向图、频率、基站高度、发射功率不变只扫描机械下倾角观察覆盖率与边缘接收功率的变化。因为下倾角是宏小区覆盖里最直接影响主瓣落点的参数物理意义明确作图也直观。% 参数扫描脚本下倾角 0:2:6 度 tiltValues 0:2:6; coverageRatio zeros(size(tiltValues)); for idx 1:length(tiltValues) bsParams.tiltAngle tiltValues(idx); % 重新计算增益包络下倾角变化后必须重新生成 G G GetBinGainmultiple(bsParams.sectorAzimuths, bsParams.tiltAngle, ... gridAzimuth, gridElevation, antennaPattern); % 路径损耗矩阵不变直接复用 rxPower bsParams.EIRP G - PL; % 覆盖门限固定 -95 dBm coverageRatio(idx) sum((rxPower -95) gridMask) / sum(gridMask); end % 绘图 figure; plot(tiltValues, coverageRatio * 100, o-, LineWidth, 1.5); xlabel(机械下倾角 (度)); ylabel(覆盖像素占比 (%)); grid on;这里唯一需要注意的是下倾角变了之后GetBinGainmultiple 必须重算但路径损耗矩阵可以复用因为它只跟距离有关与天线角度无关。这个细节体现你对仿真链路每个模块输入输出边界的理解比单纯跑出图要加分更多。跑完脚本后把三张覆盖图下倾角 0 度、3 度、6 度放在报告里再配上覆盖率曲线。这三张图放在一起既展示了覆盖半径收缩的过程又能看出塔下覆盖增强的效果比单张最终图的信息量大得多。如果还想再加一个维度可以再加一组站点位置偏移实验把基站从区域中心分别向正东偏移 200 m 和 400 m看覆盖率如何下降但那时候路径损耗矩阵也要跟着重算脚本结构会稍复杂一些。那次做完实验后我就养成了一个习惯每次跑完宏小区仿真不急着看覆盖图先把接收功率直方图画出来。花五分钟检查信号强度是否落在 -120 dBm 到 -20 dBm 的合理区间再回头看覆盖图基本能少翻一大半车。希望这篇拆解对你有帮助也欢迎把你在下倾角扫描里扫到的边界值拿来交流。本文还有配套的精品资源点击获取