PPP/INS紧组合代码学习

前言:

本文是基于IGNAV的PPP/INS紧组合学习,在此之前需要具备GNSS/INS松组合知识,武汉大学的i2nav实验室的KF-GINS项目可以作为学习模板。可以参考这篇优秀博文,链接:KF-GINS源码阅读_李郑骁学导航的博客-CSDN博客 IGNAV是基于RTKLIB进行二次开发的,因此熟悉RTKLIB源代码的学习起来会容易一些。我的这篇博文也大概介绍了RTKLIB中PPP模块,可做简略学习:RTKLIB学习(二)–2、PPP代码分析-CSDN博客 在学习一个项目之前,对算法的学习至关重要,对算法的理解程度很大程度上决定了你对代码的理解能力,因此IGNAV项目中man文件夹内的theory.pdf。算法文件中6.3.2-GNSS 伪距与载波观测信息辅助INS的过程推到,务必花时间理解。我的这篇文章总结了i2nav团队关于PPP/INS紧组合学习知识也可作为算法学习参考:PPP/INS紧组合算法-CSDN博客
本人学习能力有限,不足的地方请批评指正。

一、IGNAV的PPP/INS流程

下图是IGNAV中PPP/INS紧组合流程。其中rtksvrthread是定位计算线程,tcigpos是紧组合入口函数,也是本文主要学习对象。对文件读取,输出的内容不做介绍!

在这里插入图片描述

