LCOV - code coverage report
Current view: top level - AP_Compass - AP_Compass_Backend.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 95 121 78.5 %
Date: 2026-09-09 11:02:08 Functions: 13 16 81.2 %

          Line data    Source code
       1             : #include "AP_Compass_config.h"
       2             : 
       3             : #if AP_COMPASS_ENABLED
       4             : 
       5             : #include <AP_HAL/AP_HAL.h>
       6             : 
       7             : #include "AP_Compass.h"
       8             : #include "AP_Compass_Backend.h"
       9             : 
      10             : #include <AP_BattMonitor/AP_BattMonitor.h>
      11             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      12             : #include <AP_BoardConfig/AP_BoardConfig.h>
      13             : 
      14       16806 : AP_Compass_Backend::AP_Compass_Backend()
      15       16806 :     : _compass(AP::compass())
      16             : {
      17       16806 : }
      18             : 
      19    46650409 : void AP_Compass_Backend::rotate_field(Vector3f &mag)
      20             : {
      21    46650409 :     Compass::mag_state &state = _compass._state[Compass::StateIndex(instance)];
      22             :     if (MAG_BOARD_ORIENTATION != ROTATION_NONE) {
      23             :         mag.rotate(MAG_BOARD_ORIENTATION);
      24             :     }
      25    46650409 :     mag.rotate(state.rotation);
      26             : 
      27             : #ifdef HAL_HEATER_MAG_OFFSET
      28             :     /*
      29             :       apply compass compensations for boards that have a heater which
      30             :       interferes with an internal compass. This needs to be applied
      31             :       before the board orientation so it is independent of
      32             :       AHRS_ORIENTATION
      33             :      */
      34             :     if (!is_external()) {
      35             :         const uint32_t dev_id = uint32_t(_compass._state[Compass::StateIndex(instance)].dev_id);
      36             :         static const struct offset {
      37             :             uint32_t dev_id;
      38             :             Vector3f ofs;
      39             :         } offsets[] = HAL_HEATER_MAG_OFFSET;
      40             :         const auto *bc = AP::boardConfig();
      41             :         if (bc) {
      42             :             for (const auto &o : offsets) {
      43             :                 if (o.dev_id == dev_id) {
      44             :                     mag += o.ofs * bc->get_heater_duty_cycle() * 0.01;
      45             :                 }
      46             :             }
      47             :         }
      48             :     }
      49             : #endif
      50             : 
      51    46650409 :     if (!state.external) {
      52    31082791 :         mag.rotate(_compass._board_orientation);
      53             :     } else {
      54             :         // add user selectable orientation
      55    15567618 :         mag.rotate((enum Rotation)state.orientation.get());
      56             :     }
      57    46650409 : }
      58             : 
      59    46650409 : void AP_Compass_Backend::publish_raw_field(const Vector3f &mag)
      60             : {
      61             :     // note that we do not set last_update_usec here as otherwise the
      62             :     // EKF and DCM would end up consuming compass data at the full
      63             :     // sensor rate. We want them to consume only the filtered fields
      64             : #if COMPASS_CAL_ENABLED
      65    46650409 :     auto& cal = _compass._calibrator[_compass._get_priority(Compass::StateIndex(instance))];
      66    46650409 :     if (cal != nullptr) {
      67      596089 :         Compass::mag_state &state = _compass._state[Compass::StateIndex(instance)];
      68      596089 :         state.last_update_ms = AP_HAL::millis();
      69      596089 :         cal->new_sample(mag);
      70             :     }
      71             : #endif
      72    46650409 : }
      73             : 
      74    46650409 : void AP_Compass_Backend::correct_field(Vector3f &mag)
      75             : {
      76    46650409 :     Compass::mag_state &state = _compass._state[Compass::StateIndex(instance)];
      77             : 
      78    46650409 :     const Vector3f &offsets = state.offset.get();
      79             : #if AP_COMPASS_DIAGONALS_ENABLED
      80    46650409 :     const Vector3f &diagonals = state.diagonals.get();
      81    46650409 :     const Vector3f &offdiagonals = state.offdiagonals.get();
      82             : #endif
      83             : 
      84             :     // add in the basic offsets
      85    46650409 :     mag += offsets;
      86             : 
      87             :     // add in scale factor, use a wide sanity check. The calibrator
      88             :     // uses a narrower check.
      89    46650409 :     if (_compass.have_scale_factor(instance)) {
      90    46566542 :         mag *= state.scale_factor;
      91             :     }
      92             : 
      93             : #if AP_COMPASS_DIAGONALS_ENABLED
      94             :     // apply elliptical correction
      95    46650409 :     if (!diagonals.is_zero()) {
      96             :         Matrix3f mat(
      97    46650409 :             diagonals.x,    offdiagonals.x, offdiagonals.y,
      98    46650409 :             offdiagonals.x, diagonals.y,    offdiagonals.z,
      99    46650409 :             offdiagonals.y, offdiagonals.z, diagonals.z
     100    46650409 :             );
     101             : 
     102    46650409 :         mag = mat * mag;
     103             :     }
     104             : #endif
     105             : 
     106             : #if COMPASS_MOT_ENABLED
     107    46650409 :     const Vector3f &mot = state.motor_compensation.get();
     108             :     /*
     109             :       calculate motor-power based compensation
     110             :       note that _motor_offset[] is kept even if compensation is not
     111             :       being applied so it can be logged correctly
     112             :     */    
     113    46650409 :     state.motor_offset.zero();
     114    46650409 :     if (_compass._per_motor.enabled() && instance == 0) {
     115             :         // per-motor correction is only valid for first compass
     116           0 :         _compass._per_motor.compensate(state.motor_offset);
     117    46650409 :     } else if (_compass._motor_comp_type == AP_COMPASS_MOT_COMP_THROTTLE) {
     118           0 :         state.motor_offset = mot * _compass._thr;
     119    46650409 :     } else if (_compass._motor_comp_type == AP_COMPASS_MOT_COMP_CURRENT) {
     120        1938 :         AP_BattMonitor &battery = AP::battery();
     121             :         float current;
     122        1938 :         if (battery.current_amps(current)) {
     123        1938 :             state.motor_offset = mot * current;
     124             :         }
     125             :     }
     126             : 
     127             :     /*
     128             :       we apply the motor offsets after we apply the elliptical
     129             :       correction. This is needed to match the way that the motor
     130             :       compensation values are calculated, as they are calculated based
     131             :       on final field outputs, not on the raw outputs
     132             :     */
     133    46650409 :     mag += state.motor_offset;
     134             : #endif // COMPASS_MOT_ENABLED
     135    46650409 : }
     136             : 
     137    46650409 : void AP_Compass_Backend::accumulate_sample(Vector3f &field,
     138             :                                            uint32_t max_samples)
     139             : {
     140             :     /* rotate raw_field from sensor frame to body frame */
     141    46650409 :     rotate_field(field);
     142             : 
     143             :     /* publish raw_field (uncorrected point sample) for calibration use */
     144    46650409 :     publish_raw_field(field);
     145             : 
     146             :     /* correct raw_field for known errors */
     147    46650409 :     correct_field(field);
     148             : 
     149    46650409 :     if (!field_ok(field)) {
     150           0 :         return;
     151             :     }
     152             : 
     153    46650409 :     WITH_SEMAPHORE(_sem);
     154             : 
     155    46650409 :     Compass::mag_state &state = _compass._state[Compass::StateIndex(instance)];
     156    46650409 :     state.accum += field;
     157    46650409 :     state.accum_count++;
     158    46650409 :     if (max_samples && state.accum_count >= max_samples) {
     159     4801253 :         state.accum_count /= 2;
     160     4801253 :         state.accum /= 2;
     161             :     }
     162    46650409 : }
     163             : 
     164     4524327 : void AP_Compass_Backend::drain_accumulated_samples(const Vector3f *scaling)
     165             : {
     166     4524327 :     WITH_SEMAPHORE(_sem);
     167             : 
     168     4524327 :     Compass::mag_state &state = _compass._state[Compass::StateIndex(instance)];
     169             : 
     170     4524327 :     if (state.accum_count == 0) {
     171        5825 :         return;
     172             :     }
     173             : 
     174     4518502 :     if (scaling) {
     175           0 :         state.accum *= *scaling;
     176             :     }
     177     4518502 :     state.accum /= state.accum_count;
     178             : 
     179     4518502 :     publish_filtered_field(state.accum);
     180             : 
     181     4518502 :     state.accum.zero();
     182     4518502 :     state.accum_count = 0;
     183     4524327 : }
     184             : 
     185             : /*
     186             :   copy latest data to the frontend from a backend
     187             :  */
     188     4518502 : void AP_Compass_Backend::publish_filtered_field(const Vector3f &mag)
     189             : {
     190     4518502 :     Compass::mag_state &state = _compass._state[Compass::StateIndex(instance)];
     191             : 
     192     4518502 :     state.field = mag;
     193             : 
     194     4518502 :     state.last_update_ms = AP_HAL::millis();
     195     4518502 :     state.last_update_usec = AP_HAL::micros();
     196     4518502 : }
     197             : 
     198           0 : void AP_Compass_Backend::set_last_update_usec(uint32_t last_update)
     199             : {
     200           0 :     Compass::mag_state &state = _compass._state[Compass::StateIndex(instance)];
     201           0 :     state.last_update_usec = last_update;
     202           0 : }
     203             : 
     204             : /*
     205             :   register a new backend with frontend, returning instance which
     206             :   should be used in publish_field()
     207             :  */
     208       12620 : bool AP_Compass_Backend::register_compass(int32_t dev_id)
     209             : {
     210       12620 :     if (!_compass.register_compass(dev_id, instance)) {
     211        6341 :         return false;
     212             :     }
     213        6279 :     set_dev_id(dev_id);
     214        6279 :     return true;
     215             : }
     216             : 
     217             : /*
     218             :   set dev_id for an instance
     219             : */
     220        6279 : void AP_Compass_Backend::set_dev_id(uint32_t dev_id)
     221             : {
     222        6279 :     _compass._state[Compass::StateIndex(instance)].dev_id.set_and_notify(dev_id);
     223        6279 :     _compass._state[Compass::StateIndex(instance)].detected_dev_id = dev_id;
     224        6279 : }
     225             : 
     226             : /*
     227             :   save dev_id, used by SITL
     228             : */
     229        6264 : void AP_Compass_Backend::save_dev_id()
     230             : {
     231        6264 :     _compass._state[Compass::StateIndex(instance)].dev_id.save();
     232        6264 : }
     233             : 
     234             : /*
     235             :   set external for an instance
     236             : */
     237        2092 : void AP_Compass_Backend::set_external(bool external)
     238             : {
     239        2092 :     if (_compass._state[Compass::StateIndex(instance)].external != 2) {
     240        2087 :         _compass._state[Compass::StateIndex(instance)].external.set_and_notify(external);
     241             :     }
     242        2092 : }
     243             : 
     244           0 : bool AP_Compass_Backend::is_external()
     245             : {
     246           0 :     return _compass._state[Compass::StateIndex(instance)].external;
     247             : }
     248             : 
     249             : // set rotation of an instance
     250        6279 : void AP_Compass_Backend::set_rotation(enum Rotation rotation)
     251             : {
     252        6279 :     _compass._state[Compass::StateIndex(instance)].rotation = rotation;
     253        6279 : }
     254             : 
     255             : static constexpr float FILTER_KOEF = 0.1f;
     256             : 
     257             : /* Check that the compass value is valid by using a mean filter. If
     258             :  * the value is further than filter_range from mean value, it is
     259             :  * rejected. 
     260             : */
     261    46650409 : bool AP_Compass_Backend::field_ok(const Vector3f &field)
     262             : {
     263             : 
     264             :     
     265    46650409 :     if (field.is_inf() || field.is_nan()) {
     266           0 :         return false;
     267             :     }
     268             : 
     269    46650409 :     const float range = (float)_compass.get_filter_range();
     270    46650409 :     if (range <= 0) {
     271    46650409 :         return true;
     272             :     }
     273             : 
     274           0 :     const float length = field.length();
     275             : 
     276           0 :     if (is_zero(_mean_field_length)) {
     277           0 :         _mean_field_length = length;
     278           0 :         return true;
     279             :     }
     280             : 
     281           0 :     bool ret = true;
     282           0 :     const float d = fabsf(_mean_field_length - length) / (_mean_field_length + length);  // diff divide by mean value in percent ( with the *200.0f on later line)
     283           0 :     float koeff = FILTER_KOEF;
     284             : 
     285           0 :     if (d * 200.0f > range) {  // check the difference from mean value outside allowed range
     286             :         // printf("\nCompass field length error: mean %f got %f\n", (double)_mean_field_length, (double)length );
     287           0 :         ret = false;
     288           0 :         koeff /= (d * 10.0f);  // 2.5 and more, so one bad sample never change mean more than 4%
     289           0 :         _error_count++;
     290             :     }
     291           0 :     _mean_field_length = _mean_field_length * (1 - koeff) + length * koeff;  // complimentary filter 1/k
     292             : 
     293           0 :     return ret;
     294             : }
     295             : 
     296             : 
     297           0 : enum Rotation AP_Compass_Backend::get_board_orientation(void) const
     298             : {
     299           0 :     return _compass._board_orientation;
     300             : }
     301             : 
     302             : #endif  // AP_COMPASS_ENABLED

Generated by: LCOV version 1.14