ArduPilot开源代码之EKF3

1. 源由

EKF3是在代码AP_NavEKF3中实现。

为此,研究下相关代码实现,其算法流程应该与《ArduPilot开源代码之EKF1》类似。

2. EKF3简介

AP_NavEKF3实例化了多个称为“lanes”的滤波器实例。主要的lane提供状态估计,其余的在后台更新并可供切换。

  • 可用的lane数量与启用的IMU数量完全相同。
  • 按照传统,每个lane使用空速计、气压计、GPS和磁力计传感器的主要实例。
  • 主要传感器可以通过修改的参数进行设置,故障情况下,系统甚至可以在飞行中稍后进行更改。
  • 通过lane上所有传感器的误差分数计算,选择具有性能最佳的传感器组合的lane。

3. 举例

设备:

  • 2 baros
  • 2 GPS
  • 2 airspeeds
  • 3 mags
  • 3 IMUs

此时,可以有3个lane。

LANE123
ASD121
BAR121
GPS121
MAG123

如果GPS1故障或者信号比GPS2更差,此时系统将切换到lane#2上。相当于有一个备份系统同时工作者,当出现异常时,将会直接接管,有了更好的冗余性,从而更加安全。

4. 配置

Affinity 仅适用于 EKF3,因此请确保 EK3_ENABLE 设置为 “1”,并且 AHRS_EKF_TYPE 设置为 “3” 以确保使用它。

  • EK3_AFFINITY 参数是一个位掩码,可以选择要启用 affinity 的传感器。未启用的传感器将遵循默认的主传感器分配。

  • EK3_ERR_THRESH 参数控制lane切换的灵敏度。lane错误会随着时间的推移相对于活动的主lane累积。该阈值控制考虑非主lane性能更好的错误差异大小。降低此参数会使lane切换对较小的“相对”错误更敏感,实际应用中会看到更积极的lane切换,反之亦然。

注:错误配置 EK3_ERR_THRESH 参数可能会对lane切换机制产生不利影响,并可能导致严重后果,甚至导致飞机丢失。请谨慎调试。

5. 讨论

当然这里也引出了一个问题,就是这个lane上的传感器是如何配置的?

6. lane切换

AP_HAL_MAIN
 └──> loop
     └──> AP_AHRS::update
         └──> AP_AHRS::update_EKF3
             └──> NavEKF3::UpdateFilter
