附录C: 配置参数解析
C.1 Setting1
C.1.1 pos1-posmode (static-start适用于demo5)

图C.1-1 定位模式
1. 参数解读
由于不会用到 PPP,因此我在研究时会将关注点放在标准定位和基于 RTK 的精密定位上,以下是几种 RTK 模式的解读:
- static:如果 rover 是静止的,使用 static;
- kinematic:如果它是移动的,使用 kinematic(或 static-start);
- static-start:static-start 假设 rover 在第一次 Fix 完成之前是静止的,然后切换到动态模式,程序允许滤波器利用 rover 最开始是静止的先验信息;
- movingbase:如果 base 和 rover 都在移动,那么可以使用 movingbase 模式(通常用来定姿)。movingbase 模式与 dynamics 模式不兼容,请不要同时启用两者。如果 base 与 rover 保持固定距离,在 movingbase 模式下设置
pos2-baselen
和pos2-basesig
; - fixed:如果知道 rover 的确切位置,且只对分析残差感兴趣,可以使用fixed。
2. 源码解析
a. static和static-start
/* udpos (rtkpos.c): temporal update */
if (rtk->opt.mode==PMODE_STATIC||rtk->opt.mode==PMODE_STATIC_START) return;
如果使用了 static 或 static-start ,那么在时间更新 udpos
的时候,不会进行状态预测操作,函数会在预测的代码前返回,这样将加速滤波器的收敛。
b. static-start的状态切换
/* relpos (rtkpos.c): relative positioning */
/* switch to kinematic after qualify for hold if in static-start mode */
if (rtk->opt.mode==PMODE_STATIC_START) {
rtk->opt.mode=PMODE_KINEMA;
trace(3,"Fix and hold complete: switch to kinematic mode\n");
}
执行 relpos
时,在判断首次固定后,static-start 模式会在内部切换为 kinematic 模式。
c. movingbase
定位精度
c/* rtkpos (rtkpos.c): precise positioning */ if (!pntpos(obs+nu,nr,nav,&rtk->opt,&solb,NULL,NULL,msg)) { errmsg(rtk,"base station position error (%s)\n",msg); return 0; } /* if base position uninitialized, use full position */ if (fabs(rtk->rb[0])<0.1) for (i=0;i<3;i++) rtk->rb[i]=solb.rr[i]; /* else filter base position to reduce noise from single precision solution */ else for (i=0;i<3;i++) { rtk->rb[i]=0.95*rtk->rb[i]+0.05*solb.rr[i]; rtk->rb[i+3]=0; /* set velocity to zero */ }
使用 movingbase 模式时,将不会得到精准的定位结果,每个历元基站的位置为其单点定位的位置。此时的定位结果的精度水平将主要取决于单点定位
pntpos
。这里的结果使用了简单的平滑技术。时间同步
c/* relpos (rtkpos.c): relative positioning */ if (opt->mode!=PMODE_MOVEB) { /* check if exceeded max age of differential */ rtk->sol.age=dt; if (fabs(rtk->sol.age)>opt->maxtdiff) { errmsg(rtk,"age of differential error (age=%.1f)\n",rtk->sol.age); free(rs); free(dts); free(var); free(y); free(e); free(azel); free(freq); return 1; } } … /* rtkpos (rtkpos.c): precise positioning */ if (fabs(rtk->sol.age)>MIN(TTOL_MOVEB,opt->maxtdiff)) { errmsg(rtk,"time sync error for moving-base (age=%.1f)\n",rtk->sol.age); return 0; }
movingbase 模式下,基站和流动站通常在一个载体上,RTKLIB 在设计 movingbase 模式时,假设了基站和流动站的观测数据是实时或近实时传输的,因此对时间同步会有更严格的要求,最大的时间差小于1.05s(
TTOL_MOVEB
),此时pos2-maxage
的配置不起作用(准确来讲是换了一种方式作用)。基线约束
c/* ddres (rtkpos.c): double-differenced residuals and partial derivatives */ /* baseline length constraint, for fixed distance between base and rover */ if (rtk->opt.baseline[0]>0.0&&constbl(rtk,x,P,v,H,Ri,Rj,nv)) { vflg[nv++]=3<<4; nb[b++]++; } … /* constbl (rtkpos.c): constraint to baseline length */ v[index]=rtk->opt.baseline[0]-bb; if (H) { for (i=0;i<3;i++) H[i+index*rtk->nx]=b[i]/bb; }
constbl
函数只有在启用 movingbase 模式时才会生效。如果使用基线约束,那么会多一个观测方程(nv++
),约束项通过残差v和H作用到滤波器中。基线长度bb = sqrt(b[0]^2 + b[1]^2 + b[2]^2)
,因此对状态变量x[i]
的偏导为b[i]/bb
。计算航向
c/* outnmea_rmc (solution.c): output solution in the form of NMEA RMC sentence */ if (vel>=1.0) { dir=atan2(enuv[0],enuv[1])*R2D; if (dir<0.0) dir+=360.0; dirp=dir; }
虽然我预期的 movingbase 是用来定姿的,但是我发现输出结果中唯一和姿态相关的物理量只有 NMEA RMC 语句中的航向信息,而该航向信息来自于速度。
不过 RTKLIB 提供了 E/N/U-baseline 的输出格式,此时得到的便是基线向量,可以通过 E 和 N 向速度分量再自行计算一个航向出来。 movingbase 的更多思考可以查看附录A.7 RTKLIB中moving-base模式和基线约束。
4. kinematic
至于其余的逻辑均为 kinematic 。
C.1.2 pos1-frequency

