#include #include #include #include #include #include "Compass_learn.h" #include #include extern const AP_HAL::HAL &hal; // constructor CompassLearn::CompassLearn(Compass &_compass) : compass(_compass) { gcs().send_text(MAV_SEVERITY_INFO, "CompassLearn: Initialised"); } /* update when new compass sample available */ void CompassLearn::update(void) { const AP_AHRS &ahrs = AP::ahrs(); if (converged || compass.get_learn_type() != Compass::LEARN_INFLIGHT || !hal.util->get_soft_armed() || ahrs.get_time_flying_ms() < 3000) { // only learn when flying and with enough time to be clear of // the ground return; } if (!have_earth_field) { Location loc; if (!ahrs.get_position(loc)) { // need to wait till we have a global position return; } // remember primary mag primary_mag = compass.get_primary(); // setup the expected earth field at this location float declination_deg=0, inclination_deg=0, intensity_gauss=0; AP_Declination::get_mag_field_ef(loc.lat*1.0e-7f, loc.lng*1.0e-7f, intensity_gauss, declination_deg, inclination_deg); // create earth field mag_ef = Vector3f(intensity_gauss*1000, 0.0, 0.0); Matrix3f R; R.from_euler(0.0f, -ToRad(inclination_deg), ToRad(declination_deg)); mag_ef = R * mag_ef; 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(primary_mag); const Vector3f &offdiagonals = compass.get_offdiagonals(primary_mag); 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 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(primary_mag); 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(primary_mag); new_sample.attitude = Vector3f(ahrs.roll, ahrs.pitch, ahrs.yaw); sample_available = true; last_field = field; num_samples++; } if (sample_available) { 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(primary_mag, best_offsets); // set non-primary offsets to match primary Vector3f field_primary = compass.get_field(primary_mag); for (uint8_t i=0; i 30 && best_error < 50 && worst_error > 65) { // 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)); }