#include #include #include #include #include #include "Compass_learn.h" #include extern const AP_HAL::HAL &hal; // constructor CompassLearn::CompassLearn(AP_AHRS &_ahrs, Compass &_compass) : ahrs(_ahrs), compass(_compass) { } /* update when new compass sample available */ void CompassLearn::update(void) { 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; } // 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-7, loc.lng*1.0e-7, 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; sem = hal.util->new_semaphore(); 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 for (uint16_t i=0; iregister_io_process(FUNCTOR_BIND_MEMBER(&CompassLearn::io_timer, void)); } 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; } if (sem->take_nonblocking()) { // 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++; sem->give(); } if (sample_available) { DataFlash_Class::instance()->Log_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 && sem->take_nonblocking()) { // stop updating the offsets once converged compass.set_offsets(0, best_offsets); if (num_samples > 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 compass.save_offsets(0); compass.set_use_for_yaw(0, true); compass.set_learn_type(Compass::LEARN_EKF, true); converged = true; } sem->give(); } } /* we run the math intensive calculations in the IO thread */ void CompassLearn::io_timer(void) { if (!sample_available) { return; } struct sample s; if (!sem->take_nonblocking()) { return; } s = new_sample; sample_available = false; sem->give(); process_sample(s); } /* process a new compass sample */ void CompassLearn::process_sample(const struct sample &s) { uint16_t besti = 0; float bestv = 0, worstv=0; /* we run through the 72 possible yaw error values, and for each one we calculate a value for the compass offsets if that yaw error is correct. */ for (uint16_t i=0; i worstv) { worstv = errors[i]; } } if (sem->take_nonblocking()) { // 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)); sem->give(); } }