图C.1-2 频段选择
1. 参数解读
- L1:单频接收机使用L1;
- L1+L2/E5b:若 rover 使用 L2 GPS/GLONASS/Beidou 和 Galileo E5b,则用L1+L2/E5b;
- L1+L2+L5:L1+L2+L5则表示 rover 还会使用 L5 GPS/GLONASS/Bediou 和 Galileo E5a 数据。
注意这其中不包含 L1+L5 的单独形式,另外 RTKLIB-demo5从demo5 b34h版本开始对北斗的 B2a 频点开始支持了。
2. 源码解析
频段的选择,在代码层面上最主要的是如何将合适的频点放到指定的逻辑位置上,以北斗信号为例,分析该逻辑是如何实现的。 北斗信号解析过程在RTKLIB中主要在rtcm3.c和rinex.c中,主要关注引用obs2code和code2idx函数的地方,其他星座信号解析方法相同,具体的:
/* 1. 实时解析:save_msm_obs (rtcm3.c) */
/* signal to rinex obs type */
code[i]=obs2code(sig[i]);
idx[i]=code2idx(sys,code[i]);
…
/* get signal index */
sigindex(sys,code,h->nsig,rtcm->opt,idx);
/* 2. 后处理解析:readrnxobsb/set_index (rinex.c) */
/* set signal index */
for (i=n=0;*tobs[i];i++,n++) {
ind->code[i]=obs2code(tobs[i]+1);
ind->type[i]=(p=strchr(obscodes,tobs[i][0]))?(int)(p-obscodes):0;
ind->idx[i]=code2idx(sys,ind->code[i]);
ind->pri[i]=getcodepri(sys,ind->code[i],opt);
ind->pos[i]=-1;
}
以下是解析流程的概述:
设置索引 初始化信号标识的索引,将频率带(如B1-2、B2a、B3)及其调制类型(I、Q、I+Q、数据、导频等)映射到内部数据结构。
obs2code
cstatic char *obscodes[]={ /* rtkcmn.c: observation code strings */ "" ,"1C","1P","1W","1Y", "1M","1N","1S","1L","1E", /* 0- 9 */ "1A","1B","1X","1Z","2C", "2D","2S","2L","2X","2P", /* 10-19 */ "2W","2Y","2M","2N","5I", "5Q","5X","7I","7Q","7X", /* 20-29 */ "6A","6B","6C","6X","6Z", "6S","6L","8L","8Q","8X", /* 30-39 */ "2I","2Q","6I","6Q","3I", "3Q","3X","1I","1Q","5A", /* 40-49 */ "5B","5C","9A","9B","9C", "9X","1D","5D","5P","5Z", /* 50-59 */ "6E","7D","7P","7Z","8D", "8P","4A","4B","4X","" /* 60-69 */ };
此函数将观测码标识(如"2I"、"QX")转换为obscodes数组中的索引(对应RTKLIB中的CODE_*常量)。例如,B1-2/1561.098的"2I"可能映射为CODE_L1I,表示L1频率的同相分量。其中obscodes数组如上所示。
code2idx 确定每个频率数据的存储位置。例如,根据优先级分配位置(如L1在0号位),确保观测数据一致性。其中调用的code2freq_BDS函数,从而最终决定了北斗频点的使用规则。
getcodepri
cstatic char codepris[7][MAXFREQ][16]={ /* rtkcmn.c: code priority for each freq-index */ /* L1/E1/B1I L2/E5b/B2I L5/E5a/B2a E6/LEX/B3I E5(a+b)/B2(a+b) */ {"CPYWMNSL","CPYWMNDLSX","IQX" ,"" ,"" }, /* GPS */ {"CPABX" ,"CPABX" ,"IQX" ,"" ,"" }, /* GLO */ {"CABXZ" ,"XIQ" ,"XIQ" ,"ABCXZ" ,"IQX" }, /* GAL */ {"CLSXZ" ,"LSX" ,"IQXDPZ" ,"LSXEZ" ,"" }, /* QZS */ {"C" ,"IQX" ,"" ,"" ,"" }, /* SBS */ {"IQXDPAN" ,"IQXDPZ" ,"DPX" ,"IQXA" ,"DPX" }, /* BDS */ {"ABCX" ,"ABCX" ,"" ,"" ,"" } /* IRN */ };
评估同一频率下不同码(如I、Q、I+Q)的优先级,使用rtkcmn.c中的codepris数组。
表C-1 RINEX 3.04 北斗观测码
基于预定义优先级选择最高优先级的码,例如I优于I+Q。
C.1.3 pos1-soltype

