ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
adc_sensor_esp32.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
3#include "adc_sensor.h"
4#include "esphome/core/log.h"
5#include <cinttypes>
6
7namespace esphome {
8namespace adc {
9
10static const char *const TAG = "adc.esp32";
11
12adc_oneshot_unit_handle_t ADCSensor::shared_adc_handles[2] = {nullptr, nullptr};
13
14const LogString *attenuation_to_str(adc_atten_t attenuation) {
15 switch (attenuation) {
16 case ADC_ATTEN_DB_0:
17 return LOG_STR("0 dB");
18 case ADC_ATTEN_DB_2_5:
19 return LOG_STR("2.5 dB");
20 case ADC_ATTEN_DB_6:
21 return LOG_STR("6 dB");
22 case ADC_ATTEN_DB_12_COMPAT:
23 return LOG_STR("12 dB");
24 default:
25 return LOG_STR("Unknown Attenuation");
26 }
27}
28
29const LogString *adc_unit_to_str(adc_unit_t unit) {
30 switch (unit) {
31 case ADC_UNIT_1:
32 return LOG_STR("ADC1");
33 case ADC_UNIT_2:
34 return LOG_STR("ADC2");
35 default:
36 return LOG_STR("Unknown ADC Unit");
37 }
38}
39
41 // Check if another sensor already initialized this ADC unit
42 if (ADCSensor::shared_adc_handles[this->adc_unit_] == nullptr) {
43 adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize
44 init_config.unit_id = this->adc_unit_;
45 init_config.ulp_mode = ADC_ULP_MODE_DISABLE;
46#if USE_ESP32_VARIANT_ESP32C2 || USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || \
47 USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
48 init_config.clk_src = ADC_DIGI_CLK_SRC_DEFAULT;
49#endif // USE_ESP32_VARIANT_ESP32C2 || USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 ||
50 // USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
51 esp_err_t err = adc_oneshot_new_unit(&init_config, &ADCSensor::shared_adc_handles[this->adc_unit_]);
52 if (err != ESP_OK) {
53 ESP_LOGE(TAG, "Error initializing %s: %d", LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), err);
54 this->mark_failed();
55 return;
56 }
57 }
58 this->adc_handle_ = ADCSensor::shared_adc_handles[this->adc_unit_];
59
61
62 adc_oneshot_chan_cfg_t config = {
63 .atten = this->attenuation_,
64 .bitwidth = ADC_BITWIDTH_DEFAULT,
65 };
66 esp_err_t err = adc_oneshot_config_channel(this->adc_handle_, this->channel_, &config);
67 if (err != ESP_OK) {
68 ESP_LOGE(TAG, "Error configuring channel: %d", err);
69 this->mark_failed();
70 return;
71 }
72 this->setup_flags_.config_complete = true;
73
74 // Initialize ADC calibration
75 if (this->calibration_handle_ == nullptr) {
76 adc_cali_handle_t handle = nullptr;
77
78#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
79 USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
80 // RISC-V variants (except C2) and S3 use curve fitting calibration
81 adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first
82#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
83 cali_config.chan = this->channel_;
84#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
85 cali_config.unit_id = this->adc_unit_;
86 cali_config.atten = this->attenuation_;
87 cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
88
89 err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
90 if (err == ESP_OK) {
91 this->calibration_handle_ = handle;
93 ESP_LOGV(TAG, "Using curve fitting calibration");
94 } else {
95 ESP_LOGW(TAG, "Curve fitting calibration failed with error %d, will use uncalibrated readings", err);
97 }
98#else // ESP32, ESP32-S2, and ESP32-C2 use line fitting calibration
99 adc_cali_line_fitting_config_t cali_config = {
100 .unit_id = this->adc_unit_,
101 .atten = this->attenuation_,
102 .bitwidth = ADC_BITWIDTH_DEFAULT,
103#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
104 .default_vref = 1100, // Default reference voltage in mV
105#endif // !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
106 };
107 err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
108 if (err == ESP_OK) {
109 this->calibration_handle_ = handle;
111 ESP_LOGV(TAG, "Using line fitting calibration");
112 } else {
113 ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err);
115 }
116#endif // ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
117 }
118
119 this->setup_flags_.init_complete = true;
120}
121
123 LOG_SENSOR("", "ADC Sensor", this);
124 LOG_PIN(" Pin: ", this->pin_);
125 ESP_LOGCONFIG(
126 TAG,
127 " Channel: %d\n"
128 " Unit: %s\n"
129 " Attenuation: %s\n"
130 " Samples: %i\n"
131 " Sampling mode: %s\n"
132 " Setup Status:\n"
133 " Handle Init: %s\n"
134 " Config: %s\n"
135 " Calibration: %s\n"
136 " Overall Init: %s",
137 this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)),
138 this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_,
139 LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)),
140 this->setup_flags_.handle_init_complete ? "OK" : "FAILED", this->setup_flags_.config_complete ? "OK" : "FAILED",
141 this->setup_flags_.calibration_complete ? "OK" : "FAILED", this->setup_flags_.init_complete ? "OK" : "FAILED");
142
143 LOG_UPDATE_INTERVAL(this);
144}
145
147 if (this->autorange_) {
148 return this->sample_autorange_();
149 } else {
150 return this->sample_fixed_attenuation_();
151 }
152}
153
155 auto aggr = Aggregator<uint32_t>(this->sampling_mode_);
156
157 for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
158 int raw;
159 esp_err_t err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
160
161 if (err != ESP_OK) {
162 ESP_LOGW(TAG, "ADC read failed with error %d", err);
163 continue;
164 }
165
166 if (raw == -1) {
167 ESP_LOGW(TAG, "Invalid ADC reading");
168 continue;
169 }
170
171 aggr.add_sample(raw);
172 }
173
174 uint32_t final_value = aggr.aggregate();
175
176 if (this->output_raw_) {
177 return final_value;
178 }
179
180 if (this->calibration_handle_ != nullptr) {
181 int voltage_mv;
182 esp_err_t err = adc_cali_raw_to_voltage(this->calibration_handle_, final_value, &voltage_mv);
183 if (err == ESP_OK) {
184 return voltage_mv / 1000.0f;
185 } else {
186 ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err);
187 if (this->calibration_handle_ != nullptr) {
188#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
189 USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
190 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
191#else // Other ESP32 variants use line fitting calibration
192 adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
193#endif // ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
194 this->calibration_handle_ = nullptr;
195 }
196 }
197 }
198
199 return final_value * 3.3f / 4095.0f;
200}
201
203 // Auto-range mode
204 auto read_atten = [this](adc_atten_t atten) -> std::pair<int, float> {
205 // First reconfigure the attenuation for this reading
206 adc_oneshot_chan_cfg_t config = {
207 .atten = atten,
208 .bitwidth = ADC_BITWIDTH_DEFAULT,
209 };
210
211 esp_err_t err = adc_oneshot_config_channel(this->adc_handle_, this->channel_, &config);
212
213 if (err != ESP_OK) {
214 ESP_LOGW(TAG, "Error configuring ADC channel for autorange: %d", err);
215 return {-1, 0.0f};
216 }
217
218 // Need to recalibrate for the new attenuation
219 if (this->calibration_handle_ != nullptr) {
220 // Delete old calibration handle
221#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
222 USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
223 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
224#else
225 adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
226#endif
227 this->calibration_handle_ = nullptr;
228 }
229
230 // Create new calibration handle for this attenuation
231 adc_cali_handle_t handle = nullptr;
232
233#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
234 USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
235 adc_cali_curve_fitting_config_t cali_config = {};
236#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
237 cali_config.chan = this->channel_;
238#endif
239 cali_config.unit_id = this->adc_unit_;
240 cali_config.atten = atten;
241 cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
242
243 err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
244 ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
245 (err == ESP_OK) ? "SUCCESS" : "FAILED", err);
246#else
247 adc_cali_line_fitting_config_t cali_config = {
248 .unit_id = this->adc_unit_,
249 .atten = atten,
250 .bitwidth = ADC_BITWIDTH_DEFAULT,
251#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
252 .default_vref = 1100,
253#endif
254 };
255 err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
256 ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
257 (err == ESP_OK) ? "SUCCESS" : "FAILED", err);
258#endif
259
260 int raw;
261 err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
262 ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
263 (err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
264
265 if (err != ESP_OK) {
266 ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
267 if (handle != nullptr) {
268#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
269 USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
270 adc_cali_delete_scheme_curve_fitting(handle);
271#else
272 adc_cali_delete_scheme_line_fitting(handle);
273#endif
274 }
275 return {-1, 0.0f};
276 }
277
278 float voltage = 0.0f;
279 if (handle != nullptr) {
280 int voltage_mv;
281 err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
282 if (err == ESP_OK) {
283 voltage = voltage_mv / 1000.0f;
284 ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
285 } else {
286 voltage = raw * 3.3f / 4095.0f;
287 ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
288 }
289 // Clean up calibration handle
290#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
291 USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
292 adc_cali_delete_scheme_curve_fitting(handle);
293#else
294 adc_cali_delete_scheme_line_fitting(handle);
295#endif
296 } else {
297 voltage = raw * 3.3f / 4095.0f;
298 ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
299 }
300
301 return {raw, voltage};
302 };
303
304 auto [raw12, mv12] = read_atten(ADC_ATTEN_DB_12);
305 if (raw12 == -1) {
306 ESP_LOGE(TAG, "Failed to read ADC in autorange mode");
307 return NAN;
308 }
309
310 int raw6 = 4095, raw2 = 4095, raw0 = 4095;
311 float mv6 = 0, mv2 = 0, mv0 = 0;
312
313 if (raw12 < 4095) {
314 auto [raw6_val, mv6_val] = read_atten(ADC_ATTEN_DB_6);
315 raw6 = raw6_val;
316 mv6 = mv6_val;
317
318 if (raw6 < 4095 && raw6 != -1) {
319 auto [raw2_val, mv2_val] = read_atten(ADC_ATTEN_DB_2_5);
320 raw2 = raw2_val;
321 mv2 = mv2_val;
322
323 if (raw2 < 4095 && raw2 != -1) {
324 auto [raw0_val, mv0_val] = read_atten(ADC_ATTEN_DB_0);
325 raw0 = raw0_val;
326 mv0 = mv0_val;
327 }
328 }
329 }
330
331 if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw12 == -1) {
332 return NAN;
333 }
334
335 const int adc_half = 2048;
336 const uint32_t c12 = std::min(raw12, adc_half);
337
338 const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
339 const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0; // Clamp to prevent underflow
340
341 const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
342 const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0; // Clamp to prevent underflow
343
344 const uint32_t c0 = std::min(4095 - raw0, adc_half);
345 const uint32_t csum = c12 + c6 + c2 + c0;
346
347 ESP_LOGVV(TAG, "Autorange summary:");
348 ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
349 ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
350 ESP_LOGVV(TAG, " Coefficients: c12=%" PRIu32 ", c6=%" PRIu32 ", c2=%" PRIu32 ", c0=%" PRIu32 ", sum=%" PRIu32, c12,
351 c6, c2, c0, csum);
352
353 if (csum == 0) {
354 ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
355 return NAN;
356 }
357
358 const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
359 ESP_LOGV(TAG,
360 "Autorange final: (%.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 ")/%" PRIu32
361 " = %.6fV",
362 mv12, c12, mv6, c6, mv2, c2, mv0, c0, csum, final_result);
363
364 return final_result;
365}
366
367} // namespace adc
368} // namespace esphome
369
370#endif // USE_ESP32
uint8_t raw[35]
Definition bl0939.h:0
void mark_failed()
Mark this component as failed.
struct esphome::adc::ADCSensor::SetupFlags setup_flags_
float sample() override
Perform a single ADC sampling operation and return the measured value.
adc_oneshot_unit_handle_t adc_handle_
Definition adc_sensor.h:141
void setup() override
Set up the ADC sensor by initializing hardware and calibration parameters.
InternalGPIOPin * pin_
Definition adc_sensor.h:134
void dump_config() override
Output the configuration details of the ADC sensor for debugging purposes.
SamplingMode sampling_mode_
Definition adc_sensor.h:135
static adc_oneshot_unit_handle_t shared_adc_handles[2]
Definition adc_sensor.h:153
adc_channel_t channel_
Definition adc_sensor.h:144
adc_cali_handle_t calibration_handle_
Definition adc_sensor.h:142
const LogString * attenuation_to_str(adc_atten_t attenuation)
const LogString * sampling_mode_to_str(SamplingMode mode)
const LogString * adc_unit_to_str(adc_unit_t unit)
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
static void uint32_t