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和流动站时间对齐索引均不为0
            syn->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()匹配的观测值所在历元索引+i

        if (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历元内观测值数不为0
            for (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.imu
    return 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;
        }
    }
#endif
    ins->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)
        ) {
#endif
        trace(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_EULER
    getphi_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:
#endif
    outsolstat(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()是通过第一个传入的参数区分先验还是后验。在后验函数中主要做了检验工作,并排除故障卫星。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用功率MOSFET常见的一些问题(一)

使用功率MOSFET常见的一些问题&#xff08;一&#xff09; 1.MOS管简介2.反向阻断特性2.1 雪崩失效机制2.2 雪崩测试2.3 如何避免雪崩事件 3.MOS管额定电流和散热 刚开始用功率MOS管的时候经常会遇到炸管子的事情&#xff0c;过来人都说不炸几个管子就永远不会用MOS管&#xff…

Python向Excel写入内容的方法大全

在数据处理和分析中&#xff0c;将Python中的数据写入Excel是一项常见任务。 本文将介绍几种常见的方法&#xff0c;以及如何使用它们向Excel中写入内容。 方法一&#xff1a;使用openpyxl库 openpyxl是一个功能强大的库&#xff0c;用于读写Excel文件。以下是一个简单的使用…

DHCP协议讲解(含DHCP状态机)

加个目录 一、概述 大家都知道&#xff0c;为了使用TCP/IP协议族&#xff0c;每台主机和路由器需要一定的配置信息。 下面是一个简单的例子&#xff1a; 某学校的教学办公区域将要布置数百台计算机&#xff0c;每台都需要分配IP&#xff0c;如何实现对这些数量巨大的主机进…

【Java】泛型的简单使用

文章目录 一、包装类1.基本数据类型和对应的包装类2.自动装箱和自动拆箱3.手动装箱和手动拆箱 二、什么是泛型三、泛型的使用四、裸类型&#xff08;Raw Type&#xff09;五、泛型是如何编译的六、泛型的上界七、泛型方法总结 一、包装类 在了解泛型之前我们先了解什么是包装类…

IMU传感器用于智能假肢

截肢会给截肢者们带来生活上的不方便和极大痛苦&#xff0c;因此假肢的优化一直被关注着。近期&#xff0c;一项关于新型智能膝关节的研究&#xff0c;让假肢能更好地模拟自然膝关节&#xff0c;给截肢者们带来了希望。 此款假肢内置IMU传感器&#xff0c;减少了截肢者所需的肌…

Golang开发之------ Beego框架

1.安装go&#xff08;配置环境变量&#xff09; 2.安装gorm&#xff08;Goland编辑器举例&#xff09;&#xff1a; go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct 3.初始化项目&#xff08;首先需要在工作目录新建bin文件夹&#xff0c;pkg文件…

分油问题C++求解

原题 3个油桶&#xff0c;容量分别为&#xff08;大桶&#xff09;20&#xff0c;&#xff08;中桶&#xff09;9&#xff0c;&#xff08;小桶&#xff09;7&#xff0c;初始时大桶满油&#xff0c;如何操作可以分出17的油&#xff1f; 代码 #include<iostream> #inc…

【复盘】接口自动化测试框架建设的经验与教训!

为什么选择这个话题&#xff1f; 一是发现很多“点工”在转型迷茫期都会问一些自动化测试相关的问题&#xff0c;可以说自动化测试是“点工”升级的必经之路&#xff1b;二是Google一下接口自动化测试&#xff0c;你会发现很多自动化测试框架相关的文章&#xff0c;但是大部分…

自动化测试框架搭建步骤教程

说起自动化测试&#xff0c;我想大家都会有个疑问&#xff0c;要不要做自动化测试&#xff1f; 自动化测试给我们带来的收益是否会超出在建设时所投入的成本&#xff0c;这个嘛别说是我&#xff0c;即便是高手也很难回答&#xff0c;自动化测试的初衷是美好的&#xff0c;而测试…

golang channel执行原理与代码分析

使用的go版本为 go1.21.2 首先我们写一个简单的chan调度代码 package mainimport "fmt"func main() {ch : make(chan struct{})go func() {ch <- struct{}{}ch <- struct{}{}}()fmt.Println("xiaochuan", <-ch)data, ok : <-chfmt.Println(&…

Xiamen I Fitness Platform

厦门I健身平台程 https://ijs.sports.xm.gov.cn/mgh5/#/ 1&#xff09;公众号 2&#xff09;主页 3&#xff09;【个人中心】【我的保险】就是要买一份保险&#xff0c;10元的那种&#xff0c;不然去场地出意外咋办 4&#xff09;我的保险状态&#xff1a;未购买&#xff0c;…

VR虚拟教育展厅,为教学领域开启创新之路

线上虚拟展厅是一项全新的展示技术&#xff0c;可以为参展者带来不一样的观展体验。传统的实体展览存在着空间限制、时间限制以及高昂的成本&#xff0c;因此对于教育领域来说&#xff0c;线上虚拟教育展厅的出现&#xff0c;可以对传统教育方式带来改革&#xff0c;凭借强大的…

【Qt之QSqlRelationalDelegate】描述及使用

描述 QSqlRelationalDelegate类提供了一个委托&#xff0c;用于显示和编辑来自QSqlRelationalTableModel的数据。 与默认委托不同&#xff0c;QSqlRelationalDelegate为作为其他表的外键的字段提供了一个组合框。 要使用该类&#xff0c;只需在带有QSqlRelationalDelegate实例…

easyrecovery如何恢复手机数据及硬盘数据恢复方法

EasyRecovery16是一款优秀的数据恢复软件&#xff0c;不仅能够兼容windows和mac双重系统&#xff0c;同时还能够识别u盘、存储卡、手机等多种数据储存设备&#xff0c;可恢复的文件类型更是多达百余种。还贴心地准备个人版、专业版和企业版的下载&#xff0c;增加了用户的可选性…

Android-P CameraSerivce

0 前言 本文重点分析Android-P的CameraService实现。 验证:Goldfish模拟器 1 定义 图1.1 CameraService ICameraServiceframeworks/av/camera/aidl/android/hardware/ICameraService.aidlBnCameraServiceout/soong/.intermediates/frameworks/av/camera/libcamera_client/an…

通过分析波形,透彻理解 UART 通信

UART是一种异步全双工串行通信协议&#xff0c;由 Tx 和 Rx 两根数据线组成&#xff0c;因为没有参考时钟信号&#xff0c;所以通信的双方必须约定串口波特率、数据位宽、奇偶校验位、停止位等配置参数&#xff0c;从而按照相同的速率进行通信。 异步通信以一个字符为传输单位…

远程访问:Windows设备管理器远程访问

设备管理器是一个应用程序&#xff0c;它包含与计算机耦合的硬件的完整概述&#xff0c;远程设备管理器允许管理员访问远程计算机的设备管理器&#xff0c;而无需远程访问桌面。 为什么需要远程设备管理器 IT环境充斥着数量众多的电脑和笔记本电脑&#xff0c;对于管理员来说…

Abaqus飞机起落架扭力臂拓扑优化

Abaqus飞机起落架扭力臂拓扑优化 Abaqus除了可以对结构进行强度分析&#xff0c;同样也自带强大的优化功能&#xff0c;下面通过一个简 单的实例演示在Abaqus中进行拓扑优化&#xff0c;另外&#xff0c;如果需要更加强大的拓扑优化仿真&#xff0c;可以 在TOSCA中进行。 定义接…

30岁的测试人?软件测试“内卷“?“我“该如何冲出破圈...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、软件测试的内卷…

怎么样的软件测试工程师才算“大神”?

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…