ESPHome 2026.9.0-dev
Loading...
Searching...
No Matches
internal_temperature_rp2.cpp
Go to the documentation of this file.
1#ifdef USE_RP2
2
3#include "esphome/core/log.h"
5
6#include <cmath>
7#include <hardware/adc.h>
8#include <pico/time.h>
9
10// The RP2 variant headers (pulled in transitively by Arduino.h) define
11// ADC_RESOLUTION as the pin-level ADC bit count, which would be substituted
12// into the constant below. Nothing here uses the Arduino definition, so drop
13// it for this file. Not restored with pop_macro: the uses below would then be
14// substituted again.
15#undef ADC_RESOLUTION
16
18
19static const char *const TAG = "internal_temperature";
20
21// The on-die temperature sensor sits on the last ADC channel: input 4 on RP2040
22// and RP2350A, but input 8 on RP2350B, which has eight external channels rather
23// than four.
24//
25// This deliberately does not use the SDK's ADC_TEMPERATURE_CHANNEL_NUM. That
26// derives from NUM_ADC_CHANNELS, which <pico.h> settles from a board header, and
27// arduino-pico supplies a fixed B-die one for every RP2350 build. The real die
28// is only declared later, by the variant's pins_arduino.h, so the SDK constant
29// reads 8 on A-die boards. PICO_RP2350A itself is correct by the time this file
30// is compiled, on both arduino-pico and pico-sdk builds.
31#if defined(PICO_RP2350) && !defined(PICO_RP2350A)
32#error "PICO_RP2350A is not defined, so the RP2350 die is unknown and the temperature ADC channel cannot be chosen"
33#endif
34#if defined(PICO_RP2350) && !PICO_RP2350A
35static constexpr uint8_t TEMPERATURE_ADC_INPUT = 8;
36#else
37static constexpr uint8_t TEMPERATURE_ADC_INPUT = 4;
38#endif
39static constexpr float ADC_VREF = 3.3f;
40static constexpr float ADC_RESOLUTION = 4096.0f; // 12-bit
41// RP2040 datasheet 4.9.5 / RP2350 datasheet 12.4.6: T = 27 - (V - 0.706) / 0.001721
42static constexpr float TEMPERATURE_AT_REFERENCE = 27.0f;
43static constexpr float REFERENCE_VOLTAGE = 0.706f;
44static constexpr float VOLTS_PER_DEGREE = 0.001721f;
45// The sensor is powered down again after each read, so every conversion is the
46// first one after enabling. Let the bias circuitry settle first, matching what
47// the adc component does for its own temperature readings.
48static constexpr uint32_t SETTLE_TIME_US = 1000;
49
50static float read_internal_temperature() {
51 // adc_init() resets the ADC block, so this runs at most once for this
52 // component. The adc component guards its own adc_init() the same way, so a
53 // redundant reset is still possible when both are used. That is harmless
54 // because both re-select their input on every read.
55 static bool adc_ready = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
56 if (!adc_ready) {
57 adc_init();
58 adc_ready = true;
59 }
60
61 adc_set_temp_sensor_enabled(true);
62 busy_wait_us(SETTLE_TIME_US);
63 adc_select_input(TEMPERATURE_ADC_INPUT);
64 const uint16_t raw = adc_read();
65 adc_set_temp_sensor_enabled(false);
66
67 const float voltage = raw * (ADC_VREF / ADC_RESOLUTION);
68 return TEMPERATURE_AT_REFERENCE - (voltage - REFERENCE_VOLTAGE) / VOLTS_PER_DEGREE;
69}
70
72 float temperature = NAN;
73 bool success = false;
74
75 temperature = read_internal_temperature();
76 success = (temperature != 0.0f);
77
78 if (success && std::isfinite(temperature)) {
79 this->publish_state(temperature);
80 } else {
81 ESP_LOGD(TAG, "Ignoring invalid temperature (success=%d, value=%.1f)", success, temperature);
82 if (!this->has_state()) {
83 this->publish_state(NAN);
84 }
85 }
86}
87
88} // namespace esphome::internal_temperature
89
90#endif // USE_RP2
uint8_t raw[35]
Definition bl0939.h:0
bool has_state() const
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
static void uint32_t
uint16_t temperature
Definition sun_gtil2.cpp:12