summaryrefslogtreecommitdiff
path: root/firmware/os/algos/calibration/over_temp/over_temp_cal.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/os/algos/calibration/over_temp/over_temp_cal.c')
-rw-r--r--firmware/os/algos/calibration/over_temp/over_temp_cal.c940
1 files changed, 940 insertions, 0 deletions
diff --git a/firmware/os/algos/calibration/over_temp/over_temp_cal.c b/firmware/os/algos/calibration/over_temp/over_temp_cal.c
new file mode 100644
index 00000000..0b8b6c73
--- /dev/null
+++ b/firmware/os/algos/calibration/over_temp/over_temp_cal.c
@@ -0,0 +1,940 @@
+/*
+ * Copyright (C) 2016 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 "calibration/over_temp/over_temp_cal.h"
+
+#include <float.h>
+#include <math.h>
+#include <string.h>
+
+#include "calibration/util/cal_log.h"
+#include "common/math/vec.h"
+#include "util/nano_assert.h"
+
+/////// DEFINITIONS AND MACROS ////////////////////////////////////////////////
+
+// The 'temp_sensitivity' parameters are set to this value to indicate that the
+// model is in its initial state.
+#define MODEL_INITIAL_STATE (1e6f)
+
+// Rate-limits the search for the nearest offset estimate to every 10 seconds
+// when no model has been updated yet.
+#define OVERTEMPCAL_NEAREST_NANOS (10000000000)
+
+// Rate-limits the check of old data to every 2 hours.
+#define OVERTEMPCAL_STALE_CHECK_TIME_NANOS (7200000000000)
+
+// A common sensor operating temperature at which to start producing the model
+// jump-start data.
+#define JUMPSTART_START_TEMP_CELSIUS (30.0f)
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+// A debug version label to help with tracking results.
+#define OVERTEMPCAL_DEBUG_VERSION_STRING "[Dec 12, 2016]"
+
+// The time value used to throttle debug messaging.
+#define OVERTEMPCAL_WAIT_TIME_NANOS (250000000)
+
+// Debug log tag string used to identify debug report output data.
+#define OVERTEMPCAL_REPORT_TAG "[OVER_TEMP_CAL:REPORT]"
+
+// Converts units of radians to milli-degrees.
+#define RAD_TO_MILLI_DEGREES (float)(1e3f * 180.0f / M_PI)
+
+// Sensor axis label definition with index correspondence: 0=X, 1=Y, 2=Z.
+static const char kDebugAxisLabel[3] = "XYZ";
+#endif // OVERTEMPCAL_DBG_ENABLED
+
+/////// FORWARD DECLARATIONS //////////////////////////////////////////////////
+
+// Updates the most recent model estimate data.
+static void setLatestEstimate(struct OverTempCal *over_temp_cal,
+ const float *offset, float offset_temp,
+ uint64_t timestamp_nanos);
+
+/*
+ * Determines if a new over-temperature model fit should be performed, and then
+ * updates the model as needed.
+ *
+ * INPUTS:
+ * over_temp_cal: Over-temp data structure.
+ * timestamp_nanos: Current timestamp for the model update.
+ */
+static void computeModelUpdate(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos);
+
+/*
+ * Searches 'model_data' for the sensor offset estimate closest to the current
+ * temperature. Sets the 'latest_offset' pointer to the result.
+ */
+static void setNearestEstimate(struct OverTempCal *over_temp_cal);
+
+/*
+ * Removes the "old" offset estimates from 'model_data' (i.e., eliminates the
+ * drift-compromised data). Returns 'true' if any data was removed.
+ */
+static bool removeStaleModelData(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos);
+
+/*
+ * Removes the offset estimates from 'model_data' at index, 'model_index'.
+ * Returns 'true' if data was removed.
+ */
+static bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
+ size_t model_index);
+
+/*
+ * Since it may take a while for an empty model to build up enough data to start
+ * producing new model parameter updates, the model collection can be
+ * jump-started by using the new model parameters to insert fake data in place
+ * of actual sensor offset data.
+ */
+static bool jumpStartModelData(struct OverTempCal *over_temp_cal);
+
+/*
+ * Provides updated model parameters for the over-temperature model data.
+ *
+ * INPUTS:
+ * over_temp_cal: Over-temp data structure.
+ * OUTPUTS:
+ * temp_sensitivity: Updated modeled temperature sensitivity (array).
+ * sensor_intercept: Updated model intercept (array).
+ *
+ * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
+ *
+ * Reference: "Comparing two ways to fit a line to data", John D. Cook.
+ * http://www.johndcook.com/blog/2008/10/20/comparing-two-ways-to-fit-a-line-to-data/
+ */
+static void updateModel(const struct OverTempCal *over_temp_cal,
+ float *temp_sensitivity, float *sensor_intercept);
+
+/*
+ * Removes the over-temp compensated offset from the input sensor data.
+ *
+ * INPUTS:
+ * over_temp_cal: Over-temp data structure.
+ * vi: Single axis sensor data to be compensated.
+ * index: Index for model parameter compensation (0=x, 1=y, 2=z).
+ * OUTPUTS:
+ * vo: Single axis sensor data that has been compensated.
+ */
+static void removeSensorOffset(const struct OverTempCal *over_temp_cal,
+ float vi, size_t index, float *vo);
+
+/*
+ * Computes the over-temperature compensated sensor offset estimate based on the
+ * input model parameters. Note, this is a single axis calculation.
+ * comp_offset = (temp_sensitivity * temperature + sensor_intercept)
+ *
+ * INPUTS:
+ * temperature: Temperature value at which to compute the estimate.
+ * temp_sensitivity: Temperature sensitivity model parameter.
+ * sensor_intercept: Sensor intercept model parameter.
+ * RETURNS:
+ * comp_offset: Over-temperature compensated sensor offset estimate.
+ */
+static float getCompensatedOffset(float temperature, float temp_sensitivity,
+ float sensor_intercept);
+
+/////// FUNCTION DEFINITIONS //////////////////////////////////////////////////
+
+void overTempCalInit(struct OverTempCal *over_temp_cal,
+ size_t min_num_model_pts,
+ uint64_t min_update_interval_nanos,
+ float delta_temp_per_bin, float max_error_limit,
+ uint64_t age_limit_nanos, float temp_sensitivity_limit,
+ float sensor_intercept_limit, bool over_temp_enable) {
+ ASSERT_NOT_NULL(over_temp_cal);
+
+ // Clears OverTempCal memory.
+ memset(over_temp_cal, 0, sizeof(struct OverTempCal));
+
+ // Initializes the pointer to the most recent sensor offset estimate. Sets it
+ // as the first element in 'model_data'.
+ over_temp_cal->latest_offset = &over_temp_cal->model_data[0];
+
+ // Sets the temperature sensitivity model parameters to MODEL_INITIAL_STATE to
+ // indicate that the model is in an "initial" state.
+ over_temp_cal->temp_sensitivity[0] = MODEL_INITIAL_STATE;
+ over_temp_cal->temp_sensitivity[1] = MODEL_INITIAL_STATE;
+ over_temp_cal->temp_sensitivity[2] = MODEL_INITIAL_STATE;
+
+ // Initializes the model identification parameters.
+ over_temp_cal->new_overtemp_model_available = false;
+ over_temp_cal->min_num_model_pts = min_num_model_pts;
+ over_temp_cal->min_update_interval_nanos = min_update_interval_nanos;
+ over_temp_cal->delta_temp_per_bin = delta_temp_per_bin;
+ over_temp_cal->max_error_limit = max_error_limit;
+ over_temp_cal->age_limit_nanos = age_limit_nanos;
+ over_temp_cal->temp_sensitivity_limit = temp_sensitivity_limit;
+ over_temp_cal->sensor_intercept_limit = sensor_intercept_limit;
+ over_temp_cal->over_temp_enable = over_temp_enable;
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+ CAL_DEBUG_LOG("[OVER_TEMP_CAL:MEMORY]", "sizeof(struct OverTempCal): %lu",
+ (unsigned long int)sizeof(struct OverTempCal));
+ CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]", "Over-Temp Cal: Initialized.");
+
+#endif // OVERTEMPCAL_DBG_ENABLED
+}
+
+void overTempCalSetModel(struct OverTempCal *over_temp_cal, const float *offset,
+ float offset_temp, uint64_t timestamp_nanos,
+ const float *temp_sensitivity,
+ const float *sensor_intercept, bool jump_start_model) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(offset);
+ ASSERT_NOT_NULL(temp_sensitivity);
+ ASSERT_NOT_NULL(sensor_intercept);
+
+ // Sets the model parameters if they are within the acceptable limits.
+ size_t i;
+ for (i = 0; i < 3; i++) {
+ if (NANO_ABS(temp_sensitivity[i]) < over_temp_cal->temp_sensitivity_limit &&
+ NANO_ABS(sensor_intercept[i]) < over_temp_cal->sensor_intercept_limit) {
+ over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
+ over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
+ }
+ }
+
+ // Sets the model update time to the current timestamp.
+ over_temp_cal->modelupdate_timestamp_nanos = timestamp_nanos;
+
+ // Model "Jump-Start".
+ const bool model_jump_started =
+ (jump_start_model) ? jumpStartModelData(over_temp_cal) : false;
+
+ if (!model_jump_started) {
+ // Sets the initial over-temp calibration estimate and model data.
+ setLatestEstimate(over_temp_cal, offset, offset_temp, timestamp_nanos);
+
+ // Now there is one offset estimate in the model.
+ over_temp_cal->num_model_pts = 1;
+ }
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+ // Prints the updated model data.
+ CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
+ "Model parameters recalled from memory.");
+
+ // Trigger a debug print out to view the new model parameters.
+ over_temp_cal->debug_print_trigger = true;
+#endif // OVERTEMPCAL_DBG_ENABLED
+}
+
+void overTempCalGetModel(struct OverTempCal *over_temp_cal, float *offset,
+ float *offset_temp, uint64_t *timestamp_nanos,
+ float *temp_sensitivity, float *sensor_intercept) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(over_temp_cal->latest_offset);
+ ASSERT_NOT_NULL(offset);
+ ASSERT_NOT_NULL(offset_temp);
+ ASSERT_NOT_NULL(timestamp_nanos);
+ ASSERT_NOT_NULL(temp_sensitivity);
+ ASSERT_NOT_NULL(sensor_intercept);
+
+ // Updates the latest offset so that it is the one nearest to the current
+ // temperature.
+ setNearestEstimate(over_temp_cal);
+
+ // Gets the over-temp calibration estimate and model data.
+ memcpy(offset, over_temp_cal->latest_offset->offset, 3 * sizeof(float));
+ memcpy(temp_sensitivity, over_temp_cal->temp_sensitivity, 3 * sizeof(float));
+ memcpy(sensor_intercept, over_temp_cal->sensor_intercept, 3 * sizeof(float));
+ *offset_temp = over_temp_cal->latest_offset->offset_temp;
+ *timestamp_nanos = over_temp_cal->latest_offset->timestamp_nanos;
+}
+
+void overTempCalRemoveOffset(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos, float xi, float yi,
+ float zi, float *xo, float *yo, float *zo) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(over_temp_cal->latest_offset);
+ ASSERT_NOT_NULL(xo);
+ ASSERT_NOT_NULL(yo);
+ ASSERT_NOT_NULL(zo);
+
+ // Determines whether over-temp compensation will be applied.
+ if (!over_temp_cal->over_temp_enable) {
+ return;
+ }
+
+ // Removes very old data from the collected model estimates (eliminates
+ // drift-compromised data). Only does this when there is more than one
+ // estimate in the model (i.e., don't want to remove all data, even if it is
+ // very old [something is likely better than nothing]).
+ if ((timestamp_nanos - over_temp_cal->stale_data_timer) >=
+ OVERTEMPCAL_STALE_CHECK_TIME_NANOS &&
+ over_temp_cal->num_model_pts > 1) {
+ over_temp_cal->stale_data_timer = timestamp_nanos; // Resets timer.
+
+ if (removeStaleModelData(over_temp_cal, timestamp_nanos)) {
+ // If anything was removed, then this attempts to recompute the model.
+ computeModelUpdate(over_temp_cal, timestamp_nanos);
+ }
+ }
+
+ // Removes the over-temperature compensated offset from the input sensor data.
+ removeSensorOffset(over_temp_cal, xi, 0, xo);
+ removeSensorOffset(over_temp_cal, yi, 1, yo);
+ removeSensorOffset(over_temp_cal, zi, 2, zo);
+}
+
+bool overTempCalNewModelUpdateAvailable(struct OverTempCal *over_temp_cal) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ const bool update_available = over_temp_cal->new_overtemp_model_available &&
+ over_temp_cal->over_temp_enable;
+
+ // The 'new_overtemp_model_available' flag is reset when it is read here.
+ over_temp_cal->new_overtemp_model_available = false;
+
+ return update_available;
+}
+
+void overTempCalUpdateSensorEstimate(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos,
+ const float *offset,
+ float temperature_celsius) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(over_temp_cal->latest_offset);
+ ASSERT_NOT_NULL(offset);
+ ASSERT(over_temp_cal->delta_temp_per_bin > 0);
+
+ // Prevent a divide by zero below.
+ if (over_temp_cal->delta_temp_per_bin <= 0) {
+ return;
+ }
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+ // Updates the total count of offset estimates.
+ over_temp_cal->debug_num_estimates++;
+
+ // If there are fewer than the minimum number of points to produce a model,
+ // then trigger a debug printout to view the model building process.
+ over_temp_cal->debug_print_trigger |=
+ (over_temp_cal->num_model_pts <= over_temp_cal->min_num_model_pts);
+#endif // OVERTEMPCAL_DBG_ENABLED
+
+ // Provides an early escape if this is the first model estimate.
+ if (over_temp_cal->num_model_pts == 0) {
+ setLatestEstimate(over_temp_cal, offset, temperature_celsius,
+ timestamp_nanos);
+ over_temp_cal->num_model_pts = 1; // one estimate was added above.
+ return;
+ }
+
+ // Computes the temperature bin range data.
+ const int32_t bin_num =
+ CAL_FLOOR(temperature_celsius / over_temp_cal->delta_temp_per_bin);
+ const float temp_lo_check = bin_num * over_temp_cal->delta_temp_per_bin;
+ const float temp_hi_check = (bin_num + 1) * over_temp_cal->delta_temp_per_bin;
+
+ // The rules for accepting new offset estimates into the 'model_data'
+ // collection:
+ // 1) The temperature domain is divided into bins each spanning
+ // 'delta_temp_per_bin'.
+ // 2) Find and replace the i'th 'model_data' estimate data if:
+ // Let, bin_num = floor(temperature_celsius / delta_temp_per_bin)
+ // temp_lo_check = bin_num * delta_temp_per_bin
+ // temp_hi_check = (bin_num + 1) * delta_temp_per_bin
+ // Check condition:
+ // temp_lo_check <= model_data[i].offset_temp < temp_hi_check
+ bool replaced_one = false;
+ size_t i = 0;
+ for (i = 0; i < over_temp_cal->num_model_pts; i++) {
+ if (over_temp_cal->model_data[i].offset_temp < temp_hi_check &&
+ over_temp_cal->model_data[i].offset_temp >= temp_lo_check) {
+ // NOTE - the pointer to the estimate that is getting replaced is set
+ // here; the offset values are set below in the call to
+ // 'setLatestEstimate'.
+ over_temp_cal->latest_offset = &over_temp_cal->model_data[i];
+ replaced_one = true;
+ break;
+ }
+ }
+
+ // 3) If nothing was replaced, and the 'model_data' buffer is not full
+ // then add the estimate data to the array.
+ // 4) Otherwise (nothing was replaced and buffer is full), replace the
+ // 'latest_offset' with the incoming one. This is done below.
+ if (!replaced_one && over_temp_cal->num_model_pts < OVERTEMPCAL_MODEL_SIZE) {
+ // NOTE - the pointer to the next available array location is set here;
+ // the offset values are set below in the call to 'setLatestEstimate'.
+ over_temp_cal->latest_offset =
+ &over_temp_cal->model_data[over_temp_cal->num_model_pts];
+ over_temp_cal->num_model_pts++;
+ }
+
+ // Updates the latest model estimate data.
+ setLatestEstimate(over_temp_cal, offset, temperature_celsius,
+ timestamp_nanos);
+
+ // Conditionally updates the over-temp model. See 'computeModelUpdate' for
+ // update conditions.
+ computeModelUpdate(over_temp_cal, timestamp_nanos);
+
+}
+
+void overTempCalSetTemperature(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos,
+ float temperature_celsius) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(over_temp_cal->latest_offset);
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+#ifdef OVERTEMPCAL_DBG_LOG_TEMP
+ static uint64_t wait_timer = 0;
+ // Prints the sensor temperature trajectory for debugging purposes.
+ // This throttles the print statements.
+ if ((timestamp_nanos - wait_timer) >= 1000000000) {
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+
+ // Prints out temperature and the current timestamp.
+ CAL_DEBUG_LOG("[OVER_TEMP_CAL:TEMP]",
+ "Temperature|Time [C|nsec] = %s%d.%06d, %llu",
+ CAL_ENCODE_FLOAT(temperature_celsius, 6),
+ (unsigned long long int)timestamp_nanos);
+ }
+#endif // OVERTEMPCAL_DBG_LOG_TEMP
+#endif // OVERTEMPCAL_DBG_ENABLED
+
+ // Updates the sensor temperature.
+ over_temp_cal->temperature_celsius = temperature_celsius;
+
+ // If any of the models for the sensor axes are in an initial state, then
+ // this searches for the sensor offset estimate closest to the current
+ // temperature. A timer is used to limit the rate at which this search is
+ // performed.
+ if (over_temp_cal->num_model_pts > 1 &&
+ (over_temp_cal->temp_sensitivity[0] >= MODEL_INITIAL_STATE ||
+ over_temp_cal->temp_sensitivity[1] >= MODEL_INITIAL_STATE ||
+ over_temp_cal->temp_sensitivity[2] >= MODEL_INITIAL_STATE) &&
+ (timestamp_nanos - over_temp_cal->nearest_search_timer) >=
+ OVERTEMPCAL_NEAREST_NANOS) {
+ setNearestEstimate(over_temp_cal);
+ over_temp_cal->nearest_search_timer = timestamp_nanos; // Reset timer.
+ }
+}
+
+void getModelError(const struct OverTempCal *over_temp_cal,
+ const float *temp_sensitivity, const float *sensor_intercept,
+ float *max_error) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(temp_sensitivity);
+ ASSERT_NOT_NULL(sensor_intercept);
+ ASSERT_NOT_NULL(max_error);
+
+ size_t i;
+ size_t j;
+ float max_error_test;
+ memset(max_error, 0, 3 * sizeof(float));
+
+ for (i = 0; i < over_temp_cal->num_model_pts; i++) {
+ for (j = 0; j < 3; j++) {
+ max_error_test =
+ NANO_ABS(over_temp_cal->model_data[i].offset[j] -
+ getCompensatedOffset(over_temp_cal->model_data[i].offset_temp,
+ temp_sensitivity[j], sensor_intercept[j]));
+ if (max_error_test > max_error[j]) {
+ max_error[j] = max_error_test;
+ }
+ }
+ }
+}
+
+/////// LOCAL HELPER FUNCTION DEFINITIONS /////////////////////////////////////
+
+void setLatestEstimate(struct OverTempCal *over_temp_cal, const float *offset,
+ float offset_temp, uint64_t timestamp_nanos) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(offset);
+ ASSERT_NOT_NULL(over_temp_cal->latest_offset);
+
+ // Sets the latest over-temp calibration estimate.
+ over_temp_cal->latest_offset->offset[0] = offset[0];
+ over_temp_cal->latest_offset->offset[1] = offset[1];
+ over_temp_cal->latest_offset->offset[2] = offset[2];
+ over_temp_cal->latest_offset->offset_temp = offset_temp;
+ over_temp_cal->latest_offset->timestamp_nanos = timestamp_nanos;
+}
+
+void computeModelUpdate(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos) {
+ ASSERT_NOT_NULL(over_temp_cal);
+
+ // The rules for determining whether a new model fit is computed are:
+ // 1) A minimum number of data points must have been collected:
+ // num_model_pts >= min_num_model_pts
+ // NOTE: Collecting 'num_model_pts' and given that only one point is
+ // kept per temperature bin (spanning a thermal range specified by
+ // 'delta_temp_per_bin'), implies that model data covers at least,
+ // model_temp_span >= 'num_model_pts' * delta_temp_per_bin
+ // 2) New model updates will not occur for intervals less than:
+ // (current_timestamp_nanos - modelupdate_timestamp_nanos) <
+ // min_update_interval_nanos
+ if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts ||
+ (timestamp_nanos - over_temp_cal->modelupdate_timestamp_nanos) <
+ over_temp_cal->min_update_interval_nanos) {
+ return;
+ }
+
+ // Updates the linear model fit.
+ float temp_sensitivity[3];
+ float sensor_intercept[3];
+ updateModel(over_temp_cal, temp_sensitivity, sensor_intercept);
+
+ // Computes the maximum error over all of the model data.
+ float max_error[3];
+ getModelError(over_temp_cal, temp_sensitivity, sensor_intercept, max_error);
+
+ // 3) A new set of model parameters are accepted if:
+ // i. The model fit error is less than, 'max_error_limit'. See
+ // getModelError() for error metric description.
+ // ii. The model fit parameters must be within certain absolute
+ // bounds:
+ // a. NANO_ABS(temp_sensitivity) < temp_sensitivity_limit
+ // b. NANO_ABS(sensor_intercept) < sensor_intercept_limit
+ size_t i;
+ for (i = 0; i < 3; i++) {
+ if (max_error[i] < over_temp_cal->max_error_limit &&
+ NANO_ABS(temp_sensitivity[i]) < over_temp_cal->temp_sensitivity_limit &&
+ NANO_ABS(sensor_intercept[i]) < over_temp_cal->sensor_intercept_limit) {
+ over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
+ over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
+ } else {
+#ifdef OVERTEMPCAL_DBG_ENABLED
+ CAL_DEBUG_LOG(
+ "[OVER_TEMP_CAL:REJECT]",
+ "Rejected %c-Axis Parameters|Max Error [mdps/C|mdps|mdps] = "
+ "%s%d.%06d, %s%d.%06d, %s%d.%06d",
+ kDebugAxisLabel[i],
+ CAL_ENCODE_FLOAT(temp_sensitivity[i] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(sensor_intercept[i] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(max_error[i] * RAD_TO_MILLI_DEGREES, 6));
+#endif // OVERTEMPCAL_DBG_ENABLED
+ }
+ }
+
+ // Resets the timer and sets the update flag.
+ over_temp_cal->modelupdate_timestamp_nanos = timestamp_nanos;
+ over_temp_cal->new_overtemp_model_available = true;
+
+ // Track the total number of model updates, and set trigger to print log data.
+#ifdef OVERTEMPCAL_DBG_ENABLED
+ over_temp_cal->debug_num_model_updates++;
+ over_temp_cal->debug_print_trigger |= true;
+#endif // OVERTEMPCAL_DBG_ENABLED
+}
+
+void setNearestEstimate(struct OverTempCal *over_temp_cal) {
+ ASSERT_NOT_NULL(over_temp_cal);
+
+ size_t i = 0;
+ float dtemp_new = 0.0f;
+ float dtemp_old = FLT_MAX;
+ struct OverTempCalDataPt *nearest_estimate = &over_temp_cal->model_data[0];
+ for (i = 1; i < over_temp_cal->num_model_pts; i++) {
+ dtemp_new = NANO_ABS(over_temp_cal->model_data[i].offset_temp -
+ over_temp_cal->temperature_celsius);
+ if (dtemp_new < dtemp_old) {
+ nearest_estimate = &over_temp_cal->model_data[i];
+ dtemp_old = dtemp_new;
+ }
+ }
+
+ // Set the 'latest_offset' to the estimate nearest the current temperature.
+ over_temp_cal->latest_offset = nearest_estimate;
+}
+
+bool removeStaleModelData(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos) {
+ ASSERT_NOT_NULL(over_temp_cal);
+
+ size_t i;
+ bool removed_one = false;
+ for (i = 0; i < over_temp_cal->num_model_pts; i++) {
+ if ((timestamp_nanos - over_temp_cal->model_data[i].timestamp_nanos) >
+ over_temp_cal->age_limit_nanos) {
+ removed_one |= removeModelDataByIndex(over_temp_cal, i);
+ }
+ }
+
+ // Updates the latest offset so that it is the one nearest to the current
+ // temperature.
+ setNearestEstimate(over_temp_cal);
+
+ return removed_one;
+}
+
+bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
+ size_t model_index) {
+ ASSERT_NOT_NULL(over_temp_cal);
+
+ // This function will not remove all of the model data. At least one model
+ // sample will be left.
+ if (over_temp_cal->num_model_pts <= 1) {
+ return false;
+ }
+
+ // Remove the model data at 'model_index'.
+ size_t i;
+ for (i = model_index; i < over_temp_cal->num_model_pts - 1; i++) {
+ memcpy(&over_temp_cal->model_data[i], &over_temp_cal->model_data[i + 1],
+ sizeof(struct OverTempCalDataPt));
+ }
+ over_temp_cal->num_model_pts--;
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+ CAL_DEBUG_LOG("[OVER_TEMP_CAL:REMOVE]",
+ "Removed Stale Data: Model Index = %lu",
+ (unsigned long int)model_index);
+#endif // OVERTEMPCAL_DBG_ENABLED
+
+ return true;
+}
+
+bool jumpStartModelData(struct OverTempCal *over_temp_cal) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT(over_temp_cal->delta_temp_per_bin > 0);
+
+ // Prevent a divide by zero below.
+ if (over_temp_cal->delta_temp_per_bin <= 0) {
+ return false;
+ }
+
+ // In normal operation the offset estimates enter into the 'model_data' array
+ // complete (i.e., x, y, z values are all provided). Therefore, the jumpstart
+ // data produced here requires that the model parameters have all been fully
+ // defined (i.e., no models in an initial state) and are all within the valid
+ // range (this is assumed to have been checked prior to this function). There
+ // must also be no preexisting model data; that is, this function will not
+ // replace any actual offset estimates already buffered.
+ if (over_temp_cal->num_model_pts > 0 ||
+ over_temp_cal->temp_sensitivity[0] >= MODEL_INITIAL_STATE ||
+ over_temp_cal->temp_sensitivity[1] >= MODEL_INITIAL_STATE ||
+ over_temp_cal->temp_sensitivity[2] >= MODEL_INITIAL_STATE) {
+ return false;
+ }
+
+ // This defines the minimum contiguous set of points to allow a model update
+ // when the next offset estimate is received. They are placed at a common
+ // temperature range that is likely to get replaced with actual data soon.
+ const int32_t start_bin_num = CAL_FLOOR(JUMPSTART_START_TEMP_CELSIUS /
+ over_temp_cal->delta_temp_per_bin);
+ float offset_temp =
+ start_bin_num * (1.5f * over_temp_cal->delta_temp_per_bin);
+
+ size_t i;
+ size_t j;
+ for (i = 0; i < over_temp_cal->min_num_model_pts; i++) {
+ float offset[3];
+ const uint64_t timestamp_nanos = over_temp_cal->modelupdate_timestamp_nanos;
+ for (j = 0; j < 3; j++) {
+ offset[j] =
+ getCompensatedOffset(offset_temp, over_temp_cal->temp_sensitivity[j],
+ over_temp_cal->sensor_intercept[j]);
+ }
+ over_temp_cal->latest_offset = &over_temp_cal->model_data[i];
+ setLatestEstimate(over_temp_cal, offset, offset_temp, timestamp_nanos);
+ offset_temp += over_temp_cal->delta_temp_per_bin;
+ over_temp_cal->num_model_pts++;
+ }
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+ if (over_temp_cal->min_num_model_pts > 0) {
+ CAL_DEBUG_LOG("[OVER_TEMP_CAL]", "Jump-Started Model: #Points = %lu.",
+ (unsigned long int)over_temp_cal->min_num_model_pts);
+ }
+#endif // OVERTEMPCAL_DBG_ENABLED
+
+ return (over_temp_cal->min_num_model_pts > 0);
+}
+
+void updateModel(const struct OverTempCal *over_temp_cal,
+ float *temp_sensitivity, float *sensor_intercept) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(temp_sensitivity);
+ ASSERT_NOT_NULL(sensor_intercept);
+ ASSERT(over_temp_cal->num_model_pts > 0);
+
+ float st = 0.0f, stt = 0.0f;
+ float sx = 0.0f, stsx = 0.0f;
+ float sy = 0.0f, stsy = 0.0f;
+ float sz = 0.0f, stsz = 0.0f;
+ const size_t n = over_temp_cal->num_model_pts;
+ size_t i = 0;
+
+ // First pass computes the mean values.
+ for (i = 0; i < n; ++i) {
+ st += over_temp_cal->model_data[i].offset_temp;
+ sx += over_temp_cal->model_data[i].offset[0];
+ sy += over_temp_cal->model_data[i].offset[1];
+ sz += over_temp_cal->model_data[i].offset[2];
+ }
+
+ // Second pass computes the mean corrected second moment values.
+ const float inv_n = 1.0f / n;
+ for (i = 0; i < n; ++i) {
+ const float t = over_temp_cal->model_data[i].offset_temp - st * inv_n;
+ stt += t * t;
+ stsx += t * over_temp_cal->model_data[i].offset[0];
+ stsy += t * over_temp_cal->model_data[i].offset[1];
+ stsz += t * over_temp_cal->model_data[i].offset[2];
+ }
+
+ // Calculates the linear model fit parameters.
+ ASSERT(stt > 0);
+ const float inv_stt = 1.0f / stt;
+ temp_sensitivity[0] = stsx * inv_stt;
+ sensor_intercept[0] = (sx - st * temp_sensitivity[0]) * inv_n;
+ temp_sensitivity[1] = stsy * inv_stt;
+ sensor_intercept[1] = (sy - st * temp_sensitivity[1]) * inv_n;
+ temp_sensitivity[2] = stsz * inv_stt;
+ sensor_intercept[2] = (sz - st * temp_sensitivity[2]) * inv_n;
+}
+
+void removeSensorOffset(const struct OverTempCal *over_temp_cal, float vi,
+ size_t index, float *vo) {
+ // Removes the over-temperature compensated offset from the input sensor data.
+ if (over_temp_cal->temp_sensitivity[index] >= MODEL_INITIAL_STATE) {
+ // If this axis is in its initial state, use the nearest estimate to perform
+ // the compensation (in this case the latest estimate will be the nearest):
+ // sensor_out = sensor_in - nearest_offset
+ *vo = vi - over_temp_cal->latest_offset->offset[index];
+ } else {
+ // sensor_out = sensor_in - compensated_offset
+ // Where,
+ // compensated_offset = (temp_sensitivity * temp_meas + sensor_intercept)
+ *vo = vi - getCompensatedOffset(over_temp_cal->temperature_celsius,
+ over_temp_cal->temp_sensitivity[index],
+ over_temp_cal->sensor_intercept[index]);
+ }
+}
+
+float getCompensatedOffset(float temperature, float temp_sensitivity,
+ float sensor_intercept) {
+ return temp_sensitivity * temperature + sensor_intercept;
+}
+
+/////// DEBUG FUNCTION DEFINITIONS ////////////////////////////////////////////
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+// Debug printout state enumeration.
+enum DebugState {
+ IDLE = 0,
+ WAIT_STATE,
+ PRINT_HEADER,
+ PRINT_OFFSET,
+ PRINT_SENSITIVITY,
+ PRINT_INTERCEPT,
+ PRINT_ERROR,
+ PRINT_MODEL_PTS,
+ PRINT_MODEL_DATA
+};
+
+void overTempCalDebugPrint(struct OverTempCal *over_temp_cal,
+ uint64_t timestamp_nanos) {
+ ASSERT_NOT_NULL(over_temp_cal);
+ ASSERT_NOT_NULL(over_temp_cal->latest_offset);
+
+ static enum DebugState debug_state = IDLE;
+ static enum DebugState next_state = 0;
+ static uint64_t wait_timer = 0;
+ static size_t i = 0; // Counter.
+
+ // NOTE - The un-initialized model state is indicated by
+ // temp_sensitivity=MODEL_INITIAL_STATE. The following filters out this
+ // condition for the data printout below.
+ float compensated_offset[3];
+ float temp_sensitivity[3];
+ float max_error[3];
+ size_t j;
+ for (j = 0; j < 3; j++) {
+ temp_sensitivity[j] =
+ (over_temp_cal->temp_sensitivity[j] >= MODEL_INITIAL_STATE)
+ ? 0.0f
+ : over_temp_cal->temp_sensitivity[j];
+ }
+
+ // This is a state machine that controls the reporting out of debug data.
+ switch (debug_state) {
+ case IDLE:
+ // Wait for a trigger and start the debug printout sequence.
+ if (over_temp_cal->debug_print_trigger) {
+ debug_state = PRINT_HEADER;
+ CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "");
+ over_temp_cal->debug_print_trigger = false; // Resets trigger.
+ } else {
+ debug_state = IDLE;
+ }
+ break;
+
+ case PRINT_HEADER:
+ // Print out header.
+ CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "Debug Version: %s",
+ OVERTEMPCAL_DEBUG_VERSION_STRING);
+
+ // Prints out number of offsets.
+ CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "Total Offsets = %lu",
+ (unsigned long int)over_temp_cal->debug_num_estimates);
+
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = PRINT_OFFSET; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ break;
+
+ case PRINT_OFFSET:
+ // Computes the compensated sensor offset based on the current
+ // temperature.
+ for (j = 0; j < 3; j++) {
+ compensated_offset[j] =
+ (over_temp_cal->temp_sensitivity[j] >= MODEL_INITIAL_STATE)
+ ? over_temp_cal->latest_offset->offset[j]
+ : getCompensatedOffset(over_temp_cal->temperature_celsius,
+ over_temp_cal->temp_sensitivity[j],
+ over_temp_cal->sensor_intercept[j]);
+ }
+
+ CAL_DEBUG_LOG(
+ OVERTEMPCAL_REPORT_TAG,
+ "Offset|Temp|Time [mdps|C|nsec] = %s%d.%06d, %s%d.%06d, "
+ "%s%d.%06d, %s%d.%06d, %llu",
+ CAL_ENCODE_FLOAT(compensated_offset[0] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(compensated_offset[1] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(compensated_offset[2] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(over_temp_cal->temperature_celsius, 6),
+ (unsigned long long int)
+ over_temp_cal->latest_offset->timestamp_nanos);
+
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = PRINT_SENSITIVITY; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ break;
+
+ case WAIT_STATE:
+ // This helps throttle the print statements.
+ if ((timestamp_nanos - wait_timer) >= OVERTEMPCAL_WAIT_TIME_NANOS) {
+ debug_state = next_state;
+ }
+ break;
+
+ case PRINT_SENSITIVITY:
+ // Prints out the modeled temperature sensitivity.
+ CAL_DEBUG_LOG(
+ OVERTEMPCAL_REPORT_TAG,
+ "Modeled Temperature Sensitivity [mdps/C] = %s%d.%06d, %s%d.%06d, "
+ "%s%d.%06d",
+ CAL_ENCODE_FLOAT(temp_sensitivity[0] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(temp_sensitivity[1] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(temp_sensitivity[2] * RAD_TO_MILLI_DEGREES, 6));
+
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = PRINT_INTERCEPT; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ break;
+
+ case PRINT_INTERCEPT:
+ // Prints out the modeled temperature intercept.
+ CAL_DEBUG_LOG(
+ OVERTEMPCAL_REPORT_TAG,
+ "Modeled Temperature Intercept [mdps] = %s%d.%06d, %s%d.%06d, "
+ "%s%d.%06d",
+ CAL_ENCODE_FLOAT(
+ over_temp_cal->sensor_intercept[0] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(
+ over_temp_cal->sensor_intercept[1] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(
+ over_temp_cal->sensor_intercept[2] * RAD_TO_MILLI_DEGREES, 6));
+
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = PRINT_ERROR; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ break;
+
+ case PRINT_ERROR:
+ // Computes the maximum error over all of the model data.
+ if (over_temp_cal->num_model_pts > 0) {
+ getModelError(over_temp_cal, temp_sensitivity,
+ over_temp_cal->sensor_intercept, max_error);
+
+ // Reports the resulting model error.
+ CAL_DEBUG_LOG(
+ OVERTEMPCAL_REPORT_TAG,
+ "Model Error [mdps] = %s%d.%06d, %s%d.%06d, %s%d.%06d",
+ CAL_ENCODE_FLOAT(max_error[0] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(max_error[1] * RAD_TO_MILLI_DEGREES, 6),
+ CAL_ENCODE_FLOAT(max_error[2] * RAD_TO_MILLI_DEGREES, 6));
+ }
+
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = PRINT_MODEL_PTS; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ break;
+
+ case PRINT_MODEL_PTS:
+ // Prints out the number of model points/updates.
+ CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "Number of Model Points = %lu",
+ (unsigned long int)over_temp_cal->num_model_pts);
+
+ CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "Number of Model Updates = %lu",
+ (unsigned long int)over_temp_cal->debug_num_model_updates);
+
+ i = 0; // Resets the counter.
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = PRINT_MODEL_DATA; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ break;
+
+ case PRINT_MODEL_DATA:
+ // Prints out all of the model data.
+ if (i < over_temp_cal->num_model_pts) {
+ CAL_DEBUG_LOG(
+ OVERTEMPCAL_REPORT_TAG,
+ " Model[%lu] [mdps|C] = %s%d.%06d, %s%d.%06d, %s%d.%06d, "
+ "%s%d.%03d ",
+ (unsigned long int)i,
+ CAL_ENCODE_FLOAT(
+ over_temp_cal->model_data[i].offset[0] * RAD_TO_MILLI_DEGREES,
+ 6),
+ CAL_ENCODE_FLOAT(
+ over_temp_cal->model_data[i].offset[1] * RAD_TO_MILLI_DEGREES,
+ 6),
+ CAL_ENCODE_FLOAT(
+ over_temp_cal->model_data[i].offset[2] * RAD_TO_MILLI_DEGREES,
+ 6),
+ CAL_ENCODE_FLOAT(over_temp_cal->model_data[i].offset_temp, 3));
+
+ i++;
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = PRINT_MODEL_DATA; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ } else {
+ debug_state = IDLE; // Goes to idle state.
+ CAL_DEBUG_LOG(
+ OVERTEMPCAL_REPORT_TAG, "Last Model Update [nsec] = %llu",
+ (unsigned long long int)over_temp_cal->modelupdate_timestamp_nanos);
+ }
+ break;
+
+ default:
+ // Sends this state machine to its idle state.
+ wait_timer = timestamp_nanos; // Starts the wait timer.
+ next_state = IDLE; // Sets the next state.
+ debug_state = WAIT_STATE; // First, go to wait state.
+ }
+}
+
+#endif // OVERTEMPCAL_DBG_ENABLED