Ветер в EKF¶
EKF3 оценивает ветер сам — это state-переменные 22 и 23 (NE-компоненты). Никаких ручек «задать ветер» в EKF нет, только косвенная настройка через EK3_WIND_P_NSE.
Жизненный цикл¶
┌─ На земле ────┬─ Взлёт ──────┬─ Полёт c aiding ─┬─ Coast ─────────┬─ После CMD 43003 ─┐
│ inhibitWind │ инициализация │ active learning │ frozen (truth) │ relearn (быстро) │
│ States=true │ из airspeed │ из TAS-fusion │ │ │
└──────────────┴──────────────┴─────────────────┴─────────────────┴───────────────────┘
Фаза 1 — на земле¶
inhibitWindStates = true (default после init). State 22/23 не интегрируется, ковариация нулевая. EKF на земле ветер не учит.
Триггер активации (AP_NavEKF3_Control.cpp:62-70):
canEstimateWind = ((finalInflightYawInit && dragFusion) || assume_zero_sideslip()) &&
!onGround && // ← блокировка
PV_AidingMode != AID_NONE;
// Активация только при:
inhibitWindStates && canEstimateWind &&
(sq(vx) + sq(vy) > 25.0 || dragFusion) // ← groundspeed > 5 m/s
Фаза 2 — момент включения¶
В момент когда groundspeed превышает 5 m/s, EKF делает один snapshot инициализации (Control.cpp:84-86):
const ftype windSpeed = sqrtF(sq(vel.x) + sq(vel.y)) - tasDataDelayed.tas;
stateStruct.wind_vel.x = windSpeed * cosF(yaw);
stateStruct.wind_vel.y = windSpeed * sinF(yaw);
Формула опирается на допущение «launched into wind» — если ты взлетаешь против ветра, то groundspeed < tas (ветер тебя тормозит), и windSpeed получается отрицательным, направленным против heading. Это и есть истинный вектор ветра.
Если взлетаешь поперёк/по ветру — инициализация будет с ошибкой, но EKF исправит её в фазе 3 за 5–10 секунд активного обучения.
Фаза 3 — крейс с aiding¶
TAS-fusion каждый цикл подкармливает state 22/23 через Kalman update. Скорость дрейфа задаётся параметром EK3_WIND_P_NSE (process noise):
| Значение | Поведение |
|---|---|
| 0.1 (default upstream) | Ветер «гибче», быстро меняется за TAS-измерением |
| 0.05 (наш baseline) | Медленнее, устойчивее при шумном TAS |
| 0.02 | Очень медленно — ветер почти заморожен, для очень стабильных условий |
Фаза 4 — coast (long gap без коррекций)¶
Когда dead_reckoning=true (PV_AidingMode != AID_NONE && нет vel-источника):
// AP_NavEKF3_core.cpp:1106-1111
if (!inhibitWindStates) {
isDragFusionDeadReckoning = filterStatus.flags.dead_reckoning && !dragTimeout;
if (isDragFusionDeadReckoning || !windStateIsObservable) {
treatWindStatesAsTruth = true;
P[23][23] = P[22][22] = 0.0f; // ← ковариация = 0
}
}
Ковариация в 0 = Kalman gain в 0 = state не двигается из новых наблюдений. Это и есть «заморозка».
Зачем это нужно: при отсутствии velocity-источника EKF превращает airspeed в pure NE-velocity-обзёрвацию через замороженный ветер. Если бы ветер не замораживался — TAS-fusion бы списывал накопленную IMU-drift на «изменение ветра» и оценка позиции разъехалась бы быстрее.
Фаза 5 — возврат коррекций¶
// AP_NavEKF3_core.cpp:1113-1118
} else {
if (treatWindStatesAsTruth) {
treatWindStatesAsTruth = false;
if (windStateIsObservable) {
P[23][23] = P[22][22] = sq(WIND_VEL_VARIANCE_MAX); // ← резко вверх
}
}
...
}
Большая ковариация = большой gain = быстрое перетюнивание из накопленных TAS-наблюдений. Это умышленно: после долгого coast'а EKF должен быстро восстановить актуальную оценку ветра.
Почему AHRS_WND_* больше не работает¶
Параметры AHRS_WND_ENBL/SPD/DIR (коммит 8702e23) пишут в _wind поле DCM, не в EKF state:
// libraries/AP_AHRS/AP_AHRS_DCM.cpp:735+
if (ahrs.get_windEnableParam() == 1) {
_wind = wind; // ← перезапись DCM-оценки ветра
}
_wind — это поле AP_AHRS_DCM, не NavEKF3_core::stateStruct.wind_vel. EKF их не читает. Поиск setWind / setWindEstimate / set_external_wind_estimate в AP_NavEKF3/ — ноль вхождений.
Когда AHRS_WND_* фактически работает¶
Только когда AHRS отдаёт ответы через DCM-backend:
AHRS_EKF_TYPE = 0(DCM forced) — у нас не так.- EKF "unhealthy" → fallback на DCM. Этот fallback отключается битом 0
AHRS_OPTIONS(DisableDCMFallbackFW).
С нашим baseline (AHRS_EKF_TYPE = 3 + AHRS_OPTIONS = 32) DCM никогда не активируется → _wind никем не читается → AHRS_WND_* это dead code.
⇒ Из параметров можно убрать (вернуть в дефолт 0):
Можно ли pre-seed'нуть ветер вручную?¶
Нет, на текущем коде это невозможно:
- Внешнего API в EKF3 нет (поиск
setWindпоказывает 0). - Lua-биндинг
ahrs:get_wind()существует (read),ahrs:set_wind()— не существует. - Даже если бы существовал, он бы писал в DCM
_wind, не в EKF state.
Реализовать можно через C++ патч: добавить NavEKF3::setWindNE(wn, we) который пишет в stateStruct.wind_vel и поднимает ковариацию. Это новый код в EKF core. На текущем этапе не делаем.
Дисциплина «взлёт против ветра»¶
Из-за того что EKF инициализирует ветер из формулы wind = groundspeed − tas в heading самолёта, и эта формула опирается на «launched into wind», взлетать нужно против ветра. Иначе:
- Первоначальная оценка ветра будет некорректной.
- EKF исправит её за 5–10 секунд активного полёта.
- В эти 5–10 секунд позиция может слегка дрейфовать (на величину
wind × time).
Для критичных миссий с длинным дед-рекнингом это важно. Для обычных миссий — некритично.
Альтернатива: хорошо откалиброванный pitot (ARSPD_RATIO ≈ 2.0) сокращает время сходимости. См. также Tridge на форуме ArduPilot: разница 0.5 m/s vs 1.0 m/s drift по airspeed калибровке.
EK3_WIND_P_NSE — что подкручивать¶
Единственная ручка управления ветром в EKF:
| Сценарий | Рекомендация |
|---|---|
| Default (ArduPilot upstream) | 0.1 |
| Valkyrie baseline | 0.05 — устойчивее при шумном TAS |
| Стабильный ветер на полёт | 0.02 |
| Сильно меняющиеся условия | 0.1 (default) |
Менять следует только после стабильных лётных тестов и наблюдения за wind_vel в логах.
Что pilot должен знать о ветре¶
- ✅ EKF учит ветер сам — никаких ручных действий не требуется.
- ✅ Взлетать против ветра — операционная дисциплина.
- ✅ В полёте ветер виден в Mission Planner:
Status→wind_dir, wind_vel(это EKF-оценка). - ❌ Не пытаться «подкормить» ветер через
AHRS_WND_*— не сработает. - ❌ Не верить EKF wind estimate первые 5–10 секунд после взлёта — может быть с ошибкой.