1 /* 2 * Copyright (C) 2017 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 <inttypes.h> 21 #include <math.h> 22 #include <string.h> 23 24 #include "calibration/util/cal_log.h" 25 #include "util/nano_assert.h" 26 27 /////// DEFINITIONS AND MACROS //////////////////////////////////////////////// 28 29 // Value used to check whether OTC model data is near zero. 30 #define OTC_MODELDATA_NEAR_ZERO_TOL (1e-7f) 31 32 // Defines the default weighting function for the linear model fit routine. 33 // Weighting = 10.0; for offsets newer than 5 minutes. 34 static const struct OverTempCalWeight kOtcDefaultWeight0 = { 35 .offset_age_nanos = MIN_TO_NANOS(5), 36 .weight = 10.0f, 37 }; 38 39 // Weighting = 0.1; for offsets newer than 15 minutes. 40 static const struct OverTempCalWeight kOtcDefaultWeight1 = { 41 .offset_age_nanos = MIN_TO_NANOS(15), 42 .weight = 0.1f, 43 }; 44 45 // The default weighting used for all older offsets. 46 #define OTC_MIN_WEIGHT_VALUE (0.04f) 47 48 #ifdef OVERTEMPCAL_DBG_ENABLED 49 // A debug version label to help with tracking results. 50 #define OTC_DEBUG_VERSION_STRING "[Jan 10, 2018]" 51 52 // The time interval used to throttle debug messaging (100msec). 53 #define OTC_WAIT_TIME_NANOS (SEC_TO_NANOS(0.1)) 54 55 // The time interval used to throttle temperature print messaging (1 second). 56 #define OTC_PRINT_TEMP_NANOS (SEC_TO_NANOS(1)) 57 58 // Sensor axis label definition with index correspondence: 0=X, 1=Y, 2=Z. 59 static const char kDebugAxisLabel[3] = "XYZ"; 60 #endif // OVERTEMPCAL_DBG_ENABLED 61 62 /////// FORWARD DECLARATIONS ////////////////////////////////////////////////// 63 64 // Updates the latest received model estimate data. 65 static void setLatestEstimate(struct OverTempCal *over_temp_cal, 66 const float *offset, float offset_temp_celsius); 67 68 /* 69 * Determines if a new over-temperature model fit should be performed, and then 70 * updates the model as needed. 71 * 72 * INPUTS: 73 * over_temp_cal: Over-temp data structure. 74 * timestamp_nanos: Current timestamp for the model update. 75 */ 76 static void computeModelUpdate(struct OverTempCal *over_temp_cal, 77 uint64_t timestamp_nanos); 78 79 /* 80 * Searches 'model_data' for the sensor offset estimate closest to the specified 81 * temperature. Sets the 'nearest_offset' pointer to the result. 82 */ 83 static void findNearestEstimate(struct OverTempCal *over_temp_cal, 84 float temperature_celsius); 85 86 /* 87 * Removes the "old" offset estimates from 'model_data' (i.e., eliminates the 88 * drift-compromised data). 89 */ 90 static void removeStaleModelData(struct OverTempCal *over_temp_cal, 91 uint64_t timestamp_nanos); 92 93 /* 94 * Removes the offset estimates from 'model_data' at index, 'model_index'. 95 * Returns 'true' if data was removed. 96 */ 97 static bool removeModelDataByIndex(struct OverTempCal *over_temp_cal, 98 size_t model_index); 99 100 /* 101 * Since it may take a while for an empty model to build up enough data to start 102 * producing new model parameter updates, the model collection can be 103 * jump-started by using the new model parameters to insert "fake" data in place 104 * of actual sensor offset data. The new model data 'offset_age_nanos' is set to 105 * zero. 106 */ 107 static bool jumpStartModelData(struct OverTempCal *over_temp_cal); 108 109 /* 110 * Computes a new model fit and provides updated model parameters for the 111 * over-temperature model data. Uses a simple weighting function determined from 112 * the age of the model data. 113 * 114 * INPUTS: 115 * over_temp_cal: Over-temp data structure. 116 * OUTPUTS: 117 * temp_sensitivity: Updated modeled temperature sensitivity (array). 118 * sensor_intercept: Updated model intercept (array). 119 * 120 * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z. 121 * 122 * Reference: Press, William H. "15.2 Fitting Data to a Straight Line." 123 * Numerical Recipes: The Art of Scientific Computing. Cambridge, 1992. 124 */ 125 static void updateModel(const struct OverTempCal *over_temp_cal, 126 float *temp_sensitivity, float *sensor_intercept); 127 128 /* 129 * Computes a new over-temperature compensated offset estimate based on the 130 * temperature specified by, 'temperature_celsius'. 131 * 132 * INPUTS: 133 * over_temp_cal: Over-temp data structure. 134 * timestamp_nanos: The current system timestamp. 135 * temperature_celsius: The sensor temperature to compensate the offset for. 136 */ 137 static void updateCalOffset(struct OverTempCal *over_temp_cal, 138 uint64_t timestamp_nanos, 139 float temperature_celsius); 140 141 /* 142 * Sets the new over-temperature compensated offset estimate vector and 143 * timestamp. 144 * 145 * INPUTS: 146 * over_temp_cal: Over-temp data structure. 147 * compensated_offset: The new temperature compensated offset array. 148 * timestamp_nanos: The current system timestamp. 149 * temperature_celsius: The sensor temperature to compensate the offset for. 150 */ 151 static void setCompensatedOffset(struct OverTempCal *over_temp_cal, 152 const float *compensated_offset, 153 uint64_t timestamp_nanos, 154 float temperature_celsius); 155 156 /* 157 * Checks new offset estimates to determine if they could be an outlier that 158 * should be rejected. Operates on a per-axis basis determined by 'axis_index'. 159 * 160 * INPUTS: 161 * over_temp_cal: Over-temp data structure. 162 * offset: Offset array. 163 * axis_index: Index of the axis to check (0=x, 1=y, 2=z). 164 * 165 * Returns 'true' if the deviation of the offset value from the linear model 166 * exceeds 'outlier_limit'. 167 */ 168 static bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset, 169 size_t axis_index, float temperature_celsius); 170 171 // Sets the OTC model parameters to an "initialized" state. 172 static void resetOtcLinearModel(struct OverTempCal *over_temp_cal); 173 174 // Checks that the input temperature value is within the valid range. If outside 175 // of range, then 'temperature_celsius' is coerced to within the limits. 176 static bool checkAndEnforceTemperatureRange(float *temperature_celsius); 177 178 // Returns "true" if the candidate linear model parameters are within the valid 179 // range, and not all zeros. 180 static bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal, 181 float temp_sensitivity, 182 float sensor_intercept); 183 184 // Returns "true" if 'offset' and 'offset_temp_celsius' is valid. 185 static bool isValidOtcOffset(const float *offset, float offset_temp_celsius); 186 187 // Returns the least-squares weight based on the age of a particular offset 188 // estimate. 189 static float evaluateWeightingFunction(const struct OverTempCal *over_temp_cal, 190 uint64_t offset_age_nanos); 191 192 // Computes the age increment, adds it to the age of each OTC model data point, 193 // and resets the age update counter. 194 static void modelDataSetAgeUpdate(struct OverTempCal *over_temp_cal, 195 uint64_t timestamp_nanos); 196 197 // Updates 'compensated_offset' using the linear OTC model. 198 static void compensateWithLinearModel(struct OverTempCal *over_temp_cal, 199 uint64_t timestamp_nanos, 200 float temperature_celsius); 201 202 // Adds a linear extrapolated term to 'compensated_offset' (3-element array) 203 // based on the linear OTC model and 'delta_temp_celsius' (the difference 204 // between the current sensor temperature and the offset temperature associated 205 // with 'compensated_offset'). 206 static void addLinearTemperatureExtrapolation(struct OverTempCal *over_temp_cal, 207 float *compensated_offset, 208 float delta_temp_celsius); 209 210 // Provides an over-temperature compensated offset based on the 'estimate'. 211 static void compensateWithEstimate(struct OverTempCal *over_temp_cal, 212 uint64_t timestamp_nanos, 213 struct OverTempModelThreeAxis *estimate, 214 float temperature_celsius); 215 216 // Evaluates the nearest-temperature compensation (with linear extrapolation 217 // term due to temperature), and compares it with the compensation due to 218 // just the linear model when 'compare_with_linear_model' is true, otherwise 219 // the comparison will be made with an extrapolated version of the current 220 // compensation value. The comparison tests whether the nearest-temperature 221 // estimate deviates from the linear-model (or current-compensated) value by 222 // more than 'jump_tolerance'. If a "jump" is detected, then it keeps the 223 // linear-model (or current-compensated) value. 224 static void compareAndCompensateWithNearest(struct OverTempCal *over_temp_cal, 225 uint64_t timestamp_nanos, 226 float temperature_celsius, 227 bool compare_to_linear_model); 228 229 // Refreshes the OTC model to ensure that the most relevant model weighting is 230 // being used. 231 static void refreshOtcModel(struct OverTempCal *over_temp_cal, 232 uint64_t timestamp_nanos); 233 234 #ifdef OVERTEMPCAL_DBG_ENABLED 235 // This helper function stores all of the debug tracking information necessary 236 // for printing log messages. 237 static void updateDebugData(struct OverTempCal *over_temp_cal); 238 239 // Helper function that creates tag strings useful for identifying specific 240 // debug output data (embedded system friendly; not all systems have 'sprintf'). 241 // 'new_debug_tag' is any null-terminated string. Respect the total allowed 242 // length of the 'otc_debug_tag' string. 243 // Constructs: "[" + <otc_debug_tag> + <new_debug_tag> 244 // Example, 245 // otc_debug_tag = "OVER_TEMP_CAL" 246 // new_debug_tag = "INIT]" 247 // Output: "[OVER_TEMP_CAL:INIT]" 248 static void createDebugTag(struct OverTempCal *over_temp_cal, 249 const char *new_debug_tag); 250 #endif // OVERTEMPCAL_DBG_ENABLED 251 252 /////// FUNCTION DEFINITIONS ////////////////////////////////////////////////// 253 254 void overTempCalInit(struct OverTempCal *over_temp_cal, 255 const struct OverTempCalParameters *parameters) { 256 ASSERT_NOT_NULL(over_temp_cal); 257 258 // Clears OverTempCal memory. 259 memset(over_temp_cal, 0, sizeof(struct OverTempCal)); 260 261 // Initializes the pointers to important sensor offset estimates. 262 over_temp_cal->nearest_offset = &over_temp_cal->model_data[0]; 263 over_temp_cal->latest_offset = NULL; 264 265 // Initializes the OTC linear model parameters. 266 resetOtcLinearModel(over_temp_cal); 267 268 // Initializes the model identification parameters. 269 over_temp_cal->new_overtemp_model_available = false; 270 over_temp_cal->new_overtemp_offset_available = false; 271 over_temp_cal->min_num_model_pts = parameters->min_num_model_pts; 272 over_temp_cal->min_temp_update_period_nanos = 273 parameters->min_temp_update_period_nanos; 274 over_temp_cal->delta_temp_per_bin = parameters->delta_temp_per_bin; 275 over_temp_cal->jump_tolerance = parameters->jump_tolerance; 276 over_temp_cal->outlier_limit = parameters->outlier_limit; 277 over_temp_cal->age_limit_nanos = parameters->age_limit_nanos; 278 over_temp_cal->temp_sensitivity_limit = parameters->temp_sensitivity_limit; 279 over_temp_cal->sensor_intercept_limit = parameters->sensor_intercept_limit; 280 over_temp_cal->significant_offset_change = 281 parameters->significant_offset_change; 282 over_temp_cal->over_temp_enable = parameters->over_temp_enable; 283 284 // Initializes the over-temperature compensated offset temperature. 285 over_temp_cal->compensated_offset.offset_temp_celsius = 286 INVALID_TEMPERATURE_CELSIUS; 287 288 #ifdef OVERTEMPCAL_DBG_ENABLED 289 // Sets the default sensor descriptors for debugging. 290 overTempCalDebugDescriptors(over_temp_cal, "OVER_TEMP_CAL", "mDPS", 291 RAD_TO_MDEG); 292 293 createDebugTag(over_temp_cal, ":INIT]"); 294 if (over_temp_cal->over_temp_enable) { 295 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 296 "Over-temperature compensation ENABLED."); 297 } else { 298 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 299 "Over-temperature compensation DISABLED."); 300 } 301 #endif // OVERTEMPCAL_DBG_ENABLED 302 303 // Defines the default weighting function for the linear model fit routine. 304 overTempValidateAndSetWeight(over_temp_cal, 0, &kOtcDefaultWeight0); 305 overTempValidateAndSetWeight(over_temp_cal, 1, &kOtcDefaultWeight1); 306 } 307 308 void overTempCalSetModel(struct OverTempCal *over_temp_cal, const float *offset, 309 float offset_temp_celsius, uint64_t timestamp_nanos, 310 const float *temp_sensitivity, 311 const float *sensor_intercept, bool jump_start_model) { 312 ASSERT_NOT_NULL(over_temp_cal); 313 ASSERT_NOT_NULL(offset); 314 ASSERT_NOT_NULL(temp_sensitivity); 315 ASSERT_NOT_NULL(sensor_intercept); 316 317 // Initializes the OTC linear model parameters. 318 resetOtcLinearModel(over_temp_cal); 319 320 // Sets the model parameters if they are within the acceptable limits. 321 // Includes a check to reject input model parameters that may have been passed 322 // in as all zeros. 323 for (size_t i = 0; i < 3; i++) { 324 if (isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i], 325 sensor_intercept[i])) { 326 over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i]; 327 over_temp_cal->sensor_intercept[i] = sensor_intercept[i]; 328 } 329 } 330 331 // Model "Jump-Start". 332 const bool model_jump_started = 333 jump_start_model ? jumpStartModelData(over_temp_cal) : false; 334 335 if (!model_jump_started) { 336 // Checks that the new offset data is valid. 337 if (isValidOtcOffset(offset, offset_temp_celsius)) { 338 // Sets the initial over-temp calibration estimate. 339 memcpy(over_temp_cal->model_data[0].offset, offset, 340 sizeof(over_temp_cal->model_data[0].offset)); 341 over_temp_cal->model_data[0].offset_temp_celsius = offset_temp_celsius; 342 over_temp_cal->model_data[0].offset_age_nanos = 0; 343 over_temp_cal->num_model_pts = 1; 344 } else { 345 // No valid offset data to load. 346 over_temp_cal->num_model_pts = 0; 347 #ifdef OVERTEMPCAL_DBG_ENABLED 348 createDebugTag(over_temp_cal, ":RECALL]"); 349 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 350 "No valid sensor offset vector to load."); 351 #endif // OVERTEMPCAL_DBG_ENABLED 352 } 353 } 354 355 // If the new offset is valid, then it will be used as the current compensated 356 // offset, otherwise the current value will be kept. 357 if (isValidOtcOffset(offset, offset_temp_celsius)) { 358 memcpy(over_temp_cal->compensated_offset.offset, offset, 359 sizeof(over_temp_cal->compensated_offset.offset)); 360 over_temp_cal->compensated_offset.offset_temp_celsius = offset_temp_celsius; 361 over_temp_cal->compensated_offset.offset_age_nanos = 0; 362 } 363 364 // Resets the latest offset pointer. There are no new offset estimates to 365 // track yet. 366 over_temp_cal->latest_offset = NULL; 367 368 // Sets the model and offset update times to the current timestamp. 369 over_temp_cal->last_offset_update_nanos = timestamp_nanos; 370 over_temp_cal->last_model_update_nanos = timestamp_nanos; 371 372 #ifdef OVERTEMPCAL_DBG_ENABLED 373 // Prints the recalled model data. 374 createDebugTag(over_temp_cal, ":SET MODEL]"); 375 CAL_DEBUG_LOG( 376 over_temp_cal->otc_debug_tag, 377 "Offset|Temp [%s|C]: " CAL_FORMAT_3DIGITS_TRIPLET 378 " | " CAL_FORMAT_3DIGITS, 379 over_temp_cal->otc_unit_tag, 380 CAL_ENCODE_FLOAT(offset[0] * over_temp_cal->otc_unit_conversion, 3), 381 CAL_ENCODE_FLOAT(offset[1] * over_temp_cal->otc_unit_conversion, 3), 382 CAL_ENCODE_FLOAT(offset[2] * over_temp_cal->otc_unit_conversion, 3), 383 CAL_ENCODE_FLOAT(offset_temp_celsius, 3)); 384 385 CAL_DEBUG_LOG( 386 over_temp_cal->otc_debug_tag, 387 "Sensitivity|Intercept [%s/C|%s]: " CAL_FORMAT_3DIGITS_TRIPLET 388 " | " CAL_FORMAT_3DIGITS_TRIPLET, 389 over_temp_cal->otc_unit_tag, over_temp_cal->otc_unit_tag, 390 CAL_ENCODE_FLOAT(temp_sensitivity[0] * over_temp_cal->otc_unit_conversion, 391 3), 392 CAL_ENCODE_FLOAT(temp_sensitivity[1] * over_temp_cal->otc_unit_conversion, 393 3), 394 CAL_ENCODE_FLOAT(temp_sensitivity[2] * over_temp_cal->otc_unit_conversion, 395 3), 396 CAL_ENCODE_FLOAT(sensor_intercept[0] * over_temp_cal->otc_unit_conversion, 397 3), 398 CAL_ENCODE_FLOAT(sensor_intercept[1] * over_temp_cal->otc_unit_conversion, 399 3), 400 CAL_ENCODE_FLOAT(sensor_intercept[2] * over_temp_cal->otc_unit_conversion, 401 3)); 402 403 // Resets the debug print machine to ensure that updateDebugData() can 404 // produce a debug report and interupt any ongoing report. 405 over_temp_cal->debug_state = OTC_IDLE; 406 407 // Triggers a debug print out to view the new model parameters. 408 updateDebugData(over_temp_cal); 409 #endif // OVERTEMPCAL_DBG_ENABLED 410 } 411 412 void overTempCalGetModel(struct OverTempCal *over_temp_cal, float *offset, 413 float *offset_temp_celsius, uint64_t *timestamp_nanos, 414 float *temp_sensitivity, float *sensor_intercept) { 415 ASSERT_NOT_NULL(over_temp_cal); 416 ASSERT_NOT_NULL(offset); 417 ASSERT_NOT_NULL(offset_temp_celsius); 418 ASSERT_NOT_NULL(timestamp_nanos); 419 ASSERT_NOT_NULL(temp_sensitivity); 420 ASSERT_NOT_NULL(sensor_intercept); 421 422 // Gets the latest over-temp calibration model data. 423 memcpy(temp_sensitivity, over_temp_cal->temp_sensitivity, 424 sizeof(over_temp_cal->temp_sensitivity)); 425 memcpy(sensor_intercept, over_temp_cal->sensor_intercept, 426 sizeof(over_temp_cal->sensor_intercept)); 427 *timestamp_nanos = over_temp_cal->last_model_update_nanos; 428 429 // Gets the latest temperature compensated offset estimate. 430 overTempCalGetOffset(over_temp_cal, offset_temp_celsius, offset); 431 } 432 433 void overTempCalSetModelData(struct OverTempCal *over_temp_cal, 434 size_t data_length, uint64_t timestamp_nanos, 435 const struct OverTempModelThreeAxis *model_data) { 436 ASSERT_NOT_NULL(over_temp_cal); 437 ASSERT_NOT_NULL(model_data); 438 439 // Load only "good" data from the input 'model_data'. 440 over_temp_cal->num_model_pts = NANO_MIN(data_length, OTC_MODEL_SIZE); 441 size_t valid_data_count = 0; 442 for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) { 443 if (isValidOtcOffset(model_data[i].offset, 444 model_data[i].offset_temp_celsius)) { 445 memcpy(&over_temp_cal->model_data[i], &model_data[i], 446 sizeof(struct OverTempModelThreeAxis)); 447 valid_data_count++; 448 } 449 } 450 over_temp_cal->num_model_pts = valid_data_count; 451 452 // Initializes the OTC linear model parameters. 453 resetOtcLinearModel(over_temp_cal); 454 455 // Computes and replaces the model fit parameters. 456 computeModelUpdate(over_temp_cal, timestamp_nanos); 457 458 // Resets the latest offset pointer. There are no new offset estimates to 459 // track yet. 460 over_temp_cal->latest_offset = NULL; 461 462 // Searches for the sensor offset estimate closest to the current temperature. 463 findNearestEstimate(over_temp_cal, 464 over_temp_cal->compensated_offset.offset_temp_celsius); 465 466 // Updates the current over-temperature compensated offset estimate. 467 updateCalOffset(over_temp_cal, timestamp_nanos, 468 over_temp_cal->compensated_offset.offset_temp_celsius); 469 470 #ifdef OVERTEMPCAL_DBG_ENABLED 471 // Prints the updated model data. 472 createDebugTag(over_temp_cal, ":SET MODEL DATA SET]"); 473 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 474 "Over-temperature full model data set recalled."); 475 476 // Resets the debug print machine to ensure that a new debug report will 477 // interupt any ongoing report. 478 over_temp_cal->debug_state = OTC_IDLE; 479 480 // Triggers a log printout to show the updated sensor offset estimate. 481 updateDebugData(over_temp_cal); 482 #endif // OVERTEMPCAL_DBG_ENABLED 483 } 484 485 void overTempCalGetModelData(struct OverTempCal *over_temp_cal, 486 size_t *data_length, 487 struct OverTempModelThreeAxis *model_data) { 488 ASSERT_NOT_NULL(over_temp_cal); 489 *data_length = over_temp_cal->num_model_pts; 490 memcpy(model_data, over_temp_cal->model_data, 491 over_temp_cal->num_model_pts * sizeof(struct OverTempModelThreeAxis)); 492 } 493 494 void overTempCalGetOffset(struct OverTempCal *over_temp_cal, 495 float *compensated_offset_temperature_celsius, 496 float *compensated_offset) { 497 memcpy(compensated_offset, over_temp_cal->compensated_offset.offset, 498 sizeof(over_temp_cal->compensated_offset.offset)); 499 *compensated_offset_temperature_celsius = 500 over_temp_cal->compensated_offset.offset_temp_celsius; 501 } 502 503 void overTempCalRemoveOffset(struct OverTempCal *over_temp_cal, 504 uint64_t timestamp_nanos, float xi, float yi, 505 float zi, float *xo, float *yo, float *zo) { 506 ASSERT_NOT_NULL(over_temp_cal); 507 ASSERT_NOT_NULL(xo); 508 ASSERT_NOT_NULL(yo); 509 ASSERT_NOT_NULL(zo); 510 511 // Determines whether over-temp compensation will be applied. 512 if (over_temp_cal->over_temp_enable) { 513 // Removes the over-temperature compensated offset from the input sensor 514 // data. 515 *xo = xi - over_temp_cal->compensated_offset.offset[0]; 516 *yo = yi - over_temp_cal->compensated_offset.offset[1]; 517 *zo = zi - over_temp_cal->compensated_offset.offset[2]; 518 } else { 519 *xo = xi; 520 *yo = yi; 521 *zo = zi; 522 } 523 } 524 525 bool overTempCalNewModelUpdateAvailable(struct OverTempCal *over_temp_cal) { 526 ASSERT_NOT_NULL(over_temp_cal); 527 const bool update_available = over_temp_cal->new_overtemp_model_available && 528 over_temp_cal->over_temp_enable; 529 530 // The 'new_overtemp_model_available' flag is reset when it is read here. 531 over_temp_cal->new_overtemp_model_available = false; 532 533 return update_available; 534 } 535 536 bool overTempCalNewOffsetAvailable(struct OverTempCal *over_temp_cal) { 537 ASSERT_NOT_NULL(over_temp_cal); 538 const bool update_available = over_temp_cal->new_overtemp_offset_available && 539 over_temp_cal->over_temp_enable; 540 541 // The 'new_overtemp_offset_available' flag is reset when it is read here. 542 over_temp_cal->new_overtemp_offset_available = false; 543 544 return update_available; 545 } 546 547 void overTempCalUpdateSensorEstimate(struct OverTempCal *over_temp_cal, 548 uint64_t timestamp_nanos, 549 const float *offset, 550 float temperature_celsius) { 551 ASSERT_NOT_NULL(over_temp_cal); 552 ASSERT_NOT_NULL(offset); 553 ASSERT(over_temp_cal->delta_temp_per_bin > 0); 554 555 // Updates the age of each OTC model data point. 556 modelDataSetAgeUpdate(over_temp_cal, timestamp_nanos); 557 558 // Checks that the new offset data is valid, returns if bad. 559 if (!isValidOtcOffset(offset, temperature_celsius)) { 560 return; 561 } 562 563 // Prevent a divide by zero below. 564 if (over_temp_cal->delta_temp_per_bin <= 0) { 565 return; 566 } 567 568 // Ensures that the most relevant model weighting is being used. 569 refreshOtcModel(over_temp_cal, timestamp_nanos); 570 571 // Checks whether this offset estimate is a likely outlier. A limit is placed 572 // on 'num_outliers', the previous number of successive rejects, to prevent 573 // too many back-to-back rejections. 574 if (over_temp_cal->num_outliers < OTC_MAX_OUTLIER_COUNT) { 575 if (outlierCheck(over_temp_cal, offset, 0, temperature_celsius) || 576 outlierCheck(over_temp_cal, offset, 1, temperature_celsius) || 577 outlierCheck(over_temp_cal, offset, 2, temperature_celsius)) { 578 // Increments the count of rejected outliers. 579 over_temp_cal->num_outliers++; 580 581 #ifdef OVERTEMPCAL_DBG_ENABLED 582 createDebugTag(over_temp_cal, ":OUTLIER]"); 583 CAL_DEBUG_LOG( 584 over_temp_cal->otc_debug_tag, 585 "Offset|Temperature|Time [%s|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET 586 ", " CAL_FORMAT_3DIGITS ", %" PRIu64, 587 over_temp_cal->otc_unit_tag, 588 CAL_ENCODE_FLOAT(offset[0] * over_temp_cal->otc_unit_conversion, 3), 589 CAL_ENCODE_FLOAT(offset[1] * over_temp_cal->otc_unit_conversion, 3), 590 CAL_ENCODE_FLOAT(offset[2] * over_temp_cal->otc_unit_conversion, 3), 591 CAL_ENCODE_FLOAT(temperature_celsius, 3), timestamp_nanos); 592 #endif // OVERTEMPCAL_DBG_ENABLED 593 594 return; // Outlier detected: skips adding this offset to the model. 595 } else { 596 // Resets the count of rejected outliers. 597 over_temp_cal->num_outliers = 0; 598 } 599 } else { 600 // Resets the count of rejected outliers. 601 over_temp_cal->num_outliers = 0; 602 } 603 604 // Computes the temperature bin range data. 605 const int32_t bin_num = 606 CAL_FLOOR(temperature_celsius / over_temp_cal->delta_temp_per_bin); 607 const float temp_lo_check = bin_num * over_temp_cal->delta_temp_per_bin; 608 const float temp_hi_check = (bin_num + 1) * over_temp_cal->delta_temp_per_bin; 609 610 // The rules for accepting new offset estimates into the 'model_data' 611 // collection: 612 // 1) The temperature domain is divided into bins each spanning 613 // 'delta_temp_per_bin'. 614 // 2) Find and replace the i'th 'model_data' estimate data if: 615 // Let, bin_num = floor(temperature_celsius / delta_temp_per_bin) 616 // temp_lo_check = bin_num * delta_temp_per_bin 617 // temp_hi_check = (bin_num + 1) * delta_temp_per_bin 618 // Check condition: 619 // temp_lo_check <= model_data[i].offset_temp_celsius < temp_hi_check 620 bool replaced_one = false; 621 for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) { 622 if (over_temp_cal->model_data[i].offset_temp_celsius < temp_hi_check && 623 over_temp_cal->model_data[i].offset_temp_celsius >= temp_lo_check) { 624 // NOTE - The pointer to the new model data point is set here; the offset 625 // data is set below in the call to 'setLatestEstimate'. 626 over_temp_cal->latest_offset = &over_temp_cal->model_data[i]; 627 replaced_one = true; 628 break; 629 } 630 } 631 632 // NOTE - The pointer to the new model data point is set here; the offset 633 // data is set below in the call to 'setLatestEstimate'. 634 if (!replaced_one) { 635 if (over_temp_cal->num_model_pts < OTC_MODEL_SIZE) { 636 // 3) If nothing was replaced, and the 'model_data' buffer is not full 637 // then add the estimate data to the array. 638 over_temp_cal->latest_offset = 639 &over_temp_cal->model_data[over_temp_cal->num_model_pts]; 640 over_temp_cal->num_model_pts++; 641 } else { 642 // 4) Otherwise (nothing was replaced and buffer is full), replace the 643 // oldest data with the incoming one. 644 over_temp_cal->latest_offset = &over_temp_cal->model_data[0]; 645 for (size_t i = 1; i < over_temp_cal->num_model_pts; i++) { 646 if (over_temp_cal->latest_offset->offset_age_nanos < 647 over_temp_cal->model_data[i].offset_age_nanos) { 648 over_temp_cal->latest_offset = &over_temp_cal->model_data[i]; 649 } 650 } 651 } 652 } 653 654 // Updates the latest model estimate data. 655 setLatestEstimate(over_temp_cal, offset, temperature_celsius); 656 657 // The latest offset estimate is the nearest temperature offset. 658 over_temp_cal->nearest_offset = over_temp_cal->latest_offset; 659 660 // The rules for determining whether a new model fit is computed are: 661 // 1) A minimum number of data points must have been collected: 662 // num_model_pts >= min_num_model_pts 663 // NOTE: Collecting 'num_model_pts' and given that only one point is 664 // kept per temperature bin (spanning a thermal range specified by 665 // 'delta_temp_per_bin') implies that model data covers at least, 666 // model_temperature_span >= 'num_model_pts' * delta_temp_per_bin 667 // 2) ...shown in 'computeModelUpdate'. 668 if (over_temp_cal->num_model_pts >= over_temp_cal->min_num_model_pts) { 669 computeModelUpdate(over_temp_cal, timestamp_nanos); 670 } 671 672 // Updates the current over-temperature compensated offset estimate. 673 updateCalOffset(over_temp_cal, timestamp_nanos, temperature_celsius); 674 675 #ifdef OVERTEMPCAL_DBG_ENABLED 676 // Updates the total number of received sensor offset estimates. 677 over_temp_cal->debug_num_estimates++; 678 679 // Triggers a log printout to show the updated sensor offset estimate. 680 updateDebugData(over_temp_cal); 681 #endif // OVERTEMPCAL_DBG_ENABLED 682 } 683 684 void overTempCalSetTemperature(struct OverTempCal *over_temp_cal, 685 uint64_t timestamp_nanos, 686 float temperature_celsius) { 687 ASSERT_NOT_NULL(over_temp_cal); 688 689 #ifdef OVERTEMPCAL_DBG_ENABLED 690 #ifdef OVERTEMPCAL_DBG_LOG_TEMP 691 // Prints the sensor temperature trajectory for debugging purposes. This 692 // throttles the print statements (1Hz). 693 if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA( 694 timestamp_nanos, over_temp_cal->temperature_print_timer, 695 OTC_PRINT_TEMP_NANOS)) { 696 over_temp_cal->temperature_print_timer = 697 timestamp_nanos; // Starts the wait timer. 698 699 // Prints out temperature and the current timestamp. 700 createDebugTag(over_temp_cal, ":TEMP]"); 701 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 702 "Temperature|Time [C|nsec] = " CAL_FORMAT_3DIGITS 703 ", %" PRIu64, 704 CAL_ENCODE_FLOAT(temperature_celsius, 3), timestamp_nanos); 705 } 706 #endif // OVERTEMPCAL_DBG_LOG_TEMP 707 #endif // OVERTEMPCAL_DBG_ENABLED 708 709 // Updates the age of each OTC model data point. 710 if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA( 711 timestamp_nanos, over_temp_cal->last_age_update_nanos, 712 OTC_MODEL_AGE_UPDATE_NANOS)) { 713 modelDataSetAgeUpdate(over_temp_cal, timestamp_nanos); 714 } 715 716 // This check throttles new OTC offset compensation updates so that high data 717 // rate temperature samples do not cause excessive computational burden. Note, 718 // temperature sensor updates are expected to potentially increase the data 719 // processing load, however, computational load from new offset estimates is 720 // not a concern as they are a typically provided at a very low rate (< 1 Hz). 721 if (!NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA( 722 timestamp_nanos, over_temp_cal->last_offset_update_nanos, 723 over_temp_cal->min_temp_update_period_nanos)) { 724 return; // Time interval too short, skip further data processing. 725 } 726 727 // Checks that the offset temperature is within a valid range, saturates if 728 // outside. 729 checkAndEnforceTemperatureRange(&temperature_celsius); 730 731 // Searches for the sensor offset estimate closest to the current temperature 732 // when the temperature has changed by more than +/-10% of the 733 // 'delta_temp_per_bin'. 734 if (over_temp_cal->num_model_pts > 0) { 735 if (NANO_ABS(over_temp_cal->last_temp_check_celsius - temperature_celsius) > 736 0.1f * over_temp_cal->delta_temp_per_bin) { 737 findNearestEstimate(over_temp_cal, temperature_celsius); 738 over_temp_cal->last_temp_check_celsius = temperature_celsius; 739 } 740 } 741 742 // Updates the current over-temperature compensated offset estimate. 743 updateCalOffset(over_temp_cal, timestamp_nanos, temperature_celsius); 744 745 // Sets the OTC offset compensation time check. 746 over_temp_cal->last_offset_update_nanos = timestamp_nanos; 747 } 748 749 void overTempGetModelError(const struct OverTempCal *over_temp_cal, 750 const float *temp_sensitivity, 751 const float *sensor_intercept, float *max_error) { 752 ASSERT_NOT_NULL(over_temp_cal); 753 ASSERT_NOT_NULL(temp_sensitivity); 754 ASSERT_NOT_NULL(sensor_intercept); 755 ASSERT_NOT_NULL(max_error); 756 757 float max_error_test; 758 memset(max_error, 0, 3 * sizeof(float)); 759 760 for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) { 761 for (size_t j = 0; j < 3; j++) { 762 max_error_test = 763 NANO_ABS(over_temp_cal->model_data[i].offset[j] - 764 (temp_sensitivity[j] * 765 over_temp_cal->model_data[i].offset_temp_celsius + 766 sensor_intercept[j])); 767 if (max_error_test > max_error[j]) { 768 max_error[j] = max_error_test; 769 } 770 } 771 } 772 } 773 774 bool overTempValidateAndSetWeight( 775 struct OverTempCal *over_temp_cal, size_t index, 776 const struct OverTempCalWeight *new_otc_weight) { 777 ASSERT_NOT_NULL(over_temp_cal); 778 ASSERT_NOT_NULL(new_otc_weight); 779 780 // The input weighting coefficient must be positive. 781 if (new_otc_weight->weight <= 0.0f) { 782 #ifdef OVERTEMPCAL_DBG_ENABLED 783 createDebugTag(over_temp_cal, ":WEIGHT_FUNCTION]"); 784 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Invalid weight: Must > 0."); 785 #endif // OVERTEMPCAL_DBG_ENABLED 786 return false; 787 } 788 789 // Ensures that the 'index-1' weight's age is younger. 790 if (index == 0 || 791 over_temp_cal->weighting_function[index - 1].offset_age_nanos < 792 new_otc_weight->offset_age_nanos) { 793 over_temp_cal->weighting_function[index] = *new_otc_weight; 794 return true; 795 } 796 797 #ifdef OVERTEMPCAL_DBG_ENABLED 798 createDebugTag(over_temp_cal, ":WEIGHT_FUNCTION]"); 799 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Non monotonic weight age."); 800 #endif // OVERTEMPCAL_DBG_ENABLED 801 return false; 802 } 803 804 /////// LOCAL HELPER FUNCTION DEFINITIONS ///////////////////////////////////// 805 806 void compensateWithLinearModel(struct OverTempCal *over_temp_cal, 807 uint64_t timestamp_nanos, 808 float temperature_celsius) { 809 ASSERT_NOT_NULL(over_temp_cal); 810 811 // Defaults to using the current compensated offset value. 812 float compensated_offset[3]; 813 memcpy(compensated_offset, over_temp_cal->compensated_offset.offset, 814 sizeof(over_temp_cal->compensated_offset.offset)); 815 816 for (size_t index = 0; index < 3; index++) { 817 if (over_temp_cal->temp_sensitivity[index] < OTC_INITIAL_SENSITIVITY) { 818 // If a valid axis model is defined then the default compensation will 819 // use the linear model: 820 // compensated_offset = (temp_sensitivity * temperature + 821 // sensor_intercept) 822 compensated_offset[index] = 823 over_temp_cal->temp_sensitivity[index] * temperature_celsius + 824 over_temp_cal->sensor_intercept[index]; 825 } 826 } 827 828 // Sets the offset compensation vector, temperature, and timestamp. 829 setCompensatedOffset(over_temp_cal, compensated_offset, timestamp_nanos, 830 temperature_celsius); 831 } 832 833 void addLinearTemperatureExtrapolation(struct OverTempCal *over_temp_cal, 834 float *compensated_offset, 835 float delta_temp_celsius) { 836 ASSERT_NOT_NULL(over_temp_cal); 837 ASSERT_NOT_NULL(compensated_offset); 838 839 // Adds a delta term to the 'compensated_offset' using the temperature 840 // difference defined by 'delta_temp_celsius'. 841 for (size_t index = 0; index < 3; index++) { 842 if (over_temp_cal->temp_sensitivity[index] < OTC_INITIAL_SENSITIVITY) { 843 // If a valid axis model is defined, then use the linear model to assist 844 // with computing an extrapolated compensation term. 845 compensated_offset[index] += 846 over_temp_cal->temp_sensitivity[index] * delta_temp_celsius; 847 } 848 } 849 } 850 851 void compensateWithEstimate(struct OverTempCal *over_temp_cal, 852 uint64_t timestamp_nanos, 853 struct OverTempModelThreeAxis *estimate, 854 float temperature_celsius) { 855 ASSERT_NOT_NULL(over_temp_cal); 856 ASSERT_NOT_NULL(estimate); 857 858 // Uses the most recent offset estimate for offset compensation. 859 float compensated_offset[3]; 860 memcpy(compensated_offset, estimate->offset, sizeof(compensated_offset)); 861 862 // Checks that the offset temperature is valid. 863 if (estimate->offset_temp_celsius > INVALID_TEMPERATURE_CELSIUS) { 864 const float delta_temp_celsius = 865 temperature_celsius - estimate->offset_temp_celsius; 866 867 // Adds a delta term to the compensated offset using the temperature 868 // difference defined by 'delta_temp_celsius'. 869 addLinearTemperatureExtrapolation(over_temp_cal, compensated_offset, 870 delta_temp_celsius); 871 } 872 873 // Sets the offset compensation vector, temperature, and timestamp. 874 setCompensatedOffset(over_temp_cal, compensated_offset, timestamp_nanos, 875 temperature_celsius); 876 } 877 878 void compareAndCompensateWithNearest(struct OverTempCal *over_temp_cal, 879 uint64_t timestamp_nanos, 880 float temperature_celsius, 881 bool compare_to_linear_model) { 882 ASSERT_NOT_NULL(over_temp_cal); 883 ASSERT_NOT_NULL(over_temp_cal->nearest_offset); 884 885 // The default compensated offset is the nearest-temperature offset vector. 886 float compensated_offset[3]; 887 memcpy(compensated_offset, over_temp_cal->nearest_offset->offset, 888 sizeof(compensated_offset)); 889 const float compensated_offset_temperature_celsius = 890 over_temp_cal->nearest_offset->offset_temp_celsius; 891 892 for (size_t index = 0; index < 3; index++) { 893 if (over_temp_cal->temp_sensitivity[index] < OTC_INITIAL_SENSITIVITY) { 894 // If a valid axis model is defined, then use the linear model to assist 895 // with computing an extrapolated compensation term. 896 float delta_temp_celsius = 897 temperature_celsius - compensated_offset_temperature_celsius; 898 compensated_offset[index] += 899 over_temp_cal->temp_sensitivity[index] * delta_temp_celsius; 900 901 // Computes the test offset (based on the linear model or current offset). 902 float test_offset; 903 if (compare_to_linear_model) { 904 test_offset = 905 over_temp_cal->temp_sensitivity[index] * temperature_celsius + 906 over_temp_cal->sensor_intercept[index]; 907 } else { 908 // Adds a delta term to the compensated offset using the temperature 909 // difference defined by 'delta_temp_celsius'. 910 if (over_temp_cal->compensated_offset.offset_temp_celsius <= 911 INVALID_TEMPERATURE_CELSIUS) { 912 // If temperature is invalid, then skip further processing. 913 break; 914 } 915 delta_temp_celsius = 916 temperature_celsius - 917 over_temp_cal->compensated_offset.offset_temp_celsius; 918 test_offset = 919 over_temp_cal->compensated_offset.offset[index] + 920 over_temp_cal->temp_sensitivity[index] * delta_temp_celsius; 921 } 922 923 // Checks for "jumps" in the candidate compensated offset. If detected, 924 // then 'test_offset' is used for the offset update. 925 if (NANO_ABS(test_offset - compensated_offset[index]) >= 926 over_temp_cal->jump_tolerance) { 927 compensated_offset[index] = test_offset; 928 } 929 } 930 } 931 932 // Sets the offset compensation vector, temperature, and timestamp. 933 setCompensatedOffset(over_temp_cal, compensated_offset, timestamp_nanos, 934 temperature_celsius); 935 } 936 937 void updateCalOffset(struct OverTempCal *over_temp_cal, 938 uint64_t timestamp_nanos, float temperature_celsius) { 939 ASSERT_NOT_NULL(over_temp_cal); 940 941 // If 'temperature_celsius' is invalid, then no changes to the compensated 942 // offset are computed. 943 if (temperature_celsius <= INVALID_TEMPERATURE_CELSIUS) { 944 return; 945 } 946 947 // Removes very old data from the collected model estimates (i.e., 948 // eliminates drift-compromised data). Only does this when there is more 949 // than one estimate in the model (i.e., don't want to remove all data, even 950 // if it is very old [something is likely better than nothing]). 951 if ((timestamp_nanos >= 952 OTC_STALE_CHECK_TIME_NANOS + over_temp_cal->stale_data_timer_nanos) && 953 over_temp_cal->num_model_pts > 1) { 954 over_temp_cal->stale_data_timer_nanos = timestamp_nanos; // Resets timer. 955 removeStaleModelData(over_temp_cal, timestamp_nanos); 956 } 957 958 // Ensures that the most relevant model weighting is being used. 959 refreshOtcModel(over_temp_cal, timestamp_nanos); 960 961 // --------------------------------------------------------------------------- 962 // The following boolean expressions help determine how OTC offset updates 963 // are computed below. 964 965 // The nearest-temperature offset estimate is valid if the model data set is 966 // not empty. 967 const bool model_points_available = (over_temp_cal->num_model_pts > 0); 968 969 // To properly evaluate the logic paths that use the latest and nearest offset 970 // data below, the current age of the nearest and latest offset estimates are 971 // computed. 972 uint64_t latest_offset_age_nanos = 0; 973 if (over_temp_cal->latest_offset != NULL) { 974 latest_offset_age_nanos = 975 (over_temp_cal->last_age_update_nanos < timestamp_nanos) 976 ? over_temp_cal->latest_offset->offset_age_nanos + 977 timestamp_nanos - over_temp_cal->last_age_update_nanos 978 : over_temp_cal->latest_offset->offset_age_nanos; 979 } 980 981 uint64_t nearest_offset_age_nanos = 0; 982 if (over_temp_cal->nearest_offset != NULL) { 983 nearest_offset_age_nanos = 984 (over_temp_cal->last_age_update_nanos < timestamp_nanos) 985 ? over_temp_cal->nearest_offset->offset_age_nanos + 986 timestamp_nanos - over_temp_cal->last_age_update_nanos 987 : over_temp_cal->nearest_offset->offset_age_nanos; 988 } 989 990 // True when the latest offset estimate will be used to compute a sensor 991 // offset calibration estimate. 992 const bool use_latest_offset_compensation = 993 over_temp_cal->latest_offset != NULL && model_points_available && 994 latest_offset_age_nanos <= OTC_USE_RECENT_OFFSET_TIME_NANOS; 995 996 // True when the conditions are met to use the nearest-temperature offset to 997 // compute a sensor offset calibration estimate. 998 // The nearest-temperature offset: 999 // i. Must be defined. 1000 // ii. Offset temperature must be within a small neighborhood of the 1001 // current measured temperature (+/- 'delta_temp_per_bin'). 1002 const bool can_compensate_with_nearest = 1003 model_points_available && over_temp_cal->nearest_offset != NULL && 1004 NANO_ABS(temperature_celsius - 1005 over_temp_cal->nearest_offset->offset_temp_celsius) < 1006 over_temp_cal->delta_temp_per_bin; 1007 1008 // True if the last received sensor offset estimate is old or non-existent. 1009 const bool latest_model_point_not_relevant = 1010 (over_temp_cal->latest_offset == NULL) || 1011 (over_temp_cal->latest_offset != NULL && 1012 latest_offset_age_nanos >= OTC_OFFSET_IS_STALE_NANOS); 1013 1014 // True if the nearest-temperature offset estimate is old or non-existent. 1015 const bool nearest_model_point_not_relevant = 1016 (over_temp_cal->nearest_offset == NULL) || 1017 (over_temp_cal->nearest_offset != NULL && 1018 nearest_offset_age_nanos >= OTC_OFFSET_IS_STALE_NANOS); 1019 1020 // --------------------------------------------------------------------------- 1021 // The following conditional expressions govern new OTC offset updates. 1022 1023 if (!model_points_available) { 1024 // Computes the compensation using just the linear model if available, 1025 // otherwise the current compensated offset vector will be kept. 1026 compensateWithLinearModel(over_temp_cal, timestamp_nanos, 1027 temperature_celsius); 1028 return; // no further calculations, exit early. 1029 } 1030 1031 if (use_latest_offset_compensation) { 1032 // Computes the compensation using the latest received offset estimate plus 1033 // a term based on linear extrapolation from the offset temperature to the 1034 // current measured temperature (if a linear model is defined). 1035 compensateWithEstimate(over_temp_cal, timestamp_nanos, 1036 over_temp_cal->latest_offset, temperature_celsius); 1037 return; // no further calculations, exit early. 1038 } 1039 1040 if (can_compensate_with_nearest) { 1041 // Evaluates the nearest-temperature compensation (with a linear 1042 // extrapolation term), and compares it with the compensation due to just 1043 // the linear model, when 'compare_with_linear_model' is true. Otherwise, 1044 // the comparison will be made with an extrapolated version of the current 1045 // compensation value. The comparison determines whether the 1046 // nearest-temperature estimate deviates from the linear-model (or 1047 // current-compensated) value by more than 'jump_tolerance'. If a "jump" is 1048 // detected, then it keeps the linear-model (or current-compensated) value. 1049 const bool compare_with_linear_model = nearest_model_point_not_relevant; 1050 compareAndCompensateWithNearest(over_temp_cal, timestamp_nanos, 1051 temperature_celsius, 1052 compare_with_linear_model); 1053 } else { 1054 if (latest_model_point_not_relevant) { 1055 // If the nearest-temperature offset can't be used for compensation and 1056 // the latest offset is stale (in this case, the overall model trend may 1057 // be more useful for compensation than extending the most recent vector), 1058 // then this resorts to using only the linear model (if defined). 1059 compensateWithLinearModel(over_temp_cal, timestamp_nanos, 1060 temperature_celsius); 1061 } else { 1062 // If the nearest-temperature offset can't be used for compensation and 1063 // the latest offset is fairly recent, then the compensated offset is 1064 // based on the linear extrapolation of the current compensation vector. 1065 compensateWithEstimate(over_temp_cal, timestamp_nanos, 1066 &over_temp_cal->compensated_offset, 1067 temperature_celsius); 1068 } 1069 } 1070 } 1071 1072 void setCompensatedOffset(struct OverTempCal *over_temp_cal, 1073 const float *compensated_offset, 1074 uint64_t timestamp_nanos, float temperature_celsius) { 1075 ASSERT_NOT_NULL(over_temp_cal); 1076 ASSERT_NOT_NULL(compensated_offset); 1077 1078 // If the 'compensated_offset' value has changed significantly, then set 1079 // 'new_overtemp_offset_available' true. 1080 bool new_overtemp_offset_available = false; 1081 for (size_t i = 0; i < 3; i++) { 1082 if (NANO_ABS(over_temp_cal->compensated_offset.offset[i] - 1083 compensated_offset[i]) >= 1084 over_temp_cal->significant_offset_change) { 1085 new_overtemp_offset_available |= true; 1086 break; 1087 } 1088 } 1089 over_temp_cal->new_overtemp_offset_available |= new_overtemp_offset_available; 1090 1091 // If the offset has changed significantly, then the offset compensation 1092 // vector is updated. 1093 if (new_overtemp_offset_available) { 1094 memcpy(over_temp_cal->compensated_offset.offset, compensated_offset, 1095 sizeof(over_temp_cal->compensated_offset.offset)); 1096 over_temp_cal->compensated_offset.offset_temp_celsius = temperature_celsius; 1097 } 1098 } 1099 1100 void setLatestEstimate(struct OverTempCal *over_temp_cal, const float *offset, 1101 float offset_temp_celsius) { 1102 ASSERT_NOT_NULL(over_temp_cal); 1103 ASSERT_NOT_NULL(offset); 1104 1105 if (over_temp_cal->latest_offset != NULL) { 1106 // Sets the latest over-temp calibration estimate. 1107 memcpy(over_temp_cal->latest_offset->offset, offset, 1108 sizeof(over_temp_cal->latest_offset->offset)); 1109 over_temp_cal->latest_offset->offset_temp_celsius = offset_temp_celsius; 1110 over_temp_cal->latest_offset->offset_age_nanos = 0; 1111 } 1112 } 1113 1114 void refreshOtcModel(struct OverTempCal *over_temp_cal, 1115 uint64_t timestamp_nanos) { 1116 ASSERT_NOT_NULL(over_temp_cal); 1117 if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA( 1118 timestamp_nanos, over_temp_cal->last_model_update_nanos, 1119 OTC_REFRESH_MODEL_NANOS)) { 1120 // Checks the time since the last computed model and recalculates the model 1121 // if necessary. This ensures that waking up after a long period of time 1122 // allows the properly weighted OTC model to be used. As the estimates age, 1123 // the weighting will become more uniform and the model will fit the whole 1124 // set uniformly as a better approximation to the expected temperature 1125 // sensitivity; Younger estimates will fit tighter to emphasize a more 1126 // localized fit of the temp sensitivity function. 1127 computeModelUpdate(over_temp_cal, timestamp_nanos); 1128 over_temp_cal->last_model_update_nanos = timestamp_nanos; 1129 } 1130 } 1131 1132 void computeModelUpdate(struct OverTempCal *over_temp_cal, 1133 uint64_t timestamp_nanos) { 1134 ASSERT_NOT_NULL(over_temp_cal); 1135 1136 // Ensures that the minimum number of points required for a model fit has been 1137 // satisfied. 1138 if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts) return; 1139 1140 // Updates the linear model fit. 1141 float temp_sensitivity[3]; 1142 float sensor_intercept[3]; 1143 updateModel(over_temp_cal, temp_sensitivity, sensor_intercept); 1144 1145 // 2) A new set of model parameters are accepted if: 1146 // i. The model fit parameters must be within certain absolute bounds: 1147 // a. |temp_sensitivity| < temp_sensitivity_limit 1148 // b. |sensor_intercept| < sensor_intercept_limit 1149 // NOTE: Model parameter updates are not qualified against model fit error 1150 // here to protect against the case where there is large change in the 1151 // temperature characteristic either during runtime (e.g., temperature 1152 // conditioning due to hysteresis) or as a result of loading a poor model data 1153 // set. Otherwise, a lockout condition could occur where the entire model 1154 // data set would need to be replaced in order to bring the model fit error 1155 // below the error limit and allow a successful model update. 1156 bool updated_one = false; 1157 for (size_t i = 0; i < 3; i++) { 1158 if (isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i], 1159 sensor_intercept[i])) { 1160 over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i]; 1161 over_temp_cal->sensor_intercept[i] = sensor_intercept[i]; 1162 updated_one = true; 1163 } else { 1164 #ifdef OVERTEMPCAL_DBG_ENABLED 1165 createDebugTag(over_temp_cal, ":REJECT]"); 1166 CAL_DEBUG_LOG( 1167 over_temp_cal->otc_debug_tag, 1168 "%c-Axis Parameters|Time [%s/C|%s|nsec]: " CAL_FORMAT_3DIGITS 1169 ", " CAL_FORMAT_3DIGITS ", %" PRIu64, 1170 kDebugAxisLabel[i], over_temp_cal->otc_unit_tag, 1171 over_temp_cal->otc_unit_tag, 1172 CAL_ENCODE_FLOAT( 1173 temp_sensitivity[i] * over_temp_cal->otc_unit_conversion, 3), 1174 CAL_ENCODE_FLOAT( 1175 sensor_intercept[i] * over_temp_cal->otc_unit_conversion, 3), 1176 timestamp_nanos); 1177 #endif // OVERTEMPCAL_DBG_ENABLED 1178 } 1179 } 1180 1181 // If at least one axis updated, then consider this a valid model update. 1182 if (updated_one) { 1183 // Resets the OTC model compensation update time and sets the update flag. 1184 over_temp_cal->last_model_update_nanos = timestamp_nanos; 1185 over_temp_cal->new_overtemp_model_available = true; 1186 1187 #ifdef OVERTEMPCAL_DBG_ENABLED 1188 // Updates the total number of model updates. 1189 over_temp_cal->debug_num_model_updates++; 1190 #endif // OVERTEMPCAL_DBG_ENABLED 1191 } 1192 } 1193 1194 void findNearestEstimate(struct OverTempCal *over_temp_cal, 1195 float temperature_celsius) { 1196 ASSERT_NOT_NULL(over_temp_cal); 1197 1198 // If 'temperature_celsius' is invalid, then do not search. 1199 if (temperature_celsius <= INVALID_TEMPERATURE_CELSIUS) { 1200 return; 1201 } 1202 1203 // Performs a brute force search for the estimate nearest 1204 // 'temperature_celsius'. 1205 float dtemp_new = 0.0f; 1206 float dtemp_old = FLT_MAX; 1207 over_temp_cal->nearest_offset = &over_temp_cal->model_data[0]; 1208 for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) { 1209 dtemp_new = NANO_ABS(over_temp_cal->model_data[i].offset_temp_celsius - 1210 temperature_celsius); 1211 if (dtemp_new < dtemp_old) { 1212 over_temp_cal->nearest_offset = &over_temp_cal->model_data[i]; 1213 dtemp_old = dtemp_new; 1214 } 1215 } 1216 } 1217 1218 void removeStaleModelData(struct OverTempCal *over_temp_cal, 1219 uint64_t timestamp_nanos) { 1220 ASSERT_NOT_NULL(over_temp_cal); 1221 1222 bool removed_one = false; 1223 for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) { 1224 if (over_temp_cal->model_data[i].offset_age_nanos >= 1225 over_temp_cal->age_limit_nanos) { 1226 // If the latest offset was removed, then indicate this by setting it to 1227 // NULL. 1228 if (over_temp_cal->latest_offset == &over_temp_cal->model_data[i]) { 1229 over_temp_cal->latest_offset = NULL; 1230 } 1231 removed_one |= removeModelDataByIndex(over_temp_cal, i); 1232 } 1233 } 1234 1235 if (removed_one) { 1236 // If anything was removed, then this attempts to recompute the model. 1237 computeModelUpdate(over_temp_cal, timestamp_nanos); 1238 1239 // Searches for the sensor offset estimate closest to the current 1240 // temperature. 1241 findNearestEstimate(over_temp_cal, 1242 over_temp_cal->compensated_offset.offset_temp_celsius); 1243 } 1244 } 1245 1246 bool removeModelDataByIndex(struct OverTempCal *over_temp_cal, 1247 size_t model_index) { 1248 ASSERT_NOT_NULL(over_temp_cal); 1249 1250 // This function will not remove all of the model data. At least one model 1251 // sample will be left. 1252 if (over_temp_cal->num_model_pts <= 1) { 1253 return false; 1254 } 1255 1256 #ifdef OVERTEMPCAL_DBG_ENABLED 1257 createDebugTag(over_temp_cal, ":REMOVE]"); 1258 CAL_DEBUG_LOG( 1259 over_temp_cal->otc_debug_tag, 1260 "Offset|Temp|Age [%s|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET 1261 ", " CAL_FORMAT_3DIGITS ", %" PRIu64, 1262 over_temp_cal->otc_unit_tag, 1263 CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[0] * 1264 over_temp_cal->otc_unit_conversion, 1265 3), 1266 CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] * 1267 over_temp_cal->otc_unit_conversion, 1268 3), 1269 CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] * 1270 over_temp_cal->otc_unit_conversion, 1271 3), 1272 CAL_ENCODE_FLOAT( 1273 over_temp_cal->model_data[model_index].offset_temp_celsius, 3), 1274 over_temp_cal->model_data[model_index].offset_age_nanos); 1275 #endif // OVERTEMPCAL_DBG_ENABLED 1276 1277 // Remove the model data at 'model_index'. 1278 for (size_t i = model_index; i < over_temp_cal->num_model_pts - 1; i++) { 1279 memcpy(&over_temp_cal->model_data[i], &over_temp_cal->model_data[i + 1], 1280 sizeof(struct OverTempModelThreeAxis)); 1281 } 1282 over_temp_cal->num_model_pts--; 1283 1284 return true; 1285 } 1286 1287 bool jumpStartModelData(struct OverTempCal *over_temp_cal) { 1288 ASSERT_NOT_NULL(over_temp_cal); 1289 ASSERT(over_temp_cal->delta_temp_per_bin > 0); 1290 1291 // Prevent a divide by zero below. 1292 if (over_temp_cal->delta_temp_per_bin <= 0) { 1293 return false; 1294 } 1295 1296 // In normal operation the offset estimates enter into the 'model_data' array 1297 // complete (i.e., x, y, z values are all provided). Therefore, the jumpstart 1298 // data produced here requires that the model parameters have all been fully 1299 // defined and are all within the valid range. 1300 for (size_t i = 0; i < 3; i++) { 1301 if (!isValidOtcLinearModel(over_temp_cal, 1302 over_temp_cal->temp_sensitivity[i], 1303 over_temp_cal->sensor_intercept[i])) { 1304 return false; 1305 } 1306 } 1307 1308 // Any pre-existing model data points will be overwritten. 1309 over_temp_cal->num_model_pts = 0; 1310 1311 // This defines the minimum contiguous set of points to allow a model update 1312 // when the next offset estimate is received. They are placed at a common 1313 // temperature range that is likely to get replaced with actual data soon. 1314 const int32_t start_bin_num = CAL_FLOOR(JUMPSTART_START_TEMP_CELSIUS / 1315 over_temp_cal->delta_temp_per_bin); 1316 float offset_temp_celsius = 1317 (start_bin_num + 0.5f) * over_temp_cal->delta_temp_per_bin; 1318 1319 for (size_t i = 0; i < over_temp_cal->min_num_model_pts; i++) { 1320 for (size_t j = 0; j < 3; j++) { 1321 over_temp_cal->model_data[i].offset[j] = 1322 over_temp_cal->temp_sensitivity[j] * offset_temp_celsius + 1323 over_temp_cal->sensor_intercept[j]; 1324 } 1325 over_temp_cal->model_data[i].offset_temp_celsius = offset_temp_celsius; 1326 over_temp_cal->model_data[i].offset_age_nanos = 0; 1327 1328 offset_temp_celsius += over_temp_cal->delta_temp_per_bin; 1329 over_temp_cal->num_model_pts++; 1330 } 1331 1332 #ifdef OVERTEMPCAL_DBG_ENABLED 1333 createDebugTag(over_temp_cal, ":INIT]"); 1334 if (over_temp_cal->num_model_pts > 0) { 1335 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 1336 "Model Jump-Start: #Points = %zu.", 1337 over_temp_cal->num_model_pts); 1338 } 1339 #endif // OVERTEMPCAL_DBG_ENABLED 1340 1341 return (over_temp_cal->num_model_pts > 0); 1342 } 1343 1344 void updateModel(const struct OverTempCal *over_temp_cal, 1345 float *temp_sensitivity, float *sensor_intercept) { 1346 ASSERT_NOT_NULL(over_temp_cal); 1347 ASSERT_NOT_NULL(temp_sensitivity); 1348 ASSERT_NOT_NULL(sensor_intercept); 1349 ASSERT(over_temp_cal->num_model_pts > 0); 1350 1351 float sw = 0.0f; 1352 float st = 0.0f, stt = 0.0f; 1353 float sx = 0.0f, stsx = 0.0f; 1354 float sy = 0.0f, stsy = 0.0f; 1355 float sz = 0.0f, stsz = 0.0f; 1356 float weight = 1.0f; 1357 1358 // First pass computes the weighted mean values. 1359 const size_t n = over_temp_cal->num_model_pts; 1360 for (size_t i = 0; i < n; ++i) { 1361 weight = evaluateWeightingFunction( 1362 over_temp_cal, over_temp_cal->model_data[i].offset_age_nanos); 1363 1364 sw += weight; 1365 st += over_temp_cal->model_data[i].offset_temp_celsius * weight; 1366 sx += over_temp_cal->model_data[i].offset[0] * weight; 1367 sy += over_temp_cal->model_data[i].offset[1] * weight; 1368 sz += over_temp_cal->model_data[i].offset[2] * weight; 1369 } 1370 1371 // Second pass computes the mean corrected second moment values. 1372 ASSERT(sw > 0.0f); 1373 const float inv_sw = 1.0f / sw; 1374 for (size_t i = 0; i < n; ++i) { 1375 weight = evaluateWeightingFunction( 1376 over_temp_cal, over_temp_cal->model_data[i].offset_age_nanos); 1377 1378 const float t = 1379 over_temp_cal->model_data[i].offset_temp_celsius - st * inv_sw; 1380 stt += weight * t * t; 1381 stsx += t * over_temp_cal->model_data[i].offset[0] * weight; 1382 stsy += t * over_temp_cal->model_data[i].offset[1] * weight; 1383 stsz += t * over_temp_cal->model_data[i].offset[2] * weight; 1384 } 1385 1386 // Calculates the linear model fit parameters. 1387 ASSERT(stt > 0.0f); 1388 const float inv_stt = 1.0f / stt; 1389 temp_sensitivity[0] = stsx * inv_stt; 1390 sensor_intercept[0] = (sx - st * temp_sensitivity[0]) * inv_sw; 1391 temp_sensitivity[1] = stsy * inv_stt; 1392 sensor_intercept[1] = (sy - st * temp_sensitivity[1]) * inv_sw; 1393 temp_sensitivity[2] = stsz * inv_stt; 1394 sensor_intercept[2] = (sz - st * temp_sensitivity[2]) * inv_sw; 1395 } 1396 1397 bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset, 1398 size_t axis_index, float temperature_celsius) { 1399 ASSERT_NOT_NULL(over_temp_cal); 1400 ASSERT_NOT_NULL(offset); 1401 1402 // If a model has been defined, then check to see if this offset could be a 1403 // potential outlier: 1404 if (over_temp_cal->temp_sensitivity[axis_index] < OTC_INITIAL_SENSITIVITY) { 1405 const float outlier_test = NANO_ABS( 1406 offset[axis_index] - 1407 (over_temp_cal->temp_sensitivity[axis_index] * temperature_celsius + 1408 over_temp_cal->sensor_intercept[axis_index])); 1409 1410 if (outlier_test > over_temp_cal->outlier_limit) { 1411 return true; 1412 } 1413 } 1414 1415 return false; 1416 } 1417 1418 void resetOtcLinearModel(struct OverTempCal *over_temp_cal) { 1419 ASSERT_NOT_NULL(over_temp_cal); 1420 1421 // Sets the temperature sensitivity model parameters to 1422 // OTC_INITIAL_SENSITIVITY to indicate that the model is in an "initial" 1423 // state. 1424 over_temp_cal->temp_sensitivity[0] = OTC_INITIAL_SENSITIVITY; 1425 over_temp_cal->temp_sensitivity[1] = OTC_INITIAL_SENSITIVITY; 1426 over_temp_cal->temp_sensitivity[2] = OTC_INITIAL_SENSITIVITY; 1427 memset(over_temp_cal->sensor_intercept, 0, 1428 sizeof(over_temp_cal->sensor_intercept)); 1429 } 1430 1431 bool checkAndEnforceTemperatureRange(float *temperature_celsius) { 1432 if (*temperature_celsius > OTC_TEMP_MAX_CELSIUS) { 1433 *temperature_celsius = OTC_TEMP_MAX_CELSIUS; 1434 return false; 1435 } 1436 if (*temperature_celsius < OTC_TEMP_MIN_CELSIUS) { 1437 *temperature_celsius = OTC_TEMP_MIN_CELSIUS; 1438 return false; 1439 } 1440 return true; 1441 } 1442 1443 bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal, 1444 float temp_sensitivity, float sensor_intercept) { 1445 ASSERT_NOT_NULL(over_temp_cal); 1446 1447 return NANO_ABS(temp_sensitivity) < over_temp_cal->temp_sensitivity_limit && 1448 NANO_ABS(sensor_intercept) < over_temp_cal->sensor_intercept_limit && 1449 NANO_ABS(temp_sensitivity) > OTC_MODELDATA_NEAR_ZERO_TOL && 1450 NANO_ABS(sensor_intercept) > OTC_MODELDATA_NEAR_ZERO_TOL; 1451 } 1452 1453 bool isValidOtcOffset(const float *offset, float offset_temp_celsius) { 1454 ASSERT_NOT_NULL(offset); 1455 1456 // Simple check to ensure that: 1457 // 1. All of the input data is non "zero". 1458 // 2. The offset temperature is within the valid range. 1459 if (NANO_ABS(offset[0]) < OTC_MODELDATA_NEAR_ZERO_TOL && 1460 NANO_ABS(offset[1]) < OTC_MODELDATA_NEAR_ZERO_TOL && 1461 NANO_ABS(offset[2]) < OTC_MODELDATA_NEAR_ZERO_TOL && 1462 NANO_ABS(offset_temp_celsius) < OTC_MODELDATA_NEAR_ZERO_TOL) { 1463 return false; 1464 } 1465 1466 // Only returns the "check" result. Don't care about coercion. 1467 return checkAndEnforceTemperatureRange(&offset_temp_celsius); 1468 } 1469 1470 float evaluateWeightingFunction(const struct OverTempCal *over_temp_cal, 1471 uint64_t offset_age_nanos) { 1472 ASSERT_NOT_NULL(over_temp_cal); 1473 for (size_t i = 0; i < OTC_NUM_WEIGHT_LEVELS; i++) { 1474 if (offset_age_nanos <= 1475 over_temp_cal->weighting_function[i].offset_age_nanos) { 1476 return over_temp_cal->weighting_function[i].weight; 1477 } 1478 } 1479 1480 // Returning the default weight for all older offsets. 1481 return OTC_MIN_WEIGHT_VALUE; 1482 } 1483 1484 void modelDataSetAgeUpdate(struct OverTempCal *over_temp_cal, 1485 uint64_t timestamp_nanos) { 1486 ASSERT_NOT_NULL(over_temp_cal); 1487 if (over_temp_cal->last_age_update_nanos >= timestamp_nanos) { 1488 // Age updates must be monotonic. 1489 return; 1490 } 1491 1492 uint64_t age_increment_nanos = 1493 timestamp_nanos - over_temp_cal->last_age_update_nanos; 1494 1495 // Resets the age update counter. 1496 over_temp_cal->last_age_update_nanos = timestamp_nanos; 1497 1498 // Updates the model dataset ages. 1499 for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) { 1500 over_temp_cal->model_data[i].offset_age_nanos += age_increment_nanos; 1501 } 1502 } 1503 1504 /////// DEBUG FUNCTION DEFINITIONS //////////////////////////////////////////// 1505 1506 #ifdef OVERTEMPCAL_DBG_ENABLED 1507 void createDebugTag(struct OverTempCal *over_temp_cal, 1508 const char *new_debug_tag) { 1509 over_temp_cal->otc_debug_tag[0] = '['; 1510 memcpy(over_temp_cal->otc_debug_tag + 1, over_temp_cal->otc_sensor_tag, 1511 strlen(over_temp_cal->otc_sensor_tag)); 1512 memcpy( 1513 over_temp_cal->otc_debug_tag + strlen(over_temp_cal->otc_sensor_tag) + 1, 1514 new_debug_tag, strlen(new_debug_tag) + 1); 1515 } 1516 1517 void updateDebugData(struct OverTempCal *over_temp_cal) { 1518 ASSERT_NOT_NULL(over_temp_cal); 1519 1520 // Only update this data if debug printing is not currently in progress 1521 // (i.e., don't want to risk overwriting debug information that is actively 1522 // being reported). 1523 if (over_temp_cal->debug_state != OTC_IDLE) { 1524 return; 1525 } 1526 1527 // Triggers a debug log printout. 1528 over_temp_cal->debug_print_trigger = true; 1529 1530 // Initializes the debug data structure. 1531 memset(&over_temp_cal->debug_overtempcal, 0, sizeof(struct DebugOverTempCal)); 1532 1533 // Copies over the relevant data. 1534 for (size_t i = 0; i < 3; i++) { 1535 if (isValidOtcLinearModel(over_temp_cal, over_temp_cal->temp_sensitivity[i], 1536 over_temp_cal->sensor_intercept[i])) { 1537 over_temp_cal->debug_overtempcal.temp_sensitivity[i] = 1538 over_temp_cal->temp_sensitivity[i]; 1539 over_temp_cal->debug_overtempcal.sensor_intercept[i] = 1540 over_temp_cal->sensor_intercept[i]; 1541 } else { 1542 // If the model is not valid then just set the debug information so that 1543 // zeros are printed. 1544 over_temp_cal->debug_overtempcal.temp_sensitivity[i] = 0.0f; 1545 over_temp_cal->debug_overtempcal.sensor_intercept[i] = 0.0f; 1546 } 1547 } 1548 1549 // If 'latest_offset' is defined the copy the data for debug printing. 1550 // Otherwise, the current compensated offset will be printed. 1551 if (over_temp_cal->latest_offset != NULL) { 1552 memcpy(&over_temp_cal->debug_overtempcal.latest_offset, 1553 over_temp_cal->latest_offset, sizeof(struct OverTempModelThreeAxis)); 1554 } else { 1555 memcpy(&over_temp_cal->debug_overtempcal.latest_offset, 1556 &over_temp_cal->compensated_offset, 1557 sizeof(struct OverTempModelThreeAxis)); 1558 } 1559 1560 // Total number of OTC model data points. 1561 over_temp_cal->debug_overtempcal.num_model_pts = over_temp_cal->num_model_pts; 1562 1563 // Computes the maximum error over all of the model data. 1564 overTempGetModelError(over_temp_cal, 1565 over_temp_cal->debug_overtempcal.temp_sensitivity, 1566 over_temp_cal->debug_overtempcal.sensor_intercept, 1567 over_temp_cal->debug_overtempcal.max_error); 1568 } 1569 1570 void overTempCalDebugPrint(struct OverTempCal *over_temp_cal, 1571 uint64_t timestamp_nanos) { 1572 ASSERT_NOT_NULL(over_temp_cal); 1573 1574 // This is a state machine that controls the reporting out of debug data. 1575 createDebugTag(over_temp_cal, ":REPORT]"); 1576 switch (over_temp_cal->debug_state) { 1577 case OTC_IDLE: 1578 // Wait for a trigger and start the debug printout sequence. 1579 if (over_temp_cal->debug_print_trigger) { 1580 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, ""); 1581 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Debug Version: %s", 1582 OTC_DEBUG_VERSION_STRING); 1583 over_temp_cal->debug_print_trigger = false; // Resets trigger. 1584 over_temp_cal->debug_state = OTC_PRINT_OFFSET; 1585 } else { 1586 over_temp_cal->debug_state = OTC_IDLE; 1587 } 1588 break; 1589 1590 case OTC_WAIT_STATE: 1591 // This helps throttle the print statements. 1592 if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA( 1593 timestamp_nanos, over_temp_cal->wait_timer_nanos, 1594 OTC_WAIT_TIME_NANOS)) { 1595 over_temp_cal->debug_state = over_temp_cal->next_state; 1596 } 1597 break; 1598 1599 case OTC_PRINT_OFFSET: 1600 // Prints out the latest offset estimate (input data). 1601 CAL_DEBUG_LOG( 1602 over_temp_cal->otc_debug_tag, 1603 "Cal#|Offset|Temp|Age [%s|C|nsec]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET 1604 ", " CAL_FORMAT_3DIGITS ", %" PRIu64, 1605 over_temp_cal->otc_unit_tag, over_temp_cal->debug_num_estimates, 1606 CAL_ENCODE_FLOAT( 1607 over_temp_cal->debug_overtempcal.latest_offset.offset[0] * 1608 over_temp_cal->otc_unit_conversion, 1609 3), 1610 CAL_ENCODE_FLOAT( 1611 over_temp_cal->debug_overtempcal.latest_offset.offset[1] * 1612 over_temp_cal->otc_unit_conversion, 1613 3), 1614 CAL_ENCODE_FLOAT( 1615 over_temp_cal->debug_overtempcal.latest_offset.offset[2] * 1616 over_temp_cal->otc_unit_conversion, 1617 3), 1618 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.latest_offset 1619 .offset_temp_celsius, 1620 3), 1621 over_temp_cal->debug_overtempcal.latest_offset.offset_age_nanos); 1622 1623 // clang-format off 1624 over_temp_cal->wait_timer_nanos = 1625 timestamp_nanos; // Starts the wait timer. 1626 over_temp_cal->next_state = 1627 OTC_PRINT_MODEL_PARAMETERS; // Sets the next state. 1628 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state. 1629 // clang-format on 1630 break; 1631 1632 case OTC_PRINT_MODEL_PARAMETERS: 1633 // Prints out the model parameters. 1634 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 1635 "Cal#|Sensitivity [%s/C]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET, 1636 over_temp_cal->otc_unit_tag, 1637 over_temp_cal->debug_num_estimates, 1638 CAL_ENCODE_FLOAT( 1639 over_temp_cal->debug_overtempcal.temp_sensitivity[0] * 1640 over_temp_cal->otc_unit_conversion, 1641 3), 1642 CAL_ENCODE_FLOAT( 1643 over_temp_cal->debug_overtempcal.temp_sensitivity[1] * 1644 over_temp_cal->otc_unit_conversion, 1645 3), 1646 CAL_ENCODE_FLOAT( 1647 over_temp_cal->debug_overtempcal.temp_sensitivity[2] * 1648 over_temp_cal->otc_unit_conversion, 1649 3)); 1650 1651 CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, 1652 "Cal#|Intercept [%s]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET, 1653 over_temp_cal->otc_unit_tag, 1654 over_temp_cal->debug_num_estimates, 1655 CAL_ENCODE_FLOAT( 1656 over_temp_cal->debug_overtempcal.sensor_intercept[0] * 1657 over_temp_cal->otc_unit_conversion, 1658 3), 1659 CAL_ENCODE_FLOAT( 1660 over_temp_cal->debug_overtempcal.sensor_intercept[1] * 1661 over_temp_cal->otc_unit_conversion, 1662 3), 1663 CAL_ENCODE_FLOAT( 1664 over_temp_cal->debug_overtempcal.sensor_intercept[2] * 1665 over_temp_cal->otc_unit_conversion, 1666 3)); 1667 1668 over_temp_cal->wait_timer_nanos = 1669 timestamp_nanos; // Starts the wait timer. 1670 over_temp_cal->next_state = 1671 OTC_PRINT_MODEL_ERROR; // Sets the next state. 1672 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state. 1673 break; 1674 1675 case OTC_PRINT_MODEL_ERROR: 1676 // Computes the maximum error over all of the model data. 1677 CAL_DEBUG_LOG( 1678 over_temp_cal->otc_debug_tag, 1679 "Cal#|#Updates|#ModelPts|Model Error [%s]: %zu, " 1680 "%zu, %zu, " CAL_FORMAT_3DIGITS_TRIPLET, 1681 over_temp_cal->otc_unit_tag, over_temp_cal->debug_num_estimates, 1682 over_temp_cal->debug_num_model_updates, 1683 over_temp_cal->debug_overtempcal.num_model_pts, 1684 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[0] * 1685 over_temp_cal->otc_unit_conversion, 1686 3), 1687 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[1] * 1688 over_temp_cal->otc_unit_conversion, 1689 3), 1690 CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[2] * 1691 over_temp_cal->otc_unit_conversion, 1692 3)); 1693 1694 over_temp_cal->model_counter = 0; // Resets the model data print counter. 1695 over_temp_cal->wait_timer_nanos = 1696 timestamp_nanos; // Starts the wait timer. 1697 over_temp_cal->next_state = OTC_PRINT_MODEL_DATA; // Sets the next state. 1698 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state. 1699 break; 1700 1701 case OTC_PRINT_MODEL_DATA: 1702 // Prints out all of the model data. 1703 if (over_temp_cal->model_counter < over_temp_cal->num_model_pts) { 1704 CAL_DEBUG_LOG( 1705 over_temp_cal->otc_debug_tag, 1706 " Model[%zu] [%s|C|nsec] = " CAL_FORMAT_3DIGITS_TRIPLET 1707 ", " CAL_FORMAT_3DIGITS ", %" PRIu64, 1708 over_temp_cal->model_counter, over_temp_cal->otc_unit_tag, 1709 CAL_ENCODE_FLOAT( 1710 over_temp_cal->model_data[over_temp_cal->model_counter] 1711 .offset[0] * 1712 over_temp_cal->otc_unit_conversion, 1713 3), 1714 CAL_ENCODE_FLOAT( 1715 over_temp_cal->model_data[over_temp_cal->model_counter] 1716 .offset[1] * 1717 over_temp_cal->otc_unit_conversion, 1718 3), 1719 CAL_ENCODE_FLOAT( 1720 over_temp_cal->model_data[over_temp_cal->model_counter] 1721 .offset[2] * 1722 over_temp_cal->otc_unit_conversion, 1723 3), 1724 CAL_ENCODE_FLOAT( 1725 over_temp_cal->model_data[over_temp_cal->model_counter] 1726 .offset_temp_celsius, 1727 3), 1728 over_temp_cal->model_data[over_temp_cal->model_counter] 1729 .offset_age_nanos); 1730 1731 over_temp_cal->model_counter++; 1732 over_temp_cal->wait_timer_nanos = 1733 timestamp_nanos; // Starts the wait timer. 1734 over_temp_cal->next_state = 1735 OTC_PRINT_MODEL_DATA; // Sets the next state. 1736 over_temp_cal->debug_state = 1737 OTC_WAIT_STATE; // First, go to wait state. 1738 } else { 1739 // Sends this state machine to its idle state. 1740 over_temp_cal->wait_timer_nanos = 1741 timestamp_nanos; // Starts the wait timer. 1742 over_temp_cal->next_state = OTC_IDLE; // Sets the next state. 1743 over_temp_cal->debug_state = 1744 OTC_WAIT_STATE; // First, go to wait state. 1745 } 1746 break; 1747 1748 default: 1749 // Sends this state machine to its idle state. 1750 over_temp_cal->wait_timer_nanos = 1751 timestamp_nanos; // Starts the wait timer. 1752 over_temp_cal->next_state = OTC_IDLE; // Sets the next state. 1753 over_temp_cal->debug_state = OTC_WAIT_STATE; // First, go to wait state. 1754 } 1755 } 1756 1757 void overTempCalDebugDescriptors(struct OverTempCal *over_temp_cal, 1758 const char *otc_sensor_tag, 1759 const char *otc_unit_tag, 1760 float otc_unit_conversion) { 1761 ASSERT_NOT_NULL(over_temp_cal); 1762 ASSERT_NOT_NULL(otc_sensor_tag); 1763 ASSERT_NOT_NULL(otc_unit_tag); 1764 1765 // Sets the sensor descriptor, displayed units, and unit conversion factor. 1766 strcpy(over_temp_cal->otc_sensor_tag, otc_sensor_tag); 1767 strcpy(over_temp_cal->otc_unit_tag, otc_unit_tag); 1768 over_temp_cal->otc_unit_conversion = otc_unit_conversion; 1769 } 1770 1771 #endif // OVERTEMPCAL_DBG_ENABLED 1772