
搞GNSS的人谁没被多路径恶心过测着测着伪距突然跳上几米RTK明明在开阔地就是固定率上不去卫星颗数看着不少可结果历元之间像喝醉了酒。先别急着怀疑接收机硬件多数时候是多路径效应在背后捣乱。这次要拆的这个Matlab源码项目标题写得很直白——【卫星】GNSS多路径效应分析【含Matlab源码 15170期】。它是一套可以直接运行的GNSS多路径仿真分析工程做的事情就是用程序生成直达卫星信号叠加上经地面或墙面反射产生的多径信号再通过相关、跟踪这两步把伪距误差和载波相位误差估计出来最后画成时间曲线、仰角误差曲线、频谱图一类的结果。说白了这套代码就是一台“多径制造机加误差观察仪”。你可以任意改天线高度、反射系数、卫星仰角看多径误差怎么变。适合哪些人GNSS方向的课程设计、毕业设计、算法验证或者做RTK、自动驾驶定位的工程师想快速建立多径直觉都能用它当起跑平台。1. 项目在做什么用一场Matlab仿真实战看清多路径效应1.1 多路径效应为何让GNSS工程师头疼GNSS定位涉及的误差源其实很多卫星端的钟差和星历误差、空间传播路径上的电离层延迟和对流层延迟以及接收机端的噪声和多路径效应。前面那几个误差源业界已经有一套比较成熟的应对手段双频消电离层、模型或参数估计控制对流层、精密星历削星历误差。但多路径效应是“近端”误差它发生在接收机周边几米甚至几十米的物理环境里每一个测站的多径特征都不一样很难用统一模型扣掉。多路径效应的产生原理不复杂卫星信号除了沿着视线直达接收机天线还会打在周边地面、水面、建筑物墙面、车辆金属外壳上形成一次或多次反射信号。这些反射信号进入天线后与直达信号叠加在一起。接收机的码跟踪环和载波跟踪环看到的不再是“一个干净的相关峰”而是被反射信号扭曲过的合成信号。跟踪环被带偏伪距就产生从分米到几十米的偏差载波相位则出现厘米级甚至更大的抖动。更要命的是多径的特性。它跟观测热噪声不一样不是零均值的随机误差。卫星在天空运动反射路径的长度也在变化导致多径误差在时域上像一条缓慢振荡的曲线。也就是说它不是“平均一下就能消掉”的东西。在城市峡谷、水面、玻璃幕墙附近这类强反射环境多径误差甚至能盖过其他所有误差成为载波相位模糊度解算失败的首要嫌疑。误差源典型量级时空特性常规抑制手段电离层延迟分米到米级缓慢变化有日周期双频组合、电离层模型对流层延迟米级与气压、湿度、高度相关模型改正、参数估计星历误差米级随卫星钟差和轨道变化精密星历、差分观测热噪声毫米到厘米级高频随机、零均值滤波平滑、提高信噪比多路径效应分米到数十米低仰角明显、时变振荡天线抑制、接收机算法、环境选址所以做GNSS数据处理的人看到伪距异常跳变、RTK固定率突然下降脑子里第一反应就应该是这环境是不是有强反射体多径在这个时段是不是特别活跃1.2 这套源码的使用定位与预期效果先把预期拉到一个正确的位置这套源码是仿真分析工具不是实时接收机后处理软件。它能告诉你多径误差怎么产生、受哪些参数影响、“一条正常的多径误差曲线长什么样”。你不能指望拿真实采集的观测文件喂进去它就自动把多径误差分离出来——那是另一个维度的活了。这套源码最直接的使用体验是设置参数跑通脚本看到误差曲线。整个过程不需要真实卫星信号、不需要硬件接收机对入门者非常友好。你可以把天线高度从2米改成5米观察多径延迟和误差振荡频率的变化也可以把反射系数从0.2调到0.9看强反射环境下误差幅度如何非线性增长。适合这几类人看GNSS导航定位方向的本科生、研究生做课程设计或毕业论文里的仿真验证部分刚接触RTK、PPP的工程师想理解“为什么我的固定率总是不稳”想做多径抑制算法但缺乏数据的人先用仿真把算法跑通再迁移到真机数据上。一个常被新手问的问题是仿真出来的结果跟真机实测能一一对上吗答案是“很像但无法完全一致”。真实多径是三维电磁环境问题反射面的形状、介电常数、粗糙度、水汽都会影响反射信号仿真通常做的是镜面反射加一个衰减系数的简化模型。但简化模型抓住的是核心机理教你建立“某个参数变化误差跟着怎么变”的直觉这是真实数据很难给你的因为真实数据里所有误差耦合在一起你根本分不清哪段波动是多径哪段是电离层残差。2. 误差从哪来多路径效应的物理机理与观测特征2.1 反射、散射与天线相位中心在经典多径分析里最常用的是“水平反射面的镜面反射模型”。设想接收机天线架在高度为h的地方卫星仰角为el来自卫星的直达信号以仰角el入射有一部分能量被地面镜面反射再进入天线。几何上可以证明反射信号比直达信号多走的路径长度大约是ΔL 2·h·sin(el)如果天线高度h2米卫星仰角el30°那么附加路程ΔL2×2×0.52米对应的附加时延是ΔL除以光速约6.7纳秒。C/A码的码片长度是293米这段附加路程只有0.0068个码片从码域看是一个非常小的量。这也是为什么多径很难被简单消除的原因之一——它离直达相关峰太近了常规的相关器间隔根本分辨不开。不过要注意这个镜面反射模型是理想化的。真实的反射面分为两类一类是水面、金属面、平整玻璃幕墙这类光滑表面反射信号能量集中、相位关系稳定形成的多径是“镜面反射型”另一类是粗糙地面、碎石路面、草地这类漫反射面反射能量被打散叠加出来的效果更像抬高噪声基底。这两类多径的时域表现不一样前者是明显振荡的曲线后者是看起来像噪声但又有偏置的波动。天线相位中心这个概念也要提一下。抗多径天线比如扼流圈天线的核心做法就是压低低仰角方向的天线增益让从地面反射过来的信号进不来或少进来。但天线相位中心本身也会随信号入射方向变化多径信号进入天线后的实际相位中心与直达信号不完全一致这正是很多高精度测量要求用户使用相位中心校准文件的原因。手机里的GNSS天线尺寸很小低仰角增益裁剪能力非常有限所以手机定位在城市里更容易被多径干扰这也是这几年Android原始GNSS数据研究越来越热的原因之一。2.2 对伪距与载波相位的差异影响多径对伪距和载波相位的影响机制完全不同需要分开理解。伪距测量靠的是码跟踪环。接收机本地生成一份C/A码副本用超前、即时、滞后三路相关器与接收信号做相关运算通过比较三路相关值的大小来调整本地码相位。当反射信号叠加进来时相关函数的主峰不再是干净对称的三角形而是被反射信号的小峰拉动主峰顶部出现斜率变化码环就会把跟踪点移向错误位置。多径对伪距的误差范围可以从分米到几十米取决于反射信号强度、多径时延与码片间隔的关系、以及相关器的间距。窄相关器技术把超前滞后间距从1个码片压缩到0.1个码片甚至更小就是为了削弱长延迟多径的影响但短延迟多径依然棘手。载波相位测量靠的是载波跟踪环也就是锁相环。反射信号与直达信号叠加后合成信号的相位会偏离直达信号的相位。设直达信号幅度为A反射信号幅度为αA两者相位差为Δφ那么合成信号相对直达信号的相位偏差可以写成δφ arctan[α·sin(Δφ) / (1 α·cos(Δφ))]这个公式很关键。当反射很强、α接近1又恰好Δφ落在90度附近时载波相位误差可以达到理论最大值约四分之一的载波波长。L1载波波长约19厘米四分之一就是4.8厘米。听起来不大但对RTK这种依赖厘米级载波相位观测值的定位方式来说4.8厘米的偏差足以破坏模糊度解算让固定解变成浮点解。还有一个非常典型的观测特征多径造成的载波相位误差不是固定值而是随Δφ变化Δφ又随卫星运动、天线位置、反射面距离不断变化。所以时域上看到的载波相位多径误差通常是一条平滑振荡的慢变曲线周期从几分钟到几十分钟不等。这种“有户口的误差”很容易被误判为接收机钟跳或者电离层扰动需要结合仰角和环境信息综合判断。2.3 多路径与噪声、电离层误差的区别搞GNSS数据处理最怕的就是把多径当成噪声或者把噪声当成多径。两者在统计特性上有本质区别热噪声是零均值、宽带、不相关的多径是有偏置、窄带、与几何强相关的。对热噪声做平滑滤波效果明显对多径做平滑只能削掉上面叠加的随机噪声削不掉那个缓慢变化的偏置项。与电离层误差比多径也有明显的识别特征。电离层误差在同一颗卫星、同一频段上是慢变的且在双频观测值之间满足特定色散关系多径则与频段之间没有这种稳定的比例关系。如果打开观测文件看双频伪距的残差序列一个频段跳了另一个频段没怎么跳或者两个频段跳的幅度不成比例那大概率就是多径而不是电离层残余。再加上多径误差与卫星仰角高度相关低仰角卫星的多径残差波动明显增大这是识别它的常用线索。实际中判断多径还有一个笨但有效的办法看信噪比或载噪比。反射信号与直达信号叠加后合成信号的幅度会随Δφ起伏C/N0曲线上会出现规律性的振荡振荡周期与多径相位变化周期对应。这个特征在GNSS接收机的原始观测量里很容易看到很多多径研究正是从C/N0序列入手反演反射面的距离和特性也就是GNSS-IR技术的思路。3. 源码架构与技术拆解仿真信号叠加与误差估计是怎么实现的3.1 拿到源码后的目录结构与模块画法不同作者的源码文件组织差异很大但常见的工程设计思路是一致的入口脚本负责参数和流程控制功能尽量拆成独立函数。拿到压缩包后建议先看目录结构把文件按职责分组。一个典型的多径分析工程通常会包含下面这些模块。GNSS_Multipath_Analysis/ ├── main_analysis.m % 主脚本参数设置 调用各函数 ├── genCACode.m % 生成C/A码序列 ├── addMultipath.m % 构造直达多径合成信号 ├── estRangeError.m % 相关峰/相位偏差估计 └── plotResults.m % 结果绘图我按常见实践做了模块划分实际拿到的版本可能命名不同但角色基本一致。读代码的顺序推荐是先跑通主脚本再进函数里看别一上来就啃C/A码生成。把断点打在addMultipath函数入口逐步向下看你就能直观理解多径是怎么叠进信号的。如果压缩包里只有一个主脚本那就先找到参数区的注释确认哪些变量可以改。3.2 直达信号仿真与观测值构造生成直达信号的套路在Matlab仿真里很成熟。以GPS L1频点为例载波频率是1575.42MHzC/A码码率是1.023MHz一个C/A码周期是1023个码片。仿真时不需要真的生成10.23MHz带宽的真实射频信号通常降采样到某个便于计算的采样率生成基带或中频信号即可。% main_analysis.m 中的关键参数区示意 c 2.99792458e8; % 光速m/s fL1 1575.42e6; % L1 载波频率Hz fs 62.5e6; % 仿真采样率Hz fc 1.023e6; % C/A码码率chip/s hAnt 2.0; % 天线相位中心离反射面高度m elSat 30*pi/180; % 卫星仰角rad alpha 0.6; % 反射信号电压幅度系数0~1 SNR_dB 30; % 信噪比dB这里的采样率选择有讲究。62.5MHz意味着每个C/A码片大约有61个采样点看起来精度不错。但多径附加时延6.7纳秒在62.5MHz采样率下只对应0.42个采样点量化到整数采样点后经常变成0多径就“消失”了。所以仿真代码里要么把采样率提高要么不通过物理采样直接“在相关域叠加多径”后者是很多理论研究代码采用的方式。看到代码里直接用码相位偏移计算多径延迟而不是用采样点取整说明作者已经考虑过这个问题。生成C/A码的方式也值得留意。可以通过查表得到1023位Gold码序列再用repmat或circshift扩展成需要的长度。本地码副本需要生成超前、即时、滞后三路超前滞后间隔参数EL_spacing通常设到0.1或0.2个码片这也是工程上窄相关器的常见取值。3.3 多径叠加、误差估计与参数选择多径叠加是整个源码的核心。基于前面提到的镜面反射模型附加路程和多径时延的计算就两行dExtra 2*hAnt*sin(elSat); % 附加路程m tauM dExtra/c; % 附加时延s合成信号在基带可以写成r(t) A·c(t-τ0)·e^{j(ωtφ0)} α·A·c(t-τ0-τM)·e^{j(ω(t-τM)φ0φM)} n(t)其中第二项就是多径信号α是反射电压幅度系数φM是反射路径的附加相位。注意φM非常关键它决定了某个时刻反射信号与直达信号是“同相叠加”误差变大还是“反相抵消”误差变小。好一点的源码会让φM随仿真时间步进变化模拟卫星运动带来的几何变化这样误差曲线才会呈现真实的振荡形态。如果φM固定你看到的就只是一个静态偏差完全体现不出多径的时变特性。误差估计模块通常有两种实现。一种是做相关峰检测用本地码和接收信号做互相关找到相关峰位置与真实直达位置之差反推伪距误差另一种是模拟锁相环或锁频环估计载波相位偏差。前者的核心思路示意如下% 用即时码相关峰位置估计伪距误差示意 corrFunc xcorr(rxSignal, localCACodeMid); [~, peakIdx] max(abs(corrFunc)); rangeErr (peakIdx - refIdx) / fs * c;这段代码是思路示意真实工程里会用解扩、鉴相器、环路滤波器做更完整的跟踪环路模拟。但从理解多径角度看相关峰偏移就是多径在伪距上最直接的体现。你只需要理解反射信号把相关峰拉偏了拉偏量就是伪距误差。参数选择是玩这套仿真最有趣的部分。反射系数α从0取到0.9误差会非线性增长0.9意味着反射信号幅度几乎等于直达信号合成信号相位被严重扰动这也是金属围栏、玻璃幕墙这些环境为什么对GNSS如此不友好的原因。卫星仰角el从5度扫描到90度可以得到一条“误差-仰角包络线”低仰角误差大高仰角误差趋近于零。把这条包络线存下来就能用来设计接收机随机模型中的仰角加权函数这是仿真直接落地工程的一个典型场景。3.4 分析模块从误差序列到结论分析模块负责把误差序列变成可视化和结论。至少应该输出三类图多径误差时域曲线、误差随仰角变化的散点或包络图、合成信号的相关峰对比图。时域曲线让你直观看到振荡特性仰角包络图把“低仰角多径严重”这条铁律量化相关峰对比图则展示反射信号如何扭曲主峰形状。验证仿真代码正确性有三个边界测试这是我调试时最常用的方法仰角取90度时2·h·sin(el)等于2h多径延迟最大但实际场景里卫星在头顶时地面反射路径最长天线高度h取0时附加时延应该是0伪距误差应该只剩噪声反射系数α取0时相当于没有反射信号误差序列应该完全由噪声主导。这三个测试如果都符合预期说明模块从信号生成到误差估计的链路基本可信。4. 实操流程从Matlab环境到结果出图的完整过程4.1 环境准备与运行入口拿到源码后第一步不是改参数而是先把环境跑通。推荐在Matlab R2018b以上版本运行部分源码会用到字符串数组、tiledlayout这类较新的语法旧版本直接报错。工具箱方面至少需要Signal Processing Toolbox涉及滤波器和FFT处理有的版本会用到Communications Toolbox如果缺了报错时按提示处理。不建议到处找“万能兼容版本”直接装一个相对新的Matlab更省心。运行入口就是主脚本。打开后确认当前文件夹在源码目录再把目录添加到Matlab路径然后按F5运行。如果Command Window里出现变量输出说明代码跑通了。接着检查Figure窗口看绘图函数有没有被注释掉。有些分享版源码会把绘图部分用注释块包起来只输出数据你要自己取消注释才能看到图。这里有一个很实际的提醒源码路径和文件名尽量用英文不要带中文和空格。Matlab在Windows下对中文路径偶尔能处理但遇到“【”这类全角字符或者压缩包解压后嵌套层级太深的情况就容易出现“Unable to read file”之类的错误。把工程放到D:\GNSS_Multipath\这种干净的路径下能排除一大批莫名其妙的问题。中文注释乱码也是常见现象。源码是老版本Matlab写的注释用GBK编码在R2020b之后打开可能显示成乱码但一般不影响运行。想彻底解决可以用Notepad或VS Code把源文件转成UTF-8编码。转码前先备份防一手转码损坏脚本。4.2 关键参数该怎么改三组实用调整建议参数区是这份源码里你唯一需要频繁动手的地方。我整理了三组最实用的调整方向参数默认值示例改变什么建议操作天线高度 hAnt2.0 m多径附加时延大小取1、2、5、10米各跑一次观察伪距误差幅度变化反射系数 alpha0.6反射信号强度从0到0.9递增扫描看误差非线性增长的规律卫星仰角 elSat30°误差大小与周期做5°到85°扫描画出多径误差随仰角的包络线采样率 fs62.5 MHz时延分辨能力降到10MHz试试体会采样率不足时多径被“吞掉”的现象举个例子我想看低仰角情况下多径有多严重把elSat从80度一路扫到5度每次跑完记录误差极值最后把极值连成线。这样得到的曲线可以直接用于接收机随机模型设计——低仰角卫星的观测值权重调低就是靠这条曲线提供依据。再看反射系数扫描。α从0到0.9误差并不是线性增长的而是越来越快。当α接近1时反射信号与直达信号幅度相当合成信号的幅度可能深衰落相关峰的形态会被严重破坏仿真出来的误差曲线会非常“暴躁”。这个规律对理解真实环境很有帮助在金属围栏旁的接收机其观测质量不是“稍微差一点”而是“崩盘式恶化”。4.3 输出图怎么读别把仿真结果误读成真实数据拿到输出图第一步先别急着下结论看清楚了再说话。时域误差曲线呈现“正弦振荡”形态是正常的。这个振荡周期与卫星相对反射面的几何变化速度有关在真实验收数据里它表现为伪距残差的慢变波动。很多人第一次看到这种曲线会怀疑代码写错了觉得误差应该是随机噪声才对但恰恰是这种有规律、慢变的振荡才是多径区别于热噪声的本质特征。相关峰对比图最容易说明问题。把直达信号的相关峰和多径合成信号的相关峰叠在一起你会看到主峰顶部变圆、峰值偏移、甚至出现侧峰。峰值偏移量乘以光速就是伪距误差侧峰的强度和位置对应反射信号的多径时延。如果代码里画了载波相位误差随时间的变化你还能看到相位误差在±90度象限附近出现明显跳变特征这正是公式δφarctan[...]的典型表现。频谱图需要结合振荡周期来看。如果时域误差曲线的振荡周期大约5分钟频谱图应该在对应频率处出现一个明显的窄峰。这个窄峰越尖锐说明多径越接近单路径模型真实环境里多条反射路径叠加频谱会变成宽峰时域曲线也会更像噪声。换句话说从频谱宽度也能大致推测反射环境的复杂程度。但请记住仿真数据的容错性很好真实数据里谱峰会被卫星运动、电离层变化、接收机钟差这些因素污染读图时别把仿真结论直接套到真实数据上。5. 常见问题与排查技巧一份能帮你脱坑的实录5.1 一跑就报错的速查表这套源码运行起来不算复杂但新手遇到报错容易慌。我把最常见的几类问题整理成一张表按图索骥能省不少时间。报错现象可能原因处理方式提示 Unrecognized function缺少对应工具箱查看报错的函数名安装Signal Processing或Communications ToolboxMatrix dimensions must agree数组维度不匹配检查采样点数N与C/A码长度是否一致重新核对函数的输入参数中文注释显示乱码编码不一致用Notepad转为UTF-8编码运行不受影响就不用管Unable to read file路径含中文或全角符号把工程移到纯英文路径下Out of memory采样点数太大降低采样率或仿真时长优先保证可运行Figure窗口空白绘图函数被注释找到plotResults部分取消注释R2020a版本语法错误使用了新语法升级Matlab版本或手动替换成兼容写法我自己遇到最多的是“Out of memory”不是机器内存不够而是采样率设太高、仿真时长设太长把所有样本都一次性存入内存导致的。解决思路是分段生成、分段处理不要一次申请一个巨大的数组。如果代码里本身是整段生成再处理那就先缩短仿真时长跑通了再拉长。5.2 仿真结果“看不到多径”的四个原因比报错更挫败的是代码顺利跑完但图上根本看不到多径效果。误差曲线平得像一条直线。整理一下绝大多数是下面四个原因。第一个原因是仰角过高加反射系数过小。卫星仰角80度时2·h·sin(el)仍然不小但反射信号本身很弱α取0.1叠加效果几乎淹没在噪声里。把仰角降到10到30度α调到0.5以上多径会立刻“显形”。第二个原因是采样率不够导致多径延迟被量化成0。前面的计算已经说明低采样率下6.7纳秒的多径时延根本分辨不出来。这也是很多理论仿真代码不直接生成射频采样信号而是在相关域叠加多径的原因。拿到源码后先确认它用的是“物理采样”还是“相关域叠加”物理采样方案必须提高采样率或直接用分数延迟滤波器。第三个原因是多径相对相位φM恰好落在0度或180度附近。0度时反射信号与直达信号同相叠加误差方向是往一个方向偏但幅度小180度时两者反相抵消合成信号幅度下降但相位可能不偏。解决办法是让φM随机化或者直接扫描φM从0到360度观察误差的包络范围。好的源码会自动让φM随时间变化如果你拿到的版本把它写死了建议改成循环变化。第四个原因是图窗里画的是“没有叠加多径”的信号。检查一下代码流程是不是addMultipath函数被注释掉了是不是alpha被设置成0了是不是主流程里调用的是纯直达信号生成函数这听起来很低级但确实发生过特别是从网上拼凑的demo里最容易出这种问题。5.3 从仿真走向真实环境多径抑制手段与手机原始GNSS数据仿真跑明白之后自然会想知道真实验证和工程上怎么处理多径。先看硬件层面的传统手段扼流圈天线是目前高精度GNSS里最经典的抗多径方案它用一圈圈金属环结构压制低仰角方向的增益从空间域上挡住反射信号。架站时选择远离水面和高大建筑物的场地也是一种低成本的环境抑制手段。这类手段在长期运行的基准站、CORS站里是标配。接收机算法层面窄相关器技术、双Delta相关器、多径估计延迟锁定环MEDLL都是围绕码环相关峰畸变做文章。窄相关器把超前滞后间距从1码片压到0.1码片甚至更小显著削弱延迟大于一个码片的多径MEDLL则用多个相关器样本去拟合直达信号加多径信号的复合相关函数然后把多径参数估计出来并剔除。载波平滑、双频组合也能在一定程度上削弱伪距多径。后处理层面最常见的做法是仰角加权随机模型低仰角卫星观测值权重低等价于“把低仰角多径的恶劣影响先压下去”。现在还有一个很有意思的方向Android手机原始GNSS数据。目前很多主流安卓手机都支持通过API输出GNSS原始测量值Google官方的GNSS Logger应用可以直接记录伪距、载波相位、C/N0和信号状态。小米等多个品牌机型对这个功能支持得不错价格比专业接收机便宜得多。城市高楼环境下手机天线口径小、没有扼流圈多径特别明显这反倒成了研究多径的低成本试验场。你可以把Android原始测量值中的C/N0振荡序列与这套仿真里的理论多径振荡规律做对照体会从仿真到真实环境的跨越。先有仿真建立直觉再去折腾手机原始数据会少走很多弯路。6. 我的实操体会与下一步扩展这套仿真做完我最大的体会是多径既讨厌又迷人。说讨厌是因为它有偏、随几何变化、不能被简单平滑掉是RTK在复杂环境里的主要绊脚石说迷人是因为它的物理机制清晰、规律性强一旦你熟悉了“仰角低了误差大、反射面近了振荡快”这些规律再去看真实观测数据的残差就能一眼认出多径的特征。这种“认得出”的能力是任何公式和文档都替代不了的只能靠反复仿真、反复对比真实数据来积累。如果你想进一步扩展这套源码方向不少。一是把单反射面模型扩展成多反射面加入竖直墙面和车辆金属表面模拟城市峡谷环境二是把信号分析部分改成GNSS-IR利用信噪比振荡反演湖面水位、雪深或者土壤湿度这是目前GNSS遥感领域的一个活跃分支三是把输出的多径误差序列与RTK解算模块对接模拟多径环境下模糊度固定率的下降规律。无论往哪个方向走都建议先把单反射面、单颗卫星的工况跑透建立了肉眼判断误差曲线的能力再谈复杂模型。没有这个基础数据一多你会分不清哪段波动来自多径哪段只是随机噪声。多径仿真这件事慢就是快。