图C.1-3 滤波类型
1. 参数解读
滤波类型表示卡尔曼滤波在后处理中运行的时间方向。combined模式首先向前运行过滤器,然后向后运行并组合结果。对于每个历元:
- 如果两个方向都有Fix,那么组合的结果是两个Fix状态的平均值,除非两者之间的差异太大,在这种情况下状态将是float的。
- 如果只有一个方向有Fix,则使用该值固定状态。
- 如果两个方向都是float的,那么平均值将被使用,状态将是float的。
使用combined后的结果并不总是更好,因为在任何一个方向上运行时错误的Fix通常会导致合并后的结果是float或异常解。combined的主要优点是,它通常会为您提供数据开头的固定状态,而仅向前的解决方案需要一些时间来收敛。
在combined模式下,偏差状态在开始向后运行之前被重置,以最大限度地提高向前和向后解的独立性。在combined mode-no phase reset中,不重置偏置状态可以避免在反向解开始时需要重新收敛。只有在调试时遇到初始定位困难,想要了解正确的卫星相位偏差时,才使用backward设置。
2. 源码解析
由于我更关注实时的逻辑(类似forward),因此这里我不准备解析backward和combined的代码。
C.1.4 pos1-elmask

图C.1-4 Elevation MASK
1. 参数解读
用于计算位置的最小卫星高度,通常将其设置为 10-15 度,以减少多径效应进入解算的概率,但这个设置取决于 rover 的环境。天空视野越开阔,这个值可以设置得越低。低仰角卫星还会带来较大的大气误差,这也是排除最低仰角卫星的另一个原因。
对于城市峡谷环境,最好将仰角设高。另外在往下的角度范围内,固定率与仰角的设置并不具备可参考的规律。
2. 源码解析
elmask生效的地方主要在两个地方,一处在单点定位的rescode函数中,另一处在相对定位的zdres函数中(注意这里没有考虑PPP,后文同样如此)。
a. 单点定位
/* rescode (pntpos.c): pseudorange residuals */
if (satazel(pos,e,azel+i*2)<opt->elmin) continue;
rescode的主要作用是构建残差v和设计矩阵H,同时计算观测的权重var,rescode的主体部分是遍历所有卫星的伪距观测,而elmask就在此时生效,当检测到卫星仰角低于该阈值(opt->elmin)的时候则跳过该卫星的处理。
b. 相对定位
/* zdres (rtkpos.c): undifferenced phase/code residuals */
if (satazel(pos,e+i*3,azel+i*2)<opt->elmin) continue;
zdres计算的相位、伪距残差,都是基于的原始观测量,还没有进行单差、双差处理。EKF中的观测方程通常不会单独出现,而是以验前残差(新息)的方式存在,因此计算残差就是为了包含观测信息到滤波中(后续的双差残差也是基于非差残差计算而来)。
zdres的主体部分同样是遍历所有卫星的伪距观测,而elmask就在此时生效,当检测到卫星仰角低于该阈值(opt->elmin)的时候则跳过该卫星的处理。
C.1.5 pos1-snrmask

