1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "calibration/over_temp/over_temp_cal.h"
18
19 #include <float.h>
20 #include <math.h>
21 #include <string.h>
22
23 #include "calibration/util/cal_log.h"
24 #include "common/math/vec.h"
25 #include "util/nano_assert.h"
26
27 /////// DEFINITIONS AND MACROS ////////////////////////////////////////////////
28
29 // Rate-limits the search for the nearest offset estimate to every 2 seconds.
30 #define OVERTEMPCAL_NEAREST_NANOS (2000000000)
31
32 // Rate-limits the check of old data to every 2 hours.
33 #define OVERTEMPCAL_STALE_CHECK_TIME_NANOS (7200000000000)
34
35 // Value used to check whether OTC model parameters are near zero.
36 #define OTC_MODELDATA_NEAR_ZERO_TOL (1e-7f) // [rad/sec]
37
38 #ifdef OVERTEMPCAL_DBG_ENABLED
39 // A debug version label to help with tracking results.
40 #define OVERTEMPCAL_DEBUG_VERSION_STRING "[Apr 05, 2017]"
41
42 // The time value used to throttle debug messaging.
43 #define OVERTEMPCAL_WAIT_TIME_NANOS (300000000)
44
45 // Debug log tag string used to identify debug report output data.
46 #define OVERTEMPCAL_REPORT_TAG "[OVER_TEMP_CAL:REPORT]"
47
48 // Converts units of radians to milli-degrees.
49 #define RAD_TO_MILLI_DEGREES (float)(1e3f * 180.0f / NANO_PI)
50
51 // Sensor axis label definition with index correspondence: 0=X, 1=Y, 2=Z.
52 static const char kDebugAxisLabel[3] = "XYZ";
53 #endif // OVERTEMPCAL_DBG_ENABLED
54
55 /////// FORWARD DECLARATIONS //////////////////////////////////////////////////
56
57 // Updates the model estimate data nearest to the sensor's temperature.
58 static void setNearestEstimate(struct OverTempCal *over_temp_cal,
59 const float *offset, float offset_temp_celsius,
60 uint64_t timestamp_nanos);
61
62 /*
63 * Determines if a new over-temperature model fit should be performed, and then
64 * updates the model as needed.
65 *
66 * INPUTS:
67 * over_temp_cal: Over-temp data structure.
68 * timestamp_nanos: Current timestamp for the model update.
69 */
70 static void computeModelUpdate(struct OverTempCal *over_temp_cal,
71 uint64_t timestamp_nanos);
72
73 /*
74 * Searches 'model_data' for the sensor offset estimate closest to the current
75 * temperature. Sets the 'nearest_offset' pointer to the result.
76 */
77 static void findNearestEstimate(struct OverTempCal *over_temp_cal);
78
79 /*
80 * Provides the current over-temperature compensated offset vector.
81 *
82 * INPUTS:
83 * over_temp_cal: Over-temp data structure.
84 * timestamp_nanos: The current system timestamp.
85 * OUTPUTS:
86 * compensated_offset: Temperature compensated offset estimate array.
87 * compensated_offset_temperature_celsius: Compensated offset temperature.
88 *
89 * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
90 */
91 static void getCalOffset(struct OverTempCal *over_temp_cal,
92 uint64_t timestamp_nanos,
93 float *compensated_offset_temperature_celsius,
94 float *compensated_offset);
95
96 /*
97 * Removes the "old" offset estimates from 'model_data' (i.e., eliminates the
98 * drift-compromised data). Returns 'true' if any data was removed.
99 */
100 static bool removeStaleModelData(struct OverTempCal *over_temp_cal,
101 uint64_t timestamp_nanos);
102
103 /*
104 * Removes the offset estimates from 'model_data' at index, 'model_index'.
105 * Returns 'true' if data was removed.
106 */
107 static bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
108 size_t model_index);
109
110 /*
111 * Since it may take a while for an empty model to build up enough data to start
112 * producing new model parameter updates, the model collection can be
113 * jump-started by using the new model parameters to insert fake data in place
114 * of actual sensor offset data.
115 */
116 static bool jumpStartModelData(struct OverTempCal *over_temp_cal);
117
118 /*
119 * Provides updated model parameters for the over-temperature model data.
120 *
121 * INPUTS:
122 * over_temp_cal: Over-temp data structure.
123 * OUTPUTS:
124 * temp_sensitivity: Updated modeled temperature sensitivity (array).
125 * sensor_intercept: Updated model intercept (array).
126 *
127 * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
128 *
129 * Reference: "Comparing two ways to fit a line to data", John D. Cook.
130 * http://www.johndcook.com/blog/2008/10/20/comparing-two-ways-to-fit-a-line-to-data/
131 */
132 static void updateModel(const struct OverTempCal *over_temp_cal,
133 float *temp_sensitivity, float *sensor_intercept);
134
135 /*
136 * Checks new offset estimates to determine if they could be an outlier that
137 * should be rejected. Operates on a per-axis basis determined by 'axis_index'.
138 *
139 * INPUTS:
140 * over_temp_cal: Over-temp data structure.
141 * offset: Offset array.
142 * axis_index: Index of the axis to check (0=x, 1=y, 2=z).
143 *
144 * Returns 'true' if the deviation of the offset value from the linear model
145 * exceeds 'max_error_limit'.
146 */
147 static bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
148 size_t axis_index, float temperature_celsius);
149
150 // Sets the OTC model parameters to an "initialized" state.
resetOtcLinearModel(struct OverTempCal * over_temp_cal)151 static void resetOtcLinearModel(struct OverTempCal *over_temp_cal) {
152 ASSERT_NOT_NULL(over_temp_cal);
153
154 // Sets the temperature sensitivity model parameters to
155 // OTC_INITIAL_SENSITIVITY to indicate that the model is in an "initial"
156 // state.
157 over_temp_cal->temp_sensitivity[0] = OTC_INITIAL_SENSITIVITY;
158 over_temp_cal->temp_sensitivity[1] = OTC_INITIAL_SENSITIVITY;
159 over_temp_cal->temp_sensitivity[2] = OTC_INITIAL_SENSITIVITY;
160 memset(over_temp_cal->sensor_intercept, 0, 3 * sizeof(float));
161 }
162
163 // Checks that the input temperature value is within the valid range. If outside
164 // of range, then 'temperature_celsius' is coerced to within the limits.
checkAndEnforceTemperatureRange(float * temperature_celsius)165 static bool checkAndEnforceTemperatureRange(float *temperature_celsius) {
166 if (*temperature_celsius > OVERTEMPCAL_TEMP_MAX_CELSIUS) {
167 *temperature_celsius = OVERTEMPCAL_TEMP_MAX_CELSIUS;
168 return false;
169 }
170 if (*temperature_celsius < OVERTEMPCAL_TEMP_MIN_CELSIUS) {
171 *temperature_celsius = OVERTEMPCAL_TEMP_MIN_CELSIUS;
172 return false;
173 }
174 return true;
175 }
176
177 // Returns "true" if the candidate linear model parameters are within the valid
178 // range, and not all zeros.
isValidOtcLinearModel(const struct OverTempCal * over_temp_cal,float temp_sensitivity,float sensor_intercept)179 static bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal,
180 float temp_sensitivity, float sensor_intercept) {
181 ASSERT_NOT_NULL(over_temp_cal);
182
183 return NANO_ABS(temp_sensitivity) < over_temp_cal->temp_sensitivity_limit &&
184 NANO_ABS(sensor_intercept) < over_temp_cal->sensor_intercept_limit &&
185 NANO_ABS(temp_sensitivity) > OTC_MODELDATA_NEAR_ZERO_TOL &&
186 NANO_ABS(sensor_intercept) > OTC_MODELDATA_NEAR_ZERO_TOL;
187 }
188
189 // Returns "true" if 'offset' and 'offset_temp_celsius' is valid.
isValidOtcOffset(const float * offset,float offset_temp_celsius)190 static bool isValidOtcOffset(const float *offset, float offset_temp_celsius) {
191 ASSERT_NOT_NULL(offset);
192
193 // Simple check to ensure that:
194 // 1. All of the input data is non "zero".
195 // 2. The offset temperature is within the valid range.
196 if (NANO_ABS(offset[0]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
197 NANO_ABS(offset[1]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
198 NANO_ABS(offset[2]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
199 NANO_ABS(offset_temp_celsius) < OTC_MODELDATA_NEAR_ZERO_TOL) {
200 return false;
201 }
202
203 // Only returns the "check" result. Don't care about coercion.
204 return checkAndEnforceTemperatureRange(&offset_temp_celsius);
205 }
206
207 #ifdef OVERTEMPCAL_DBG_ENABLED
208 // This helper function stores all of the debug tracking information necessary
209 // for printing log messages.
210 static void updateDebugData(struct OverTempCal* over_temp_cal);
211 #endif // OVERTEMPCAL_DBG_ENABLED
212
213 /////// FUNCTION DEFINITIONS //////////////////////////////////////////////////
214
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)215 void overTempCalInit(struct OverTempCal *over_temp_cal,
216 size_t min_num_model_pts,
217 uint64_t min_update_interval_nanos,
218 float delta_temp_per_bin, float max_error_limit,
219 uint64_t age_limit_nanos, float temp_sensitivity_limit,
220 float sensor_intercept_limit, bool over_temp_enable) {
221 ASSERT_NOT_NULL(over_temp_cal);
222
223 // Clears OverTempCal memory.
224 memset(over_temp_cal, 0, sizeof(struct OverTempCal));
225
226 // Initializes the pointer to the most recent sensor offset estimate. Sets it
227 // as the first element in 'model_data'.
228 over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
229
230 // Initializes the OTC linear model parameters.
231 resetOtcLinearModel(over_temp_cal);
232
233 // Initializes the model identification parameters.
234 over_temp_cal->new_overtemp_model_available = false;
235 over_temp_cal->min_num_model_pts = min_num_model_pts;
236 over_temp_cal->min_update_interval_nanos = min_update_interval_nanos;
237 over_temp_cal->delta_temp_per_bin = delta_temp_per_bin;
238 over_temp_cal->max_error_limit = max_error_limit;
239 over_temp_cal->age_limit_nanos = age_limit_nanos;
240 over_temp_cal->temp_sensitivity_limit = temp_sensitivity_limit;
241 over_temp_cal->sensor_intercept_limit = sensor_intercept_limit;
242 over_temp_cal->over_temp_enable = over_temp_enable;
243
244 // Initialize the sensor's temperature with a good initial operating point.
245 over_temp_cal->temperature_celsius = JUMPSTART_START_TEMP_CELSIUS;
246
247 #ifdef OVERTEMPCAL_DBG_ENABLED
248 CAL_DEBUG_LOG("[OVER_TEMP_CAL:MEMORY]", "sizeof(struct OverTempCal): %lu",
249 (unsigned long int)sizeof(struct OverTempCal));
250
251 if (over_temp_cal->over_temp_enable) {
252 CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]",
253 "Over-temperature compensation ENABLED.");
254 } else {
255 CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]",
256 "Over-temperature compensation DISABLED.");
257 }
258 #endif // OVERTEMPCAL_DBG_ENABLED
259 }
260
overTempCalSetModel(struct OverTempCal * over_temp_cal,const float * offset,float offset_temp_celsius,uint64_t timestamp_nanos,const float * temp_sensitivity,const float * sensor_intercept,bool jump_start_model)261 void overTempCalSetModel(struct OverTempCal *over_temp_cal, const float *offset,
262 float offset_temp_celsius, uint64_t timestamp_nanos,
263 const float *temp_sensitivity,
264 const float *sensor_intercept, bool jump_start_model) {
265 ASSERT_NOT_NULL(over_temp_cal);
266 ASSERT_NOT_NULL(offset);
267 ASSERT_NOT_NULL(temp_sensitivity);
268 ASSERT_NOT_NULL(sensor_intercept);
269
270 // Initializes the OTC linear model parameters.
271 resetOtcLinearModel(over_temp_cal);
272
273 // Sets the model parameters if they are within the acceptable limits.
274 // Includes a check to reject input model parameters that may have been passed
275 // in as all zeros.
276 size_t i;
277 for (i = 0; i < 3; i++) {
278 if (isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
279 sensor_intercept[i])) {
280 over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
281 over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
282 }
283 }
284
285 // Sets the model update time to the current timestamp.
286 over_temp_cal->modelupdate_timestamp_nanos = timestamp_nanos;
287
288 // Model "Jump-Start".
289 const bool model_jump_started =
290 (jump_start_model) ? jumpStartModelData(over_temp_cal) : false;
291
292 if (!model_jump_started) {
293 // Checks that the new offset data is valid.
294 if (isValidOtcOffset(offset, offset_temp_celsius)) {
295 // Sets the initial over-temp calibration estimate and model data.
296 over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
297 setNearestEstimate(over_temp_cal, offset, offset_temp_celsius,
298 timestamp_nanos);
299 over_temp_cal->num_model_pts = 1;
300 } else {
301 // No valid offset data to load.
302 over_temp_cal->num_model_pts = 0;
303 #ifdef OVERTEMPCAL_DBG_ENABLED
304 CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
305 "No valid sensor offset vector to load.");
306 #endif // OVERTEMPCAL_DBG_ENABLED
307 }
308 } else {
309 // Finds the offset nearest the sensor's current temperature.
310 findNearestEstimate(over_temp_cal);
311 }
312
313 // Updates the 'compensated_offset_previous' vector to prevent from
314 // immediately triggering a new calibration update.
315 float compensated_offset_temperature_celsius = 0.0f;
316 getCalOffset(over_temp_cal, timestamp_nanos,
317 &compensated_offset_temperature_celsius,
318 over_temp_cal->compensated_offset_previous);
319
320 #ifdef OVERTEMPCAL_DBG_ENABLED
321 // Prints the updated model data.
322 CAL_DEBUG_LOG(
323 "[OVER_TEMP_CAL:RECALL]",
324 "Temperature|Offset|Sensitivity|Intercept [rps]: %s%d.%06d, | %s%d.%06d, "
325 "%s%d.%06d, %s%d.%06d | %s%d.%06d, %s%d.%06d, %s%d.%06d | "
326 "%s%d.%06d, "
327 "%s%d.%06d, %s%d.%06d",
328 CAL_ENCODE_FLOAT(offset_temp_celsius, 6),
329 CAL_ENCODE_FLOAT(offset[0], 6),
330 CAL_ENCODE_FLOAT(offset[1], 6),
331 CAL_ENCODE_FLOAT(offset[2], 6),
332 CAL_ENCODE_FLOAT(temp_sensitivity[0], 6),
333 CAL_ENCODE_FLOAT(temp_sensitivity[1], 6),
334 CAL_ENCODE_FLOAT(temp_sensitivity[2], 6),
335 CAL_ENCODE_FLOAT(sensor_intercept[0], 6),
336 CAL_ENCODE_FLOAT(sensor_intercept[1], 6),
337 CAL_ENCODE_FLOAT(sensor_intercept[2], 6));
338
339 // Resets the debug print machine to ensure that updateDebugData() can
340 // produce a debug report and interupt any ongoing report.
341 over_temp_cal->debug_state = OTC_IDLE;
342
343 // Triggers a debug print out to view the new model parameters.
344 updateDebugData(over_temp_cal);
345 #endif // OVERTEMPCAL_DBG_ENABLED
346 }
347
overTempCalGetModel(struct OverTempCal * over_temp_cal,float * offset,float * offset_temp_celsius,uint64_t * timestamp_nanos,float * temp_sensitivity,float * sensor_intercept)348 void overTempCalGetModel(struct OverTempCal *over_temp_cal, float *offset,
349 float *offset_temp_celsius, uint64_t *timestamp_nanos,
350 float *temp_sensitivity, float *sensor_intercept) {
351 ASSERT_NOT_NULL(over_temp_cal);
352 ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
353 ASSERT_NOT_NULL(offset);
354 ASSERT_NOT_NULL(offset_temp_celsius);
355 ASSERT_NOT_NULL(timestamp_nanos);
356 ASSERT_NOT_NULL(temp_sensitivity);
357 ASSERT_NOT_NULL(sensor_intercept);
358
359 // Gets the latest over-temp calibration model data.
360 memcpy(temp_sensitivity, over_temp_cal->temp_sensitivity, 3 * sizeof(float));
361 memcpy(sensor_intercept, over_temp_cal->sensor_intercept, 3 * sizeof(float));
362 *timestamp_nanos = over_temp_cal->modelupdate_timestamp_nanos;
363
364 // Gets the latest temperature compensated offset estimate.
365 getCalOffset(over_temp_cal, *timestamp_nanos, offset_temp_celsius, offset);
366
367 #ifdef OVERTEMPCAL_DBG_ENABLED
368 // Prints the updated model data.
369 CAL_DEBUG_LOG(
370 "[OVER_TEMP_CAL:STORED]",
371 "Temperature|Offset|Sensitivity|Intercept [rps]: %s%d.%06d, | %s%d.%06d, "
372 "%s%d.%06d, %s%d.%06d | %s%d.%06d, %s%d.%06d, %s%d.%06d | "
373 "%s%d.%06d, "
374 "%s%d.%06d, %s%d.%06d",
375 CAL_ENCODE_FLOAT(*offset_temp_celsius, 6),
376 CAL_ENCODE_FLOAT(offset[0], 6),
377 CAL_ENCODE_FLOAT(offset[1], 6),
378 CAL_ENCODE_FLOAT(offset[2], 6),
379 CAL_ENCODE_FLOAT(temp_sensitivity[0], 6),
380 CAL_ENCODE_FLOAT(temp_sensitivity[1], 6),
381 CAL_ENCODE_FLOAT(temp_sensitivity[2], 6),
382 CAL_ENCODE_FLOAT(sensor_intercept[0], 6),
383 CAL_ENCODE_FLOAT(sensor_intercept[1], 6),
384 CAL_ENCODE_FLOAT(sensor_intercept[2], 6));
385 #endif // OVERTEMPCAL_DBG_ENABLED
386 }
387
overTempCalSetModelData(struct OverTempCal * over_temp_cal,size_t data_length,const struct OverTempCalDataPt * model_data)388 void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
389 size_t data_length,
390 const struct OverTempCalDataPt *model_data) {
391 ASSERT_NOT_NULL(over_temp_cal);
392 ASSERT_NOT_NULL(model_data);
393
394 // Load only "good" data from the input 'model_data'.
395 over_temp_cal->num_model_pts = NANO_MIN(data_length, OVERTEMPCAL_MODEL_SIZE);
396 size_t i;
397 size_t valid_data_count = 0;
398 for (i = 0; i < over_temp_cal->num_model_pts; i++) {
399 if (isValidOtcOffset(model_data[i].offset,
400 model_data[i].offset_temp_celsius)) {
401 memcpy(&over_temp_cal->model_data[i], &model_data[i],
402 sizeof(struct OverTempCalDataPt));
403 valid_data_count++;
404 }
405 }
406 over_temp_cal->num_model_pts = valid_data_count;
407
408 // Initializes the OTC linear model parameters.
409 resetOtcLinearModel(over_temp_cal);
410
411 // Finds the offset nearest the sensor's current temperature.
412 findNearestEstimate(over_temp_cal);
413
414 // Updates the 'compensated_offset_previous' vector to prevent from
415 // immediately triggering a new calibration update.
416 float compensated_offset_temperature_celsius = 0.0f;
417 getCalOffset(over_temp_cal, /*timestamp_nanos=*/0,
418 &compensated_offset_temperature_celsius,
419 over_temp_cal->compensated_offset_previous);
420
421 #ifdef OVERTEMPCAL_DBG_ENABLED
422 // Prints the updated model data.
423 CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
424 "Over-temperature full model data set recalled.");
425 // Resets the debug print machine to ensure that computeModelUpdate() can
426 // produce a debug report and interupt any ongoing report.
427 over_temp_cal->debug_state = OTC_IDLE;
428 #endif // OVERTEMPCAL_DBG_ENABLED
429
430 // Ensures that minimum number of points required for a model fit has been
431 // satisfied and recomputes the OTC model parameters.
432 if (over_temp_cal->num_model_pts > over_temp_cal->min_num_model_pts) {
433 // Computes and replaces the model parameters. If successful, this will
434 // trigger a "new calibration" update.
435 computeModelUpdate(over_temp_cal,
436 over_temp_cal->modelupdate_timestamp_nanos);
437 }
438 }
439
overTempCalGetModelData(struct OverTempCal * over_temp_cal,size_t * data_length,struct OverTempCalDataPt * model_data)440 void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
441 size_t *data_length,
442 struct OverTempCalDataPt *model_data) {
443 ASSERT_NOT_NULL(over_temp_cal);
444 *data_length = over_temp_cal->num_model_pts;
445 memcpy(model_data, over_temp_cal->model_data,
446 over_temp_cal->num_model_pts * sizeof(struct OverTempCalDataPt));
447 }
448
overTempCalGetOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float * compensated_offset_temperature_celsius,float * compensated_offset)449 bool overTempCalGetOffset(struct OverTempCal *over_temp_cal,
450 uint64_t timestamp_nanos,
451 float *compensated_offset_temperature_celsius,
452 float *compensated_offset) {
453 // Gets the temperature compensated sensor offset estimate.
454 getCalOffset(over_temp_cal, timestamp_nanos,
455 compensated_offset_temperature_celsius, compensated_offset);
456
457 // If the compensated_offset value has changed significantly then return
458 // 'true' status.
459 bool offset_has_changed = false;
460 int i;
461 for (i = 0; i < 3; i++) {
462 if (NANO_ABS(over_temp_cal->compensated_offset_previous[i] -
463 compensated_offset[i]) >= SIGNIFICANT_OFFSET_CHANGE_RPS) {
464 offset_has_changed = true;
465
466 // Update the 'compensated_offset_previous' vector.
467 memcpy(over_temp_cal->compensated_offset_previous, compensated_offset,
468 3 * sizeof(float));
469 break;
470 }
471 }
472
473 return offset_has_changed;
474 }
475
overTempCalRemoveOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float xi,float yi,float zi,float * xo,float * yo,float * zo)476 void overTempCalRemoveOffset(struct OverTempCal *over_temp_cal,
477 uint64_t timestamp_nanos, float xi, float yi,
478 float zi, float *xo, float *yo, float *zo) {
479 ASSERT_NOT_NULL(over_temp_cal);
480 ASSERT_NOT_NULL(xo);
481 ASSERT_NOT_NULL(yo);
482 ASSERT_NOT_NULL(zo);
483
484 // Determines whether over-temp compensation will be applied.
485 if (over_temp_cal->over_temp_enable) {
486 // Gets the temperature compensated sensor offset estimate.
487 float compensated_offset[3] = {0.0f, 0.0f, 0.0f};
488 float compensated_offset_temperature_celsius = 0.0f;
489 getCalOffset(over_temp_cal, timestamp_nanos,
490 &compensated_offset_temperature_celsius, compensated_offset);
491
492 // Removes the over-temperature compensated offset from the input sensor
493 // data.
494 *xo = xi - compensated_offset[0];
495 *yo = yi - compensated_offset[1];
496 *zo = zi - compensated_offset[2];
497 } else {
498 *xo = xi;
499 *yo = yi;
500 *zo = zi;
501 }
502 }
503
overTempCalNewModelUpdateAvailable(struct OverTempCal * over_temp_cal)504 bool overTempCalNewModelUpdateAvailable(struct OverTempCal *over_temp_cal) {
505 ASSERT_NOT_NULL(over_temp_cal);
506 const bool update_available = over_temp_cal->new_overtemp_model_available &&
507 over_temp_cal->over_temp_enable;
508
509 // The 'new_overtemp_model_available' flag is reset when it is read here.
510 over_temp_cal->new_overtemp_model_available = false;
511
512 return update_available;
513 }
514
overTempCalUpdateSensorEstimate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,const float * offset,float temperature_celsius)515 void overTempCalUpdateSensorEstimate(struct OverTempCal *over_temp_cal,
516 uint64_t timestamp_nanos,
517 const float *offset,
518 float temperature_celsius) {
519 ASSERT_NOT_NULL(over_temp_cal);
520 ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
521 ASSERT_NOT_NULL(offset);
522 ASSERT(over_temp_cal->delta_temp_per_bin > 0);
523
524 // Checks that the new offset data is valid, returns if bad.
525 if (!isValidOtcOffset(offset, temperature_celsius)) {
526 return;
527 }
528
529 // Prevent a divide by zero below.
530 if (over_temp_cal->delta_temp_per_bin <= 0) {
531 return;
532 }
533
534 // Checks whether this offset estimate is a likely outlier. A limit is placed
535 // on 'num_outliers', the previous number of successive rejects, to prevent
536 // too many back-to-back rejections.
537 if (over_temp_cal->num_outliers < OVERTEMPCAL_MAX_OUTLIER_COUNT) {
538 if (outlierCheck(over_temp_cal, offset, 0, temperature_celsius) ||
539 outlierCheck(over_temp_cal, offset, 1, temperature_celsius) ||
540 outlierCheck(over_temp_cal, offset, 2, temperature_celsius)) {
541 // Increments the count of rejected outliers.
542 over_temp_cal->num_outliers++;
543
544 #ifdef OVERTEMPCAL_DBG_ENABLED
545 CAL_DEBUG_LOG("[OVER_TEMP_CAL:OUTLIER]",
546 "Offset|Temperature|Time [mdps|Celcius|nsec] = "
547 "%s%d.%06d, %s%d.%06d, %s%d.%06d, %s%d.%03d, %llu",
548 CAL_ENCODE_FLOAT(offset[0] * RAD_TO_MILLI_DEGREES, 6),
549 CAL_ENCODE_FLOAT(offset[1] * RAD_TO_MILLI_DEGREES, 6),
550 CAL_ENCODE_FLOAT(offset[2] * RAD_TO_MILLI_DEGREES, 6),
551 CAL_ENCODE_FLOAT(temperature_celsius, 3),
552 (unsigned long long int)timestamp_nanos);
553 #endif // OVERTEMPCAL_DBG_ENABLED
554
555 return; // Outlier detected: skips adding this offset to the model.
556 } else {
557 // Resets the count of rejected outliers.
558 over_temp_cal->num_outliers = 0;
559 }
560 } else {
561 // Resets the count of rejected outliers.
562 over_temp_cal->num_outliers = 0;
563 }
564
565 // Computes the temperature bin range data.
566 const int32_t bin_num =
567 CAL_FLOOR(temperature_celsius / over_temp_cal->delta_temp_per_bin);
568 const float temp_lo_check = bin_num * over_temp_cal->delta_temp_per_bin;
569 const float temp_hi_check = (bin_num + 1) * over_temp_cal->delta_temp_per_bin;
570
571 // The rules for accepting new offset estimates into the 'model_data'
572 // collection:
573 // 1) The temperature domain is divided into bins each spanning
574 // 'delta_temp_per_bin'.
575 // 2) Find and replace the i'th 'model_data' estimate data if:
576 // Let, bin_num = floor(temperature_celsius / delta_temp_per_bin)
577 // temp_lo_check = bin_num * delta_temp_per_bin
578 // temp_hi_check = (bin_num + 1) * delta_temp_per_bin
579 // Check condition:
580 // temp_lo_check <= model_data[i].offset_temp_celsius < temp_hi_check
581 bool replaced_one = false;
582 size_t i = 0;
583 for (i = 0; i < over_temp_cal->num_model_pts; i++) {
584 if (over_temp_cal->model_data[i].offset_temp_celsius < temp_hi_check &&
585 over_temp_cal->model_data[i].offset_temp_celsius >= temp_lo_check) {
586 // NOTE - the pointer to the new model data point is set here; the offset
587 // data is set below in the call to 'setNearestEstimate'.
588 over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
589 replaced_one = true;
590 break;
591 }
592 }
593
594 // NOTE - the pointer to the new model data point is set here; the offset
595 // data is set below in the call to 'setNearestEstimate'.
596 if (!replaced_one && over_temp_cal->num_model_pts < OVERTEMPCAL_MODEL_SIZE) {
597 if (over_temp_cal->num_model_pts < OVERTEMPCAL_MODEL_SIZE) {
598 // 3) If nothing was replaced, and the 'model_data' buffer is not full
599 // then add the estimate data to the array.
600 over_temp_cal->nearest_offset =
601 &over_temp_cal->model_data[over_temp_cal->num_model_pts];
602 over_temp_cal->num_model_pts++;
603 } else {
604 // 4) Otherwise (nothing was replaced and buffer is full), replace the
605 // oldest data with the incoming one.
606 over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
607 for (i = 1; i < over_temp_cal->num_model_pts; i++) {
608 if (over_temp_cal->nearest_offset->timestamp_nanos <
609 over_temp_cal->model_data[i].timestamp_nanos) {
610 over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
611 }
612 }
613 }
614 }
615
616 // Updates the model estimate data nearest to the sensor's temperature.
617 setNearestEstimate(over_temp_cal, offset, temperature_celsius,
618 timestamp_nanos);
619
620 #ifdef OVERTEMPCAL_DBG_ENABLED
621 // Updates the total number of received sensor offset estimates.
622 over_temp_cal->debug_num_estimates++;
623 #endif // OVERTEMPCAL_DBG_ENABLED
624
625 // The rules for determining whether a new model fit is computed are:
626 // 1) A minimum number of data points must have been collected:
627 // num_model_pts >= min_num_model_pts
628 // NOTE: Collecting 'num_model_pts' and given that only one point is
629 // kept per temperature bin (spanning a thermal range specified by
630 // 'delta_temp_per_bin'), implies that model data covers at least,
631 // model_temperature_span >= 'num_model_pts' * delta_temp_per_bin
632 // 2) New model updates will not occur for intervals less than:
633 // (current_timestamp_nanos - modelupdate_timestamp_nanos) <
634 // min_update_interval_nanos
635 if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts ||
636 timestamp_nanos < over_temp_cal->min_update_interval_nanos +
637 over_temp_cal->modelupdate_timestamp_nanos) {
638 #ifdef OVERTEMPCAL_DBG_ENABLED
639 // Triggers a log printout to show the updated sensor offset estimate.
640 updateDebugData(over_temp_cal);
641 #endif // OVERTEMPCAL_DBG_ENABLED
642 } else {
643 // The conditions satisfy performing a new model update.
644 computeModelUpdate(over_temp_cal, timestamp_nanos);
645 }
646 }
647
overTempCalSetTemperature(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float temperature_celsius)648 void overTempCalSetTemperature(struct OverTempCal *over_temp_cal,
649 uint64_t timestamp_nanos,
650 float temperature_celsius) {
651 ASSERT_NOT_NULL(over_temp_cal);
652
653 #ifdef OVERTEMPCAL_DBG_ENABLED
654 #ifdef OVERTEMPCAL_DBG_LOG_TEMP
655 static uint64_t wait_timer = 0;
656 // Prints the sensor temperature trajectory for debugging purposes.
657 // This throttles the print statements.
658 if (timestamp_nanos >= 1000000000 + wait_timer) {
659 wait_timer = timestamp_nanos; // Starts the wait timer.
660
661 // Prints out temperature and the current timestamp.
662 CAL_DEBUG_LOG("[OVER_TEMP_CAL:TEMP]",
663 "Temperature|Time [C|nsec] = %s%d.%06d, %llu",
664 CAL_ENCODE_FLOAT(temperature_celsius, 6),
665 (unsigned long long int)timestamp_nanos);
666 }
667 #endif // OVERTEMPCAL_DBG_LOG_TEMP
668 #endif // OVERTEMPCAL_DBG_ENABLED
669
670 // Checks that the offset temperature is within a valid range, saturates if
671 // outside.
672 checkAndEnforceTemperatureRange(&temperature_celsius);
673
674 // Updates the sensor temperature.
675 over_temp_cal->temperature_celsius = temperature_celsius;
676
677 // Searches for the sensor offset estimate closest to the current
678 // temperature. A timer is used to limit the rate at which this search is
679 // performed.
680 if (over_temp_cal->num_model_pts > 0 &&
681 timestamp_nanos >=
682 OVERTEMPCAL_NEAREST_NANOS + over_temp_cal->nearest_search_timer) {
683 findNearestEstimate(over_temp_cal);
684 over_temp_cal->nearest_search_timer = timestamp_nanos; // Reset timer.
685 }
686 }
687
overTempGetModelError(const struct OverTempCal * over_temp_cal,const float * temp_sensitivity,const float * sensor_intercept,float * max_error)688 void overTempGetModelError(const struct OverTempCal *over_temp_cal,
689 const float *temp_sensitivity, const float *sensor_intercept,
690 float *max_error) {
691 ASSERT_NOT_NULL(over_temp_cal);
692 ASSERT_NOT_NULL(temp_sensitivity);
693 ASSERT_NOT_NULL(sensor_intercept);
694 ASSERT_NOT_NULL(max_error);
695
696 size_t i;
697 size_t j;
698 float max_error_test;
699 memset(max_error, 0, 3 * sizeof(float));
700
701 for (i = 0; i < over_temp_cal->num_model_pts; i++) {
702 for (j = 0; j < 3; j++) {
703 max_error_test =
704 NANO_ABS(over_temp_cal->model_data[i].offset[j] -
705 (temp_sensitivity[j] *
706 over_temp_cal->model_data[i].offset_temp_celsius +
707 sensor_intercept[j]));
708 if (max_error_test > max_error[j]) {
709 max_error[j] = max_error_test;
710 }
711 }
712 }
713 }
714
715 /////// LOCAL HELPER FUNCTION DEFINITIONS /////////////////////////////////////
716
getCalOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float * compensated_offset_temperature_celsius,float * compensated_offset)717 void getCalOffset(struct OverTempCal *over_temp_cal, uint64_t timestamp_nanos,
718 float *compensated_offset_temperature_celsius,
719 float *compensated_offset) {
720 ASSERT_NOT_NULL(over_temp_cal);
721 ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
722 ASSERT_NOT_NULL(compensated_offset);
723 ASSERT_NOT_NULL(compensated_offset_temperature_celsius);
724
725 // Sets the sensor temperature associated with the compensated offset.
726 *compensated_offset_temperature_celsius = over_temp_cal->temperature_celsius;
727
728 // Removes very old data from the collected model estimates (eliminates
729 // drift-compromised data). Only does this when there is more than one
730 // estimate in the model (i.e., don't want to remove all data, even if it is
731 // very old [something is likely better than nothing]).
732 if ((timestamp_nanos >=
733 OVERTEMPCAL_STALE_CHECK_TIME_NANOS + over_temp_cal->stale_data_timer) &&
734 over_temp_cal->num_model_pts > 1) {
735 over_temp_cal->stale_data_timer = timestamp_nanos; // Resets timer.
736
737 if (removeStaleModelData(over_temp_cal, timestamp_nanos)) {
738 // If anything was removed, then this attempts to recompute the model.
739 if (over_temp_cal->num_model_pts >= over_temp_cal->min_num_model_pts) {
740 computeModelUpdate(over_temp_cal, timestamp_nanos);
741 }
742 }
743 }
744
745 size_t index;
746 for (index = 0; index < 3; index++) {
747 if (over_temp_cal->temp_sensitivity[index] >= OTC_INITIAL_SENSITIVITY ||
748 NANO_ABS(over_temp_cal->temperature_celsius -
749 over_temp_cal->nearest_offset->offset_temp_celsius) <
750 over_temp_cal->delta_temp_per_bin) {
751 // Use the nearest estimate to perform the compensation if either of the
752 // following is true:
753 // 1) This axis model is in its initial state.
754 // 2) The sensor's temperature is within a small neighborhood of the
755 // 'nearest_offset'.
756 // compensated_offset = nearest_offset
757 //
758 // If either of the above conditions applies and 'nearest_offset' is not
759 // defined, then the offset returned is zero.
760 compensated_offset[index] =
761 (over_temp_cal->num_model_pts > 0)
762 ? over_temp_cal->nearest_offset->offset[index]
763 : 0.0f;
764 } else {
765 // Offset computed from the linear model:
766 // compensated_offset = (temp_sensitivity * temperature +
767 // sensor_intercept)
768 compensated_offset[index] = (over_temp_cal->temp_sensitivity[index] *
769 over_temp_cal->temperature_celsius +
770 over_temp_cal->sensor_intercept[index]);
771 }
772 }
773 }
774
setNearestEstimate(struct OverTempCal * over_temp_cal,const float * offset,float offset_temp_celsius,uint64_t timestamp_nanos)775 void setNearestEstimate(struct OverTempCal *over_temp_cal, const float *offset,
776 float offset_temp_celsius, uint64_t timestamp_nanos) {
777 ASSERT_NOT_NULL(over_temp_cal);
778 ASSERT_NOT_NULL(offset);
779 ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
780
781 // Sets the latest over-temp calibration estimate.
782 memcpy(over_temp_cal->nearest_offset->offset, offset, 3 * sizeof(float));
783 over_temp_cal->nearest_offset->offset_temp_celsius = offset_temp_celsius;
784 over_temp_cal->nearest_offset->timestamp_nanos = timestamp_nanos;
785 }
786
computeModelUpdate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)787 void computeModelUpdate(struct OverTempCal *over_temp_cal,
788 uint64_t timestamp_nanos) {
789 ASSERT_NOT_NULL(over_temp_cal);
790
791 // Updates the linear model fit.
792 float temp_sensitivity[3];
793 float sensor_intercept[3];
794 updateModel(over_temp_cal, temp_sensitivity, sensor_intercept);
795
796 // Computes the maximum error over all of the model data.
797 float max_error[3];
798 overTempGetModelError(over_temp_cal, temp_sensitivity, sensor_intercept,
799 max_error);
800
801 // 3) A new set of model parameters are accepted if:
802 // i. The model fit error is less than, 'max_error_limit'. See
803 // overTempGetModelError() for error metric description.
804 // ii. The model fit parameters must be within certain absolute
805 // bounds:
806 // a. NANO_ABS(temp_sensitivity) < temp_sensitivity_limit
807 // b. NANO_ABS(sensor_intercept) < sensor_intercept_limit
808 size_t i;
809 bool updated_one = false;
810 for (i = 0; i < 3; i++) {
811 if (max_error[i] < over_temp_cal->max_error_limit &&
812 isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
813 sensor_intercept[i])) {
814 over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
815 over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
816 updated_one = true;
817 } else {
818 #ifdef OVERTEMPCAL_DBG_ENABLED
819 CAL_DEBUG_LOG(
820 "[OVER_TEMP_CAL:REJECT]",
821 "%c-Axis Parameters|Max Error|Time [mdps/C|mdps|mdps|nsec] = "
822 "%s%d.%06d, %s%d.%06d, %s%d.%06d, %llu",
823 kDebugAxisLabel[i],
824 CAL_ENCODE_FLOAT(temp_sensitivity[i] * RAD_TO_MILLI_DEGREES, 6),
825 CAL_ENCODE_FLOAT(sensor_intercept[i] * RAD_TO_MILLI_DEGREES, 6),
826 CAL_ENCODE_FLOAT(max_error[i] * RAD_TO_MILLI_DEGREES, 6),
827 (unsigned long long int)timestamp_nanos);
828 #endif // OVERTEMPCAL_DBG_ENABLED
829 }
830 }
831
832 // If at least one of the axes updated then consider this a valid model
833 // update.
834 if (updated_one) {
835 // Resets the timer and sets the update flag.
836 over_temp_cal->modelupdate_timestamp_nanos = timestamp_nanos;
837 over_temp_cal->new_overtemp_model_available = true;
838
839 #ifdef OVERTEMPCAL_DBG_ENABLED
840 // Updates the total number of model updates, the debug data package, and
841 // triggers a log printout.
842 over_temp_cal->debug_num_model_updates++;
843 updateDebugData(over_temp_cal);
844 #endif // OVERTEMPCAL_DBG_ENABLED
845 }
846 }
847
findNearestEstimate(struct OverTempCal * over_temp_cal)848 void findNearestEstimate(struct OverTempCal *over_temp_cal) {
849 ASSERT_NOT_NULL(over_temp_cal);
850
851 // Performs a brute force search for the estimate nearest the current sensor
852 // temperature.
853 size_t i = 0;
854 float dtemp_new = 0.0f;
855 float dtemp_old = FLT_MAX;
856 over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
857 for (i = 0; i < over_temp_cal->num_model_pts; i++) {
858 dtemp_new = NANO_ABS(over_temp_cal->model_data[i].offset_temp_celsius -
859 over_temp_cal->temperature_celsius);
860 if (dtemp_new < dtemp_old) {
861 over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
862 dtemp_old = dtemp_new;
863 }
864 }
865 }
866
removeStaleModelData(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)867 bool removeStaleModelData(struct OverTempCal *over_temp_cal,
868 uint64_t timestamp_nanos) {
869 ASSERT_NOT_NULL(over_temp_cal);
870
871 size_t i;
872 bool removed_one = false;
873 for (i = 0; i < over_temp_cal->num_model_pts; i++) {
874 if (timestamp_nanos > over_temp_cal->model_data[i].timestamp_nanos &&
875 timestamp_nanos > over_temp_cal->age_limit_nanos +
876 over_temp_cal->model_data[i].timestamp_nanos) {
877 removed_one |= removeModelDataByIndex(over_temp_cal, i);
878 }
879 }
880
881 // Updates the latest offset so that it is the one nearest to the current
882 // temperature.
883 findNearestEstimate(over_temp_cal);
884
885 return removed_one;
886 }
887
removeModelDataByIndex(struct OverTempCal * over_temp_cal,size_t model_index)888 bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
889 size_t model_index) {
890 ASSERT_NOT_NULL(over_temp_cal);
891
892 // This function will not remove all of the model data. At least one model
893 // sample will be left.
894 if (over_temp_cal->num_model_pts <= 1) {
895 return false;
896 }
897
898 #ifdef OVERTEMPCAL_DBG_ENABLED
899 CAL_DEBUG_LOG(
900 "[OVER_TEMP_CAL:REMOVE]",
901 "Offset|Temp|Time [mdps|C|nsec] = %s%d.%06d, %s%d.%06d, %s%d.%06d, "
902 "%s%d.%03d, %llu",
903 CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[0] *
904 RAD_TO_MILLI_DEGREES,
905 6),
906 CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] *
907 RAD_TO_MILLI_DEGREES,
908 6),
909 CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] *
910 RAD_TO_MILLI_DEGREES,
911 6),
912 CAL_ENCODE_FLOAT(
913 over_temp_cal->model_data[model_index].offset_temp_celsius, 3),
914 (unsigned long long int)over_temp_cal->model_data[model_index]
915 .timestamp_nanos);
916 #endif // OVERTEMPCAL_DBG_ENABLED
917
918 // Remove the model data at 'model_index'.
919 size_t i;
920 for (i = model_index; i < over_temp_cal->num_model_pts - 1; i++) {
921 memcpy(&over_temp_cal->model_data[i], &over_temp_cal->model_data[i + 1],
922 sizeof(struct OverTempCalDataPt));
923 }
924 over_temp_cal->num_model_pts--;
925
926 return true;
927 }
928
jumpStartModelData(struct OverTempCal * over_temp_cal)929 bool jumpStartModelData(struct OverTempCal *over_temp_cal) {
930 ASSERT_NOT_NULL(over_temp_cal);
931 ASSERT(over_temp_cal->delta_temp_per_bin > 0);
932
933 // Prevent a divide by zero below.
934 if (over_temp_cal->delta_temp_per_bin <= 0) {
935 return false;
936 }
937
938 // In normal operation the offset estimates enter into the 'model_data' array
939 // complete (i.e., x, y, z values are all provided). Therefore, the jumpstart
940 // data produced here requires that the model parameters have all been fully
941 // defined and are all within the valid range.
942 size_t i;
943 for (i = 0; i < 3; i++) {
944 if (!isValidOtcLinearModel(over_temp_cal,
945 over_temp_cal->temp_sensitivity[i],
946 over_temp_cal->sensor_intercept[i])) {
947 return false;
948 }
949 }
950
951 // Any pre-existing model data points will be overwritten.
952 over_temp_cal->num_model_pts = 0;
953
954 // This defines the minimum contiguous set of points to allow a model update
955 // when the next offset estimate is received. They are placed at a common
956 // temperature range that is likely to get replaced with actual data soon.
957 const int32_t start_bin_num = CAL_FLOOR(JUMPSTART_START_TEMP_CELSIUS /
958 over_temp_cal->delta_temp_per_bin);
959 float offset_temp_celsius =
960 (start_bin_num + 0.5f) * over_temp_cal->delta_temp_per_bin;
961
962 size_t j;
963 for (i = 0; i < over_temp_cal->min_num_model_pts; i++) {
964 float offset[3];
965 const uint64_t timestamp_nanos = over_temp_cal->modelupdate_timestamp_nanos;
966 for (j = 0; j < 3; j++) {
967 offset[j] = over_temp_cal->temp_sensitivity[j] * offset_temp_celsius +
968 over_temp_cal->sensor_intercept[j];
969 }
970 over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
971 setNearestEstimate(over_temp_cal, offset, offset_temp_celsius,
972 timestamp_nanos);
973 offset_temp_celsius += over_temp_cal->delta_temp_per_bin;
974 over_temp_cal->num_model_pts++;
975 }
976
977 #ifdef OVERTEMPCAL_DBG_ENABLED
978 if (over_temp_cal->num_model_pts > 0) {
979 CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]", "Model Jump-Start: #Points = %lu.",
980 (unsigned long int)over_temp_cal->num_model_pts);
981 }
982 #endif // OVERTEMPCAL_DBG_ENABLED
983
984 return (over_temp_cal->num_model_pts > 0);
985 }
986
updateModel(const struct OverTempCal * over_temp_cal,float * temp_sensitivity,float * sensor_intercept)987 void updateModel(const struct OverTempCal *over_temp_cal,
988 float *temp_sensitivity, float *sensor_intercept) {
989 ASSERT_NOT_NULL(over_temp_cal);
990 ASSERT_NOT_NULL(temp_sensitivity);
991 ASSERT_NOT_NULL(sensor_intercept);
992 ASSERT(over_temp_cal->num_model_pts > 0);
993
994 float st = 0.0f, stt = 0.0f;
995 float sx = 0.0f, stsx = 0.0f;
996 float sy = 0.0f, stsy = 0.0f;
997 float sz = 0.0f, stsz = 0.0f;
998 const size_t n = over_temp_cal->num_model_pts;
999 size_t i = 0;
1000
1001 // First pass computes the mean values.
1002 for (i = 0; i < n; ++i) {
1003 st += over_temp_cal->model_data[i].offset_temp_celsius;
1004 sx += over_temp_cal->model_data[i].offset[0];
1005 sy += over_temp_cal->model_data[i].offset[1];
1006 sz += over_temp_cal->model_data[i].offset[2];
1007 }
1008
1009 // Second pass computes the mean corrected second moment values.
1010 const float inv_n = 1.0f / n;
1011 for (i = 0; i < n; ++i) {
1012 const float t =
1013 over_temp_cal->model_data[i].offset_temp_celsius - st * inv_n;
1014 stt += t * t;
1015 stsx += t * over_temp_cal->model_data[i].offset[0];
1016 stsy += t * over_temp_cal->model_data[i].offset[1];
1017 stsz += t * over_temp_cal->model_data[i].offset[2];
1018 }
1019
1020 // Calculates the linear model fit parameters.
1021 ASSERT(stt > 0);
1022 const float inv_stt = 1.0f / stt;
1023 temp_sensitivity[0] = stsx * inv_stt;
1024 sensor_intercept[0] = (sx - st * temp_sensitivity[0]) * inv_n;
1025 temp_sensitivity[1] = stsy * inv_stt;
1026 sensor_intercept[1] = (sy - st * temp_sensitivity[1]) * inv_n;
1027 temp_sensitivity[2] = stsz * inv_stt;
1028 sensor_intercept[2] = (sz - st * temp_sensitivity[2]) * inv_n;
1029 }
1030
outlierCheck(struct OverTempCal * over_temp_cal,const float * offset,size_t axis_index,float temperature_celsius)1031 bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
1032 size_t axis_index, float temperature_celsius) {
1033 ASSERT_NOT_NULL(over_temp_cal);
1034 ASSERT_NOT_NULL(offset);
1035
1036 // If a model has been defined, then check to see if this offset could be a
1037 // potential outlier:
1038 if (over_temp_cal->temp_sensitivity[axis_index] < OTC_INITIAL_SENSITIVITY) {
1039 const float max_error_test = NANO_ABS(
1040 offset[axis_index] -
1041 (over_temp_cal->temp_sensitivity[axis_index] * temperature_celsius +
1042 over_temp_cal->sensor_intercept[axis_index]));
1043
1044 if (max_error_test > over_temp_cal->max_error_limit) {
1045 return true;
1046 }
1047 }
1048
1049 return false;
1050 }
1051
1052 /////// DEBUG FUNCTION DEFINITIONS ////////////////////////////////////////////
1053
1054 #ifdef OVERTEMPCAL_DBG_ENABLED
updateDebugData(struct OverTempCal * over_temp_cal)1055 void updateDebugData(struct OverTempCal* over_temp_cal) {
1056 ASSERT_NOT_NULL(over_temp_cal);
1057 ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
1058
1059 // Only update this data if debug printing is not currently in progress
1060 // (i.e., don't want to risk overwriting debug information that is actively
1061 // being reported).
1062 if (over_temp_cal->debug_state != OTC_IDLE) {
1063 return;
1064 }
1065
1066 // Triggers a debug log printout.
1067 over_temp_cal->debug_print_trigger = true;
1068
1069 // Initializes the debug data structure.
1070 memset(&over_temp_cal->debug_overtempcal, 0, sizeof(struct DebugOverTempCal));
1071
1072 // Copies over the relevant data.
1073 memcpy(over_temp_cal->debug_overtempcal.temp_sensitivity,
1074 over_temp_cal->temp_sensitivity, 3 * sizeof(float));
1075 memcpy(over_temp_cal->debug_overtempcal.sensor_intercept,
1076 over_temp_cal->sensor_intercept, 3 * sizeof(float));
1077 memcpy(&over_temp_cal->debug_overtempcal.nearest_offset,
1078 over_temp_cal->nearest_offset, sizeof(struct OverTempCalDataPt));
1079
1080 over_temp_cal->debug_overtempcal.num_model_pts = over_temp_cal->num_model_pts;
1081 over_temp_cal->debug_overtempcal.modelupdate_timestamp_nanos =
1082 over_temp_cal->modelupdate_timestamp_nanos;
1083 over_temp_cal->debug_overtempcal.temperature_celsius =
1084 over_temp_cal->temperature_celsius;
1085
1086 // Computes the maximum error over all of the model data.
1087 overTempGetModelError(over_temp_cal,
1088 over_temp_cal->debug_overtempcal.temp_sensitivity,
1089 over_temp_cal->debug_overtempcal.sensor_intercept,
1090 over_temp_cal->debug_overtempcal.max_error);
1091 }
1092
overTempCalDebugPrint(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)1093 void overTempCalDebugPrint(struct OverTempCal *over_temp_cal,
1094 uint64_t timestamp_nanos) {
1095 ASSERT_NOT_NULL(over_temp_cal);
1096
1097 static enum OverTempCalDebugState next_state = 0;
1098 static uint64_t wait_timer = 0;
1099 static size_t i = 0; // Counter.
1100
1101 // This is a state machine that controls the reporting out of debug data.
1102 switch (over_temp_cal->debug_state) {
1103 case OTC_IDLE:
1104 // Wait for a trigger and start the debug printout sequence.
1105 if (over_temp_cal->debug_print_trigger) {
1106 CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "");
1107 CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "Debug Version: %s",
1108 OVERTEMPCAL_DEBUG_VERSION_STRING);
1109 over_temp_cal->debug_print_trigger = false; // Resets trigger.
1110 over_temp_cal->debug_state = OTC_PRINT_OFFSET;
1111 } else {
1112 over_temp_cal->debug_state = OTC_IDLE;
1113 }
1114 break;
1115
1116 case OTC_WAIT_STATE:
1117 // This helps throttle the print statements.
1118 if (timestamp_nanos >= OVERTEMPCAL_WAIT_TIME_NANOS + wait_timer) {
1119 over_temp_cal->debug_state = next_state;
1120 }
1121 break;
1122
1123 case OTC_PRINT_OFFSET:
1124 // Prints out the latest GyroCal offset estimate (input data).
1125 CAL_DEBUG_LOG(
1126 OVERTEMPCAL_REPORT_TAG,
1127 "Cal#|Offset|Temp|Time [mdps|C|nsec]: %lu, %s%d.%06d, "
1128 "%s%d.%06d, %s%d.%06d, %s%d.%03d, %llu",
1129 (unsigned long int)over_temp_cal->debug_num_estimates,
1130 CAL_ENCODE_FLOAT(
1131 over_temp_cal->debug_overtempcal.nearest_offset.offset[0] *
1132 RAD_TO_MILLI_DEGREES,
1133 6),
1134 CAL_ENCODE_FLOAT(
1135 over_temp_cal->debug_overtempcal.nearest_offset.offset[1] *
1136 RAD_TO_MILLI_DEGREES,
1137 6),
1138 CAL_ENCODE_FLOAT(
1139 over_temp_cal->debug_overtempcal.nearest_offset.offset[2] *
1140 RAD_TO_MILLI_DEGREES,
1141 6),
1142 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.nearest_offset
1143 .offset_temp_celsius,
1144 6),
1145 (unsigned long long int)
1146 over_temp_cal->debug_overtempcal.nearest_offset.timestamp_nanos);
1147
1148 wait_timer = timestamp_nanos; // Starts the wait timer.
1149 next_state = OTC_PRINT_MODEL_PARAMETERS; // Sets the next state.
1150 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state.
1151 break;
1152
1153 case OTC_PRINT_MODEL_PARAMETERS:
1154 // Prints out the model parameters.
1155 CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG,
1156 "Cal#|Sensitivity|Intercept [mdps/C|mdps]: %lu, %s%d.%06d, "
1157 "%s%d.%06d, %s%d.%06d, %s%d.%06d, %s%d.%06d, %s%d.%06d",
1158 (unsigned long int)over_temp_cal->debug_num_estimates,
1159 CAL_ENCODE_FLOAT(
1160 over_temp_cal->debug_overtempcal.temp_sensitivity[0] *
1161 RAD_TO_MILLI_DEGREES,
1162 6),
1163 CAL_ENCODE_FLOAT(
1164 over_temp_cal->debug_overtempcal.temp_sensitivity[1] *
1165 RAD_TO_MILLI_DEGREES,
1166 6),
1167 CAL_ENCODE_FLOAT(
1168 over_temp_cal->debug_overtempcal.temp_sensitivity[2] *
1169 RAD_TO_MILLI_DEGREES,
1170 6),
1171 CAL_ENCODE_FLOAT(
1172 over_temp_cal->debug_overtempcal.sensor_intercept[0] *
1173 RAD_TO_MILLI_DEGREES,
1174 6),
1175 CAL_ENCODE_FLOAT(
1176 over_temp_cal->debug_overtempcal.sensor_intercept[1] *
1177 RAD_TO_MILLI_DEGREES,
1178 6),
1179 CAL_ENCODE_FLOAT(
1180 over_temp_cal->debug_overtempcal.sensor_intercept[2] *
1181 RAD_TO_MILLI_DEGREES,
1182 6));
1183
1184 wait_timer = timestamp_nanos; // Starts the wait timer.
1185 next_state = OTC_PRINT_MODEL_ERROR; // Sets the next state.
1186 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state.
1187 break;
1188
1189 case OTC_PRINT_MODEL_ERROR:
1190 // Computes the maximum error over all of the model data.
1191 CAL_DEBUG_LOG(
1192 OVERTEMPCAL_REPORT_TAG,
1193 "Cal#|#Updates|#ModelPts|Model Error|Update Time [mdps|nsec]: %lu, "
1194 "%lu, %lu, %s%d.%06d, %s%d.%06d, %s%d.%06d, %llu",
1195 (unsigned long int)over_temp_cal->debug_num_estimates,
1196 (unsigned long int)over_temp_cal->debug_num_model_updates,
1197 (unsigned long int)over_temp_cal->debug_overtempcal.num_model_pts,
1198 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[0] *
1199 RAD_TO_MILLI_DEGREES,
1200 6),
1201 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[1] *
1202 RAD_TO_MILLI_DEGREES,
1203 6),
1204 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[2] *
1205 RAD_TO_MILLI_DEGREES,
1206 6),
1207 (unsigned long long int)
1208 over_temp_cal->debug_overtempcal.modelupdate_timestamp_nanos);
1209
1210 i = 0; // Resets the model data printer counter.
1211 wait_timer = timestamp_nanos; // Starts the wait timer.
1212 next_state = OTC_PRINT_MODEL_DATA; // Sets the next state.
1213 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state.
1214 break;
1215
1216 case OTC_PRINT_MODEL_DATA:
1217 // Prints out all of the model data.
1218 if (i < over_temp_cal->num_model_pts) {
1219 CAL_DEBUG_LOG(
1220 OVERTEMPCAL_REPORT_TAG,
1221 " Model[%lu] [mdps|C|nsec] = %s%d.%06d, %s%d.%06d, %s%d.%06d, "
1222 "%s%d.%03d, %llu",
1223 (unsigned long int)i,
1224 CAL_ENCODE_FLOAT(
1225 over_temp_cal->model_data[i].offset[0] * RAD_TO_MILLI_DEGREES,
1226 6),
1227 CAL_ENCODE_FLOAT(
1228 over_temp_cal->model_data[i].offset[1] * RAD_TO_MILLI_DEGREES,
1229 6),
1230 CAL_ENCODE_FLOAT(
1231 over_temp_cal->model_data[i].offset[2] * RAD_TO_MILLI_DEGREES,
1232 6),
1233 CAL_ENCODE_FLOAT(over_temp_cal->model_data[i].offset_temp_celsius,
1234 3),
1235 (unsigned long long int)over_temp_cal->model_data[i]
1236 .timestamp_nanos);
1237
1238 i++;
1239 wait_timer = timestamp_nanos; // Starts the wait timer.
1240 next_state = OTC_PRINT_MODEL_DATA; // Sets the next state.
1241 over_temp_cal->debug_state =
1242 OTC_WAIT_STATE; // First, go to wait state.
1243 } else {
1244 // Sends this state machine to its idle state.
1245 wait_timer = timestamp_nanos; // Starts the wait timer.
1246 next_state = OTC_IDLE; // Sets the next state.
1247 over_temp_cal->debug_state =
1248 OTC_WAIT_STATE; // First, go to wait state.
1249 }
1250 break;
1251
1252 default:
1253 // Sends this state machine to its idle state.
1254 wait_timer = timestamp_nanos; // Starts the wait timer.
1255 next_state = OTC_IDLE; // Sets the next state.
1256 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state.
1257 }
1258 }
1259 #endif // OVERTEMPCAL_DBG_ENABLED
1260