#include #include #include #include #include #include "Compass_learn.h" #include #include #include #if COMPASS_LEARN_ENABLED extern const AP_HAL::HAL &hal; static const uint8_t COMPASS_LEARN_NUM_SAMPLES = 30; static const uint8_t COMPASS_LEARN_BEST_ERROR_THRESHOLD = 50; static const uint8_t COMPASS_LEARN_WORST_ERROR_THRESHOLD = 65; // constructor CompassLearn::CompassLearn(Compass &_compass) : compass(_compass) { gcs().send_text(MAV_SEVERITY_INFO, "CompassLearn: Initialised"); for (Compass::Priority i(0); iget_soft_armed() || vehicle->get_time_flying_ms() < 3000) { // only learn when flying and with enough time to be clear of // the ground return; } const AP_AHRS &ahrs = AP::ahrs(); if (!have_earth_field) { Location loc; if (!ahrs.get_position(loc)) { // need to wait till we have a global position return; } // setup the expected earth field in mGauss at this location mag_ef = AP_Declination::get_earth_field_ga(loc) * 1000; have_earth_field = true; // form eliptical correction matrix and invert it. This is // needed to remove the effects of the eliptical correction // when calculating new offsets const Vector3f &diagonals = compass.get_diagonals(0); const Vector3f &offdiagonals = compass.get_offdiagonals(0); mat = Matrix3f( diagonals.x, offdiagonals.x, offdiagonals.y, offdiagonals.x, diagonals.y, offdiagonals.z, offdiagonals.y, offdiagonals.z, diagonals.z ); if (!mat.invert()) { // if we can't invert, use the identity matrix mat.identity(); } // set initial error to field intensity float intensity = mag_ef.length(); for (uint16_t i=0; iregister_io_process(FUNCTOR_BIND_MEMBER(&CompassLearn::io_timer, void)); } AP_Notify::flags.compass_cal_running = true; if (sample_available) { // last sample still being processed by IO thread return; } Vector3f field = compass.get_field(0); Vector3f field_change = field - last_field; if (field_change.length() < min_field_change) { return; } { WITH_SEMAPHORE(sem); // give a sample to the backend to process new_sample.field = field; new_sample.offsets = compass.get_offsets(0); new_sample.attitude = Vector3f(ahrs.roll, ahrs.pitch, ahrs.yaw); sample_available = true; last_field = field; num_samples++; } if (sample_available) { // @LoggerMessage: COFS // @Description: Current compass learn offsets // @Field: TimeUS: Time since system startup // @Field: OfsX: best learnt offset, x-axis // @Field: OfsY: best learnt offset, y-axis // @Field: OfsZ: best learnt offset, z-axis // @Field: Var: error of best offset vector // @Field: Yaw: best learnt yaw // @Field: WVar: error of best learn yaw // @Field: N: number of samples used AP::logger().Write("COFS", "TimeUS,OfsX,OfsY,OfsZ,Var,Yaw,WVar,N", "QffffffI", AP_HAL::micros64(), (double)best_offsets.x, (double)best_offsets.y, (double)best_offsets.z, (double)best_error, (double)best_yaw_deg, (double)worst_error, num_samples); } if (!converged) { WITH_SEMAPHORE(sem); // set offsets to current best guess compass.set_offsets(0, best_offsets); // set non-primary offsets to match primary Vector3f field_primary = compass.get_field(0); for (uint8_t i=1; i COMPASS_LEARN_NUM_SAMPLES && best_error < COMPASS_LEARN_BEST_ERROR_THRESHOLD && worst_error > COMPASS_LEARN_WORST_ERROR_THRESHOLD) { // set the offsets and enable compass for EKF use. Let the // EKF learn the remaining compass offset error for (uint8_t i=0; i worstv) { worstv = errors[i]; } } WITH_SEMAPHORE(sem); // pass the current estimate to the front-end best_offsets = predicted_offsets[besti]; best_error = bestv; worst_error = worstv; best_yaw_deg = wrap_360(degrees(s.attitude.z) + besti * (360/num_sectors)); // send current learn state to gcs const uint32_t now = AP_HAL::millis(); if (!converged && now - last_learn_progress_sent_ms >= 5000) { float percent = (MIN(num_samples / COMPASS_LEARN_NUM_SAMPLES, 1.0f) + MIN(COMPASS_LEARN_BEST_ERROR_THRESHOLD / (best_error + 1.0f), 1.0f) + MIN(worst_error / COMPASS_LEARN_WORST_ERROR_THRESHOLD, 1.0f)) / 3.0f * 100.f; gcs().send_text(MAV_SEVERITY_INFO, "CompassLearn: %d%%", (int) percent); last_learn_progress_sent_ms = now; } } #endif // COMPASS_LEARN_ENABLED