图C.1-5 SNR MASK
C/N0(dB-Hz)是GNSS领域中最标准的信号质量指标。但是由于叫法上的习惯,大家也会称作SNR(信噪比),其具体区别参考附录A.6。
1. 参数解读
snrmask由于两类配置参数共同作用,分别是:pos1-snrmask-r/b和pos1-snrmask_L1/L2/L5。
snrmask的最优值会因接收机类型和天线类型而异,不过在处理较具挑战性的数据集时,最好启用它以移除低质量卫星,从而改善结果。
为每 5 度仰角设置信噪比(SNR)阈值。可以将所有值设为相同,根据标称信噪比选择30到38分贝之间的某个值。这些值仅在pos1-snrmask_x设置为on时使用。如果使用双频,你还需要设置pos1-snrmask_L2及pos1-snrmask_L5。
2. 源码解析
与elmask相同,snrmask生效的地方同样在两个地方,一处在单点定位的rescode函数中,另一处在相对定位的zdres函数中,具体的是在zdres的zdres_sat函数内部(注意这里没有考虑PPP,后文同样如此)。具体的代码可以参考C.1.4节。
a. 单点定位
/* rescode (pntpos.c): pseudorange residuals */
if (!snrmask(obs+i,azel+i*2,opt)) continue;
b. 相对定位
/* zdres_sat (rtkpos.c): UD (undifferenced) phase/code residual for satellite */
if (testsnr(base,0,azel[1],obs->SNR[0]*SNR_UNIT,&opt->snrmask)||
testsnr(base,f2,azel[1],obs->SNR[f2]*SNR_UNIT,&opt->snrmask)) return;
C.1.6 pos1-dynamics

图C.1-6 接收机动态模式
1. 参数解读
将 rover 设置为 dynamic 会在滤波器中为 rover 添加速度和加速度状态。这将改善kinematic和static-start模式的结果,但对static模式没有影响。确保根据 rover 的加速度特性适当设置prnaccelh和prnaccelv。dynamic 与movingbase模式不兼容,因此在使用movingbase模式时需将其关闭。
2. 源码解析
/* rtkpos.c: number of pos parameters */
#define NP(opt) ((opt)->dynamics==0?3:9)
…
/* relpos (rtkpos.c): relative positioning
if (rtk->opt.dynamics) { /* velocity and covariance */
for (i=3;i<6;i++) {
rtk->sol.rr[i]=rtk->xa[i];
rtk->sol.qv[i-3]=(float)rtk->Pa[i+i*rtk->na];
}
rtk->sol.qv[3]=(float)rtk->Pa[4+3*rtk->na];
rtk->sol.qv[4]=(float)rtk->Pa[5+4*rtk->na];
rtk->sol.qv[5]=(float)rtk->Pa[5+3*rtk->na];
}
启用了dynamic,系统状态会增加6个维度,分别是速度和加速度,这样的状态约束会更紧凑,从而提升了滤波的性能。
C.1.7 pos1-posopt5

