LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduCopter - compassmot.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 98 126 77.8 %
Date: 2026-09-09 11:02:08 Functions: 1 1 100.0 %

          Line data    Source code
       1             : #include "Copter.h"
       2             : 
       3             : /*
       4             :   compass/motor interference calibration
       5             :  */
       6             : 
       7             : // setup_compassmot - sets compass's motor interference parameters
       8           1 : MAV_RESULT Copter::mavlink_compassmot(const GCS_MAVLINK &gcs_chan)
       9             : {
      10             : #if FRAME_CONFIG == HELI_FRAME
      11             :     // compassmot not implemented for tradheli
      12           0 :     return MAV_RESULT_UNSUPPORTED;
      13             : #else
      14             :     int8_t   comp_type;                 // throttle or current based compensation
      15           1 :     Vector3f compass_base[COMPASS_MAX_INSTANCES];           // compass vector when throttle is zero
      16           1 :     Vector3f motor_impact[COMPASS_MAX_INSTANCES];           // impact of motors on compass vector
      17           1 :     Vector3f motor_impact_scaled[COMPASS_MAX_INSTANCES];    // impact of motors on compass vector scaled with throttle
      18           1 :     Vector3f motor_compensation[COMPASS_MAX_INSTANCES];     // final compensation to be stored to eeprom
      19             :     float    throttle_pct;              // throttle as a percentage 0.0 ~ 1.0
      20           1 :     float    throttle_pct_max = 0.0f;   // maximum throttle reached (as a percentage 0~1.0)
      21           1 :     float    current_amps_max = 0.0f;   // maximum current reached
      22           1 :     float    interference_pct[COMPASS_MAX_INSTANCES]{};       // interference as a percentage of total mag field (for reporting purposes only)
      23             :     uint32_t last_run_time_ms;
      24             :     uint32_t last_send_time_ms;
      25           1 :     bool     updated = false;           // have we updated the compensation vector at least once
      26           1 :     uint8_t  command_ack_start = command_ack_counter;
      27             : 
      28             :     // exit immediately if we are already in compassmot
      29           1 :     if (ap.compass_mot) {
      30             :         // ignore restart messages
      31           0 :         return MAV_RESULT_TEMPORARILY_REJECTED;
      32             :     } else {
      33           1 :         ap.compass_mot = true;
      34             :     }
      35             : 
      36             :     // check compass is enabled
      37           1 :     if (!AP::compass().available()) {
      38           0 :         gcs_chan.send_text(MAV_SEVERITY_CRITICAL, "Compass disabled");
      39           0 :         ap.compass_mot = false;
      40           0 :         return MAV_RESULT_TEMPORARILY_REJECTED;
      41             :     }
      42             : 
      43             :     // check compass health
      44           1 :     compass.read();
      45           4 :     for (uint8_t i=0; i<compass.get_count(); i++) {
      46           3 :         if (!compass.healthy(i)) {
      47           0 :             gcs_chan.send_text(MAV_SEVERITY_CRITICAL, "Check compass");
      48           0 :             ap.compass_mot = false;
      49           0 :             return MAV_RESULT_TEMPORARILY_REJECTED;
      50             :         }
      51             :     }
      52             : 
      53             :     // check if radio is calibrated
      54           1 :     if (!arming.rc_calibration_checks(true)) {
      55           0 :         gcs_chan.send_text(MAV_SEVERITY_CRITICAL, "RC not calibrated");
      56           0 :         ap.compass_mot = false;
      57           0 :         return MAV_RESULT_TEMPORARILY_REJECTED;
      58             :     }
      59             : 
      60             :     // check throttle is at zero
      61           1 :     read_radio();
      62           1 :     if (channel_throttle->get_control_in() != 0) {
      63           0 :         gcs_chan.send_text(MAV_SEVERITY_CRITICAL, "Throttle not zero");
      64           0 :         ap.compass_mot = false;
      65           0 :         return MAV_RESULT_TEMPORARILY_REJECTED;
      66             :     }
      67             : 
      68             :     // check we are landed
      69           1 :     if (!ap.land_complete) {
      70           0 :         gcs_chan.send_text(MAV_SEVERITY_CRITICAL, "Not landed");
      71           0 :         ap.compass_mot = false;
      72           0 :         return MAV_RESULT_TEMPORARILY_REJECTED;
      73             :     }
      74             : 
      75             :     // disable cpu failsafe
      76           1 :     failsafe_disable();
      77             : 
      78             :     float current;
      79             : 
      80             :     // default compensation type to use current if possible
      81           1 :     if (battery.current_amps(current)) {
      82           1 :         comp_type = AP_COMPASS_MOT_COMP_CURRENT;
      83             :     } else {
      84           0 :         comp_type = AP_COMPASS_MOT_COMP_THROTTLE;
      85             :     }
      86             : 
      87             :     // send back initial ACK
      88           1 :     mavlink_msg_command_ack_send(gcs_chan.get_chan(), MAV_CMD_PREFLIGHT_CALIBRATION,0,
      89             :                                  0, 0, 0, 0);
      90             : 
      91             :     // flash leds
      92           1 :     AP_Notify::flags.esc_calibration = true;
      93             : 
      94             :     // warn user we are starting calibration
      95           1 :     gcs_chan.send_text(MAV_SEVERITY_INFO, "Starting calibration");
      96             : 
      97             :     // inform what type of compensation we are attempting
      98           1 :     if (comp_type == AP_COMPASS_MOT_COMP_CURRENT) {
      99           1 :         gcs_chan.send_text(MAV_SEVERITY_INFO, "Current");
     100             :     } else {
     101           0 :         gcs_chan.send_text(MAV_SEVERITY_INFO, "Throttle");
     102             :     }
     103             : 
     104             :     // disable throttle failsafe
     105           1 :     g.failsafe_throttle.set(FS_THR_Action::DISABLED);
     106             : 
     107             :     // disable motor compensation
     108           1 :     compass.motor_compensation_type(AP_COMPASS_MOT_COMP_DISABLED);
     109           4 :     for (uint8_t i=0; i<compass.get_count(); i++) {
     110           3 :         compass.set_motor_compensation(i, Vector3f{0,0,0});
     111             :     }
     112             : 
     113             :     // get initial compass readings
     114           1 :     compass.read();
     115             : 
     116             :     // store initial x,y,z compass values
     117             :     // initialise interference percentage
     118           4 :     for (uint8_t i=0; i<compass.get_count(); i++) {
     119           3 :         compass_base[i] = compass.get_field(i);
     120           3 :         interference_pct[i] = 0.0f;
     121             :     }
     122             : 
     123           1 :     EXPECT_DELAY_MS(5000);
     124             : 
     125             :     // enable motors and pass through throttle
     126           1 :     motors->output_min();  // output lowest possible value to motors
     127           1 :     motors->armed(true);
     128           1 :     hal.util->set_soft_armed(true);
     129             : 
     130             :     // initialise run time
     131           1 :     last_run_time_ms = millis();
     132           1 :     last_send_time_ms = millis();
     133             : 
     134             :     // main run while there is no user input and the compass is healthy
     135       11319 :     while (command_ack_start == command_ack_counter && compass.healthy() && motors->armed()) {
     136       11318 :         EXPECT_DELAY_MS(5000);
     137             : 
     138             :         // 50hz loop
     139       11318 :         if (millis() - last_run_time_ms < 20) {
     140        9055 :             hal.scheduler->delay(5);
     141        9055 :             continue;
     142             :         }
     143        2263 :         last_run_time_ms = millis();
     144             : 
     145             :         // read radio input
     146        2263 :         read_radio();
     147             : 
     148             :         // pass through throttle to motors
     149        2263 :         auto &srv = AP::srv();
     150        2263 :         srv.cork();
     151        2263 :         motors->set_throttle_passthrough_for_esc_calibration(channel_throttle->get_control_in() * 0.001f);
     152        2263 :         srv.push();
     153             : 
     154             :         // read some compass values
     155        2263 :         compass.read();
     156             : 
     157             :         // read current
     158        2263 :         battery.read();
     159             : 
     160             :         // calculate scaling for throttle
     161        2263 :         throttle_pct = (float)channel_throttle->get_control_in() * 0.001f;
     162        2263 :         throttle_pct = constrain_float(throttle_pct,0.0f,1.0f);
     163             : 
     164             :         // record maximum throttle
     165        2263 :         throttle_pct_max = MAX(throttle_pct_max, throttle_pct);
     166             : 
     167        2263 :         if (!battery.current_amps(current)) {
     168           0 :             current = 0;
     169             :         }
     170        2263 :         current_amps_max = MAX(current_amps_max, current);
     171             : 
     172             :         // if throttle is near zero, update base x,y,z values
     173        2263 :         if (!is_positive(throttle_pct)) {
     174        1032 :             for (uint8_t i=0; i<compass.get_count(); i++) {
     175         774 :                 compass_base[i] = compass_base[i] * 0.99f + compass.get_field(i) * 0.01f;
     176             :             }
     177             :         } else {
     178             : 
     179             :             // calculate diff from compass base and scale with throttle
     180        8020 :             for (uint8_t i=0; i<compass.get_count(); i++) {
     181        6015 :                 motor_impact[i] = compass.get_field(i) - compass_base[i];
     182             :             }
     183             : 
     184             :             // throttle based compensation
     185        2005 :             if (comp_type == AP_COMPASS_MOT_COMP_THROTTLE) {
     186             :                 // for each compass
     187           0 :                 for (uint8_t i=0; i<compass.get_count(); i++) {
     188             :                     // scale by throttle
     189           0 :                     motor_impact_scaled[i] = motor_impact[i] / throttle_pct;
     190             :                     // adjust the motor compensation to negate the impact
     191           0 :                     motor_compensation[i] = motor_compensation[i] * 0.99f - motor_impact_scaled[i] * 0.01f;
     192             :                 }
     193           0 :                 updated = true;
     194             :             } else {
     195             :                 // for each compass
     196        8020 :                 for (uint8_t i=0; i<compass.get_count(); i++) {
     197             :                     // adjust the motor compensation to negate the impact if drawing over 3amps
     198        6015 :                     if (current >= 3.0f) {
     199        4782 :                         motor_impact_scaled[i] = motor_impact[i] / current;
     200        4782 :                         motor_compensation[i] = motor_compensation[i] * 0.99f - motor_impact_scaled[i] * 0.01f;
     201        4782 :                         updated = true;
     202             :                     }
     203             :                 }
     204             :             }
     205             : 
     206             :             // calculate interference percentage at full throttle as % of total mag field
     207        2005 :             if (comp_type == AP_COMPASS_MOT_COMP_THROTTLE) {
     208           0 :                 for (uint8_t i=0; i<compass.get_count(); i++) {
     209             :                     // interference is impact@fullthrottle / mag field * 100
     210           0 :                     interference_pct[i] = motor_compensation[i].length() / (float)arming.compass_magfield_expected() * 100.0f;
     211             :                 }
     212             :             } else {
     213        8020 :                 for (uint8_t i=0; i<compass.get_count(); i++) {
     214             :                     // interference is impact/amp * (max current seen / max throttle seen) / mag field * 100
     215        6015 :                     interference_pct[i] = motor_compensation[i].length() * (current_amps_max/throttle_pct_max) / (float)arming.compass_magfield_expected() * 100.0f;
     216             :                 }
     217             :             }
     218             :         }
     219             : 
     220        2263 :         if (AP_HAL::millis() - last_send_time_ms > 500) {
     221          88 :             last_send_time_ms = AP_HAL::millis();
     222          88 :             mavlink_msg_compassmot_status_send(gcs_chan.get_chan(),
     223          88 :                                                channel_throttle->get_control_in(),
     224             :                                                current,
     225          88 :                                                interference_pct[0],
     226             :                                                motor_compensation[0].x,
     227             :                                                motor_compensation[0].y,
     228             :                                                motor_compensation[0].z);
     229             : #if HAL_WITH_ESC_TELEM
     230             :             // send ESC telemetry to monitor ESC and motor temperatures
     231          88 :             AP::esc_telem().send_esc_telemetry_mavlink(gcs_chan.get_chan());
     232             : #endif
     233             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     234             :             // a lot of autotest timeouts are based on receiving system time
     235          88 :             gcs_chan.send_system_time();
     236             :             // autotesting of compassmot wants to see RC channels message
     237          88 :             gcs_chan.send_rc_channels();
     238             : #endif
     239             :         }
     240       11318 :     }
     241             : 
     242             :     // stop motors
     243           1 :     motors->output_min();
     244           1 :     motors->armed(false);
     245           1 :     hal.util->set_soft_armed(false);
     246             : 
     247             :     // set and save motor compensation
     248           1 :     if (updated) {
     249           1 :         compass.motor_compensation_type(comp_type);
     250           4 :         for (uint8_t i=0; i<compass.get_count(); i++) {
     251           3 :             compass.set_motor_compensation(i, motor_compensation[i]);
     252             :         }
     253           1 :         compass.save_motor_compensation();
     254             :         // display success message
     255           1 :         gcs_chan.send_text(MAV_SEVERITY_INFO, "Calibration successful");
     256             :     } else {
     257             :         // compensation vector never updated, report failure
     258           0 :         gcs_chan.send_text(MAV_SEVERITY_NOTICE, "Failed");
     259           0 :         compass.motor_compensation_type(AP_COMPASS_MOT_COMP_DISABLED);
     260             :     }
     261             : 
     262             :     // turn off notify leds
     263           1 :     AP_Notify::flags.esc_calibration = false;
     264             : 
     265             :     // re-enable cpu failsafe
     266           1 :     failsafe_enable();
     267             : 
     268             :     // re-enable failsafes
     269           1 :     g.failsafe_throttle.load();
     270             : 
     271             :     // flag we have completed
     272           1 :     ap.compass_mot = false;
     273             : 
     274           1 :     return MAV_RESULT_ACCEPTED;
     275             : #endif  // FRAME_CONFIG != HELI_FRAME
     276           1 : }

Generated by: LCOV version 1.14