/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "nano_calibration.h" #include #include #ifdef DIVERSITY_CHECK_ENABLED #include "calibration/common/diversity_checker.h" #endif // DIVERSITY_CHECK_ENABLED #include "calibration/util/cal_log.h" #include "chre/util/nanoapp/log.h" #include "common/math/macros.h" namespace nano_calibration { namespace { // Nano calibration log macros. #ifdef NANO_SENSOR_CAL_DBG_ENABLED #define NANO_CAL_LOGD(tag, format, ...) \ chreLog(CHRE_LOG_DEBUG, "%s " format, tag, ##__VA_ARGS__) #define NANO_CAL_LOGI(tag, format, ...) \ chreLog(CHRE_LOG_INFO, "%s " format, tag, ##__VA_ARGS__) #define NANO_CAL_LOGW(tag, format, ...) \ chreLog(CHRE_LOG_WARN, "%s " format, tag, ##__VA_ARGS__) #define NANO_CAL_LOGE(tag, format, ...) \ chreLog(CHRE_LOG_ERROR, "%s " format, tag, ##__VA_ARGS__) #else #define NANO_CAL_LOGD(tag, format, ...) \ chreLogNull(format, ##__VA_ARGS__) #define NANO_CAL_LOGI(tag, format, ...) \ chreLogNull(format, ##__VA_ARGS__) #define NANO_CAL_LOGW(tag, format, ...) \ chreLogNull(format, ##__VA_ARGS__) #define NANO_CAL_LOGE(tag, format, ...) \ chreLogNull(format, ##__VA_ARGS__) #endif // NANO_SENSOR_CAL_DBG_ENABLED // Indicates and invalid sensor temperature. constexpr float kInvalidTemperatureCelsius = -274.0f; #ifdef GYRO_CAL_ENABLED // Limits NanoSensorCal gyro notifications to once every minute. constexpr uint64_t kNanoSensorCalMessageIntervalNanos = MIN_TO_NANOS(1); #endif // GYRO_CAL_ENABLED #ifdef MAG_CAL_ENABLED // Unit conversion from nanoseconds to microseconds. constexpr float kNanoToMicroseconds = 1e-3f; #endif // MAG_CAL_ENABLED #ifdef SPHERE_FIT_ENABLED constexpr size_t kSamplesToAverageForOdrEstimateMag = 10; // Helper function that estimates the ODR based on the incoming data timestamp. void SamplingRateEstimate(struct SampleRateData *sample_rate_data, float *mean_sampling_rate_hz, uint64_t timestamp_nanos, bool reset_stats) { // If 'mean_sampling_rate_hz' is not nullptr then this function just reads // out the estimate of the sampling rate. if (mean_sampling_rate_hz != nullptr) { if (sample_rate_data->num_samples > 1 && sample_rate_data->time_delta_accumulator > 0) { // Computes the final mean calculation. *mean_sampling_rate_hz = sample_rate_data->num_samples / (static_cast(sample_rate_data->time_delta_accumulator) * NANOS_TO_SEC); } else { // Not enough samples to compute a valid sample rate estimate. Indicate // this with a -1 value. *mean_sampling_rate_hz = -1.0f; } reset_stats = true; } // Resets the sampling rate mean estimator data. if (reset_stats) { sample_rate_data->last_timestamp_nanos = 0; sample_rate_data->time_delta_accumulator = 0; sample_rate_data->num_samples = 0; return; } // Skip adding this data to the accumulator if: // 1. A bad timestamp was received (i.e., time not monotonic). // 2. 'last_timestamp_nanos' is zero. if (timestamp_nanos <= sample_rate_data->last_timestamp_nanos || sample_rate_data->last_timestamp_nanos == 0) { sample_rate_data->last_timestamp_nanos = timestamp_nanos; return; } // Increments the number of samples. sample_rate_data->num_samples++; // Accumulate the time steps. sample_rate_data->time_delta_accumulator += timestamp_nanos - sample_rate_data->last_timestamp_nanos; sample_rate_data->last_timestamp_nanos = timestamp_nanos; } #endif // SPHERE_FIT_ENABLED // Helper function that resets calibration data to a known initial state. void ResetCalParams(struct ashCalParams *cal_params) { // Puts 'cal_params' into a known "default" pass-through state (i.e., // calibration data will not influence sensor streams). memset(cal_params, 0, sizeof(struct ashCalParams)); // Sets 'scaleFactor' to unity. cal_params->scaleFactor[0] = 1.0f; cal_params->scaleFactor[1] = 1.0f; cal_params->scaleFactor[2] = 1.0f; } // Helper function that resets calibration info to a known initial state. void ResetCalInfo(struct ashCalInfo *cal_info) { // Puts 'cal_info' into a known "default" pass-through state (i.e., // calibration info will not influence sensor streams). memset(cal_info, 0, sizeof(struct ashCalInfo)); // Sets 'compMatrix' to the Identity matrix. cal_info->compMatrix[0] = 1.0f; cal_info->compMatrix[4] = 1.0f; cal_info->compMatrix[8] = 1.0f; cal_info->accuracy = ASH_CAL_ACCURACY_MEDIUM; } // Helper function to print out calibration data. void PrintAshCalParams(const struct ashCalParams &cal_params, const char *tag) { NANO_CAL_LOGI(tag, "Offset | Temp [Celsius]: %.6f, %.6f, %.6f | %.6f", cal_params.offset[0], cal_params.offset[1], cal_params.offset[2], cal_params.offsetTempCelsius); NANO_CAL_LOGI(tag, "Temp Sensitivity [rad/sec/C]: %.6f, %.6f, %.6f", cal_params.tempSensitivity[0], cal_params.tempSensitivity[1], cal_params.tempSensitivity[2]); NANO_CAL_LOGI(tag, "Temp Intercept [rad/sec]: %.6f, %.6f, %.6f", cal_params.tempIntercept[0], cal_params.tempIntercept[1], cal_params.tempIntercept[2]); NANO_CAL_LOGI(tag, "Scale Factor: %.6f, %.6f, %.6f", cal_params.scaleFactor[0], cal_params.scaleFactor[1], cal_params.scaleFactor[2]); NANO_CAL_LOGI(tag, "Cross-Axis in [yx, zx, zy] order: %.6f, %.6f, %.6f", cal_params.crossAxis[0], cal_params.crossAxis[1], cal_params.crossAxis[2]); } // Detects and converts Factory Calibration data into a format consumable by the // runtime accelerometer calibration algorithm. #ifdef ACCEL_CAL_ENABLED void HandleAccelFactoryCalibration(struct ashCalParams *cal_params) { // Checks for factory calibration data and performs any processing on the // input to make it compatible with this runtime algorithm. NOTE: Factory // calibrations are distinguished by 'offsetSource'=ASH_CAL_PARAMS_SOURCE_NONE // and 'offsetTempCelsiusSource'=ASH_CAL_PARAMS_SOURCE_FACTORY. bool factory_cal_detected = cal_params->offsetSource == ASH_CAL_PARAMS_SOURCE_NONE && cal_params->offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_FACTORY; if (factory_cal_detected) { // Prints the received factory data. PrintAshCalParams(*cal_params,"[NanoSensorCal:ACCEL_FACTORY_CAL]"); // Sets the parameter source to runtime calibration. cal_params->offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; cal_params->offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; // Ensures that the offset vector is zero in case it has been overwritten by // mistake. memset(cal_params->offset, 0, sizeof(cal_params->offset)); //TODO: Incorporate over-temperature offset compensation. } } #endif // ACCEL_CAL_ENABLED // Detects and converts Factory Calibration data into a format consumable by the // runtime gyroscope calibration algorithm. #ifdef GYRO_CAL_ENABLED void HandleGyroFactoryCalibration(struct ashCalParams *cal_params) { #ifdef OVERTEMPCAL_GYRO_ENABLED // Checks for factory calibration data and performs any processing on the // input to make it compatible with this runtime algorithm. NOTE: Factory // calibrations are distinguished by 'offsetSource'=ASH_CAL_PARAMS_SOURCE_NONE // and 'offsetTempCelsiusSource'=ASH_CAL_PARAMS_SOURCE_FACTORY bool factory_cal_detected = cal_params->offsetSource == ASH_CAL_PARAMS_SOURCE_NONE && cal_params->offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_FACTORY && cal_params->tempSensitivitySource == ASH_CAL_PARAMS_SOURCE_FACTORY && cal_params->tempInterceptSource == ASH_CAL_PARAMS_SOURCE_FACTORY; if (factory_cal_detected) { // Prints the received factory data. PrintAshCalParams(*cal_params, "[NanoSensorCal:OTC_GYRO_FACTORY_CAL]"); #ifdef GYRO_OTC_FACTORY_CAL_ENABLED // Factory OTC calibration initialization is ENABLED. // Since the Factory-Cal OTC model is computed from raw measured data and // the 'offset' at 'offsetTempCelsius' is removed from the input sensor // stream, the intercept must be adjusted so that the runtime OTC produces a // zero offset vector at 'offsetTempCelsius'. for (size_t i = 0; i < 3; i++) { // Shifts the OTC linear model intercept by 'offset_at_offsetTempCelsius'. float offset_at_offsetTempCelsius = cal_params->tempSensitivity[i] * cal_params->offsetTempCelsius + cal_params->tempIntercept[i]; cal_params->tempIntercept[i] -= offset_at_offsetTempCelsius; } #else // Factory OTC calibration initialization is DISABLED. This resets the // AshCalParams and invalidates factory initialization. No factory // initialized model data will be loaded. ResetCalParams(cal_params); #endif // GYRO_OTC_FACTORY_CAL_ENABLED // Sets the parameter source to runtime calibration. cal_params->offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; cal_params->offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; cal_params->tempSensitivitySource = ASH_CAL_PARAMS_SOURCE_RUNTIME; cal_params->tempInterceptSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; // Ensures that the offset vector is zero in case it has been overwritten by // mistake. memset(cal_params->offset, 0, sizeof(cal_params->offset)); } #else // Checks for factory calibration data and performs any processing on the // input to make it compatible with this runtime algorithm. bool factory_cal_detected = cal_params->offsetSource == ASH_CAL_PARAMS_SOURCE_NONE && cal_params->offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_FACTORY; if (factory_cal_detected) { // Prints the received factory data. PrintAshCalParams(*cal_params,"[NanoSensorCal:GYRO_FACTORY_CAL]"); // Sets the parameter source to runtime calibration. cal_params->offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; cal_params->offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; // Ensures that the offset vector is zero in case it has been overwritten by // mistake. memset(cal_params->offset, 0, sizeof(cal_params->offset)); } #endif // OVERTEMPCAL_GYRO_ENABLED } #endif // GYRO_CAL_ENABLED // Detects and converts Factory Calibration data into a format consumable by the // runtime magnetometer calibration algorithm. #ifdef MAG_CAL_ENABLED void HandleMagFactoryCalibration(struct ashCalParams *cal_params) { // Checks for factory calibration data and performs any processing on the // input to make it compatible with this runtime algorithm. bool factory_cal_detected = cal_params->offsetSource == ASH_CAL_PARAMS_SOURCE_NONE && cal_params->offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_FACTORY; if (factory_cal_detected) { // Prints the received factory data. PrintAshCalParams(*cal_params,"[NanoSensorCal:MAG_FACTORY_CAL]"); // Sets the parameter source to runtime calibration. cal_params->offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; cal_params->offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; // Ensures that the offset vector is zero in case it has been overwritten by // mistake. memset(cal_params->offset, 0, sizeof(cal_params->offset)); } } #endif // MAG_CAL_ENABLED } // anonymous namespace NanoSensorCal::NanoSensorCal() { // Initializes the calibration data to a known default state. ResetCalParams(&accel_cal_params_); ResetCalParams(&gyro_cal_params_); ResetCalParams(&mag_cal_params_); // Initializes sensor temperature. temperature_celsius_ = kInvalidTemperatureCelsius; } void NanoSensorCal::Initialize() { NANO_CAL_LOGI("[NanoSensorCal]", "Initialized."); #ifdef ACCEL_CAL_ENABLED // Initializes the accelerometer offset calibration algorithm. accelCalInit(&accel_cal_, 800000000, // Stillness Time in ns (0.8s) 5, // Minimum Sample Number 0.00025f, // Threshold 15, // nx bucket count 15, // nxb bucket count 15, // ny bucket count 15, // nyb bucket count 15, // nz bucket count 15, // nzb bucket count 15); // nle bucket count // Retrieves stored calibration data using the ASH API. LoadAshAccelCal(); #endif // ACCEL_CAL_ENABLED #ifdef GYRO_CAL_ENABLED // Initializes the gyroscope offset calibration algorithm. gyroCalInit( &gyro_cal_, SEC_TO_NANOS(1.4f), // Min stillness period = 1.4 seconds SEC_TO_NANOS(1.4f), // Max stillness period = 1.5 seconds (NOTE 1) 0, 0, 0, // Initial bias offset calibration 0, // Time stamp of initial bias calibration SEC_TO_NANOS(0.5f), // Analysis window length = 0.5 seconds 3.0e-5f, // Gyroscope variance threshold [rad/sec]^2 3.0e-6f, // Gyroscope confidence delta [rad/sec]^2 4.5e-3f, // Accelerometer variance threshold [m/sec^2]^2 9.0e-4f, // Accelerometer confidence delta [m/sec^2]^2 5.0f, // Magnetometer variance threshold [uT]^2 1.0f, // Magnetometer confidence delta [uT]^2 0.95f, // Stillness threshold [0,1] 60.0f * MDEG_TO_RAD, // Stillness mean variation limit [rad/sec] 1.5f, // Max temperature delta during stillness [C] true); // Gyro calibration enable // NOTE 1: This parameter is set to 1.4 seconds to achieve a max stillness // period of 1.5 seconds and avoid buffer boundary conditions that could push // the max stillness to the next multiple of the analysis window length // (i.e., 2.0 seconds). #ifdef OVERTEMPCAL_GYRO_ENABLED // Initializes the over-temperature compensated gyroscope (OTC-Gyro) offset // calibration algorithm. overTempCalInit( &over_temp_gyro_cal_, 5, // Min num of points to enable model update SEC_TO_NANOS(0.1f), // Min temperature update interval [nsec] 0.75f, // Temperature span of bin method [C] 40.0f * MDEG_TO_RAD, // Jump tolerance [rad/sec] 100.0f * MDEG_TO_RAD, // Outlier rejection tolerance [rad/sec] DAYS_TO_NANOS(2), // Model data point age limit [nsec] 250.0f * MDEG_TO_RAD, // Limit for temp. sensitivity [rad/sec/C] 8.0e3f * MDEG_TO_RAD, // Limit for model intercept parameter [rad/sec] 0.1f * MDEG_TO_RAD, // Significant offset change [rad/sec] true); // Over-temp compensation enable #endif // OVERTEMPCAL_GYRO_ENABLED // Retrieves stored calibration data using the ASH API. #ifdef OVERTEMPCAL_GYRO_ENABLED LoadAshOtcGyroCal(); #else LoadAshGyroCal(); #endif // OVERTEMPCAL_GYRO_ENABLED #endif // GYRO_CAL_ENABLED #ifdef MAG_CAL_ENABLED #ifdef DIVERSITY_CHECK_ENABLED #ifdef SPHERE_FIT_ENABLED // Full Sphere Fit. // TODO: Replace function parameters with a struct, to avoid swapping them per // accident. initMagCalSphere(&mag_cal_sphere_, 0.0f, 0.0f, 0.0f, // Bias x, y, z 1.0f, 0.0f, 0.0f, // c00, c01, c02 0.0f, 1.0f, 0.0f, // c10, c11, c12 0.0f, 0.0f, 1.0f, // c20, c21, c22 7357000, // min_batch_window_in_micros 15, // min_num_diverse_vectors 1, // max_num_max_distance 5.0f, // var_threshold 8.0f, // max_min_threshold 48.f, // local_field 0.49f, // threshold_tuning_param 2.5f); // max_distance_tuning_param magCalSphereOdrUpdate(&mag_cal_sphere_, 50 /* Default sample rate Hz */); // ODR init. memset(&mag_sample_rate_data_, 0, sizeof(SampleRateData)); #endif // SPHERE_FIT_ENABLED // Initializes the magnetometer offset calibration algorithm (with diversity // checker). initMagCal(&mag_cal_, 0.0f, 0.0f, 0.0f, // bias x, y, z 1.0f, 0.0f, 0.0f, // c00, c01, c02 0.0f, 1.0f, 0.0f, // c10, c11, c12 0.0f, 0.0f, 1.0f, // c20, c21, c22 3000000, // min_batch_window_in_micros 8, // min_num_diverse_vectors 1, // max_num_max_distance 6.0f, // var_threshold 10.0f, // max_min_threshold 48.f, // local_field 0.49f, // threshold_tuning_param 2.5f); // max_distance_tuning_param #else // Initializes the magnetometer offset calibration algorithm. initMagCal(&mag_cal_, 0.0f, 0.0f, 0.0f, // bias x, y, z 1.0f, 0.0f, 0.0f, // c00, c01, c02 0.0f, 1.0f, 0.0f, // c10, c11, c12 0.0f, 0.0f, 1.0f, // c20, c21, c22 3000000); // min_batch_window_in_micros #endif // DIVERSITY_CHECK_ENABLED // Retrieves stored calibration data using the ASH API. LoadAshMagCal(); #endif // MAG_CAL_ENABLED // Resets the calibration ready flags. accel_calibration_ready_ = false; gyro_calibration_ready_ = false; mag_calibration_ready_ = false; // NanoSensorCal algorithms have been initialized. nanosensorcal_initialized_ = true; } // TODO: Evaluate the impact of sensor batching on the performance of the // calibration algorithms (versus processing on a per-sample basis). For // example, some of the internal algorithms rely on the temperature signal to // determine when temperature variation is too high to perform calibrations. void NanoSensorCal::HandleSensorSamples( uint16_t event_type, const chreSensorThreeAxisData *event_data) { if (nanosensorcal_initialized_) { HandleSensorSamplesAccelCal(event_type, event_data); HandleSensorSamplesGyroCal(event_type, event_data); HandleSensorSamplesMagCal(event_type, event_data); } } void NanoSensorCal::HandleTemperatureSamples( uint16_t event_type, const chreSensorFloatData *event_data) { if (!nanosensorcal_initialized_) return; // Takes the mean of the batched temperature samples and delivers it to the // calibration algorithms. The latency setting determines the minimum update // interval. if (event_type == CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA && event_data->header.readingCount > 0) { const auto header = event_data->header; const auto *data = event_data->readings; uint64_t timestamp_nanos = header.baseTimestamp; float mean_temperature_celsius = 0.0f; for (size_t i = 0; i < header.readingCount; i++) { timestamp_nanos += data[i].timestampDelta; mean_temperature_celsius += data[i].value; } mean_temperature_celsius /= header.readingCount; temperature_celsius_ = mean_temperature_celsius; #ifdef GYRO_CAL_ENABLED #ifdef OVERTEMPCAL_GYRO_ENABLED // Updates the OTC gyro temperature. overTempCalSetTemperature(&over_temp_gyro_cal_, timestamp_nanos, temperature_celsius_); #endif // OVERTEMPCAL_GYRO_ENABLED #endif // GYRO_CAL_ENABLED } } void NanoSensorCal::HandleSensorSamplesAccelCal( uint16_t event_type, const chreSensorThreeAxisData *event_data) { #ifdef ACCEL_CAL_ENABLED if (event_type == CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA) { const auto header = event_data->header; const auto *data = event_data->readings; uint64_t timestamp_nanos = header.baseTimestamp; for (size_t i = 0; i < header.readingCount; i++) { timestamp_nanos += data[i].timestampDelta; accelCalRun(&accel_cal_, timestamp_nanos, data[i].v[0], // x-axis data [m/sec^2] data[i].v[1], // y-axis data [m/sec^2] data[i].v[2], // z-axis data [m/sec^2] temperature_celsius_); } // Checks for an accelerometer bias calibration change. float offset[3] = {0.0f, 0.0f, 0.0f}; if (accelCalUpdateBias(&accel_cal_, &offset[0], &offset[1], &offset[2])) { // Provides a new accelerometer calibration update. accel_calibration_ready_ = true; NotifyAshAccelCal(); } #ifdef ACCEL_CAL_DBG_ENABLED // Prints debug data report. accelCalDebPrint(&accel_cal_, temperature_celsius_); #endif } #endif // ACCEL_CAL_ENABLED } // TODO: Factor common code to shorten function and improve readability. void NanoSensorCal::HandleSensorSamplesGyroCal( uint16_t event_type, const chreSensorThreeAxisData *event_data) { #ifdef GYRO_CAL_ENABLED uint64_t timestamp_nanos = 0; // Only updates the gyroscope calibration algorithm when measured // temperature is valid. if (temperature_celsius_ <= kInvalidTemperatureCelsius) { return; } switch (event_type) { case CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA: { const auto header = event_data->header; const auto *data = event_data->readings; timestamp_nanos = header.baseTimestamp; for (size_t i = 0; i < header.readingCount; i++) { timestamp_nanos += data[i].timestampDelta; gyroCalUpdateAccel(&gyro_cal_, timestamp_nanos, data[i].v[0], // x-axis data [m/sec^2] data[i].v[1], // y-axis data [m/sec^2] data[i].v[2]); // z-axis data [m/sec^2] } break; } case CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA: { const auto header = event_data->header; const auto *data = event_data->readings; timestamp_nanos = header.baseTimestamp; for (size_t i = 0; i < header.readingCount; i++) { timestamp_nanos += data[i].timestampDelta; gyroCalUpdateGyro(&gyro_cal_, timestamp_nanos, data[i].v[0], // x-axis data [rad/sec] data[i].v[1], // y-axis data [rad/sec] data[i].v[2], // z-axis data [rad/sec] temperature_celsius_); } if (gyroCalNewBiasAvailable(&gyro_cal_)) { #ifdef OVERTEMPCAL_GYRO_ENABLED // Sends new GyroCal offset estimate to the OTC-Gyro. float offset[3] = {0.0f, 0.0f, 0.0f}; float offset_temperature_celsius = 0.0f; gyroCalGetBias(&gyro_cal_, &offset[0], &offset[1], &offset[2], &offset_temperature_celsius); overTempCalUpdateSensorEstimate(&over_temp_gyro_cal_, timestamp_nanos, offset, offset_temperature_celsius); #else // Provides a new gyroscope calibration update. gyro_calibration_ready_ = true; NotifyAshGyroCal(); #endif // OVERTEMPCAL_GYRO_ENABLED } #ifdef OVERTEMPCAL_GYRO_ENABLED // Checks OTC for new calibration model update. bool new_otc_model_update = overTempCalNewModelUpdateAvailable(&over_temp_gyro_cal_); // Checks for a change in the OTC-Gyro temperature compensated offset // estimate. bool new_otc_offset = overTempCalNewOffsetAvailable(&over_temp_gyro_cal_); if (new_otc_model_update || new_otc_offset) { // Provides a temperature compensated gyroscope calibration update. gyro_calibration_ready_ = true; NotifyAshGyroCal(); } #endif // OVERTEMPCAL_GYRO_ENABLED break; } case CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA: { const auto header = event_data->header; const auto *data = event_data->readings; timestamp_nanos = header.baseTimestamp; for (size_t i = 0; i < header.readingCount; i++) { timestamp_nanos += data[i].timestampDelta; gyroCalUpdateMag(&gyro_cal_, timestamp_nanos, data[i].v[0], // x-axis data [uT] data[i].v[1], // y-axis data [uT] data[i].v[2]); // z-axis data [uT] } break; } default: break; } if (timestamp_nanos > 0) { #ifdef GYRO_CAL_DBG_ENABLED // Prints debug data report. gyroCalDebugPrint(&gyro_cal_, timestamp_nanos); #endif // GYRO_CAL_DBG_ENABLED #if defined(OVERTEMPCAL_GYRO_ENABLED) && defined(OVERTEMPCAL_DBG_ENABLED) // Prints debug data report. overTempCalDebugPrint(&over_temp_gyro_cal_, timestamp_nanos); #endif // OVERTEMPCAL_GYRO_ENABLED && OVERTEMPCAL_DBG_ENABLED } #endif // GYRO_CAL_ENABLED } void NanoSensorCal::HandleSensorSamplesMagCal( uint16_t event_type, const chreSensorThreeAxisData *event_data) { #ifdef MAG_CAL_ENABLED if (event_type == CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA) { const auto header = event_data->header; const auto *data = event_data->readings; uint64_t timestamp_nanos = header.baseTimestamp; MagUpdateFlags new_calibration_update_mag_cal = MagUpdate::NO_UPDATE; for (size_t i = 0; i < header.readingCount; i++) { timestamp_nanos += data[i].timestampDelta; // Sets the flag to indicate a new calibration update. new_calibration_update_mag_cal |= magCalUpdate( &mag_cal_, static_cast(timestamp_nanos * kNanoToMicroseconds), data[i].v[0], // x-axis data [uT] data[i].v[1], // y-axis data [uT] data[i].v[2]); // z-axis data [uT] #ifdef SPHERE_FIT_ENABLED // Sphere Fit Algo Part. // getting ODR. if (mag_sample_rate_data_.num_samples < kSamplesToAverageForOdrEstimateMag) { SamplingRateEstimate(&mag_sample_rate_data_, nullptr, timestamp_nanos, false); } else { SamplingRateEstimate(&mag_sample_rate_data_, &mag_odr_estimate_hz_, 0, true); // Sphere fit ODR update. magCalSphereOdrUpdate(&mag_cal_sphere_, mag_odr_estimate_hz_); } // Running Sphere fit, and getting trigger. new_calibration_update_mag_cal |= magCalSphereUpdate( &mag_cal_sphere_, static_cast(timestamp_nanos * kNanoToMicroseconds), data[i].v[0], // x-axis data [uT] data[i].v[1], // y-axis data [uT] data[i].v[2]); // z-axis data [uT] #endif // SPHERE_FIT_ENABLED } if ((MagUpdate::UPDATE_BIAS & new_calibration_update_mag_cal) || (MagUpdate::UPDATE_SPHERE_FIT & new_calibration_update_mag_cal)) { // Sets the flag to indicate a new calibration update is pending. mag_calibration_ready_ = true; NotifyAshMagCal(new_calibration_update_mag_cal); } } #endif // MAG_CAL_ENABLED } void NanoSensorCal::GetAccelerometerCalibration( struct ashCalParams *accel_cal_params) const { // Resets the calibration ready flag; and returns the calibration data. accel_calibration_ready_ = false; memcpy(accel_cal_params, &accel_cal_params_, sizeof(struct ashCalParams)); } void NanoSensorCal::GetGyroscopeCalibration( struct ashCalParams *gyro_cal_params) const { // Resets the calibration ready flag; and returns the calibration data. gyro_calibration_ready_ = false; memcpy(gyro_cal_params, &gyro_cal_params_, sizeof(struct ashCalParams)); } void NanoSensorCal::GetMagnetometerCalibration( struct ashCalParams *mag_cal_params) const { // Resets the calibration ready flag; and returns the calibration data. mag_calibration_ready_ = false; memcpy(mag_cal_params, &mag_cal_params_, sizeof(struct ashCalParams)); } void NanoSensorCal::UpdateAccelCalParams() { #ifdef ACCEL_CAL_ENABLED // Gets the accelerometer's offset vector and temperature. accelCalUpdateBias(&accel_cal_, &accel_cal_params_.offset[0], &accel_cal_params_.offset[1], &accel_cal_params_.offset[2]); accel_cal_params_.offsetTempCelsius = temperature_celsius_; // Sets the parameter source to runtime calibration. accel_cal_params_.offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; accel_cal_params_.offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; #endif // ACCEL_CAL_ENABLED } void NanoSensorCal::UpdateGyroCalParams() { #ifdef GYRO_CAL_ENABLED #ifdef OVERTEMPCAL_GYRO_ENABLED // Gets the gyroscope's offset vector and temperature; and OTC linear model // parameters. uint64_t timestamp_nanos = 0; overTempCalGetModel(&over_temp_gyro_cal_, gyro_cal_params_.offset, &gyro_cal_params_.offsetTempCelsius, ×tamp_nanos, gyro_cal_params_.tempSensitivity, gyro_cal_params_.tempIntercept); // Sets the parameter source to runtime calibration. gyro_cal_params_.offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; gyro_cal_params_.offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; gyro_cal_params_.tempSensitivitySource = ASH_CAL_PARAMS_SOURCE_RUNTIME; gyro_cal_params_.tempInterceptSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; #else // Gets the gyroscope's offset vector and temperature. gyroCalGetBias(&gyro_cal_, &gyro_cal_params_.offset[0], &gyro_cal_params_.offset[1], &gyro_cal_params_.offset[2], &gyro_cal_params_.offsetTempCelsius); // Sets the parameter source to runtime calibration. gyro_cal_params_.offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; gyro_cal_params_.offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; #endif // OVERTEMPCAL_GYRO_ENABLED #endif // GYRO_CAL_ENABLED } void NanoSensorCal::UpdateMagCalParams(MagUpdateFlags new_update) { #ifdef MAG_CAL_ENABLED if (MagUpdate::UPDATE_SPHERE_FIT & new_update) { #ifdef SPHERE_FIT_ENABLED // Updating the mag offset from sphere fit. mag_cal_params_.offset[0] = mag_cal_sphere_.sphere_fit.sphere_param.bias[0]; mag_cal_params_.offset[1] = mag_cal_sphere_.sphere_fit.sphere_param.bias[1]; mag_cal_params_.offset[2] = mag_cal_sphere_.sphere_fit.sphere_param.bias[2]; // Updating the Sphere Param. mag_cal_params_.scaleFactor[0] = mag_cal_sphere_.sphere_fit.sphere_param.scale_factor_x; mag_cal_params_.scaleFactor[1] = mag_cal_sphere_.sphere_fit.sphere_param.scale_factor_y; mag_cal_params_.scaleFactor[2] = mag_cal_sphere_.sphere_fit.sphere_param.scale_factor_z; mag_cal_params_.crossAxis[0] = mag_cal_sphere_.sphere_fit.sphere_param.skew_yx; mag_cal_params_.crossAxis[1] = mag_cal_sphere_.sphere_fit.sphere_param.skew_zx; mag_cal_params_.crossAxis[2] = mag_cal_sphere_.sphere_fit.sphere_param.skew_zy; // Updating the temperature. mag_cal_params_.offsetTempCelsius = temperature_celsius_; // Sets the parameter source to runtime calibration. mag_cal_params_.offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; mag_cal_params_.scaleFactorSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; mag_cal_params_.crossAxisSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; mag_cal_params_.offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; #endif // SPHERE_FIT_ENABLED } else if (MagUpdate::UPDATE_BIAS & new_update) { // Gets the magnetometer's offset vector and temperature. magCalGetBias(&mag_cal_, &mag_cal_params_.offset[0], &mag_cal_params_.offset[1], &mag_cal_params_.offset[2]); mag_cal_params_.offsetTempCelsius = temperature_celsius_; // Sets the parameter source to runtime calibration. mag_cal_params_.offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; mag_cal_params_.offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME; } #endif // MAG_CAL_ENABLED } void NanoSensorCal::LoadAshAccelCal() { #ifdef ACCEL_CAL_ENABLED struct ashCalParams cal_params; if (!ashLoadCalibrationParams(CHRE_SENSOR_TYPE_ACCELEROMETER, ASH_CAL_STORAGE_ASH, &cal_params)) { NANO_CAL_LOGE("[NanoSensorCal:RECALL ACCEL]", "ASH failed to recall accelerometer calibration data from " "persistent memory."); } else { // Checks for and performs required processing on input factory cal data. HandleAccelFactoryCalibration(&cal_params); // Checks for valid calibration data. bool runtime_cal_detected = cal_params.offsetSource == ASH_CAL_PARAMS_SOURCE_RUNTIME && cal_params.offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_RUNTIME; if (!runtime_cal_detected) { NANO_CAL_LOGW("[NanoSensorCal:RECALL ACCEL]", "No valid calibration data found."); } else { // On a successful load, copies the new set of calibration parameters. memcpy(&accel_cal_params_, &cal_params, sizeof(struct ashCalParams)); // Sets the accelerometer algorithm's calibration data. accelCalBiasSet(&accel_cal_, accel_cal_params_.offset[0], accel_cal_params_.offset[1], accel_cal_params_.offset[2]); // Prints recalled calibration data. NANO_CAL_LOGI( "[NanoSensorCal:RECALL ACCEL]", "Offset [m/sec^2] | Temp [Celsius]: %.6f, %.6f, %.6f | %.6f", accel_cal_params_.offset[0], accel_cal_params_.offset[1], accel_cal_params_.offset[2], accel_cal_params_.offsetTempCelsius); // Updates the calibration data using ASH. NotifyAshAccelCal(); } } #endif // ACCEL_CAL_ENABLED } void NanoSensorCal::LoadAshGyroCal() { #ifdef GYRO_CAL_ENABLED struct ashCalParams cal_params; if (!ashLoadCalibrationParams(CHRE_SENSOR_TYPE_GYROSCOPE, ASH_CAL_STORAGE_ASH, &cal_params)) { NANO_CAL_LOGE("[NanoSensorCal:RECALL GYRO]", "ASH failed to recall gyroscope calibration data from " "persistent memory."); } else { // Checks for and performs required processing on input factory cal data. HandleGyroFactoryCalibration(&cal_params); // Gyroscope offset calibration parameters were recalled. bool runtime_cal_detected = cal_params.offsetSource == ASH_CAL_PARAMS_SOURCE_RUNTIME && cal_params.offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_RUNTIME; if (!runtime_cal_detected) { NANO_CAL_LOGW("[NanoSensorCal:RECALL GYRO]", "No valid calibration data found."); } else { // On a successful load, copies the new set of calibration parameters. memcpy(&gyro_cal_params_, &cal_params, sizeof(struct ashCalParams)); // Sets the gyroscope algorithm's calibration data. gyroCalSetBias(&gyro_cal_, gyro_cal_params_.offset[0], gyro_cal_params_.offset[1], gyro_cal_params_.offset[2], /*calibration_time_nanos=*/0); // Prints recalled calibration data. NANO_CAL_LOGI( "[NanoSensorCal:RECALL GYRO]", "Offset [rad/sec] | Temp [Celsius]: %.6f, %.6f, %.6f | %.6f", gyro_cal_params_.offset[0], gyro_cal_params_.offset[1], gyro_cal_params_.offset[2], gyro_cal_params_.offsetTempCelsius); // Updates the calibration data using ASH. NotifyAshGyroCal(); } } #endif // GYRO_CAL_ENABLED } void NanoSensorCal::LoadAshOtcGyroCal() { #ifdef GYRO_CAL_ENABLED #ifdef OVERTEMPCAL_GYRO_ENABLED struct ashCalParams cal_params; if (!ashLoadCalibrationParams(CHRE_SENSOR_TYPE_GYROSCOPE, ASH_CAL_STORAGE_ASH, &cal_params)) { NANO_CAL_LOGE("[NanoSensorCal:RECALL OTC-GYRO]", "ASH failed to recall gyroscope calibration data from " "persistent memory."); } else { // Checks for and performs required processing on input factory cal data. HandleGyroFactoryCalibration(&cal_params); // Gyroscope offset calibration with over-temperature compensation (OTC) // parameters were recalled. bool runtime_cal_detected = cal_params.offsetSource == ASH_CAL_PARAMS_SOURCE_RUNTIME && cal_params.offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_RUNTIME && cal_params.tempSensitivitySource == ASH_CAL_PARAMS_SOURCE_RUNTIME && cal_params.tempInterceptSource == ASH_CAL_PARAMS_SOURCE_RUNTIME; if (!runtime_cal_detected) { NANO_CAL_LOGW("[NanoSensorCal:RECALL OTC-GYRO]", "No valid calibration data found."); } else { // On a successful load, copies the new set of calibration parameters. memcpy(&gyro_cal_params_, &cal_params, sizeof(struct ashCalParams)); // Sets the gyroscope algorithm's calibration data. const uint64_t timestamp_nanos = chreGetTime(); gyroCalSetBias(&gyro_cal_, gyro_cal_params_.offset[0], gyro_cal_params_.offset[1], gyro_cal_params_.offset[2], timestamp_nanos); overTempCalSetModel(&over_temp_gyro_cal_, gyro_cal_params_.offset, gyro_cal_params_.offsetTempCelsius, timestamp_nanos, gyro_cal_params_.tempSensitivity, gyro_cal_params_.tempIntercept, /*jump_start_model=*/false); // Prints recalled calibration data. NANO_CAL_LOGI( "[NanoSensorCal:RECALL OTC-GYRO]", "Offset [rad/sec] | Temp [Celsius]: %.6f, %.6f, %.6f | %.6f", gyro_cal_params_.offset[0], gyro_cal_params_.offset[1], gyro_cal_params_.offset[2], gyro_cal_params_.offsetTempCelsius); NANO_CAL_LOGI("[NanoSensorCal:RECALL OTC-GYRO]", "Temp Sensitivity [rad/sec/C]: %.6f, %.6f, %.6f", gyro_cal_params_.tempSensitivity[0], gyro_cal_params_.tempSensitivity[1], gyro_cal_params_.tempSensitivity[2]); NANO_CAL_LOGI("[NanoSensorCal:RECALL OTC-GYRO]", "Temp Intercept [rad/sec]: %.6f, %.6f, %.6f", gyro_cal_params_.tempIntercept[0], gyro_cal_params_.tempIntercept[1], gyro_cal_params_.tempIntercept[2]); // Updates the calibration data using ASH. NotifyAshGyroCal(); } } #endif // OVERTEMPCAL_GYRO_ENABLED #endif // GYRO_CAL_ENABLED } void NanoSensorCal::LoadAshMagCal() { #ifdef MAG_CAL_ENABLED struct ashCalParams cal_params; if (!ashLoadCalibrationParams(CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD, ASH_CAL_STORAGE_ASH, &cal_params)) { NANO_CAL_LOGE("[NanoSensorCal:RECALL MAG]", "ASH failed to recall Magnetometer calibration data from " "persistent memory."); } else { // Checks for and performs required processing on input factory cal data. HandleMagFactoryCalibration(&cal_params); // Checks for valid calibration data. bool runtime_cal_detected = cal_params.offsetSource == ASH_CAL_PARAMS_SOURCE_RUNTIME && cal_params.offsetTempCelsiusSource == ASH_CAL_PARAMS_SOURCE_RUNTIME; if (runtime_cal_detected) { // On a successful load, copies the new set of calibration parameters. memcpy(&mag_cal_params_, &cal_params, sizeof(struct ashCalParams)); // Sets the magnetometer algorithm's calibration data. magCalReset(&mag_cal_); // Resets the magnetometer's offset vector. magCalAddBias(&mag_cal_, mag_cal_params_.offset[0], mag_cal_params_.offset[1], mag_cal_params_.offset[2]); #ifdef SPHERE_FIT_ENABLED // Sets Sphere Fit calibration data. mag_cal_sphere_.sphere_fit.sphere_param.scale_factor_x = mag_cal_params_.scaleFactor[0]; mag_cal_sphere_.sphere_fit.sphere_param.scale_factor_y = mag_cal_params_.scaleFactor[1]; mag_cal_sphere_.sphere_fit.sphere_param.scale_factor_z = mag_cal_params_.scaleFactor[2]; mag_cal_sphere_.sphere_fit.sphere_param.skew_yx = mag_cal_params_.crossAxis[0]; mag_cal_sphere_.sphere_fit.sphere_param.skew_zx = mag_cal_params_.crossAxis[1]; mag_cal_sphere_.sphere_fit.sphere_param.skew_zy = mag_cal_params_.crossAxis[2]; mag_cal_sphere_.sphere_fit.sphere_param.bias[0] = mag_cal_params_.offset[0]; mag_cal_sphere_.sphere_fit.sphere_param.bias[1] = mag_cal_params_.offset[1]; mag_cal_sphere_.sphere_fit.sphere_param.bias[2] = mag_cal_params_.offset[2]; #endif // SPHERE_FIT_ENABLED // Prints recalled calibration data. NANO_CAL_LOGI("[NanoSensorCal:RECALL MAG]", "Offset [uT] | Temp [Celsius]: %.3f, %.3f, %.3f | %.3f", mag_cal_params_.offset[0], mag_cal_params_.offset[1], mag_cal_params_.offset[2], mag_cal_params_.offsetTempCelsius); #ifdef SPHERE_FIT_ENABLED NANO_CAL_LOGI( "[NanoSensorCal:RECALL MAG]", "Scale Factor [%] | Cross Axis [%]: %.3f, %.3f, %.3f |" " %.3f, %.3f, %.3f", mag_cal_params_.scaleFactor[0], mag_cal_params_.scaleFactor[1], mag_cal_params_.scaleFactor[2], mag_cal_params_.crossAxis[0], mag_cal_params_.crossAxis[1], mag_cal_params_.crossAxis[2]); #endif // SPHERE_FIT_ENABLED // Updates the calibration data using ASH. #ifdef SPHERE_FIT_ENABLED NotifyAshMagCal(MagUpdate::UPDATE_SPHERE_FIT); #else NotifyAshMagCal(MagUpdate::UPDATE_BIAS); #endif // SPHERE_FIT_ENABLED } else { NANO_CAL_LOGW("[NanoSensorCal:RECALL MAG]", "No valid calibration data found."); } } #endif // MAG_CAL_ENABLED } void NanoSensorCal::NotifyAshAccelCal() { #ifdef ACCEL_CAL_ENABLED // Update ASH with the latest calibration data. UpdateAccelCalParams(); struct ashCalInfo cal_info; ResetCalInfo(&cal_info); memcpy(cal_info.bias, accel_cal_params_.offset, sizeof(cal_info.bias)); cal_info.accuracy = ASH_CAL_ACCURACY_HIGH; if (!ashSetCalibration(CHRE_SENSOR_TYPE_ACCELEROMETER, &cal_info)) { NANO_CAL_LOGE("[NanoSensorCal:UPDATE ACCEL]", "ASH failed to apply calibration update."); } else { NANO_CAL_LOGD("[NanoSensorCal:UPDATE ACCEL]", "Offset [m/sec^2] | Temp [Celsius]: %.6f, %.6f, %.6f | %.2f", accel_cal_params_.offset[0], accel_cal_params_.offset[1], accel_cal_params_.offset[2], accel_cal_params_.offsetTempCelsius); } // Store the calibration parameters using the ASH API. if (!ashSaveCalibrationParams(CHRE_SENSOR_TYPE_ACCELEROMETER, &accel_cal_params_)) { NANO_CAL_LOGE("[NanoSensorCal:STORE ACCEL]", "ASH failed to write calibration update."); } #endif // ACCEL_CAL_ENABLED } void NanoSensorCal::NotifyAshGyroCal() { #ifdef GYRO_CAL_ENABLED // Update ASH with the latest calibration data. UpdateGyroCalParams(); struct ashCalInfo cal_info; ResetCalInfo(&cal_info); memcpy(cal_info.bias, gyro_cal_params_.offset, sizeof(cal_info.bias)); cal_info.accuracy = ASH_CAL_ACCURACY_HIGH; if (!ashSetCalibration(CHRE_SENSOR_TYPE_GYROSCOPE, &cal_info)) { NANO_CAL_LOGE("[NanoSensorCal:UPDATE GYRO]", "ASH failed to apply calibration update."); } else { const uint64_t timestamp_nanos = chreGetTime(); if (timestamp_nanos >= gyro_notification_time_check_ + kNanoSensorCalMessageIntervalNanos) { gyro_notification_time_check_ = timestamp_nanos; #ifdef OVERTEMPCAL_GYRO_ENABLED NANO_CAL_LOGD( "[NanoSensorCal:UPDATE OTC-GYRO]", "Offset [rad/sec] | Temp [Celsius]: %.6f, %.6f, %.6f | %.2f", gyro_cal_params_.offset[0], gyro_cal_params_.offset[1], gyro_cal_params_.offset[2], gyro_cal_params_.offsetTempCelsius); NANO_CAL_LOGD("[NanoSensorCal:UPDATE OTC-GYRO]", "Temp Sensitivity [rad/sec/C]: %.6f, %.6f, %.6f", gyro_cal_params_.tempSensitivity[0], gyro_cal_params_.tempSensitivity[1], gyro_cal_params_.tempSensitivity[2]); NANO_CAL_LOGD("[NanoSensorCal:UPDATE OTC-GYRO]", "Temp Intercept [rad/sec]: %.6f, %.6f, %.6f", gyro_cal_params_.tempIntercept[0], gyro_cal_params_.tempIntercept[1], gyro_cal_params_.tempIntercept[2]); #else NANO_CAL_LOGD( "[NanoSensorCal:UPDATE GYRO]", "Offset [rad/sec] | Temp [Celsius]: %.6f, %.6f, %.6f | %.2f", gyro_cal_params_.offset[0], gyro_cal_params_.offset[1], gyro_cal_params_.offset[2], gyro_cal_params_.offsetTempCelsius); #endif // OVERTEMPCAL_GYRO_ENABLED } } // Store the calibration parameters using the ASH API. if (!ashSaveCalibrationParams(CHRE_SENSOR_TYPE_GYROSCOPE, &gyro_cal_params_)) { NANO_CAL_LOGE("[NanoSensorCal:STORE GYRO]", "ASH failed to write calibration update."); } #endif // GYRO_CAL_ENABLED } void NanoSensorCal::NotifyAshMagCal(MagUpdateFlags new_update) { #ifdef MAG_CAL_ENABLED // Update ASH with the latest calibration data. UpdateMagCalParams(new_update); struct ashCalInfo cal_info; ResetCalInfo(&cal_info); memcpy(cal_info.bias, mag_cal_params_.offset, sizeof(cal_info.bias)); // TODO: Adding Sphere Parameters to compensation matrix. cal_info.accuracy = ASH_CAL_ACCURACY_HIGH; if (!ashSetCalibration(CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD, &cal_info)) { NANO_CAL_LOGE("[NanoSensorCal:UPDATE MAG]", "ASH failed to apply calibration update."); } else { NANO_CAL_LOGD("[NanoSensorCal:UPDATE MAG]", "Offset [uT] | Temp [Celsius]: %.6f, %.6f, %.6f | %.2f", mag_cal_params_.offset[0], mag_cal_params_.offset[1], mag_cal_params_.offset[2], mag_cal_params_.offsetTempCelsius); #ifdef SPHERE_FIT_ENABLED NANO_CAL_LOGD("[NanoSensorCal:UPDATE MAG]", "Scale Factor [%] | Cross Axis [%]: %.3f, %.3f, %.3f | " " %.3f, %.3f, %.3f", mag_cal_params_.scaleFactor[0], mag_cal_params_.scaleFactor[1], mag_cal_params_.scaleFactor[2], mag_cal_params_.crossAxis[0], mag_cal_params_.crossAxis[1], mag_cal_params_.crossAxis[2]); #endif // SPHERE_FIT_ENABLED } // Store the calibration parameters using the ASH API. if (!ashSaveCalibrationParams(CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD, &mag_cal_params_)) { NANO_CAL_LOGE("[NanoSensorCal:STORE MAG]", "ASH failed to write calibration update."); } #endif // MAG_CAL_ENABLED } } // namespace nano_calibration