图C.1-7 RAIM
1. 参数解读
如果某颗卫星的残差超过阈值,该卫星将被排除。这只会排除误差非常大的卫星,但需要相当多的计算量,因此通常将其禁用。
2. 源码解析
/* pntpos (pntpos.c): single-point positioning */
/* RAIM FDE */
if (!stat&&n>=6&&opt->posopt[4]) {
stat=raim_fde(obs,n,rs,dts,var,svh,nav,&opt_,ssat,sol,azel_,vsat,resp,msg);
}
RAIM作用在单点定位中,实际上它带来的增益并不明显。
RAIM FDE 是在此前单点定位失败后触发。RAIM 通过逐一排除一颗卫星(即子集测试),使用剩余卫星重新调用 estpos 计算定位解。对于所有定位成功的子集(可用卫星数 ≥ 5),RAIM 会计算残差均方根(RMS),并选择 RMS 最小的一组作为最终解,并更新接收机位置和其他输出参数。如果没有子集显著优于初始解,RAIM 可能不剔除任何卫星。
C.1.8 pos1-exclsats

图C.1-8 指定剔除的卫星
1. 参数解读
pos1-exclsats用于设置定位时排除的卫星。输入以空格分隔的卫星PRN编号。对于GLONASS、Galileo、QZSS、北斗和SBAS,分别使用Rnn、Enn、Jnn、Cnn和Snn(nn:卫星PRN或槽号)。如果在卫星ID前加上“+”,表示即使卫星状态不佳,该卫星也会被包括在定位中,通常强制添加一颗不健康的卫星用于算法分析。
2. 源码解析
与elmask相同,exclsat生效的地方同样在两个地方,一处在单点定位的rescode函数中,另一处在相对定位的zdres函数中(注意这里没有考虑PPP,后文同样如此)。
a. 单点定位
/* rescode (pntpos.c): pseudorange residuals */
if (!snrmask(obs+i,azel+i*2,opt)) continue;
b. 相对定位
/* zdres (rtkpos.c): UD (undifferenced) phase/code residual for satellite */
if (satexclude(obs[i].sat,var[i],svh[i],opt)) continue;
C.1.9 pos1-navsys

图C.1-9 星座选择
1. 参数解读
尽量启用所有可用的卫星系统,因为更多观测信息通常更好。例外情况是,如果使用EGNOS卫星,则不应启用SBAS(卫星增强系统)。如果是直接在config文件中设置pos1-navsys的设定,以下是一些常见的取值:
- 41:GPS+GAL+BDS
- 57:GPS+GAL+QZS+BDS
- 61:GPS+GLO+GAL+QZS+BDS
最终取值为1:GPS+2:SBAS+4:GLO+8:GAL+16:QZS+32:BDS+64:NAVIC。在国内CORS运营商没有播发GLO卫星的差分数据,因此通常做相对定位解算时不会考虑GLO卫星,并且GLO卫星的处理也很麻烦,因为它是基于FDMA的,每颗卫星的频段都不同。而欧美场景下没有QZS卫星,它是区域性星座,因此模拟国外场景的话,不会考虑QZS。
2. 源码解析
/* rtksvrthread->decoderaw->update_svr->update_obs */
/* update_obs (rktsvr.c): update observation data */
if (svr->rtk.opt.exsats[sat-1]==1||!(sys&svr->rtk.opt.navsys)) {
continue;
}
/* procpos (postpos.c): process positioning */
if ((satsys(obs_ptr[i].sat,NULL)&popt->navsys)&&
popt->exsats[obs_ptr[i].sat-1]!=1) obs_ptr[n++]= obs_ptr[i];
这部分代码并不在算法代码rtkpos.c和pntpos.c中,而是在更外层的应用层,分别对应了实时程序rtksvr.c和后处理程序postpos.c。