updatetimediff()

 /* ins tightly coupled position mode */if (svr->rtk.opt.mode==PMODE_INS_TGNSS) {if (syn->ni&&syn->nr) {//IMU和流动站时间对齐索引均不为0syn->dt[0]=time2gpst(syn->time[2],NULL)-//IMU时间-流动站观测值时间time2gpst(syn->time[0],NULL);}if (syn->ni&&syn->nb) {//基站模式syn->dt[1]=time2gpst(syn->time[2],NULL)-time2gpst(syn->time[1],NULL);}if (svr->rtk.opt.insopt.pose_aid||svr->rtk.opt.insopt.align_dualants) {//双天线模式if (syn->np&&syn->ni) {syn->dt[2]=time2gpst(syn->time[5],NULL)-time2gpst(syn->time[2],NULL);}}}

在PPP/INS紧组合中,主要更新**dt[0]:**当前历元的IMU时间-流动站时间

timealign()

此处进入紧组合时间对齐

if (svr->rtk.opt.mode==PMODE_INS_TGNSS) return imuobsalign(svr);

imuobsalign()

    int i,j,k,n; double sow1,sow2,sow3;obs_t *p1=NULL,*p2=NULL;syn_t *psyn=&svr->syn;/* observation and imu data time alignment */n=psyn->of[2]?MAXIMUBUF:psyn->ni;//n为IMU观测历元数p1=svr->obs[0];//流动站观测值p2=svr->obs[1];//基站观测值

在navlib.h中查看syn_t结构体(存储了各类数据的索引):

typedef struct {         /* time synchronization index in buffer */int ni,nip,imu;      /* current/precious number and sync index of imu measurement data  */int nr,nrp,rover;    /* current/precious number and sync index of observation data */int ns,nsp,pvt;      /* current/precious number and sync index of pvt solution data */int nb,nbp,base;     /* current/precious number and sync index of base observation data */int nm,nmp,img;      /* current/precious number and sync index of image raw data */int np,npp,pose;     /* current/precious number and sync index of pose measurement */int of[6];           /* overflow flag (rover,base,imu,pvt,image,pose) */unsigned int tali[6];/* time alignment for data (rover-base,pvt-imu,rover-base-imu,imu-image,pose-imu) */unsigned int ws;     /* search window size */double dt[6];        /* time difference between input streams */gtime_t time[6];     /* current time of rover,base,imu,pvt,image and pose measurement */
} syn_t;

在imuobsalign()中时间对齐只能匹配一个IMU和GNSS观测值,且对齐方式与KF_GINS的区别还是挺大的。

/* observation and imu data time alignment */n=psyn->of[2]?MAXIMUBUF:psyn->ni;//n为IMU观测历元数p1=svr->obs[0];//流动站观测值窗口p2=svr->obs[1];//基站观测值窗口for (i=0;i<n&&svr->syn.tali[2]!=2;i++) { /* start time alignment 从窗口内第一个IMU观测值开始*/sow1=time2gpst(svr->imu[i].time,NULL);//将imu时间转换为周内秒/* match rover observation匹配流动站观测值 */for (j=0;j<(psyn->of[0]?MAXOBSBUF:psyn->nr);j++) {//循环遍历流动站每一个历元观测值sow2=time2gpst(p1[j].data[0].time,NULL);//将流动站观测值时间转换为周内秒if (p1[j].n) {//流动站观测值存在if (fabs(sow1-sow2)>DTTOL) continue;//流动站观测值时间不在IMU时间阈值0.0025s内}psyn->imu    =i;//记录第i个IMU匹配到相应的流动站观测值psyn->rover  =j;//记录第j个流动站观测值匹配到相应的IMU观测值psyn->tali[2]=1;//完成标识break;}/* match base observation 基站同上*/if (psyn->tali[2]==1) {for (k=0;k<(psyn->of[1]?MAXOBSBUF:psyn->nb);k++) {sow3=time2gpst(p2[k].data[0].time,NULL);if (p2[k].n) {if (fabs(sow2-sow3)>DTTOLM) continue;}else continue;psyn->base   =k;psyn->tali[2]=2;break;}}if (psyn->tali[2]==2) {//匹配完一个GNSS观测值就退出tracet(3,"imu and rover/base align ok\n");return 1;}else psyn->tali[2]=0; /* fail */

inputobstc()

 if ((obsd.n=inputobstc(svr,imus.data[i].time,obsd.data))) {j=INSUPD_MEAS;}

**rtksvr_t *srv:**RTK server type囊括所有原始数据,中间数据,结果数据,参数设置等

**imus.data[i].time:**imu数据窗口第i个数据的时间

**obsd.data:**GNSS观测值窗口内某个历元的观测数据,为空

    const obs_t *robs=svr->obs[0],*bobs=svr->obs[1];//流动站与基站观测数据窗口/* match rover observation data 匹配流动站观测值数据*/for (i=0,dt=0.0,n=NS(svr->syn.rover,svr->syn.nr,MAXOBSBUF);i<n+1&&svr->syn.nr;i++) {j=svr->syn.rover+i;//timealign()匹配的观测值所在历元索引+iif (j>=MAXOBSBUF) j=j%MAXOBSBUF;//超出历元窗口时,余上窗口大小if (dt&&fabs(dt)<fabs(timediff(time,robs[j].data[0].time))) {break;//第j个历元的时间与目标IMU测量时间大于大于指定阈值}if (fabs((dt=timediff(time,robs[j].data[0].time)))<DTTOL//当前观测值历元时间与指定IMU时间小于阈值时&&robs[j].n!=0) {//该GNSS历元内观测值数不为0for (k=0,m=0;k<robs[j].n;k++) obs[m++]=robs[j].data[k];//将时间对齐的数据输入到obs[]svr->syn.rover=j; tr=obs[0].time;//记录流动站观测值历元索引和历元时间flag=1; break;//流动站配置完毕}}

inputimu()

/* input imu measurement data输入IMU数据并确保正确的时间对齐。-----------*/
static int inputimu(rtksvr_t *svr,imud_t *data)
{int i,j,k,n=0;tracet(3,"inputimu:\n");/* check time alignment of input streams */if (!svr->syn.tali[1]&&!svr->syn.tali[2]&&!svr->syn.tali[3]) {trace(2,"check time alignment fail\n");return 0;}for (i=0,k=0,n=NS(svr->syn.imu,svr->syn.ni,MAXIMUBUF),j=svr->syn.imu;i<n+1;i++) {//j为IMU时间对齐索引j=svr->syn.imu+i;//从对齐索引的数据开始if (j>=MAXIMUBUF) j=j%MAXIMUBUF;//超出MAXIMUBUF时,j取模循环if (k<MAXIMU) data[k++]=svr->imu[j]; else break;//提取数据}svr->syn.imu=(j+1)%MAXIMUBUF;//重置时间对齐索引svr->syn.imureturn k;//返回k的值,表示处理的IMU数据历元数。
}

其中

n=((((svr->syn.ni)-1)%(1000)-(svr->syn.imu))<0?(((svr->syn.ni)-1)%(1000)-(svr->syn.imu)+(1000)):(((svr->syn.ni)-1)%(1000)-(svr->syn.imu)))

计算了IMU当前历元编号和IMU同步索引之间的历元数。如果结果为负数,它会加上1000,以确保得到正确的差值。

insinirtobs()

 double vr[3]={0}; insstate_t *ins=&svr->rtk.ins;// 初始化速度为零/* global variables for rtk positioning */static int first=1,i;static prcopt_t popt=svr->rtk.opt;static rtk_t rtk={0};// 初始化 RTK 结构体static sol_t sols[MAXSOL]={0};// 保存位置解的数组trace(3,"insinirtobs: n=%d\n",n);svr->rtk.ins.stat=INSS_INIT;if (n<=0) {trace(2,"no observation data to initial\n"); return 0;}/* initial gps position options */if (first) {//如果是首次调用,初始化RTK定位选项initrtkpos(&rtk,&popt); first=0;}rtkpos(&rtk,obs,n,&svr->nav);// GNSS计算定位结果/* save position solution to buffer将最新的位置解保存到缓冲区 */for (i=0;i<MAXSOL-1;i++) sols[i]=sols[i+1]; sols[i]=rtk.sol;for (i=0;i<MAXSOL;i++) {//检查解的状态,确保解是有效的if (sols[i].stat>popt.insopt.iisu||sols[i].stat==SOLQ_NONE) {trace(2,"check solution status fail\n");return 0;}}//检查解的时间差异,确保时间差异在 MAXDIFF=10s 内for (i=0;i<MAXSOL-1;i++) {if (timediff(sols[i+1].time,sols[i].time)>MAXDIFF) {return 0;}}/* compute velocity from solutions 从解中计算速度*/matcpy(vr,sols[MAXSOL-1].rr+3,1,3);if (norm(vr,3)==0.0) {sol2vel(sols+MAXSOL-1,sols+MAXSOL-2,vr);}//检查陀螺仪测量值和速度的范数是否在合理的范围内if (norm(imu->gyro,3)>MAXGYRO||norm(vr,3)<MINVEL) {return 0;}/* initialize ins states初始化 INS 状态 */initinsrt(svr);if (!ant2inins(sols[MAXSOL-1].time,sols[MAXSOL-1].rr,vr,&popt.insopt,NULL,ins,NULL)) {//将位置解和速度传递给INS进行初始化return 0;}ins->time=sols[MAXSOL-1].time;/* update ins state in n-frame n系(导航系下)更新INS状态 */update_ins_state_n(ins);

二、tcigpos()

    trace(3,"tcigpos: update=%d,time=%s\n",upd,time_str(imu->time,4));#if CHKNUMERIC/* check numeric of estimate state检查状态估计数字 */for (i=0;i<3;i++) {if (isnan(ins->re[i])||isnan(ins->ve[i])||isnan(ins->ae[i])||isinf(ins->re[i])||isinf(ins->ve[i])||isinf(ins->ae[i])) {fprintf(stderr,"check numeric error: nan or inf\n");return 0;}}
#endifins->stat=INSS_NONE; /* start ins mechanization开始机械编排 */if (
#if 0/* update ins states based on llh position mechanization */!updateinsn(insopt,ins,imu);
#else/* update ins states in e-frame */!updateins(insopt,ins,imu)) {
#endiftrace(2,"ins mechanization update fail\n");return 0;}P=zeros(nx,nx);/* propagate ins states 传播INS状态到GNSS历元时刻*/propinss(ins,insopt,ins->dt,ins->x,ins->P);/* check variance of estimated position检查位置方差 */chkpcov(nx,insopt,ins->P);/* updates ins states using imu or observation data开始更新INS状态 */if (upd==INSUPD_TIME) {ins->stat=INSS_TIME;info=1;}else {for (i=0;i<6;i++) rtk->sol.pqr[i]=rtk->sol.qr[i];rtk->sol.pstat=rtk->sol.stat;ins->gstat=SOLQ_NONE;ins->ns=0;
#if REBOOT/* reboot tightly coupled if need 重启紧组合*/if ((flag=rebootc(ins,opt,obs,n,imu,nav))) {if (flag==1) {trace(2,"ins tightly coupled still reboot\n");info=0; goto EXIT;}trace(3,"ins tightly coupled reboot ok\n");ins->stat=INSS_REBOOT; info=1;goto EXIT;}
#endif/* updates by measurement data通过测量数据更新 */if (obs&&imu&&n) {/* count rover/base station observations 记录流动站/基站观测数*/for (nu=0;nu   <n&&obs[nu   ].rcv==1;nu++) ;for (nr=0;nu+nr<n&&obs[nu+nr].rcv==2;nr++) ;dt=timediff(obs[0].time,ins->time);/* check synchronization 检查同步*/if (fabs(dt)>3.0) {trace(2,"observation and imu sync error\n");info=0;}/* tightly coupled进入紧组合 */if (info) {info=rtkpos(rtk,obs,nu+nr,nav);}}else info=0;/* update coupled solution status 更新组合状态解/反馈*/if (info) {ins->ptct=ins->time;ins->stat=ins->stat==INSS_REBOOT?INSS_REBOOT:INSS_TCUD;trace(3,"tightly couple ok\n");/* lack satellites but tightly-coupled run 缺少卫星的*/if (ins->ns<4) {ins->stat=INSS_LACK;}/* save precious epoch gps measurement 保存GPS精密?历元测量值*/savegmeas(ins,&rtk->sol,NULL);
#if 1/* recheck attitude检查姿态 */rechkatt(ins,imu);
#endif/* ins state in n-frame 导航坐标系下的ins状态*/update_ins_state_n(ins);}else {trace(2,"tightly coupled fail\n");info=0;}}
EXIT:free(P); return info;

其中机械编排updateins(),状态传播propinss(),紧组合rtkpos(),函数是重要学习内容。

三、机械编排updateins()

通过结合陀螺仪和加速度计的测量值,更新姿态、速度和位置信息

   /* save precious epoch ins states保存上一个历元的 INS 状态 */savepins(ins,data);//当前IMU测量时间 data->time与上一个INS状态的时间ins->time之间的时间差if ((dt=timediff(data->time,ins->time))>MAXDT||fabs(dt)<1E-6) {/* update time information */ins->dt=timediff(data->time,ins->time);//时间间隔ins->ptime=ins->time;//上一个历元解算时间ins->time =data->time;//最新IMU测量时间trace(2,"time difference too large: %.0fs\n",dt);return 0;}

1、IMU测量值校正

    for (i=0;i<3;i++) {ins->omgb0[i]=data->gyro[i];ins->fb0  [i]=data->accl[i];if (insopt->exinserr) {//ins测量值校正。ins_errmodel(data->accl,data->gyro,ins->fb,ins->omgb,ins);}else {//否则,直接更新陀螺仪和加速度计的测量值。ins->omgb[i]=data->gyro[i]-ins->bg[i];ins->fb  [i]=data->accl[i]-ins->ba[i];}}

用ins_errmodel()函数对IMU测量值(角速率,比力)进行改正,否则,直接更新陀螺仪和加速度计的测量值。

2、更新姿态

首先用**rotscull_corr()**函数进行旋转和划桨运动校正

**rvec2quat(domgb,dqb)😗*旋转向量domgb[]->四元数dqb[]

**quat2dcmx(dqe,dCe)😗*四元数dqe[]->转换矩阵dCe[]

dcm2quatx(ins->Cbe,qk_1):

**quatmulx(qk_1,dqb,qtmp)😗*四元数乘法

**normquat(qk)😗*规范化四元数

**quat2dcmx(qk,ins->Cbe)😗*四元数qk->转换矩阵ins->Cbe

3、更新速度

**gravity(ins->re,ge):**根据地心地固坐标re[]和重力模型求这点的重力ge[]

 for (i=0;i<3;i++) {ins->ve[i]+=dvfk[i]+(ge[i]-2.0*wv[i])*dt;//更新速度}

4、更新位置

 /* update position */matcpy(vek_1,ins->ve,1,3);for (i=0;i<3;i++) {ins->re[i]+=0.5*(vek_1[i]+ins->ve[i])*dt;}

理论应该是位置增量=(前一历元速度+当前历元速度)*dt *0.5建模成匀加速运动,但是在此处,vek_1[]获取的值还是当前的历元速度,实际上建模成了匀速运动,在短时间内,效果基本一样。

**updinsn(ins):**更新INS在n下系的坐标,之前的更新都是在e系下的

四、propinss()

进行惯导递推(将INS状态和协方差递推到当前IMU测量时刻)。

updstst()

此函数更新状态和协方差,为后面的扩展卡尔曼滤波做基础

 /* determine approximate system noise covariance matrix确定近似系统噪声协方差矩阵 */opt->exprn?getprn(ins,opt,dt,Q):getQ(opt,dt,Q);/* determine transition matrix确定转移矩阵* using last epoch ins states (first-order approx)使用最后历元ins状态(一阶近似值)*/opt->exphi?precPhi(opt,dt,ins->Cbe,ins->re,ins->omgb,ins->fb,phi):getPhi1(opt,dt,ins->Cbe,ins->re,ins->omgb,ins->fb,phi);#if UPD_IN_EULERgetphi_euler(opt,dt,ins->Cbe,ins->re,ins->omgb,ins->fb,phi);
#endif/* propagate state estimation error covariance 传播状态估计误差协方差*/if (fabs(dt)>=MAXUPDTIMEINT) {//如果时间步长大于等于MAXUPDTIMEINT,getP0(opt,P);           //则直接调用函数getP0来获取初始状态估计误差协方差矩阵}else {//通过转移矩阵和协方差矩阵来预测下一个时间步的状态估计误差协方差矩阵propP(opt,Q,phi,P0,P);}/* propagate state estimates noting that* all states are zero due to close-loop correction */if (x) propx(opt,x0,x);//预测状态估计值/* predict info. */if (ins->P0) matcpy(ins->P0,P  ,ins->nx,ins->nx);if (ins->F ) matcpy(ins->F ,phi,ins->nx,ins->nx);

之后在chkpcov()检查INS状态协方差矩阵

int i; double var=0.0;for (i=xiP(opt);i<xiP(opt)+3;i++) var+=SQRT(P[i+i*nx]);if ((var/3)>MAXVAR) if (P) getP0(opt,P);

如果P矩阵位置参数对应的对角阵阵上的均值大于阈值MAXVAR,就初始化P

五、rtkpos()、pppos()

 prcopt_t *opt=&rtk->opt;insopt_t *insopt=&opt->insopt;sol_t solb={{0}};gtime_t time;static obsd_t obsd[MAXOBS];int fi=0,fj=1,fk=2;int i,j,nu,nr,stat=0,tcs=0,tcp=0;char msg[128]="";trace(3,"rtkpos  : time=%s n=%d\n",time_str(obs[0].time,3),n);trace(4,"obs=\n"); traceobs(4,obs,n);trace(5,"nav=\n"); tracenav(5,nav);/* check tc-mode */tcs=opt->mode==PMODE_INS_TGNSS&&insopt->tc==INSTC_SINGLE;//spp_ins紧组合tcp=opt->mode==PMODE_INS_TGNSS&&insopt->tc==INSTC_PPK;//ppp_ins紧组合if (n<=0) return 0;/* set base staion position 基站位置*/if (opt->refpos<=POSOPT_RINEX&&opt->mode!=PMODE_SINGLE&&opt->mode!=PMODE_MOVEB) {for (i=0;i<6;i++) rtk->rb[i]=i<3?opt->rb[i]:0.0;}/* count rover/base station observations */for (nu=0;nu   <n&&obs[nu   ].rcv==1;nu++) ;//流动站观测值计数for (nr=0;nu+nr<n&&obs[nu+nr].rcv==2;nr++) ;//基站观测值计数/* for rover and base observation data */for (i=0;i<nu+nr&&opt->adjobs;i++) {memcpy(&obsd[i],&obs[i],sizeof(obsd_t));if (adjsind(opt,&obs[i],&fi,&fj,&fk)) {//调整观测数据trace(4,"adjust observation data signal index ok\n");}/* here just adjust three frequency */for (j=0;j<3;j++) {obsd[i].LLI[j]=obs[i].LLI[j==0?fi:j==1?fj:fk];obsd[i].SNR[j]=obs[i].SNR[j==0?fi:j==1?fj:fk];obsd[i].P[j]=obs[i].P[j==0?fi:j==1?fj:fk];obsd[i].L[j]=obs[i].L[j==0?fi:j==1?fj:fk];obsd[i].D[j]=obs[i].D[j==0?fi:j==1?fj:fk];}/* index of frequency */fi=0; fj=1; fk=2;}if (opt->adjobs) {trace(4,"adjust obs=\n"); traceobs(4,obsd,n);}/* previous epoch */time=rtk->sol.time;/* rover position by single point positioning */if (!pntpos(opt->adjobs?obsd:obs,nu,nav,&rtk->opt,&rtk->sol,tcs?&rtk->ins:NULL,NULL,rtk->ssat,msg)) {errmsg(rtk,"point pos error (%s)\n",msg);if (!rtk->opt.dynamics) {outsolstat(rtk);return 0;}}if (time.time!=0) rtk->tt=timediff(rtk->sol.time,time);if (fabs(rtk->tt)<DTTOL&&opt->mode<=PMODE_FIXED) return stat;/* single point positioning spp或spp_ins紧组合*/if (opt->mode==PMODE_SINGLE||tcs) {outsolstat(rtk);return 1;}/* suppress output of single solution抑制单个解的输出 */if (!opt->outsingle) {rtk->sol.stat=SOLQ_NONE;}/* precise point positioning ppp或ppp_ins紧组合*/if ((opt->mode>=PMODE_PPP_KINEMA&&opt->mode<PMODE_INS_UPDATE)||tcp) {pppos(rtk,opt->adjobs?obsd:obs,nu,nav);outsolstat(rtk);return 1;}//检查该历元流动站观测时间和基准站观测时间是否对应/* check number of data of base station and age of differential */if (nr==0) {errmsg(rtk,"no base station observation data for rtk\n");outsolstat(rtk);return 1;}//动基线与其他差分定位方式,动基线的基站坐标需要随时间同步变化,所以需要计算出变化速率if (opt->mode==PMODE_MOVEB) { /*  moving baseline *///解释了为什么第二步除了单点定位,动基线也不参与基站解算,动基线在这里单独解算/* estimate position/velocity of base station */if (!pntpos(opt->adjobs?obsd:obs+nu,nr,nav,&rtk->opt,&solb,NULL,NULL,NULL,msg)) {errmsg(rtk,"base station position error (%s)\n",msg);return 0;}rtk->sol.age=(float)timediff(rtk->sol.time,solb.time);if (fabs(rtk->sol.age)>TTOL_MOVEB) {errmsg(rtk,"time sync error for moving-base (age=%.1f)\n",rtk->sol.age);return 0;}for (i=0;i<6;i++) rtk->rb[i]=solb.rr[i];/* time-synchronized position of base station */for (i=0;i<3;i++) {rtk->rb[i]+=rtk->rb[i+3]*rtk->sol.age;}}else {rtk->sol.age=(float)timediff(obs[0].time,obs[nu].time);if (fabs(rtk->sol.age)>opt->maxtdiff) {errmsg(rtk,"age of differential error (age=%.1f)\n",rtk->sol.age);outsolstat(rtk);return 1;}}/* relative positioning */stat=relpos(rtk,opt->adjobs?obsd:obs,nu,nr,nav);#if DEGRADETC/* degrade to dgps-tc mode if rtk-tc fail */if (stat==0&&opt->mode==PMODE_INS_TGNSS) {insopt->tc=INSTC_DGPS;stat=relpos(rtk,opt->adjobs?obsd:obs,nu,nr,nav);insopt->tc=INSTC_RTK;if (stat) goto exit;}/* degrade to single-tc mode if dgps-tc fail */if (stat==0&&opt->mode==PMODE_INS_TGNSS) {stat=pntpos(opt->adjobs?obsd:obs,nu,nav,&rtk->opt,&rtk->sol,&rtk->ins,NULL,NULL,msg);goto exit;}
exit:
#endifoutsolstat(rtk);return stat;

需要注意到的是:此处状态参数x中的位置参数是IMU坐标系下的,而rr[]存储的是地心地固下的坐标 。这里也是spp/ins,rtk/ins紧组合入口函数,注意区分。这里只介绍PPP/INS紧组合入口函数pppos()。pppos()代码在下面:

extern void pppos(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
{const prcopt_t *opt=&rtk->opt;double *rs,*dts,*var,*v,*H,*R,*azel,*xp,*Pp,dr[3]={0},std[3];double *x,*P,rr[3];char str[32];int i,j,nv,info,svh[MAXOBS],exc[MAXOBS]={0},stat=SOLQ_SINGLE,tc;int nx;insstate_t insp;time2str(obs[0].time,str,2);trace(3,"pppos   : time=%s nx=%d n=%d\n",str,rtk->nx,n);rs=mat(6,n); dts=mat(2,n); var=mat(1,n); azel=zeros(2,n);for (i=0;i<MAXSAT;i++) for (j=0;j<opt->nf;j++) rtk->ssat[i].fix[j]=0;/* temporal update of ekf states */udstate_ppp(rtk,obs,n,nav);/* satellite positions and clocks */satposs(obs[0].time,obs,n,nav,rtk->opt.sateph,rs,dts,var,svh);//排除被遮住的卫星(地影)/* exclude measurements of eclipsing satellite (block IIA) */if (rtk->opt.posopt[3]) {testeclipse(obs,n,nav,rs);}/* earth tides correction地球潮汐校正 */if (opt->tidecorr) {tidedisp(gpst2utc(obs[0].time),rtk->x,opt->tidecorr==1?1:7,&nav->erp,opt->odisp[0],dr);}nv=n*rtk->opt.nf*2+MAXSAT+3;xp=zeros(rtk->nx,1); Pp=zeros(rtk->nx,rtk->nx);v=mat(nv,1); H=mat(rtk->nx,nv); R=mat(nv,nv);/* tc=0: common rtk mode* tc=1: tightly coupled mode* */tc=opt->mode==PMODE_INS_TGNSS?1:0;x=tc?rtk->ins.x:rtk->x;//通过INS解算的状态先验值P=tc?rtk->ins.P:rtk->P;//通过INS解算的参数协方差//若为紧组合模式,nx为紧组合模型状态参数nx=tc?rtk->ins.nx:rtk->nx;/* backup ins states ins状态参数反馈矩阵*/if (tc) {memcpy(&insp,&rtk->ins,sizeof(insstate_t));}for (i=0;i<MAX_ITER;i++) {//迭代//若为组合模式,将IMU位置坐标转换到GNSS天线位置tc?insp2antp(&insp,rr):matcpy(rr,xp,1,3);matcpy(xp,x,nx,1);matcpy(Pp,P,nx,nx);/* prefit residuals先验残差 */if (!(nv=ppp_res(0,obs,n,rs,dts,var,svh,dr,exc,nav,xp,rtk,v,H,R,azel,rr))) {trace(2,"%s ppp (%d) no valid obs data\n",str,i+1);break;}/* measurement update of ekf states */if ((info=filter(xp,Pp,H,v,R,nx,nv))) {trace(2,"%s ppp (%d) filter error info=%d\n",str,i+1,info);break;}/* postfit residuals后验残差 */if (ppp_res(i+1,obs,n,rs,dts,var,svh,dr,exc,nav,xp,rtk,v,H,R,azel,rr)) {matcpy(x,xp,nx,1);matcpy(P,Pp,nx,nx);stat=SOLQ_PPP;if (tc) {clp(&insp,&opt->insopt,xp);for (j=0;j<xnCl(&opt->insopt);j++) xp[j]=0.0;}break;}}if (i>=MAX_ITER) {trace(2,"%s ppp (%d) iteration overflows\n",str,i);}if (stat==SOLQ_PPP) {/* update ins states if solution is ok */if (tc) {upd_ins_states(rtk,&insp);}tc?insp2antp(&insp,rr):matcpy(rr,xp,1,3);/* todo: add ppp-ar fix ambiguity *//* ambiguity resolution in ppp */if (ppp_ar(rtk,obs,n,exc,nav,azel,xp,Pp)&&ppp_res(9,obs,n,rs,dts,var,svh,dr,exc,nav,xp,rtk,v,H,R,azel,rr)) {double *xa,*Pa;xa=tc?rtk->ins.xb:rtk->xa;Pa=tc?rtk->ins.Pb:rtk->Pa;for (i=0;i<3;i++) std[i]=sqrt(Pp[i+i*nx]);if (norm(std,3)<MAX_STD_FIX) stat=SOLQ_FIX;if (stat==SOLQ_FIX) {if (tc) {clp(&insp,&opt->insopt,xp);for (j=0;j<xnCl(&opt->insopt);j++) xp[j]=0.0;/* update ins states if solution is ok */if (tc) {upd_ins_states(rtk,&insp);}}matcpy(xa,xp,nx, 1);matcpy(Pa,Pp,nx,nx);}}else {rtk->nfix=0;}/* update solution status */update_stat(rtk,obs,n,stat);/* hold fixed ambiguities */if (stat==SOLQ_FIX&&test_hold_amb(rtk)) {matcpy(x,xp,nx,1);matcpy(P,Pp,nx,nx);trace(2,"%s hold ambiguity\n",str);rtk->nfix=0;}}free(rs); free(dts); free(var); free(azel);free(xp); free(Pp); free(v); free(H); free(R);
}

其中udstate_ppp()、satposs()、testeclipe()、tidedisp()、pppar()、update_stat()、update_stat()并没有多大改变,不作阐述。

1、先验残差ppp_res()

 const double *lam;prcopt_t *opt=&rtk->opt;insopt_t *insopt=&opt->insopt;double y,r,cdtr,bias,C,rr[3],pos[3],e[3],dtdx[3],L[NFREQ],P[NFREQ],Lc,Pc;double var[MAXOBS*2],dtrp=0.0,dion=0.0,vart=0.0,vari=0.0,dcb;double dantr[NFREQ]={0},dants[NFREQ]={0},uddp[3],udda[3],uddl[3];double ve[MAXOBS*2*NFREQ]={0},vmax=0;char str[32];int ne=0,obsi[MAXOBS*2*NFREQ]={0},frqi[MAXOBS*2*NFREQ],maxobs,maxfrq,rej;int i,j,k,sat,sys,nv=0,nx=rtk->nx,stat=1,tc;/* tc=0: common rtk mode* tc=1: tightly coupled mode* */tc=opt->mode==PMODE_INS_TGNSS?1:0;time2str(obs[0].time,str,2);//将第一个观测历元的时间转换为字符串,存储在str中。//将 rtk->ssat 中的卫星状态标记为未使用for (i=0;i<MAXSAT;i++) for (j=0;j<opt->nf;j++) rtk->ssat[i].vsat[j]=0;for (i=0;i<3;i++) rr[i]=rpos[i]+dr[i];//dr[]为地球潮汐对坐标改正量ecef2pos(rr,pos);for (i=0;i<n&&i<MAXOBS;i++) {sat=obs[i].sat;lam=nav->lam[sat-1];//波长if (lam[j/2]==0.0||lam[0]==0.0) continue;if ((r=geodist(rs+i*6,rr,e))<=0.0||satazel(pos,e,azel+i*2)<opt->elmin) {exc[i]=1;continue;}if (!(sys=satsys(sat,NULL))||!rtk->ssat[sat-1].vs||satexclude(obs[i].sat,svh[i],opt)||exc[i]) {exc[i]=1;continue;}/* tropospheric and ionospheric model */if (!model_trop(obs[i].time,pos,azel+i*2,opt,x,dtdx,nav,&dtrp,&vart)||!model_iono(obs[i].time,pos,azel+i*2,opt,sat,x,nav,&dion,&vari)) {continue;}/* satellite and receiver antenna model */if (opt->posopt[0]) satantpcv(rs+i*6,rr,nav->pcvs+sat-1,dants);antmodel(opt->pcvr,opt->antdel[0],azel+i*2,opt->posopt[1],dantr);/* phase windup model */if (!model_phw(rtk->sol.time,sat,nav->pcvs[sat-1].type,opt->posopt[2]?2:0,rs+i*6,rr,&rtk->ssat[sat-1].phw)) {continue;}/* corrected phase and code measurements */corr_meas(obs+i,nav,azel+i*2,&rtk->opt,dantr,dants,rtk->ssat[sat-1].phw,L,P,&Lc,&Pc);/* stack phase and code residuals {L1,P1,L2,P2,...} */for (j=0;j<2*NF(opt);j++) {dcb=bias=0.0;if (opt->ionoopt==IONOOPT_IFLC) {if ((y=j%2==0?Lc:Pc)==0.0) continue;}else {if ((y=j%2==0?L[j/2]:P[j/2])==0.0) continue;/* receiver DCB correction for P2 */if (j/2==1) dcb=-nav->rbias[0][sys==SYS_GLO?1:0][0];}C=SQR(lam[j/2]/lam[0])*ionmapf(pos,azel+i*2)*(j%2==0?-1.0:1.0);if (tc) {//IMU位置状态参数---对观测方程xcom对应的雅克比矩阵jacob_ud_dp(e,uddp);//e[]为卫星到接收机的向量除以几何距离H[xiP(insopt)+0]=uddp[0];H[xiP(insopt)+1]=uddp[1];H[xiP(insopt)+2]=uddp[2];}else {for (k=0;k<nx;k++) H[k+nx*nv]=k<3?-e[k]:0.0;}/* partial derivation by ins attitude error利用ins姿态误差进行部分推导 */if (H&&tc) {jacob_ud_da(e,rtk->ins.lever,rtk->ins.Cbe,udda);H[xiA(insopt)+0]=udda[0];H[xiA(insopt)+1]=udda[1];H[xiA(insopt)+2]=udda[2];}/* partial derivation by lever arm 杆臂偏导数*/if (H&&tc) {jacob_ud_la(e,rtk->ins.Cbe,uddl);H[xiLa(insopt)+0]=uddl[0];H[xiLa(insopt)+1]=uddl[1];H[xiLa(insopt)+2]=uddl[2];}/* receiver clock接收机钟差雅克比矩阵系数 */k=sys==SYS_GLO?1:(sys==SYS_GAL?2:(sys==SYS_CMP?3:0));/* xiRc(insopt)+0: GPS receiver clock* xiRc(insopt)+1: GLO receiver clock* xiRc(insopt)+2: GAL receiver clock* xiRc(insopt)+3: BDS receiver clock* */cdtr=x[tc?xiRc(insopt)+k:IC(k,opt)];H[tc?xiRc(insopt)+k:IC(k,opt)+nx*nv]=1.0;/* todo: ppp/ins tightly coupled */if (opt->tropopt==TROPOPT_EST||opt->tropopt==TROPOPT_ESTG) {for (k=0;k<(opt->tropopt>=TROPOPT_ESTG?3:1);k++) {H[tc?xiTr(insopt,k):IT(opt)+k+nx*nv]=dtdx[k];//对流层湿延迟雅克比矩阵系数}}if (opt->ionoopt==IONOOPT_EST) {//电离层估计int ii=tc?xiIo(insopt,sat):II(sat,opt);if (rtk->x[ii]==0.0) continue;H[ii+nx*nv]=C;}if (j/2==2&&j%2==1) { /* L5-receiver-dcb */dcb+=rtk->x[tc?xiDl(insopt):ID(opt)];H[tc?xiDl(insopt):ID(opt)+nx*nv]=1.0;}if (j%2==0) { /* phase bias 相位偏差*/int ib=tc?xiBs(insopt,sat,j/2):IB(sat,j/2,opt);if ((bias=x[ib])==0.0) continue;H[ib+nx*nv]=1.0;}/* residual y是GNSS观测值*/v[nv]=y-(r+cdtr-CLIGHT*dts[i*2]+dtrp+C*dion+dcb+bias);if (j%2==0) rtk->ssat[sat-1].resc[j/2]=v[nv];else        rtk->ssat[sat-1].resp[j/2]=v[nv];/* variance */var[nv]=varerr(obs[i].sat,sys,azel[1+i*2],j/2,j%2,opt)+vart+SQR(C)*vari+var_rs[i];if (sys==SYS_GLO&&j%2==1) var[nv]+=VAR_GLO_IFB;trace(4,"%s sat=%2d %s%d res=%9.4f sig=%9.4f el=%4.1f\n",str,sat,j%2?"P":"L",j/2+1,v[nv],sqrt(var[nv]),azel[1+i*2]*R2D);/* reject satellite by pre-fit residuals用先验残差排除卫星 */if (!post&&opt->maxinno>0.0&&fabs(v[nv])>opt->maxinno) {trace(2,"outlier (%d) rejected %s sat=%2d %s%d res=%9.4f el=%4.1f\n",post,str,sat,j%2?"P":"L",j/2+1,v[nv],azel[1+i*2]*R2D);exc[i]=1; rtk->ssat[sat-1].rejc[j%2]++;continue;}/* record large post-fit residuals记录大的后验残差 */if (post&&fabs(v[nv])>sqrt(var[nv])*THRES_REJECT) {obsi[ne]=i; frqi[ne]=j; ve[ne]=v[nv]; ne++;}if (j%2==0) rtk->ssat[sat-1].vsat[j/2]=1;nv++;}}/* reject satellite with large and max post-fit residual 排除卫星*/if (post&&ne>0) {vmax=ve[0]; maxobs=obsi[0]; maxfrq=frqi[0]; rej=0;for (j=1;j<ne;j++) {if (fabs(vmax)>=fabs(ve[j])) continue;vmax=ve[j]; maxobs=obsi[j]; maxfrq=frqi[j]; rej=j;}sat=obs[maxobs].sat;trace(2,"outlier (%d) rejected %s sat=%2d %s%d res=%9.4f el=%4.1f\n",post,str,sat,maxfrq%2?"P":"L",maxfrq/2+1,vmax,azel[1+maxobs*2]*R2D);exc[maxobs]=1; rtk->ssat[sat-1].rejc[maxfrq%2]++; stat=0;ve[rej]=0;}/* constraint to local correction 局部校正约束*/nv+=const_corr(obs,n,exc,nav,x,pos,azel,rtk,v+nv,H+nv*rtk->nx,var+nv);for (i=0;i<nv;i++) for (j=0;j<nv;j++) {R[i+j*nv]=i==j?var[i]:0.0;//观测值方差}return post?stat:nv;

如果对自己的要求比较高,需要了解雅克比矩阵H中矩阵块赋值情况,建议对i2nav团队上传在bilibili上的组合导航最后一章:PPP/INS紧组合进行详细的学习。注意在细节上此项目与视频有差异。此项目貌似并没有使用一步解法(存疑)。

2、测量更新扩展卡尔曼滤波

算法详情;RTKLIB学习(二)–1、PPP方程和扩展卡尔曼滤波等算法详解-CSDN博客

extern int filter(double *x, double *P, const double *H, const double *v,const double *R, int n, int m)

需要注意到的是:此处输入的*x为ins解算出来的先验状态参数,解算出来的状态参数x中的位置参数是IMU坐标系下的。但是在KF-GINS中扩展卡尔曼滤波中的状态参数是改正量。

3、后验残差

if (ppp_res(i+1,obs,n,rs,dts,var,svh,dr,exc,nav,xp,rtk,v,H,R,azel,rr))

ppp_res()是通过第一个传入的参数区分先验还是后验。在后验函数中主要做了检验工作,并排除故障卫星。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/182202.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes技术与架构-安全性

本文主要从不同层面与多个维度描述Kubernetes技术与架构的安全性。 云原生的安全性 从系统分层架构的角度分析&#xff0c;自底向上&#xff0c;云原生的安全性主要包括云、集群、容器以及代码四个层面&#xff0c;简称云原生4C安全&#xff0c;其架构图如下所示&#xff1a;…

Vue3水印(Watermark)

APIs 参数说明类型默认值必传width水印的宽度&#xff0c;默认值为 content 自身的宽度numberundefinedfalseheight水印的高度&#xff0c;默认值为 content 自身的高度numberundefinedfalserotate水印绘制时&#xff0c;旋转的角度&#xff0c;单位 number-22falsezIndex追加…

ubuntu系统进入休眠后cuda初始化报错

layout: post # 使用的布局&#xff08;不需要改&#xff09; title: torch.cuda.is_available()报错 # 标题 subtitle: ubuntu系统进入休眠后cuda初始化报错 #副标题 date: 2023-11-29 # 时间 author: BY ThreeStones1029 # 作者 header-img: img/about_bg.jpg #这篇文章标题背…

解锁Jira本地部署的数据中心版高级功能,打造高效、智能、精细化的项目管理

近日&#xff0c;在龙智携手Atlassian与JFrog共同举办的“大规模开发创新&#xff1a;如何提升企业级开发效率与质量”的线下研讨会中&#xff0c;龙智高级咨询顾问、Atlassian认证专家叶燕秀为大家带来了精彩演讲&#xff0c;解锁Jira Data Center版的诸多高级功能&#xff0c…

【LeetCode刷题-字符串】--71.简化路径

71.简化路径 思路&#xff1a; 对于给定的字符串&#xff0c;先根据/分割成一个由若干字符串组成的列表&#xff0c;记为names&#xff0c;根据题意names中包含的字符串只能是以下几种&#xff1a; 空字符串一个点两个点只包含英文字母、数字或_的目录名 对于空字符串和一个…

Windows下命令行启动与关闭WebLogic的相关服务

WebLogic 的服务器类型 WebLogic提供了三种类型的服务器&#xff1a; 管理服务器节点服务器托管服务器 示例和关系如下图&#xff1a; 对应三类服务器&#xff0c; 就有三种启动和关闭的方式。本篇介绍使用命令行脚本的方式启动和关闭这三种类型的服务器。 关于WebLogic 的…

分析某款go端口扫描器之一

一、概述 进来在学go的端口检测部分&#xff0c;但是自己写遇到很多问题&#xff0c;又不知道从何入手&#xff0c;故找来网上佬们写的现成工具&#xff0c;学习一波怎么实现的。分析过程杂乱&#xff0c;没啥思路&#xff0c;勿喷。 项目来源&#xff1a;https://github.com/…

如何高效解析不定长度的协议帧

通信设计中考虑协议的灵活性&#xff0c;经常把协议设计成“不定长度”。一个实例如下图&#xff1a;锐米LoRa终端的通信协议帧。 如果一个系统接收上述“不定长度”的协议帧&#xff0c;将会有一个挑战--如何高效接收与解析。 为简化系统设计&#xff0c;我们强烈建议您采用“…

hql面试题之字符串使用split分割,并选择其中的一部分字段的问题

版本&#xff1a;20231109 1.题目&#xff1a; 有两张表,a表有id和abstringr两个字段&#xff0c;b表也有id和bstr两个字段&#xff0c;具体如下 A表&#xff1a; 1abc,bcd,cdf2123,456,789 B表: 1acddef2123456 在a表的abstring字段中用‘,’分割&#xff0c;并取出前两…

关于MySQL的66个问题

SQL基础掌握不错的小伙伴可以跳过这一部分。当然&#xff0c;可能会现场写一些SQL语句&#xff0c;SQ语句可以通过牛客、LeetCode、LintCode之类的网站来练习。 1. 什么是内连接、外连接、交叉连接、笛卡尔积呢&#xff1f; 内连接&#xff08;inner join&#xff09;&#xf…

05_MySQL主从复制架构

任务背景 ##一、真实案例 某同学刚入职公司&#xff0c;在熟悉公司业务环境的时候&#xff0c;发现他们的数据库架构是一主两从&#xff0c;但是两台从数据库和主库不同步。询问得知&#xff0c;已经好几个月不同步了&#xff0c;但是每天会全库备份主服务器上的数据到从服务…

k8s安装步骤

环境&#xff1a; 操作系统&#xff1a;win10 虚拟机&#xff1a;VMware linux发行版&#xff1a;CentOS7.9 CentOS镜像&#xff1a;CentOS-7-x86_64-DVD-2009 master和node节点通信的ip(master)&#xff1a; 192.168.29.164 0.检查配置 本次搭建的集群共三个节点&#xff0c;…

2023年【安全员-A证】考试题及安全员-A证最新解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-A证考试题考前必练&#xff01;安全生产模拟考试一点通每个月更新安全员-A证最新解析题目及答案&#xff01;多做几遍&#xff0c;其实通过安全员-A证模拟考试题很简单。 1、【多选题】下列关于高处作业吊篮叙…

【Redis基础】Redis基本的全局命令

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; Redis基本的全局命令 1&#xff0c;KEYS命令 语法&#xff1a;KEYS pattern KEYS命令用来查询服…

Python内置类属性`__name__`属性的使用教程

更多Python学习内容&#xff1a;ipengtao.com Python中的__name__是一种内置的特殊属性&#xff0c;通常用于判断模块是作为主程序运行还是作为模块被导入。本文将深入讲解__name__属性的用法&#xff0c;通过丰富的示例代码展示其在不同情景下的应用。 模块作为主程序运行 当一…

统信UOS_麒麟KYLINOS上使用远程SSH连接的工具electerm

原文链接&#xff1a;统信UOS/麒麟KYLINOS上使用SSH工具electerm Hello&#xff0c;大家好啊&#xff01;在我们日常的工作和学习中&#xff0c;远程控制和管理服务器已经成为一项常见且必要的技能。尤其是对于IT专业人士和开发者来说&#xff0c;一个高效、稳定的远程SSH连接工…

一款LED段码显示屏驱动芯片方案

一、基本概述 TM1620是一种LED&#xff08;发光二极管显示器&#xff09;驱动控制专用IC,内部集成有MCU数字接口、数据锁存器、LED驱动等电路。本产品质量可靠、稳定性好、抗干扰能力强。 二、基本特性 采用CMOS工艺 显示模式&#xff08;8段6位&#xff5e;10段4位&#xff…

模拟Spring源码思想,手写源码,理解@Component,@Value,@Autowired,@Qualifier四个注解

1、BeanDefinition package com.csdn.myspring; import lombok.AllArgsConstructor; import lombok.Data; Data AllArgsConstructor public class BeanDefinition {private String beanName;private Class beanClass; }2、扫描包的工具类MyTools package com.csdn.myspring; im…

排序算法基本原理及实现2

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 &#x1f324;️冒泡排序 &#x1…

简单位运算

文章目录 求 n n n 的第 k k k 位是二进制的几lowbit(n)操作求解 n n n 的最后一个 1 1 1题目练习AcWing 801. 二进制中1的个数CODE1 原码、补码、反码 求 n n n 的第 k k k 位是二进制的几 我们需要用到&运算符&#xff1a;两位都为 1 1 1 时结果才为 1 1 1 &…