PPP/INS紧组合代码学习

news2024/12/26 15:52:55

前言:

本文是基于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()是通过第一个传入的参数区分先验还是后验。在后验函数中主要做了检验工作,并排除故障卫星。

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

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

相关文章

数据链路层——以太网协议、ARP协议

目录 以太网协议 以太网协议的简介 以太网协议所处的位置 以太网帧&#xff08;或者说MAC帧&#xff09;的格式 局域网通信原理 碰撞避免算法&#xff08;包含MTU的知识点&#xff09; 局域网攻击原理 ARP协议 ARP协议所在的位置 为什么要存在ARP协议&#xff08;或者…

win10 下 mvn install 报错:编码GBK不可映射字符

问题背景 由于jenkins需要部署不同的项目&#xff0c;需要使用不同的jdk版本&#xff0c;所以需要配置单独的settings.xml&#xff0c;使用指定的jdk版本进行编译&#xff0c;这里需要单独的maven设置&#xff0c;在配置完后进行mvn的install的时候&#xff0c;由于存在中文注释…

文件操作利器:Python十大库全面解析

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Python拥有多个库用于文件操作&#xff0c;提供了各种功能来管理、读取和写入文件。这些库覆盖了从基本的文件系统交互到高级的文件压缩和数据格式处理等多个方面。文件操作是编程中不可或缺的一部分&#xff0c…

【海思SS528 | VDEC】MPP媒体处理软件V5.0 | 视频解码模块——学习笔记

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

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文件…

[Python入门系列之十二]安装Jupyter notebook与代码运行

引言 Jupyter Notebook将代码、图片和文本完美结合在一起&#xff0c;为编程学习带来了前所未有的便捷性。本文旨在为初学者提供一个关于Jupyter Notebook的入门指南。 什么是Jupyter Notebook Jupyter Notebook是一个开源的Web应用程序&#xff0c;允许你创建和共享包含代码…

五、初识FreeRTOS之FreeRTOS的任务创建和删除

本节主要学习以下内容&#xff1a; 1&#xff0c;任务创建和删除的API函数&#xff08;熟悉&#xff09; 2&#xff0c;任务创建和删除&#xff08;动态方法&#xff09;&#xff08;掌握&#xff09; 3&#xff0c;任务创建和删除&#xff08;静态方法&#xff09;&#xf…

大数据之 Hadoop

hadoop主要解决&#xff1a;海量数据的存储和海量数据的分析计算 hadoop发展历史 Google是hadoop的思想之源&#xff08;Google在大数据方面的三篇论文&#xff09; 2006年3月&#xff0c;Map-reduce和Nutch Distributed File System(NDFS)分别被纳入到Hadoop项目&#xff0c…

计算机视觉:使用dlib实现人脸检测

1 dlib介绍 Dlib是一个广泛使用的开源库&#xff0c;在计算机视觉和机器学习领域具有重要影响。它是由Davis King在2002年开发&#xff0c;主要用C语言编写&#xff0c;但也提供了Python接口。Dlib结合了高效的算法和易用性&#xff0c;使其成为学术界和工业界的热门选择。 1.…

C++基础 -18-继承中类继承的区别

无论使用公有&#xff0c;保护&#xff0c;私有继承 都无法访问基类私有成员 在多级继承中 使用公有继承 派生的派生可以访问基类的公有&#xff0c;保护成员 在多级继承中 使用保护继承 派生的派生可以访问基类的公有&#xff0c;保护成员 在多级继承中 使用私有继承 派生的派…

车辆动力学 | 轮胎纵滑和侧滑下的简化模型

1、轮胎模型的定义&#xff1a; ——反应轮胎力学性能&#xff08;所有侧向力、纵向力以及会正力矩等&#xff09;与侧偏角和运动状态&#xff08;滑转率和滑移率&#xff09;关系的数学模型 2、四个组成部分 胎面层、带束层、胎体、轮辋 3、简化模型的假设条件 4、起滑点&am…

电脑微信多开怎么操作?电脑微信多开bat代码

电脑微信多开怎么操作&#xff1f; 方法一&#xff1a;电脑微信多开bat代码方法二 微信是我们日常使用率很高的社交软件&#xff0c;在工作中因工作需要可能会用到两个微信号&#xff0c;如果是只登录一个微信&#xff0c;另外一个微信的聊天信息很容易漏看&#xff0c;这时候微…

<Linux>冯诺依曼体系结构||操作系统||系统调用于用户操作接口

前言:本文从软硬件角度计算机解释软硬件结构 硬件—冯诺依曼体系结构 软件—操作系统 文章目录 冯诺依曼计算机体系结构背景理解举例 操作系统(OS)OS的管理为什么要有操作系统? 系统调用与用户操作接口系统调用用户操作接口引入:printf&&scanf的重新理解库函数 计算机…

力扣 41 42.接雨水问题详细讲解,保证看完必会接雨水问题!!!时间复杂度最优解 o(n)

首先来个开胃小菜&#xff0c;41.缺少最小整数&#xff08;难度&#xff1a;困难&#xff09;真实感觉像是个简单级别 41. 缺失的第一个正数 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额…

干货:机器学习之线性代码基础

资料地址&#xff1a;https://machine-learning-from-scratch.rea 线性代数 0. 要点汇总1. 向量 Vector 1.1 向量是什么1.2 向量的运算 1.2.1 向量的加法1.2.2 向量的数乘 2. 线性组合、张成的空间与基 Linear Combination, Span and Basis 2.1 运算封闭2.2 线性组合2.2 向量…

Android进阶之路 - TextView文本渐变

那天做需求的时候&#xff0c;遇到一个小功能&#xff0c;建立在前人栽树&#xff0c;后人乘凉的情况下&#xff0c;仅用片刻就写完了&#xff1b;说来惭愧&#xff0c;我以前并未写过文本渐变的需求&#xff0c;脑中也仅有一个shape渐变带来的大概思路&#xff0c;回头来看想着…

Proteus的网络标号与总线

Proteus为了减少过多、复杂的连线&#xff0c;可以使用网络标号与总线配合使用。 Proteus的导线上添加了网络标号&#xff0c;意味着在Proteus上相同的网络标号是连在一起的&#xff0c;所说在图纸上看不出来。 如下图是比较好的Proteus中使用总线的绘制的图纸。可以效仿着画…

【坤坤之夜 KUNKUNNIGHT】- 探索神秘世界,开启刺激冒险之旅!

你是否准备好迎接一个充满挑战和惊喜的单机游戏体验&#xff1f;坤坤之夜&#xff08;KUNKUNNIGHT&#xff09;将带你进入一个神秘而刺激的世界&#xff0c;让你尽情探索&#xff0c;解锁各种有趣的技能和道具&#xff0c;解决谜题&#xff0c;完成各种挑战。 坤坤之夜的游戏画…

CodeMeter软件保护及授权管理解决方案(二)

客户端管理工具 CodeMeter Runtime是CodeMeter解决方案中的重要组成部分&#xff0c;其为独立软件包&#xff0c;开发者需要把CodeMeter Runtime和加密后的软件一起发布。CodeMeter Runtim包括以下组件用于实现授权的使用&#xff1a; CodeMeter License Server授权服务器 Co…

我叫:基数排序【JAVA】

1.自我介绍 基数排序(radix sort)属于“分配式排序” (distribution sort)&#xff0c;又称“桶子法” (bucket sort)或bin sort,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,是‘桶排序’的扩展 2.基本思想 将所有待比较数值统一为同样的数位长度,数位较短的数…