/* 
  更新滤波器状态 - 每当有新的IMU数据可用时应调用此函数
  执行速度受 SCHED_LOOP_RATE 控制
*/
void NavEKF3::UpdateFilter(void)
{
    AP::dal().start_frame(AP_DAL::FrameType::UpdateFilterEKF3);  // 开始一个更新滤波器的数据帧

    if (!core) {
        return;
    }

    imuSampleTime_us = AP::dal().micros64();  // 记录IMU采样时间戳

    for (uint8_t i=0; i<num_cores; i++) {
        // 如果超过3个IMU帧的时间没有超过,且已经使用了超过此循环的1/3 CPU预算,则抑制预测步骤。
        // 这允许多个EKF实例在调度上进行合作
        bool allow_state_prediction = true;
        if (core[i].getFramesSincePredict() < (_framesPerPrediction+3) &&
            AP::dal().ekf_low_time_remaining(AP_DAL::EKFType::EKF3, i)) {
            allow_state_prediction = false;
        }
        core[i].UpdateFilter(allow_state_prediction);  // 更新每个核心的滤波器
    }

    // 如果当前核心选择具有较高的错误分数或不健康,则切换到最低错误分数的健康核心
    // 确保主要核心至少在过去的10秒内处于健康状态,以避免由于初始对齐波动和竞争条件而进行切换
    if (!runCoreSelection) {
        static uint64_t lastUnhealthyTime_us = 0;
        if (!core[primary].healthy() || lastUnhealthyTime_us == 0) {
            lastUnhealthyTime_us = imuSampleTime_us;
        }
        runCoreSelection = (imuSampleTime_us - lastUnhealthyTime_us) > 1E7;
    }

    const bool armed  = AP::dal().get_armed();  // 获取飞行器的武装状态

    // 核心选择仅在飞行器武装后才可用,否则强制使用第0个lane(健康的)
    if (runCoreSelection && armed) {
        // 更新所有活动核心的错误分数,并获取主要核心的错误分数
        float primaryErrorScore = updateCoreErrorScores();

        // 更新所有活动核心的相对错误累积分数
        updateCoreRelativeErrors();

        bool betterCore = false;
        bool altCoreAvailable = false;
        uint8_t newPrimaryIndex = primary;

        // 遍历所有可用核心以查找是否有可用的备用核心
        for (uint8_t coreIndex=0; coreIndex<num_cores; coreIndex++) {
            if (coreIndex != primary) {
                float altCoreError = coreRelativeErrors[coreIndex];

                // 根据以下条件选择备用核心 -
                // 1. 健康且状态已在此时间步更新
                // 2. 相对误差低于主要核心误差
                // 3. 不是主要核心至少已经有10秒
                altCoreAvailable = coreBetterScore(coreIndex, newPrimaryIndex) &&
                    imuSampleTime_us - coreLastTimePrimary_us[coreIndex] > 1E7;

                if (altCoreAvailable) {
                    // 如果此核心的相对误差明显低于活动主要核心,则考虑切换到它
                    betterCore = altCoreError <= -BETTER_THRESH; // 如果其相对误差低于主要核心的显著水平,则为更好的核心
                    // 处理次要核心在完成航向对齐时更快的情况,这可能发生在没有磁力计时的飞行中
                    const NavEKF3_core &newCore = core[coreIndex];
                    const NavEKF3_core &oldCore = core[primary];
                    betterCore |= newCore.have_aligned_yaw() && !oldCore.have_aligned_yaw();
                    newPrimaryIndex = coreIndex;
                }
            }
        }
        altCoreAvailable = newPrimaryIndex != primary;

        // 如果有其他核心可用,并且当前主要核心满足以下条件之一,则切换核心 -
        // 1. 具有较高的错误分数
        // 2. 不健康
        // 3. 健康,但有更好的核心可用
        // 同时更新航向和位置复位数据以捕获由于lane切换引起的变化
        if (altCoreAvailable && (primaryErrorScore > 1.0f || !core[primary].healthy() || betterCore)) {
            updateLaneSwitchYawResetData(newPrimaryIndex, primary);
            updateLaneSwitchPosResetData(newPrimaryIndex, primary);
            updateLaneSwitchPosDownResetData(newPrimaryIndex, primary);
            resetCoreErrors();
            coreLastTimePrimary_us[primary] = imuSampleTime_us;
            primary = newPrimaryIndex;
            lastLaneSwitch_ms = AP::dal().millis();
            GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "EKF3 lane switch %u", primary);
        }       
    }

    const uint8_t user_primary = uint8_t(_primary_core) < num_cores? _primary_core : 0;
    if (primary != user_primary && core[user_primary].healthy() && !armed) {
        // 当在地面并解除武装时,强制选择用户选择的主要核心
        // 这避免了在每次飞行中使用IMU时的抽签
        // 否则,核心选择更新的时间与GPS更新的时间不一致可能导致
        // 某些飞行中使用非第一个核心作为主要核心
        // 不同的IMU可能具有非常不同的噪声特性,这会导致性能不一致
        primary = user_primary;
    }

    // 将非活动源的位置对齐到姿态估计
    sources.align_inactive_sources();
}

7. 参考资料

【1】ArduPilot开源飞控系统之简单介绍
【2】ArduPilot之开源代码Task介绍
【3】ArduPilot飞控启动&运行过程简介
【4】ArduPilot之开源代码Library&Sketches设计
【5】ArduPilot之开源代码Sensor Drivers设计
【6】ArduPilot开源代码之EKF系列研读

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值