LCOV - code coverage report
Current view: top level - AP_Airspeed - Airspeed_Calibration.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 85 88 96.6 %
Date: 2026-09-09 11:02:08 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /*
       2             :  *   auto_calibration.cpp - airspeed auto calibration
       3             :  *
       4             :  * Algorithm by Paul Riseborough
       5             :  *
       6             :  */
       7             : 
       8             : #include "AP_Airspeed_config.h"
       9             : 
      10             : #if AP_AIRSPEED_ENABLED
      11             : 
      12             : #include <AP_Common/AP_Common.h>
      13             : #include <AP_Math/AP_Math.h>
      14             : #include <GCS_MAVLink/GCS.h>
      15             : #include <AP_AHRS/AP_AHRS.h>
      16             : #include <SRV_Channel/SRV_Channel.h>
      17             : 
      18             : #include "AP_Airspeed.h"
      19             : 
      20             : 
      21             : // constructor - fill in all the initial values
      22        4196 : Airspeed_Calibration::Airspeed_Calibration()
      23        4196 :     : P(100,   0,         0,
      24             :         0,   100,         0,
      25             :         0,     0,  0.000001f)
      26        4196 :     , Q0(0.01f)
      27        4196 :     , Q1(0.0000005f)
      28        4196 :     , state(0, 0, 0)
      29             : {
      30        4196 : }
      31             : 
      32             : /*
      33             :   initialise the ratio
      34             :  */
      35        1398 : void Airspeed_Calibration::init(float initial_ratio)
      36             : {
      37        1398 :     state.z = 1.0f / sqrtf(initial_ratio);
      38        1398 : }
      39             : 
      40             : /*
      41             :   update the state of the airspeed calibration - needs to be called
      42             :   once a second
      43             :  */
      44         402 : float Airspeed_Calibration::update(float airspeed, const Vector3f &vg, int16_t max_airspeed_allowed_during_cal)
      45             : {
      46             :     // Perform the covariance prediction
      47             :     // Q is a diagonal matrix so only need to add three terms in
      48             :     // C code implementation
      49             :     // P = P + Q;
      50         402 :     P.a.x += Q0;
      51         402 :     P.b.y += Q0;
      52         402 :     P.c.z += Q1;
      53             : 
      54             :     // Perform the predicted measurement using the current state estimates
      55             :     // No state prediction required because states are assumed to be time
      56             :     // invariant plus process noise
      57             :     // Ignore vertical wind component
      58         402 :     float TAS_pred = state.z * norm(vg.x - state.x, vg.y - state.y, vg.z);
      59         402 :     float TAS_mea  = airspeed;
      60             : 
      61             :     // Calculate the observation Jacobian H_TAS
      62         402 :     float SH1 = sq(vg.y - state.y) + sq(vg.x - state.x);
      63         402 :     if (SH1 < 0.000001f) {
      64             :         // avoid division by a small number
      65           0 :         return state.z;
      66             :     }
      67         402 :     float SH2 = 1/sqrtf(SH1);
      68             : 
      69             :     // observation Jacobian
      70             :     Vector3f H_TAS(
      71         402 :         -(state.z*SH2*(2*vg.x - 2*state.x))/2,
      72         402 :         -(state.z*SH2*(2*vg.y - 2*state.y))/2,
      73         402 :         1/SH2);
      74             : 
      75             :     // Calculate the fusion innovation covariance assuming a TAS measurement
      76             :     // noise of 1.0 m/s
      77             :     // S = H_TAS*P*H_TAS' + 1.0; % [1 x 3] * [3 x 3] * [3 x 1] + [1 x 1]
      78         402 :     Vector3f PH = P * H_TAS;
      79         402 :     float S = H_TAS * PH + 1.0f;
      80             : 
      81             :     // Calculate the Kalman gain
      82             :     // [3 x 3] * [3 x 1] / [1 x 1]
      83         402 :     Vector3f KG = PH / S;
      84             : 
      85             :     // Update the states
      86         402 :     state += KG*(TAS_mea - TAS_pred); // [3 x 1] + [3 x 1] * [1 x 1]
      87             : 
      88             :     // Update the covariance matrix
      89         402 :     Vector3f HP2 = H_TAS.row_times_mat(P);
      90         402 :     P -= KG.mul_rowcol(HP2);
      91             : 
      92             :     // force symmetry on the covariance matrix - necessary due to rounding
      93             :     // errors
      94         402 :     float P12 = 0.5f * (P.a.y + P.b.x);
      95         402 :     float P13 = 0.5f * (P.a.z + P.c.x);
      96         402 :     float P23 = 0.5f * (P.b.z + P.c.y);
      97         402 :     P.a.y = P.b.x = P12;
      98         402 :     P.a.z = P.c.x = P13;
      99         402 :     P.b.z = P.c.y = P23;
     100             : 
     101             :     // Constrain diagonals to be non-negative - protects against rounding errors
     102         402 :     P.a.x = MAX(P.a.x, 0.0f);
     103         402 :     P.b.y = MAX(P.b.y, 0.0f);
     104         402 :     P.c.z = MAX(P.c.z, 0.0f);
     105             : 
     106         402 :     state.x = constrain_float(state.x, -max_airspeed_allowed_during_cal, max_airspeed_allowed_during_cal);
     107         402 :     state.y = constrain_float(state.y, -max_airspeed_allowed_during_cal, max_airspeed_allowed_during_cal);
     108         402 :     state.z = constrain_float(state.z, 0.5f, 1.0f);
     109             : 
     110         402 :     return state.z;
     111             : }
     112             : 
     113             : 
     114             : /*
     115             :   called once a second to do calibration update
     116             :  */
     117       56510 : void AP_Airspeed::update_calibration(uint8_t i, const Vector3f &vground, int16_t max_airspeed_allowed_during_cal)
     118             : {
     119             : #if AP_AIRSPEED_AUTOCAL_ENABLE
     120       56510 :     if (!param[i].autocal && !calibration_enabled) {
     121             :         // auto-calibration not enabled
     122       56108 :         return;
     123             :     }
     124             : 
     125         402 :     if (param[i].use == 2 && !is_zero(SRV_Channels::get_output_scaled(SRV_Channel::k_throttle))) {
     126             :         // special case for gliders with airspeed sensors behind the
     127             :         // propeller. Allow airspeed to be disabled when throttle is
     128             :         // running
     129           0 :         return;
     130             :     }
     131             : 
     132             :     // set state.z based on current ratio, this allows the operator to
     133             :     // override the current ratio in flight with autocal, which is
     134             :     // very useful both for testing and to force a reasonable value.
     135         402 :     float ratio = constrain_float(param[i].ratio, 1.0f, 4.0f);
     136             : 
     137         402 :     state[i].calibration.state.z = 1.0f / sqrtf(ratio);
     138             : 
     139             :     // calculate true airspeed, assuming a airspeed ratio of 1.0
     140         402 :     float dpress = MAX(get_differential_pressure(i), 0);
     141         402 :     float true_airspeed = sqrtf(dpress) * AP::ahrs().get_EAS2TAS();
     142             : 
     143         402 :     float zratio = state[i].calibration.update(true_airspeed, vground, max_airspeed_allowed_during_cal);
     144             : 
     145         402 :     if (isnan(zratio) || isinf(zratio)) {
     146           0 :         return;
     147             :     }
     148             : 
     149             :     // this constrains the resulting ratio to between 1.0 and 4.0
     150         402 :     zratio = constrain_float(zratio, 0.5f, 1.0f);
     151         402 :     param[i].ratio.set(1/sq(zratio));
     152         402 :     if (state[i].counter > 60) {
     153         233 :         if (state[i].last_saved_ratio > 1.05f*param[i].ratio ||
     154         116 :             state[i].last_saved_ratio < 0.95f*param[i].ratio) {
     155           2 :             param[i].ratio.save();
     156           2 :             state[i].last_saved_ratio = param[i].ratio;
     157           2 :             state[i].counter = 0;
     158           2 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Airspeed %u ratio reset: %f", i , static_cast<double> (param[i].ratio));
     159             :         }
     160             :     } else {
     161         285 :         state[i].counter++;
     162             :     }
     163             : #endif // AP_AIRSPEED_AUTOCAL_ENABLE
     164             : }
     165             : 
     166             : /*
     167             :   called once a second to do calibration update
     168             :  */
     169       28255 : void AP_Airspeed::update_calibration(const Vector3f &vground, int16_t max_airspeed_allowed_during_cal)
     170             : {
     171       84765 :     for (uint8_t i=0; i<AIRSPEED_MAX_SENSORS; i++) {
     172       56510 :         update_calibration(i, vground, max_airspeed_allowed_during_cal);
     173             :     }
     174             : #if HAL_GCS_ENABLED && AP_AIRSPEED_AUTOCAL_ENABLE
     175       28255 :     send_airspeed_calibration(vground);
     176             : #endif
     177       28255 : }
     178             : 
     179             : 
     180             : #if HAL_GCS_ENABLED && AP_AIRSPEED_AUTOCAL_ENABLE
     181       28255 : void AP_Airspeed::send_airspeed_calibration(const Vector3f &vground)
     182             : {
     183             :     /*
     184             :       the AIRSPEED_AUTOCAL message doesn't have an instance number
     185             :       so we can only send it for one sensor at a time
     186             :      */
     187       84363 :     for (uint8_t i=0; i<AIRSPEED_MAX_SENSORS; i++) {
     188       56397 :         if (!param[i].autocal && !calibration_enabled) {
     189             :             // auto-calibration not enabled on this sensor
     190       56108 :             continue;
     191             :         }
     192             :         const mavlink_airspeed_autocal_t packet{
     193         289 :         vx: vground.x,
     194         289 :         vy: vground.y,
     195         289 :         vz: vground.z,
     196         289 :         diff_pressure: get_differential_pressure(i),
     197         289 :         EAS2TAS: AP::ahrs().get_EAS2TAS(),
     198         578 :         ratio: param[i].ratio.get(),
     199         289 :         state_x: state[i].calibration.state.x,
     200         289 :         state_y: state[i].calibration.state.y,
     201         289 :         state_z: state[i].calibration.state.z,
     202         289 :         Pax: state[i].calibration.P.a.x,
     203         289 :         Pby: state[i].calibration.P.b.y,
     204         289 :         Pcz: state[i].calibration.P.c.z
     205         578 :         };
     206         289 :         gcs().send_to_active_channels(MAVLINK_MSG_ID_AIRSPEED_AUTOCAL,
     207             :                                       (const char *)&packet);
     208         289 :         break; // we can only send for one sensor
     209             :     }
     210       28255 : }
     211             : #endif  // HAL_GCS_ENABLED && AP_AIRSPEED_AUTOCAL_ENABLE
     212             : 
     213             : #endif  // AP_AIRSPEED_ENABLED

Generated by: LCOV version 1.14