├── .gitattributes ├── .gitignore ├── InternalTemperature.cpp ├── InternalTemperature.h ├── InternalTemperature.odt ├── InternalTemperature.pdf ├── README.md ├── examples ├── calibration_single_point │ └── calibration_single_point.ino ├── overtempAlarmFahrenheit │ └── overtempAlarmFahrenheit.ino ├── simpleCelsius │ └── simpleCelsius.ino ├── simpleFahrenheit │ └── simpleFahrenheit.ino ├── teensy4CPUThrottling │ └── teensy4CPUThrottling.ino └── tftThermometer │ └── tftThermometer.ino └── library.properties /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /InternalTemperature.cpp: -------------------------------------------------------------------------------- 1 | /* InternalTemperature - read internal temperature of ARM processor 2 | * Copyright (C) 2021 LAtimes2 3 | * 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission 14 | * notice shall be included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | 27 | #include "InternalTemperature.h" 28 | #include 29 | 30 | // common static variables 31 | InternalTemperatureClass::alarmType InternalTemperatureClass::alarm = InternalTemperatureClass::NoAlarm; 32 | InternalTemperatureClass::voidFuncPtr InternalTemperatureClass::highTempISR = NULL; 33 | InternalTemperatureClass::voidFuncPtr InternalTemperatureClass::lowTempISR = NULL; 34 | 35 | // singleton instance of the class 36 | InternalTemperatureClass InternalTemperature; 37 | 38 | // Teensy 4.0 is at the end 39 | #if not defined(__IMXRT1062__) 40 | 41 | // Teensy 3.0,3.1,3.2 42 | #if defined(__MK20DX128__) || defined(__MK20DX256__) 43 | #define DEFAULT_VTEMP25 0.719 // volts 44 | #define DEFAULT_SLOPE 0.00172 // volts/degrees C 45 | #else 46 | #define DEFAULT_VTEMP25 0.716 // volts 47 | #define DEFAULT_SLOPE 0.00162 // volts/degrees C 48 | #endif 49 | 50 | #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5, 3.6 51 | #define TEMPERATURE_CHANNEL 26 52 | #define TEMPERATURE_PIN 70 53 | #define VREF_PIN 71 54 | #else 55 | #define TEMPERATURE_CHANNEL 26 56 | #define TEMPERATURE_PIN 38 57 | #define VREF_PIN 39 58 | #endif 59 | 60 | #define MIN_COUNT 0 61 | #define MAX_COUNT 0xFFFF 62 | 63 | // static variables 64 | bool InternalTemperatureClass::initialized = false; 65 | int InternalTemperatureClass::temperatureSettingsType = TEMPERATURE_MAX_ACCURACY; 66 | float InternalTemperatureClass::slope = DEFAULT_SLOPE; 67 | float InternalTemperatureClass::vTemp25 = DEFAULT_VTEMP25; 68 | float InternalTemperatureClass::voltsPerBit = 1.0; 69 | 70 | // Constructor 71 | InternalTemperatureClass::InternalTemperatureClass() 72 | { 73 | } 74 | 75 | bool InternalTemperatureClass::begin (int temperature_settings_type) { 76 | 77 | // TeensyLC needs the Bandgap voltage for measurements. All Teensy's need 78 | // the Bandgap turned on in low power mode. 79 | // So always turn it on. If it is already on, it doesn't hurt anything. 80 | enableBandgap (); 81 | 82 | temperatureSettingsType = temperature_settings_type; 83 | 84 | if (temperature_settings_type == TEMPERATURE_MAX_ACCURACY) 85 | { 86 | // set to internal 1.2V reference 87 | analogReference(INTERNAL); 88 | 89 | // set to maximum number of bits 90 | analogReadResolution(16); 91 | 92 | // set to maximum averaging 93 | analogReadAveraging(32); 94 | } 95 | 96 | voltsPerBit = computeVoltsPerBit (); 97 | 98 | initialized = true; 99 | 100 | return true; 101 | } 102 | 103 | void InternalTemperatureClass::enableBandgap (void) { 104 | 105 | // Bandgap enable 106 | PMC_REGSC |= PMC_REGSC_BGEN | PMC_REGSC_BGBE; 107 | 108 | // Delay for regulator to start up 109 | delay (2); 110 | } 111 | 112 | float InternalTemperatureClass::readRawTemperatureVoltage () { 113 | return readRawVoltage (TEMPERATURE_PIN); 114 | } 115 | 116 | float InternalTemperatureClass::readRawVoltage (int signalNumber) { 117 | 118 | int analogValue; 119 | float volts; 120 | 121 | if (!initialized) { 122 | begin (); 123 | } 124 | 125 | // save previous values because this routine or readAnalog modify them 126 | int previousADC0_CFG1 = ADC0_CFG1; 127 | int previousADC0_CFG2 = ADC0_CFG2; 128 | int previousADC0_SC1A = ADC0_SC1A; 129 | int previousADC0_SC2 = ADC0_SC2; 130 | int previousADC0_SC3 = ADC0_SC3; 131 | 132 | // 133 | // set to max sample time (for best accuracy). This is 134 | // especially needed when using external Vref. 135 | // 136 | 137 | // enable long sample times 138 | ADC0_CFG1 |= ADC_CFG1_ADLSMP; 139 | 140 | // 00 = 32 clocks 141 | ADC0_CFG2 &= ~ADC_CFG2_ADLSTS(3); 142 | 143 | // disable compare (prevents COCO) 144 | ADC0_SC2 &= ~ADC_SC2_ACFE; 145 | 146 | // 147 | // read temperature 148 | // 149 | analogValue = analogRead(signalNumber); 150 | 151 | /* 152 | Serial.print(" 0x"); 153 | Serial.print(analogValue, HEX); 154 | Serial.print(" "); 155 | 156 | Serial.print("SC1A: 0x"); 157 | Serial.print(ADC0_SC1A, HEX); 158 | Serial.print(" "); 159 | 160 | Serial.print("SC2: 0x"); 161 | Serial.print(ADC0_SC2, HEX); 162 | Serial.print(" "); 163 | */ 164 | // convert analog temperature reading to volts 165 | volts = analogValue * computeVoltsPerBit (); 166 | 167 | /* 168 | Serial.print(", "); 169 | Serial.print(volts, 3); 170 | Serial.print(" V "); 171 | */ 172 | // restore previous values 173 | ADC0_CFG1 = previousADC0_CFG1; 174 | ADC0_CFG2 = previousADC0_CFG2; 175 | ADC0_SC1A = previousADC0_SC1A; 176 | ADC0_SC2 = previousADC0_SC2; 177 | ADC0_SC3 = previousADC0_SC3; 178 | ADC0_SC1A = previousADC0_SC1A; 179 | 180 | /* 181 | Serial.print("SC1A: 0x"); 182 | Serial.print(previousADC0_SC1A, HEX); 183 | Serial.print(" "); 184 | 185 | Serial.print("SC2: 0x"); 186 | Serial.print(ADC0_SC2, HEX); 187 | Serial.print(" "); 188 | 189 | Serial.print("SC3: 0x"); 190 | Serial.print(ADC0_SC3, HEX); 191 | Serial.print(" "); 192 | 193 | delay (5); 194 | 195 | Serial.print("SC1A: 0x"); 196 | Serial.print(ADC0_SC1A, HEX); 197 | Serial.print(" "); 198 | */ 199 | 200 | return volts; 201 | } 202 | 203 | float InternalTemperatureClass::computeVoltsPerBit () { 204 | 205 | #if defined(__MKL26Z64__) // Teensy LC 206 | // uses Bandgap voltage since it doesn't have a reference voltage 207 | const float vRef = 1.0; 208 | #else 209 | const float vRef = 1.195; 210 | #endif 211 | 212 | int analogVref; 213 | float floatVref; 214 | float voltsPerBit; 215 | 216 | int previousADC0_CFG1 = ADC0_CFG1; 217 | int previousADC0_CFG2 = ADC0_CFG2; 218 | int previousADC0_SC1A = ADC0_SC1A; 219 | int previousADC0_SC2 = ADC0_SC2; 220 | int previousADC0_SC3 = ADC0_SC3; 221 | 222 | // disable compare (prevents COCO) 223 | ADC0_SC2 &= ~ADC_SC2_ACFE; 224 | 225 | // read Vref 226 | analogVref = analogRead(VREF_PIN); 227 | 228 | // restore previous values 229 | ADC0_CFG1 = previousADC0_CFG1; 230 | ADC0_CFG2 = previousADC0_CFG2; 231 | ADC0_SC1A = previousADC0_SC1A; 232 | ADC0_SC2 = previousADC0_SC2; 233 | ADC0_SC3 = previousADC0_SC3; 234 | ADC0_SC1A = previousADC0_SC1A; 235 | 236 | /* 237 | Serial.print(" 0x"); 238 | Serial.print(analogVref, HEX); 239 | Serial.print(" "); 240 | */ 241 | 242 | floatVref = analogVref; 243 | 244 | // compute scaling for Least Significant Bit 245 | voltsPerBit = vRef / floatVref; 246 | 247 | return voltsPerBit; 248 | } 249 | 250 | float InternalTemperatureClass::convertTemperatureC (float volts, float vTemp25, float slope) { 251 | 252 | float temperatureCelsius; 253 | 254 | // convert voltage to temperature using equation from CPU Reference Manual 255 | temperatureCelsius = 25 - ((volts - vTemp25) / slope); 256 | 257 | return temperatureCelsius; 258 | } 259 | 260 | float InternalTemperatureClass::convertTemperatureC (float volts) { 261 | 262 | return convertTemperatureC (volts, vTemp25, slope); 263 | } 264 | 265 | float InternalTemperatureClass::convertUncalibratedTemperatureC (float volts) { 266 | return convertTemperatureC (volts, DEFAULT_VTEMP25, DEFAULT_SLOPE); 267 | } 268 | 269 | float InternalTemperatureClass::readTemperatureC () { 270 | return convertTemperatureC (readRawTemperatureVoltage ()); 271 | } 272 | 273 | float InternalTemperatureClass::readTemperatureF () { 274 | // convert celsius to fahrenheit 275 | return toFahrenheit(readTemperatureC()); 276 | } 277 | 278 | float InternalTemperatureClass::readUncalibratedTemperatureC () { 279 | return convertUncalibratedTemperatureC (readRawTemperatureVoltage ()); 280 | } 281 | 282 | float InternalTemperatureClass::readUncalibratedTemperatureF () { 283 | // convert celsius to fahrenheit 284 | return toFahrenheit(readUncalibratedTemperatureC()); 285 | } 286 | 287 | // 288 | // Temperature Alarm functions 289 | // 290 | 291 | int InternalTemperatureClass::attachInterruptCelsius (float triggerTemperature, temperatureAlarmType whichTemperature) { 292 | 293 | int returnValue = 0; 294 | uint32_t temperatureCount; 295 | 296 | // if don't change settings, do nothing and return error code 297 | if (temperatureSettingsType == TEMPERATURE_NO_ADC_SETTING_CHANGES) { 298 | 299 | alarm = NoAlarm; 300 | 301 | returnValue = -1; 302 | 303 | } else { 304 | 305 | if (!initialized) { 306 | begin (); 307 | } 308 | 309 | temperatureCount = celsiusToCount (triggerTemperature); 310 | 311 | // limit to max value 312 | if (temperatureCount > MAX_COUNT) { 313 | temperatureCount = MAX_COUNT; 314 | } 315 | 316 | // disable interrupts and compare while setting up 317 | ADC0_SC1A &= ~ADC_SC1_AIEN; 318 | ADC0_SC2 &= ~ADC_SC2_ACFE; 319 | 320 | // 3 cases for compare: 321 | // 1. only high - use CV1, less than 322 | // 2. only low - use CV1, greater than 323 | // 3. both - high in CV1, low in CV2, less than (outside range) 324 | 325 | if (alarm == HighTempAlarm) { 326 | ADC0_CV1 = temperatureCount; 327 | ADC0_SC2 &= ~ADC_SC2_ACFGT; // less than 328 | ADC0_SC2 &= ~ADC_SC2_ACREN; // range disable 329 | } 330 | else if (alarm == LowTempAlarm) { 331 | ADC0_CV1 = temperatureCount; 332 | ADC0_CV2 = temperatureCount; // set second in case a range is used 333 | ADC0_SC2 |= ADC_SC2_ACFGT; // greater than 334 | ADC0_SC2 &= ~ADC_SC2_ACREN; // range disable 335 | } 336 | else if (alarm == BothAlarms) { 337 | if (whichTemperature == LowTemperature) { 338 | ADC0_CV2 = temperatureCount; 339 | } 340 | else { 341 | ADC0_CV1 = temperatureCount; 342 | } 343 | ADC0_SC2 &= ~ADC_SC2_ACFGT; // less than 344 | ADC0_SC2 |= ADC_SC2_ACREN; // range enable 345 | } 346 | 347 | // enable continuous conversions 348 | ADC0_SC3 |= ADC_SC3_ADCO; 349 | 350 | attachInterruptVector(IRQ_ADC0, &InternalTemperatureClass::alarmISR); 351 | 352 | // read register to clear COCO 353 | (void) ADC0_RA; 354 | 355 | // enable compare 356 | ADC0_SC2 |= ADC_SC2_ACFE; 357 | 358 | // enable interrupts 359 | ADC0_SC1A |= ADC_SC1_AIEN; 360 | 361 | // clear channel, then set channel to temperature sensor. 362 | // This also initiates a conversion to start the continuous conversions 363 | ADC0_SC1A &= ~ADC_SC1_ADCH(0xFF); 364 | ADC0_SC1A |= ADC_SC1_ADCH(TEMPERATURE_CHANNEL); 365 | 366 | /* 367 | Serial.println(""); 368 | Serial.print("SC1A: 0x"); 369 | Serial.print(ADC0_SC1A, HEX); 370 | Serial.print(" "); 371 | 372 | Serial.print("SC2: 0x"); 373 | Serial.print(ADC0_SC2, HEX); 374 | Serial.print(" "); 375 | 376 | Serial.print("SC3: 0x"); 377 | Serial.print(ADC0_SC3, HEX); 378 | Serial.println(" "); 379 | Serial.print(" "); 380 | 381 | Serial.print("CV1: 0x"); 382 | Serial.print(ADC0_CV1, HEX); 383 | Serial.print(", "); 384 | Serial.print("CV2: 0x"); 385 | Serial.print(ADC0_CV2, HEX); 386 | Serial.println(" "); 387 | */ 388 | NVIC_CLEAR_PENDING(IRQ_ADC0); 389 | NVIC_ENABLE_IRQ(IRQ_ADC0); 390 | } 391 | 392 | return returnValue; 393 | } 394 | 395 | int InternalTemperatureClass::detachHighTempInterrupt () { 396 | 397 | int returnValue = 0; 398 | 399 | // if don't change settings, do nothing and return error code 400 | if (temperatureSettingsType == TEMPERATURE_NO_ADC_SETTING_CHANGES) { 401 | 402 | returnValue = -1; 403 | 404 | } else { 405 | 406 | // if High Temp is only alarm 407 | if (alarm == HighTempAlarm) { 408 | alarm = NoAlarm; 409 | ADC0_CV1 = MIN_COUNT; // prevent more interrupts 410 | NVIC_DISABLE_IRQ(IRQ_ADC0); 411 | } 412 | else if (alarm == BothAlarms) { 413 | alarm = LowTempAlarm; 414 | ADC0_CV1 = MIN_COUNT; // prevent more interrupts 415 | } 416 | 417 | } 418 | 419 | return returnValue; 420 | } 421 | 422 | int InternalTemperatureClass::detachLowTempInterrupt () { 423 | 424 | int returnValue = 0; 425 | 426 | // if don't change settings, do nothing and return error code 427 | if (temperatureSettingsType == TEMPERATURE_NO_ADC_SETTING_CHANGES) { 428 | 429 | returnValue = -1; 430 | 431 | } else { 432 | 433 | // if Low Temp is only alarm 434 | if (alarm == LowTempAlarm) { 435 | alarm = NoAlarm; 436 | ADC0_CV1 = MAX_COUNT; // prevent more interrupts 437 | NVIC_DISABLE_IRQ(IRQ_ADC0); 438 | } 439 | else if (alarm == BothAlarms) { 440 | alarm = HighTempAlarm; 441 | ADC0_SC2 &= ~ADC_SC2_ACREN; // range disable 442 | } 443 | 444 | } 445 | 446 | return returnValue; 447 | } 448 | 449 | // convert celsius temperature to a raw count value 450 | uint32_t InternalTemperatureClass::celsiusToCount (float temperatureCelsius) { 451 | 452 | uint32_t rawCount; 453 | float volts; 454 | 455 | // convert temperature to voltage using equation from CPU Reference Manual solved for volts 456 | volts = -((temperatureCelsius - 25) * slope - vTemp25); 457 | 458 | rawCount = volts / voltsPerBit; 459 | 460 | return rawCount; 461 | } 462 | 463 | void InternalTemperatureClass::getRegisterCounts (int *currentCount, int *highTempCount, int *lowTempCount) { 464 | *currentCount = (int)ADC0_RA; 465 | *highTempCount = (int)ADC0_CV1; 466 | *lowTempCount = (int)ADC0_CV2; 467 | } 468 | 469 | // 470 | // Calibration functions 471 | // 472 | 473 | bool InternalTemperatureClass::singlePointCalibrationC ( 474 | float actualTemperatureC, float measuredTemperatureC, bool fromDefault) { 475 | 476 | float theSlope = slope; 477 | float theVTemp25 = vTemp25; 478 | 479 | if (fromDefault) { 480 | theSlope = DEFAULT_SLOPE; 481 | theVTemp25 = DEFAULT_VTEMP25; 482 | } 483 | 484 | // adjust vTemp25 for the delta temperature 485 | float deltaTemperature = measuredTemperatureC - actualTemperatureC; 486 | 487 | float deltaVolts = deltaTemperature * theSlope; 488 | 489 | return setVTemp25 (theVTemp25 - deltaVolts); 490 | } 491 | 492 | bool InternalTemperatureClass::singlePointCalibrationF ( 493 | float actualTemperatureF, float measuredTemperatureF, bool fromDefault) { 494 | 495 | return singlePointCalibrationC (toCelsius(actualTemperatureF), toCelsius(measuredTemperatureF), fromDefault); 496 | } 497 | 498 | bool InternalTemperatureClass::dualPointCalibrationC ( 499 | float actualTemperature1C, float measuredTemperature1C, 500 | float actualTemperature2C, float measuredTemperature2C, bool fromDefault) { 501 | 502 | float deltaActual = actualTemperature2C - actualTemperature1C; 503 | float deltaMeasured = measuredTemperature2C - measuredTemperature1C; 504 | float newSlope; 505 | bool returnValue = false; 506 | 507 | float originalSlope = slope; 508 | float originalVTemp25 = vTemp25; 509 | 510 | if (fromDefault) { 511 | originalSlope = DEFAULT_SLOPE; 512 | originalVTemp25 = DEFAULT_VTEMP25; 513 | } 514 | 515 | // adjust slope first, then the offset 516 | newSlope = originalSlope * deltaMeasured / deltaActual; 517 | 518 | if (setSlope (newSlope)) { 519 | 520 | // offset at 25 degrees C 521 | 522 | // Original: measured voltage = originalVTemp25 - (measuredTemperature1C - 25) * originalSlope 523 | // New : measured voltage = newVTemp25 - (actualTemperature1C - 25) * newSlope 524 | // 525 | // Since measured voltage is the same: 526 | // newVTemp25 - (actualTemperature1C - 25) * newSlope = originalVTemp25 - (measuredTemperature1C - 25) * originalSlope 527 | // 528 | // Rearranging: 529 | // newVTemp25 = originalVTemp25 - (measuredTemperature1C - 25) * originalSlope + (actualTemperature1C - 25) * newSlope 530 | 531 | float newVTemp25 = originalVTemp25 - (measuredTemperature1C - 25) * originalSlope + (actualTemperature1C - 25) * newSlope; 532 | 533 | returnValue = setVTemp25 (newVTemp25); 534 | } 535 | 536 | return returnValue; 537 | } 538 | 539 | bool InternalTemperatureClass::dualPointCalibrationF ( 540 | float actualTemperature1F, float measuredTemperature1F, 541 | float actualTemperature2F, float measuredTemperature2F, bool fromDefault) { 542 | 543 | return dualPointCalibrationC (toCelsius(actualTemperature1F), toCelsius(measuredTemperature1F), 544 | toCelsius(actualTemperature2F), toCelsius(measuredTemperature2F), fromDefault); 545 | } 546 | 547 | 548 | bool InternalTemperatureClass::setVTemp25 (float volts) 549 | { 550 | // perform a range check (0-5 volts) 551 | if (volts < 0.0 || volts > 5.0) { 552 | return false; 553 | } 554 | vTemp25 = volts; 555 | return true; 556 | } 557 | 558 | bool InternalTemperatureClass::setSlope (float voltsPerDegreeC) 559 | { 560 | // perform a range check (factor of 10 around default value) 561 | if (voltsPerDegreeC < (DEFAULT_SLOPE / 10.0) || voltsPerDegreeC > (DEFAULT_SLOPE * 10.0)) { 562 | return false; 563 | } 564 | slope = voltsPerDegreeC; 565 | return true; 566 | } 567 | 568 | float InternalTemperatureClass::getVTemp25 () { 569 | return vTemp25; 570 | } 571 | 572 | float InternalTemperatureClass::getSlope () { 573 | return slope; 574 | } 575 | 576 | // Unique ID can be used to set calibration values by serial number 577 | int InternalTemperatureClass::getUniqueID () { 578 | return SIM_UIDL; 579 | } 580 | 581 | #else // Teensy 4 582 | 583 | // 584 | // Teensy 4.0 uses tempMon, so this is just a thin wrapper around it for backward compatibility 585 | // 586 | 587 | #define MIN_COUNT 0 588 | #define MAX_COUNT 0xFFF 589 | 590 | // Constructor 591 | InternalTemperatureClass::InternalTemperatureClass() 592 | { 593 | } 594 | 595 | bool InternalTemperatureClass::begin (int temperature_settings_type) { 596 | return true; 597 | } 598 | 599 | float InternalTemperatureClass::readTemperatureC () { 600 | return tempmonGetTemp(); 601 | } 602 | 603 | float InternalTemperatureClass::readTemperatureF () { 604 | // convert celsius to fahrenheit 605 | return toFahrenheit(readTemperatureC()); 606 | } 607 | 608 | float InternalTemperatureClass::readUncalibratedTemperatureC () { 609 | // calibration is internal 610 | return readTemperatureC(); 611 | } 612 | 613 | float InternalTemperatureClass::readUncalibratedTemperatureF () { 614 | // calibration is internal 615 | return readTemperatureF(); 616 | } 617 | 618 | int InternalTemperatureClass::attachInterruptCelsius (float triggerTemperature, temperatureAlarmType whichTemperature) { 619 | 620 | uint32_t tempCodeVal = celsiusToCount (triggerTemperature); 621 | 622 | // limit to max value 623 | if (tempCodeVal > MAX_COUNT) { 624 | tempCodeVal = MAX_COUNT; 625 | } 626 | 627 | // stop while updating to prevent interrupts 628 | tempmon_Stop (); 629 | 630 | if (whichTemperature == HighTemperature) { 631 | // clear previous value 632 | TEMPMON_TEMPSENSE0_CLR = TEMPMON_CTRL0_ALARM_VALUE (MAX_COUNT); 633 | 634 | // set new value 635 | TEMPMON_TEMPSENSE0_SET = TEMPMON_CTRL0_ALARM_VALUE (tempCodeVal); 636 | } 637 | else { // LowTemperature 638 | // clear previous value 639 | TEMPMON_TEMPSENSE2_CLR = TEMPMON_CTRL2_LOW_ALARM_VALUE (MAX_COUNT); 640 | 641 | // set new value 642 | TEMPMON_TEMPSENSE2_SET = TEMPMON_CTRL2_LOW_ALARM_VALUE (tempCodeVal); 643 | } 644 | 645 | attachInterruptVector(IRQ_TEMPERATURE, &InternalTemperatureClass::alarmISR); 646 | 647 | NVIC_CLEAR_PENDING(IRQ_TEMPERATURE); 648 | NVIC_ENABLE_IRQ(IRQ_TEMPERATURE); 649 | 650 | // start converting again 651 | tempmon_Start (); 652 | 653 | return 0; 654 | } 655 | 656 | int InternalTemperatureClass::detachHighTempInterrupt () { 657 | 658 | // if High Temp is only alarm 659 | if (alarm == HighTempAlarm) { 660 | alarm = NoAlarm; 661 | TEMPMON_TEMPSENSE0_CLR = TEMPMON_CTRL0_ALARM_VALUE (MAX_COUNT); // prevent more interrupts 662 | NVIC_DISABLE_IRQ(IRQ_TEMPERATURE); 663 | } 664 | else if (alarm == BothAlarms) { 665 | alarm = LowTempAlarm; 666 | TEMPMON_TEMPSENSE0_CLR = TEMPMON_CTRL0_ALARM_VALUE (MAX_COUNT); // prevent more interrupts 667 | } 668 | 669 | return 0; 670 | } 671 | 672 | int InternalTemperatureClass::detachLowTempInterrupt () { 673 | 674 | // if Low Temp is only alarm 675 | if (alarm == LowTempAlarm) { 676 | alarm = NoAlarm; 677 | TEMPMON_TEMPSENSE2_SET = TEMPMON_CTRL2_LOW_ALARM_VALUE (MAX_COUNT); // prevent more interrupts 678 | NVIC_DISABLE_IRQ(IRQ_TEMPERATURE); 679 | } 680 | else if (alarm == BothAlarms) { 681 | alarm = HighTempAlarm; 682 | TEMPMON_TEMPSENSE2_SET = TEMPMON_CTRL2_LOW_ALARM_VALUE (MAX_COUNT); // prevent more interrupts 683 | } 684 | 685 | return 0; 686 | } 687 | 688 | // convert celsius temperature to a raw count value 689 | uint32_t InternalTemperatureClass::celsiusToCount (float temperatureCelsius) { 690 | 691 | uint32_t tempCodeVal; 692 | 693 | // read calibration data 694 | uint32_t calibrationData = HW_OCOTP_ANA1; 695 | uint32_t s_hotTemp = (uint32_t)(calibrationData & 0xFFU) >> 0x00U; 696 | uint32_t s_hotCount = (uint32_t)(calibrationData & 0xFFF00U) >> 0X08U; 697 | uint32_t roomCount = (uint32_t)(calibrationData & 0xFFF00000U) >> 0x14U; 698 | float s_hot_ROOM = s_hotTemp - 25.0f; 699 | uint32_t s_roomC_hotC = roomCount - s_hotCount; 700 | 701 | // calculate count from temperature and calibration data 702 | tempCodeVal = (uint32_t)(s_hotCount + (s_hotTemp - temperatureCelsius) * s_roomC_hotC / s_hot_ROOM); 703 | 704 | return tempCodeVal; 705 | } 706 | 707 | void InternalTemperatureClass::getRegisterCounts (int *currentCount, int *highTempCount, int *lowTempCount) { 708 | *currentCount = (int)(TEMPMON_TEMPSENSE0 & 0xFFF00U) >> 8U; 709 | *highTempCount = (int)(TEMPMON_TEMPSENSE0 & 0xFFF00000U) >> 20U; 710 | *lowTempCount = (int)(TEMPMON_TEMPSENSE2 & 0xFFFU) >> 0U; 711 | } 712 | 713 | // 714 | // Calibration functions - Teensy 4 comes calibrated from the factory, so does nothing 715 | // 716 | 717 | bool InternalTemperatureClass::singlePointCalibrationC ( 718 | float actualTemperatureC, float measuredTemperatureC, bool fromDefault) { 719 | return true; 720 | } 721 | 722 | bool InternalTemperatureClass::singlePointCalibrationF ( 723 | float actualTemperatureF, float measuredTemperatureF, bool fromDefault) { 724 | return true; 725 | } 726 | 727 | bool InternalTemperatureClass::dualPointCalibrationC ( 728 | float actualTemperature1C, float measuredTemperature1C, 729 | float actualTemperature2C, float measuredTemperature2C, bool fromDefault) { 730 | return true; 731 | } 732 | 733 | bool InternalTemperatureClass::dualPointCalibrationF ( 734 | float actualTemperature1F, float measuredTemperature1F, 735 | float actualTemperature2F, float measuredTemperature2F, bool fromDefault) { 736 | return true; 737 | } 738 | 739 | bool InternalTemperatureClass::setVTemp25 (float volts) { 740 | return true; 741 | } 742 | 743 | bool InternalTemperatureClass::setSlope (float voltsPerDegreeC) { 744 | return true; 745 | } 746 | 747 | float InternalTemperatureClass::getVTemp25 () { 748 | return 0.0; 749 | } 750 | 751 | float InternalTemperatureClass::getSlope () { 752 | return 0.0; 753 | } 754 | 755 | // Unique ID can be used to set calibration values by serial number 756 | int InternalTemperatureClass::getUniqueID () { 757 | return HW_OCOTP_MAC0; 758 | } 759 | 760 | #endif // if Teensy 4.0 else 761 | 762 | // 763 | // Common to Teemsy 3 and Teensy 4 764 | // 765 | 766 | void InternalTemperatureClass::alarmISR () { 767 | 768 | if (alarm == HighTempAlarm) { 769 | highTempISR (); 770 | 771 | // prevent future interrupts 772 | detachHighTempInterrupt (); 773 | } 774 | else if (alarm == LowTempAlarm) { 775 | lowTempISR (); 776 | 777 | // prevent future interrupts 778 | detachLowTempInterrupt (); 779 | } 780 | else if (alarm == BothAlarms) { 781 | 782 | // figure out which one it was by closest temperature 783 | int currentCount = 0; 784 | int highTempCount = 0; 785 | int lowTempCount = 0; 786 | 787 | getRegisterCounts (¤tCount, &highTempCount, &lowTempCount); 788 | 789 | // if closer to high temp than low temp 790 | if (abs (currentCount - highTempCount) < abs (currentCount - lowTempCount)) { 791 | highTempISR (); 792 | 793 | // prevent future interrupts 794 | detachHighTempInterrupt (); 795 | } 796 | else { 797 | lowTempISR (); 798 | 799 | // prevent future interrupts 800 | detachLowTempInterrupt (); 801 | } 802 | } 803 | } 804 | 805 | int InternalTemperatureClass::attachHighTempInterruptCelsius (float triggerTemperature, void (*function)(void)) { 806 | 807 | highTempISR = function; 808 | 809 | if (alarm == NoAlarm) { 810 | alarm = HighTempAlarm; 811 | } 812 | else if (alarm == LowTempAlarm) { 813 | alarm = BothAlarms; 814 | } 815 | 816 | return attachInterruptCelsius (triggerTemperature, HighTemperature); 817 | } 818 | 819 | int InternalTemperatureClass::attachHighTempInterruptFahrenheit (float triggerTemperature, void (*function)(void)) { 820 | return attachHighTempInterruptCelsius (toCelsius (triggerTemperature), function); 821 | } 822 | 823 | int InternalTemperatureClass::attachLowTempInterruptCelsius (float triggerTemperature, void (*function)(void)) { 824 | 825 | lowTempISR = function; 826 | 827 | if (alarm == NoAlarm) { 828 | alarm = LowTempAlarm; 829 | } 830 | else if (alarm == HighTempAlarm) { 831 | alarm = BothAlarms; 832 | } 833 | 834 | return attachInterruptCelsius (triggerTemperature, LowTemperature); 835 | } 836 | 837 | int InternalTemperatureClass::attachLowTempInterruptFahrenheit (float triggerTemperature, void (*function)(void)) { 838 | return attachLowTempInterruptCelsius (toCelsius (triggerTemperature), function); 839 | } 840 | 841 | 842 | // Utilities 843 | 844 | float InternalTemperatureClass::toCelsius (float temperatureFahrenheit) { 845 | // convert fahrenheit to celsius 846 | return (temperatureFahrenheit - 32) * 5.0 / 9.0; 847 | } 848 | 849 | float InternalTemperatureClass::toFahrenheit (float temperatureCelsius) { 850 | // convert celsius to fahrenheit 851 | return temperatureCelsius * 9.0 / 5.0 + 32; 852 | } 853 | -------------------------------------------------------------------------------- /InternalTemperature.h: -------------------------------------------------------------------------------- 1 | /* InternalTemperature - read internal temperature of ARM processor 2 | * Copyright (C) 2020 LAtimes2 3 | * 4 | * MIT License 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission 14 | * notice shall be included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /* Typical usage: 26 | * #include "InternalTemperature.h" 27 | * 28 | * Serial.println(InternalTemperature.readTemperatureC()); 29 | */ 30 | 31 | #ifndef InternalTemperature_h_ 32 | #define InternalTemperature_h_ 33 | 34 | #define TEMPERATURE_MAX_ACCURACY 0 35 | #define TEMPERATURE_NO_ADC_SETTING_CHANGES 1 36 | 37 | #include 38 | 39 | class InternalTemperatureClass 40 | { 41 | public: 42 | InternalTemperatureClass(); 43 | 44 | // 45 | // Main functions 46 | // 47 | 48 | // Note: If settings_type is TEMPERATURE_MAX_ACCURACY, it will change 49 | // the ADC settings to be optimal for reading temperature. 50 | // If settings_type is TEMPERATURE_NO_ADC_SETTING_CHANGES, it will 51 | // keep the default ADC settings or any other settings changes. 52 | // readTemperature will detect the current settings and use them. 53 | static bool begin (int temperature_settings_type = TEMPERATURE_MAX_ACCURACY); 54 | 55 | static float readTemperatureC (void); 56 | static float readTemperatureF (void); 57 | 58 | // 59 | // functions to handle going above a high temperature 60 | // 61 | static int attachHighTempInterruptCelsius (float triggerTemperature, void (*function)(void)); 62 | static int attachHighTempInterruptFahrenheit (float triggerTemperature, void (*function)(void)); 63 | static int detachHighTempInterrupt (void); 64 | 65 | // 66 | // functions to handle going below a low temperature 67 | // 68 | static int attachLowTempInterruptCelsius (float triggerTemperature, void (*function)(void)); 69 | static int attachLowTempInterruptFahrenheit (float triggerTemperature, void (*function)(void)); 70 | static int detachLowTempInterrupt (void); 71 | 72 | // 73 | // Calibration functions 74 | // 75 | bool singlePointCalibrationC (float actualTemperatureC, float measuredTemperatureC, bool fromDefault = false); 76 | bool singlePointCalibrationF (float actualTemperatureF, float measuredTemperatureF, bool fromDefault = false); 77 | 78 | bool dualPointCalibrationC (float actualTemperature1C, float measuredTemperature1C, 79 | float actualTemperature2C, float measuredTemperature2C, bool fromDefault = false); 80 | bool dualPointCalibrationF (float actualTemperature1F, float measuredTemperature1F, 81 | float actualTemperature2F, float measuredTemperature2F, bool fromDefault = false); 82 | 83 | bool setVTemp25 (float volts); 84 | bool setSlope (float voltsPerDegreeC); 85 | float getVTemp25 (void); 86 | float getSlope (void); 87 | static int getUniqueID (void); 88 | 89 | // 90 | // low level utilities 91 | // 92 | static float convertTemperatureC (float volts); 93 | static float convertUncalibratedTemperatureC (float volts); 94 | static float readRawTemperatureVoltage (void); 95 | static float readRawVoltage (int signalNumber); 96 | static float readUncalibratedTemperatureC (void); 97 | static float readUncalibratedTemperatureF (void); 98 | static float toCelsius (float temperatureFahrenheit); 99 | static float toFahrenheit (float temperatureCelsius); 100 | 101 | private: 102 | enum temperatureAlarmType { 103 | LowTemperature, 104 | HighTemperature 105 | }; 106 | 107 | enum alarmType { 108 | NoAlarm, 109 | LowTempAlarm, 110 | HighTempAlarm, 111 | BothAlarms 112 | }; 113 | 114 | private: 115 | static float convertTemperatureC (float volts, float vTemp25, float slope); 116 | static void enableBandgap (void); 117 | static void getRegisterCounts (int *currentCount, int *highTempCount, int *lowTempCount); 118 | 119 | static void alarmISR (); 120 | static uint32_t celsiusToCount (float temperatureCelsius); 121 | 122 | // Teensy 3 123 | static float computeVoltsPerBit (); 124 | static int attachInterruptCelsius (float triggerTemperature, temperatureAlarmType whichTemperature); 125 | 126 | private: 127 | static bool initialized; 128 | static int temperatureSettingsType; 129 | static float slope; 130 | static float vTemp25; 131 | static float voltsPerBit; 132 | 133 | typedef void (*voidFuncPtr)(void); 134 | 135 | static alarmType alarm; 136 | static voidFuncPtr highTempISR; 137 | static voidFuncPtr lowTempISR; 138 | }; 139 | 140 | // create instance of the class 141 | extern InternalTemperatureClass InternalTemperature; 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /InternalTemperature.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAtimes2/InternalTemperature/c8679a6008bef9f3b1d62a238257f3987daac353/InternalTemperature.odt -------------------------------------------------------------------------------- /InternalTemperature.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAtimes2/InternalTemperature/c8679a6008bef9f3b1d62a238257f3987daac353/InternalTemperature.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InternalTemperature 2 | 3 | --- 4 | The Kinetis Cortex-M processor on the Teensy 3/LC boards has a built-in temperature sensor. This library provides functions to read the temperature in both Celsius and Fahrenheit. 5 | 6 | The Teensy 4 has a built-in temperature sensor, along with built-in functions to read temperature. This library provides a wrapper around the built-in functions (to support common code for Teensy 3 and 4). 7 | 8 | In addition, the library has high and low temperature alarm functions to perform actions when temperature limits are exceeded. 9 | 10 | Examples are provided to show how to read temperatures, set alarms, and, for Teensy 4, change the CPU clock speed based on temperature. 11 | 12 | Here is a simple example of how to read the temperature: 13 | ```c++ 14 | #include 15 | 16 | void setup() 17 | { 18 | } 19 | 20 | void loop() 21 | { 22 | float temp = InternalTemperature.readTemperatureC(); 23 | } 24 | ``` 25 | 26 | For more details and information on calibration, see 27 | 28 | https://github.com/LAtimes2/InternalTemperature/blob/master/InternalTemperature.pdf 29 | -------------------------------------------------------------------------------- /examples/calibration_single_point/calibration_single_point.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Teensy 3.x/LC single point calibration example 3 | // 4 | 5 | #include 6 | 7 | boolean celsius = true; 8 | 9 | void setup() 10 | { 11 | float currentTemperature; 12 | 13 | Serial.begin(115200); 14 | while (!Serial); 15 | 16 | Serial.print("Teensy unique id : "); 17 | Serial.println(InternalTemperature.getUniqueID(), HEX); 18 | 19 | Serial.print("Enter 1 for Celsius or 2 for Fahrenheit : "); 20 | while (!Serial.available()); 21 | 22 | if (Serial.parseInt() == 2) { 23 | celsius = false; 24 | } 25 | 26 | Serial.println(""); 27 | 28 | Serial.print("Enter current temperature : "); 29 | Serial.clear(); 30 | while (!Serial.available()); 31 | 32 | currentTemperature = Serial.parseFloat(); 33 | 34 | if (celsius) { 35 | if (!InternalTemperature.singlePointCalibrationC(currentTemperature, InternalTemperature.readTemperatureC())) { 36 | Serial.println(" ERROR - invalid calibration temperature"); 37 | } 38 | } else { 39 | if (!InternalTemperature.singlePointCalibrationF(currentTemperature, InternalTemperature.readTemperatureF())) { 40 | Serial.println(" ERROR - invalid calibration temperature"); 41 | } 42 | } 43 | 44 | Serial.println(""); 45 | Serial.println(""); 46 | 47 | Serial.println("To make change permanent in a sketch for this Teensy, add these lines after call to temperature.begin:"); 48 | Serial.println(""); 49 | 50 | Serial.print(" if (temperature.getUniqueID() == 0x"); 51 | Serial.print(InternalTemperature.getUniqueID(), HEX); 52 | Serial.println(") {"); 53 | 54 | Serial.print(" temperature.setSlope("); 55 | Serial.print(InternalTemperature.getSlope(), 6); 56 | Serial.println(");"); 57 | 58 | Serial.print(" temperature.setVTemp25("); 59 | Serial.print(InternalTemperature.getVTemp25(), 4); 60 | Serial.println(");"); 61 | 62 | Serial.println(" }"); 63 | Serial.println(""); 64 | } 65 | 66 | void loop() 67 | { 68 | Serial.print("Calibrated Temperature: "); 69 | if (celsius) { 70 | Serial.print(InternalTemperature.readTemperatureC(), 1); 71 | Serial.print("°C"); 72 | } else { 73 | Serial.print(InternalTemperature.readTemperatureF(), 1); 74 | Serial.print("°F"); 75 | } 76 | Serial.print(", Uncalibrated Temperature: "); 77 | if (celsius) { 78 | Serial.print(InternalTemperature.readUncalibratedTemperatureC(), 1); 79 | Serial.print("°C"); 80 | } else { 81 | Serial.print(InternalTemperature.readUncalibratedTemperatureF(), 1); 82 | Serial.print("°F"); 83 | } 84 | Serial.println(""); 85 | delay(10000); 86 | } 87 | -------------------------------------------------------------------------------- /examples/overtempAlarmFahrenheit/overtempAlarmFahrenheit.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Teensy simple internal temperature 3 | // 4 | 5 | #include 6 | 7 | bool highAlarmTriggered = false; 8 | bool lowAlarmTriggered = false; 9 | 10 | const bool ShouldNotTrigger = false; 11 | const bool ShouldTrigger = true; 12 | 13 | void setup() 14 | { 15 | float currentTemperature; 16 | 17 | Serial.begin(115200); 18 | while (!Serial); 19 | 20 | // wait 1 second for temperature to stabilize a bit 21 | delay (1000); 22 | 23 | currentTemperature = InternalTemperature.readTemperatureF (); 24 | 25 | testHighAlarm (currentTemperature + 5.0, ShouldNotTrigger); 26 | testHighAlarm (currentTemperature - 5.0, ShouldTrigger); 27 | InternalTemperature.detachHighTempInterrupt (); 28 | 29 | testLowAlarm (currentTemperature - 5.0, ShouldNotTrigger); 30 | testLowAlarm (currentTemperature + 5.0, ShouldTrigger); 31 | testLowAlarm (currentTemperature - 5.0, ShouldNotTrigger); 32 | 33 | currentTemperature = InternalTemperature.readTemperatureF (); 34 | 35 | // Low Alarm is still set 36 | testHighAlarm (currentTemperature + 5.0, ShouldNotTrigger); 37 | testHighAlarm (currentTemperature - 2.0, ShouldTrigger); 38 | 39 | testHighAlarm (currentTemperature + 5.0, ShouldNotTrigger); 40 | 41 | // High Alarm is still set 42 | testLowAlarm (currentTemperature - 5.0, ShouldNotTrigger); 43 | testLowAlarm (currentTemperature + 5.0, ShouldTrigger); 44 | } 45 | 46 | void loop() 47 | { 48 | Serial.print("Temperature: "); 49 | Serial.print(InternalTemperature.readTemperatureF(), 1); 50 | Serial.println("°F"); 51 | delay(1000); 52 | } 53 | 54 | void HighAlarmISR (void) { 55 | highAlarmTriggered = true; 56 | Serial.print(" (Overtemp ISR) "); 57 | } 58 | 59 | void LowAlarmISR (void) { 60 | lowAlarmTriggered = true; 61 | Serial.print(" (Undertemp ISR) "); 62 | } 63 | 64 | void testHighAlarm (float temperature, bool alarmExpected) { 65 | 66 | Serial.print ("Setting High Temp Alarm to "); 67 | Serial.print (temperature); 68 | Serial.print (" degrees - "); 69 | highAlarmTriggered = false; 70 | InternalTemperature.attachHighTempInterruptFahrenheit (temperature, &HighAlarmISR); 71 | 72 | // allow time for conversions 73 | delay (100); 74 | 75 | if (alarmExpected) { 76 | if (highAlarmTriggered) { 77 | Serial.println("triggered as expected"); 78 | } 79 | else { 80 | Serial.println("did not trigger - FAILED"); 81 | } 82 | } 83 | else { // not expected 84 | if (highAlarmTriggered) { 85 | Serial.println("triggered - FAILED"); 86 | } 87 | else { 88 | Serial.println("did not trigger, as expected"); 89 | } 90 | } 91 | } 92 | 93 | void testLowAlarm (float temperature, bool alarmExpected) { 94 | 95 | Serial.print ("Setting Low Temp Alarm to "); 96 | Serial.print (temperature); 97 | Serial.print (" degrees - "); 98 | lowAlarmTriggered = false; 99 | InternalTemperature.attachLowTempInterruptFahrenheit (temperature, &LowAlarmISR); 100 | 101 | delay (500); 102 | 103 | if (alarmExpected) { 104 | if (lowAlarmTriggered) { 105 | Serial.println("triggered as expected"); 106 | } 107 | else { 108 | Serial.println("did not trigger - FAILED"); 109 | } 110 | } 111 | else { // not expected 112 | if (lowAlarmTriggered) { 113 | Serial.println("triggered - FAILED"); 114 | } 115 | else { 116 | Serial.println("did not trigger, as expected"); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/simpleCelsius/simpleCelsius.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Teensy 4.x/3.x/LC simple internal temperature 3 | // 4 | 5 | #include 6 | 7 | void setup() 8 | { 9 | Serial.begin(115200); 10 | while (!Serial); 11 | } 12 | 13 | void loop() 14 | { 15 | Serial.print("Temperature: "); 16 | Serial.print(InternalTemperature.readTemperatureC(), 1); 17 | Serial.println("°C"); 18 | delay(10000); 19 | } 20 | -------------------------------------------------------------------------------- /examples/simpleFahrenheit/simpleFahrenheit.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Teensy 4.x/3.x/LC simple internal temperature 3 | // 4 | 5 | #include 6 | 7 | void setup() 8 | { 9 | InternalTemperature.begin(TEMPERATURE_NO_ADC_SETTING_CHANGES); 10 | 11 | Serial.begin(115200); 12 | while (!Serial); 13 | } 14 | 15 | void loop() 16 | { 17 | Serial.print("Temperature: "); 18 | Serial.print(InternalTemperature.readTemperatureF(), 1); 19 | Serial.println("°F"); 20 | delay(10000); 21 | } 22 | -------------------------------------------------------------------------------- /examples/teensy4CPUThrottling/teensy4CPUThrottling.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Teensy 4 CPU Throttling at high temperature 3 | // 4 | 5 | // This example only works for Teensy 4 - it will not compile for Teensy 3. 6 | // It sets the clock to 600 MHz until the CPU temperature gets above 45C, 7 | // then sets the clock to 150 MHz until it cools to 40C, and repeats. 8 | 9 | #include 10 | 11 | extern "C" uint32_t set_arm_clock(uint32_t frequency); // required prototype 12 | 13 | #define LOW_TEMP 40 14 | #define HIGH_TEMP 45 15 | 16 | volatile bool highTempAlarm = false; 17 | volatile bool lowTempAlarm = false; 18 | 19 | void setup() 20 | { 21 | Serial.begin(115200); 22 | while (!Serial); 23 | 24 | // set an alarm at high temperature 25 | InternalTemperature.attachHighTempInterruptCelsius (HIGH_TEMP, &HighAlarmISR); 26 | 27 | Serial.println("Starting at 600 MHz"); 28 | } 29 | 30 | void loop() 31 | { 32 | if (highTempAlarm) 33 | { 34 | highTempAlarm = false; 35 | Serial.println(" (Overtemp ISR - switching to 150 MHz) "); 36 | } 37 | 38 | if (lowTempAlarm) 39 | { 40 | lowTempAlarm = false; 41 | Serial.println(" (Undertemp ISR - switching to 600 MHz) "); 42 | } 43 | 44 | Serial.print("Temperature: "); 45 | Serial.print(InternalTemperature.readTemperatureC(), 1); 46 | Serial.println("°C"); 47 | 48 | delay(1000); 49 | } 50 | 51 | void HighAlarmISR (void) 52 | { 53 | set_arm_clock (150000000); 54 | highTempAlarm = true; 55 | 56 | // set an alarm at low temperature 57 | InternalTemperature.attachLowTempInterruptCelsius (LOW_TEMP, &LowAlarmISR); 58 | } 59 | 60 | void LowAlarmISR (void) 61 | { 62 | set_arm_clock (600000000); 63 | lowTempAlarm = true; 64 | 65 | // set an alarm at high temperature 66 | InternalTemperature.attachHighTempInterruptCelsius (HIGH_TEMP, &HighAlarmISR); 67 | } 68 | -------------------------------------------------------------------------------- /examples/tftThermometer/tftThermometer.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Teensy 3.x/LC Thermometer on an ILI9341 TFT display 3 | // 4 | // This example goes into low speed/low power mode to give a good approximation of the air temperature. 5 | // It uses the Snooze and ILI9341_t3 or Adafruit_ILI9341 libraries. 6 | // 7 | // Because low speed is too slow for USB, it requires a pushbutton reset to write a new sketch to it. 8 | // 9 | 10 | #include 11 | 12 | // User settings 13 | bool useCelsius = true; 14 | 15 | // Teensy LC can't use the optimized library 16 | #if defined(__MKL26Z64__) 17 | #define useOptimizedLibrary false 18 | #else 19 | #define useOptimizedLibrary true 20 | #endif 21 | 22 | #if useOptimizedLibrary 23 | #include 24 | #else 25 | #include 26 | #endif 27 | 28 | #include 29 | 30 | // For optimized ILI9341_t3 library 31 | #define TFT_DC 20 32 | #define TFT_CS 21 33 | #define TFT_RST 255 // 255 = unused, connect to 3.3V 34 | #define TFT_MOSI 11 35 | #define TFT_SCLK 13 36 | #define TFT_MISO 12 37 | 38 | #if useOptimizedLibrary 39 | ILI9341_t3 display = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO); 40 | #else 41 | Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC); 42 | #endif 43 | 44 | void setup() 45 | { 46 | //********** 47 | // Calibration can go here 48 | //********** 49 | 50 | display.begin(); 51 | display.setRotation(3); 52 | display.fillScreen(ILI9341_BLACK); 53 | display.setTextColor(ILI9341_YELLOW, ILI9341_BLACK); 54 | display.setTextSize(4); 55 | 56 | Serial.begin(115200); 57 | 58 | // wait up to 1 second for USB serial port 59 | int startTime = millis(); 60 | while (!Serial && (millis() - startTime < 10000)); 61 | 62 | Serial.println("Switching to low power mode - say good-bye to USB :)"); 63 | delay(10); 64 | } 65 | 66 | void loop() 67 | { 68 | char scale = 'C'; 69 | 70 | // REDUCED_CPU_BLOCK needs a SnoozeBlock passed to it 71 | // so we pass a dummy SnoozeBlock with no Drivers installed. 72 | SnoozeBlock dummyConfig; 73 | 74 | REDUCED_CPU_BLOCK(dummyConfig) { 75 | 76 | // need to call begin again to reset voltage reference 77 | InternalTemperature.begin(true); 78 | 79 | while (1) { 80 | // print to TFT 81 | display.setCursor(40,20); 82 | 83 | if (useCelsius) { 84 | display.print(InternalTemperature.readTemperatureC(), 1); 85 | } else { 86 | display.print(InternalTemperature.readTemperatureF(), 1); 87 | scale = 'F'; 88 | } 89 | 90 | display.print((char)247); // degree symbol 91 | display.println(scale); 92 | 93 | // wait 10 seconds 94 | delay(10000); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=InternalTemperature 2 | version=2.1.1a 3 | author=LAtimes2 4 | maintainer=LAtimes2 5 | sentence=Teensy internal CPU temperatures 6 | paragraph=Read temperature of all versions of Teensy. Attach functions to high and low temperature alarms. 7 | category=Sensors 8 | url=https://github.com/LAtimes2/InternalTemperature 9 | architectures=* 10 | --------------------------------------------------------------------------------