diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 10b7df8638..e1cb6a9e01 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -51,82 +51,83 @@ SAMPLING_MODES = { "max": sampling_mode.MAX, } -adc1_channel_t = cg.global_ns.enum("adc1_channel_t") -adc2_channel_t = cg.global_ns.enum("adc2_channel_t") +adc_unit_t = cg.global_ns.enum("adc_unit_t", is_class=True) + +adc_channel_t = cg.global_ns.enum("adc_channel_t", is_class=True) # pin to adc1 channel mapping # https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h VARIANT_ESP32: { - 36: adc1_channel_t.ADC1_CHANNEL_0, - 37: adc1_channel_t.ADC1_CHANNEL_1, - 38: adc1_channel_t.ADC1_CHANNEL_2, - 39: adc1_channel_t.ADC1_CHANNEL_3, - 32: adc1_channel_t.ADC1_CHANNEL_4, - 33: adc1_channel_t.ADC1_CHANNEL_5, - 34: adc1_channel_t.ADC1_CHANNEL_6, - 35: adc1_channel_t.ADC1_CHANNEL_7, + 36: adc_channel_t.ADC_CHANNEL_0, + 37: adc_channel_t.ADC_CHANNEL_1, + 38: adc_channel_t.ADC_CHANNEL_2, + 39: adc_channel_t.ADC_CHANNEL_3, + 32: adc_channel_t.ADC_CHANNEL_4, + 33: adc_channel_t.ADC_CHANNEL_5, + 34: adc_channel_t.ADC_CHANNEL_6, + 35: adc_channel_t.ADC_CHANNEL_7, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h VARIANT_ESP32C2: { - 0: adc1_channel_t.ADC1_CHANNEL_0, - 1: adc1_channel_t.ADC1_CHANNEL_1, - 2: adc1_channel_t.ADC1_CHANNEL_2, - 3: adc1_channel_t.ADC1_CHANNEL_3, - 4: adc1_channel_t.ADC1_CHANNEL_4, + 0: adc_channel_t.ADC_CHANNEL_0, + 1: adc_channel_t.ADC_CHANNEL_1, + 2: adc_channel_t.ADC_CHANNEL_2, + 3: adc_channel_t.ADC_CHANNEL_3, + 4: adc_channel_t.ADC_CHANNEL_4, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h VARIANT_ESP32C3: { - 0: adc1_channel_t.ADC1_CHANNEL_0, - 1: adc1_channel_t.ADC1_CHANNEL_1, - 2: adc1_channel_t.ADC1_CHANNEL_2, - 3: adc1_channel_t.ADC1_CHANNEL_3, - 4: adc1_channel_t.ADC1_CHANNEL_4, + 0: adc_channel_t.ADC_CHANNEL_0, + 1: adc_channel_t.ADC_CHANNEL_1, + 2: adc_channel_t.ADC_CHANNEL_2, + 3: adc_channel_t.ADC_CHANNEL_3, + 4: adc_channel_t.ADC_CHANNEL_4, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h VARIANT_ESP32C6: { - 0: adc1_channel_t.ADC1_CHANNEL_0, - 1: adc1_channel_t.ADC1_CHANNEL_1, - 2: adc1_channel_t.ADC1_CHANNEL_2, - 3: adc1_channel_t.ADC1_CHANNEL_3, - 4: adc1_channel_t.ADC1_CHANNEL_4, - 5: adc1_channel_t.ADC1_CHANNEL_5, - 6: adc1_channel_t.ADC1_CHANNEL_6, + 0: adc_channel_t.ADC_CHANNEL_0, + 1: adc_channel_t.ADC_CHANNEL_1, + 2: adc_channel_t.ADC_CHANNEL_2, + 3: adc_channel_t.ADC_CHANNEL_3, + 4: adc_channel_t.ADC_CHANNEL_4, + 5: adc_channel_t.ADC_CHANNEL_5, + 6: adc_channel_t.ADC_CHANNEL_6, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h VARIANT_ESP32H2: { - 1: adc1_channel_t.ADC1_CHANNEL_0, - 2: adc1_channel_t.ADC1_CHANNEL_1, - 3: adc1_channel_t.ADC1_CHANNEL_2, - 4: adc1_channel_t.ADC1_CHANNEL_3, - 5: adc1_channel_t.ADC1_CHANNEL_4, + 1: adc_channel_t.ADC_CHANNEL_0, + 2: adc_channel_t.ADC_CHANNEL_1, + 3: adc_channel_t.ADC_CHANNEL_2, + 4: adc_channel_t.ADC_CHANNEL_3, + 5: adc_channel_t.ADC_CHANNEL_4, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h VARIANT_ESP32S2: { - 1: adc1_channel_t.ADC1_CHANNEL_0, - 2: adc1_channel_t.ADC1_CHANNEL_1, - 3: adc1_channel_t.ADC1_CHANNEL_2, - 4: adc1_channel_t.ADC1_CHANNEL_3, - 5: adc1_channel_t.ADC1_CHANNEL_4, - 6: adc1_channel_t.ADC1_CHANNEL_5, - 7: adc1_channel_t.ADC1_CHANNEL_6, - 8: adc1_channel_t.ADC1_CHANNEL_7, - 9: adc1_channel_t.ADC1_CHANNEL_8, - 10: adc1_channel_t.ADC1_CHANNEL_9, + 1: adc_channel_t.ADC_CHANNEL_0, + 2: adc_channel_t.ADC_CHANNEL_1, + 3: adc_channel_t.ADC_CHANNEL_2, + 4: adc_channel_t.ADC_CHANNEL_3, + 5: adc_channel_t.ADC_CHANNEL_4, + 6: adc_channel_t.ADC_CHANNEL_5, + 7: adc_channel_t.ADC_CHANNEL_6, + 8: adc_channel_t.ADC_CHANNEL_7, + 9: adc_channel_t.ADC_CHANNEL_8, + 10: adc_channel_t.ADC_CHANNEL_9, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h VARIANT_ESP32S3: { - 1: adc1_channel_t.ADC1_CHANNEL_0, - 2: adc1_channel_t.ADC1_CHANNEL_1, - 3: adc1_channel_t.ADC1_CHANNEL_2, - 4: adc1_channel_t.ADC1_CHANNEL_3, - 5: adc1_channel_t.ADC1_CHANNEL_4, - 6: adc1_channel_t.ADC1_CHANNEL_5, - 7: adc1_channel_t.ADC1_CHANNEL_6, - 8: adc1_channel_t.ADC1_CHANNEL_7, - 9: adc1_channel_t.ADC1_CHANNEL_8, - 10: adc1_channel_t.ADC1_CHANNEL_9, + 1: adc_channel_t.ADC_CHANNEL_0, + 2: adc_channel_t.ADC_CHANNEL_1, + 3: adc_channel_t.ADC_CHANNEL_2, + 4: adc_channel_t.ADC_CHANNEL_3, + 5: adc_channel_t.ADC_CHANNEL_4, + 6: adc_channel_t.ADC_CHANNEL_5, + 7: adc_channel_t.ADC_CHANNEL_6, + 8: adc_channel_t.ADC_CHANNEL_7, + 9: adc_channel_t.ADC_CHANNEL_8, + 10: adc_channel_t.ADC_CHANNEL_9, }, } @@ -135,24 +136,24 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h VARIANT_ESP32: { - 4: adc2_channel_t.ADC2_CHANNEL_0, - 0: adc2_channel_t.ADC2_CHANNEL_1, - 2: adc2_channel_t.ADC2_CHANNEL_2, - 15: adc2_channel_t.ADC2_CHANNEL_3, - 13: adc2_channel_t.ADC2_CHANNEL_4, - 12: adc2_channel_t.ADC2_CHANNEL_5, - 14: adc2_channel_t.ADC2_CHANNEL_6, - 27: adc2_channel_t.ADC2_CHANNEL_7, - 25: adc2_channel_t.ADC2_CHANNEL_8, - 26: adc2_channel_t.ADC2_CHANNEL_9, + 4: adc_channel_t.ADC_CHANNEL_0, + 0: adc_channel_t.ADC_CHANNEL_1, + 2: adc_channel_t.ADC_CHANNEL_2, + 15: adc_channel_t.ADC_CHANNEL_3, + 13: adc_channel_t.ADC_CHANNEL_4, + 12: adc_channel_t.ADC_CHANNEL_5, + 14: adc_channel_t.ADC_CHANNEL_6, + 27: adc_channel_t.ADC_CHANNEL_7, + 25: adc_channel_t.ADC_CHANNEL_8, + 26: adc_channel_t.ADC_CHANNEL_9, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h VARIANT_ESP32C2: { - 5: adc2_channel_t.ADC2_CHANNEL_0, + 5: adc_channel_t.ADC_CHANNEL_0, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h VARIANT_ESP32C3: { - 5: adc2_channel_t.ADC2_CHANNEL_0, + 5: adc_channel_t.ADC_CHANNEL_0, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h VARIANT_ESP32C6: {}, # no ADC2 @@ -160,29 +161,29 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { VARIANT_ESP32H2: {}, # no ADC2 # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h VARIANT_ESP32S2: { - 11: adc2_channel_t.ADC2_CHANNEL_0, - 12: adc2_channel_t.ADC2_CHANNEL_1, - 13: adc2_channel_t.ADC2_CHANNEL_2, - 14: adc2_channel_t.ADC2_CHANNEL_3, - 15: adc2_channel_t.ADC2_CHANNEL_4, - 16: adc2_channel_t.ADC2_CHANNEL_5, - 17: adc2_channel_t.ADC2_CHANNEL_6, - 18: adc2_channel_t.ADC2_CHANNEL_7, - 19: adc2_channel_t.ADC2_CHANNEL_8, - 20: adc2_channel_t.ADC2_CHANNEL_9, + 11: adc_channel_t.ADC_CHANNEL_0, + 12: adc_channel_t.ADC_CHANNEL_1, + 13: adc_channel_t.ADC_CHANNEL_2, + 14: adc_channel_t.ADC_CHANNEL_3, + 15: adc_channel_t.ADC_CHANNEL_4, + 16: adc_channel_t.ADC_CHANNEL_5, + 17: adc_channel_t.ADC_CHANNEL_6, + 18: adc_channel_t.ADC_CHANNEL_7, + 19: adc_channel_t.ADC_CHANNEL_8, + 20: adc_channel_t.ADC_CHANNEL_9, }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h VARIANT_ESP32S3: { - 11: adc2_channel_t.ADC2_CHANNEL_0, - 12: adc2_channel_t.ADC2_CHANNEL_1, - 13: adc2_channel_t.ADC2_CHANNEL_2, - 14: adc2_channel_t.ADC2_CHANNEL_3, - 15: adc2_channel_t.ADC2_CHANNEL_4, - 16: adc2_channel_t.ADC2_CHANNEL_5, - 17: adc2_channel_t.ADC2_CHANNEL_6, - 18: adc2_channel_t.ADC2_CHANNEL_7, - 19: adc2_channel_t.ADC2_CHANNEL_8, - 20: adc2_channel_t.ADC2_CHANNEL_9, + 11: adc_channel_t.ADC_CHANNEL_0, + 12: adc_channel_t.ADC_CHANNEL_1, + 13: adc_channel_t.ADC_CHANNEL_2, + 14: adc_channel_t.ADC_CHANNEL_3, + 15: adc_channel_t.ADC_CHANNEL_4, + 16: adc_channel_t.ADC_CHANNEL_5, + 17: adc_channel_t.ADC_CHANNEL_6, + 18: adc_channel_t.ADC_CHANNEL_7, + 19: adc_channel_t.ADC_CHANNEL_8, + 20: adc_channel_t.ADC_CHANNEL_9, }, } diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 28dfd2262c..7b1f69e454 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -3,12 +3,15 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #ifdef USE_ESP32 -#include -#include "driver/adc.h" -#endif // USE_ESP32 +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" +#include "esp_adc/adc_oneshot.h" +#include "hal/adc_types.h" // This defines ADC_CHANNEL_MAX +#endif // USE_ESP32 namespace esphome { namespace adc { @@ -49,33 +52,72 @@ class Aggregator { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { public: + /// Update the sensor's state by reading the current ADC value. + /// This method is called periodically based on the update interval. + void update() override; + + /// Set up the ADC sensor by initializing hardware and calibration parameters. + /// This method is called once during device initialization. + void setup() override; + + /// Output the configuration details of the ADC sensor for debugging purposes. + /// This method is called during the ESPHome setup process to log the configuration. + void dump_config() override; + + /// Return the setup priority for this component. + /// Components with higher priority are initialized earlier during setup. + /// @return A float representing the setup priority. + float get_setup_priority() const override; + + /// Set the GPIO pin to be used by the ADC sensor. + /// @param pin Pointer to an InternalGPIOPin representing the ADC input pin. + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } + + /// Enable or disable the output of raw ADC values (unprocessed data). + /// @param output_raw Boolean indicating whether to output raw ADC values (true) or processed values (false). + void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; } + + /// Set the number of samples to be taken for ADC readings to improve accuracy. + /// A higher sample count reduces noise but increases the reading time. + /// @param sample_count The number of samples (e.g., 1, 4, 8). + void set_sample_count(uint8_t sample_count); + + /// Set the sampling mode for how multiple ADC samples are combined into a single measurement. + /// + /// When multiple samples are taken (controlled by set_sample_count), they can be combined + /// in one of three ways: + /// - SamplingMode::AVG: Compute the average (default) + /// - SamplingMode::MIN: Use the lowest sample value + /// - SamplingMode::MAX: Use the highest sample value + /// @param sampling_mode The desired sampling mode to use for aggregating ADC samples. + void set_sampling_mode(SamplingMode sampling_mode); + + /// Perform a single ADC sampling operation and return the measured value. + /// This function handles raw readings, calibration, and averaging as needed. + /// @return The sampled value as a float. + float sample() override; + #ifdef USE_ESP32 - /// Set the attenuation for this pin. Only available on the ESP32. + /// Set the ADC attenuation level to adjust the input voltage range. + /// This determines how the ADC interprets input voltages, allowing for greater precision + /// or the ability to measure higher voltages depending on the chosen attenuation level. + /// @param attenuation The desired ADC attenuation level (e.g., ADC_ATTEN_DB_0, ADC_ATTEN_DB_11). void set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } - void set_channel1(adc1_channel_t channel) { - this->channel1_ = channel; - this->channel2_ = ADC2_CHANNEL_MAX; - } - void set_channel2(adc2_channel_t channel) { - this->channel2_ = channel; - this->channel1_ = ADC1_CHANNEL_MAX; + + /// Configure the ADC to use a specific channel on ADC1. + /// This sets the channel for single-shot or continuous ADC measurements. + /// @param channel The ADC1 channel to configure, such as ADC_CHANNEL_0, ADC_CHANNEL_3, etc. + void set_channel(adc_unit_t unit, adc_channel_t channel) { + this->adc_unit_ = unit; + this->channel_ = channel; } + + /// Set whether autoranging should be enabled for the ADC. + /// Autoranging automatically adjusts the attenuation level to handle a wide range of input voltages. + /// @param autorange Boolean indicating whether to enable autoranging. void set_autorange(bool autorange) { this->autorange_ = autorange; } #endif // USE_ESP32 - /// Update ADC values - void update() override; - /// Setup ADC - void setup() override; - void dump_config() override; - /// `HARDWARE_LATE` setup priority - float get_setup_priority() const override; - void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } - void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; } - void set_sample_count(uint8_t sample_count); - void set_sampling_mode(SamplingMode sampling_mode); - float sample() override; - #ifdef USE_ESP8266 std::string unique_id() override; #endif // USE_ESP8266 @@ -90,17 +132,28 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage InternalGPIOPin *pin_; SamplingMode sampling_mode_{SamplingMode::AVG}; +#ifdef USE_ESP32 + float sample_autorange_(); + float sample_fixed_attenuation_(); + bool autorange_{false}; + adc_oneshot_unit_handle_t adc_handle_{nullptr}; + adc_cali_handle_t calibration_handle_{nullptr}; + adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + adc_channel_t channel_; + adc_unit_t adc_unit_; + struct SetupFlags { + uint8_t init_complete : 1; + uint8_t config_complete : 1; + uint8_t handle_init_complete : 1; + uint8_t calibration_complete : 1; + uint8_t reserved : 4; + } setup_flags_{}; + static adc_oneshot_unit_handle_t shared_adc_handles[2]; +#endif // USE_ESP32 + #ifdef USE_RP2040 bool is_temperature_{false}; #endif // USE_RP2040 - -#ifdef USE_ESP32 - adc_atten_t attenuation_{ADC_ATTEN_DB_0}; - adc1_channel_t channel1_{ADC1_CHANNEL_MAX}; - adc2_channel_t channel2_{ADC2_CHANNEL_MAX}; - bool autorange_{false}; - esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; -#endif // USE_ESP32 }; } // namespace adc diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index ed1f3329ab..f38d339304 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -8,145 +8,308 @@ namespace adc { static const char *const TAG = "adc.esp32"; -static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast(ADC_WIDTH_MAX - 1); +adc_oneshot_unit_handle_t ADCSensor::shared_adc_handles[2] = {nullptr, nullptr}; -#ifndef SOC_ADC_RTC_MAX_BITWIDTH -#if USE_ESP32_VARIANT_ESP32S2 -static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13; -#else -static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12; -#endif // USE_ESP32_VARIANT_ESP32S2 -#endif // SOC_ADC_RTC_MAX_BITWIDTH - -static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; -static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; - -void ADCSensor::setup() { - ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str()); - - if (this->channel1_ != ADC1_CHANNEL_MAX) { - adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); - if (!this->autorange_) { - adc1_config_channel_atten(this->channel1_, this->attenuation_); - } - } else if (this->channel2_ != ADC2_CHANNEL_MAX) { - if (!this->autorange_) { - adc2_config_channel_atten(this->channel2_, this->attenuation_); - } - } - - for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) { - auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; - auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, - 1100, // default vref - &this->cal_characteristics_[i]); - switch (cal_value) { - case ESP_ADC_CAL_VAL_EFUSE_VREF: - ESP_LOGV(TAG, "Using eFuse Vref for calibration"); - break; - case ESP_ADC_CAL_VAL_EFUSE_TP: - ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration"); - break; - case ESP_ADC_CAL_VAL_DEFAULT_VREF: - default: - break; - } +const LogString *attenuation_to_str(adc_atten_t attenuation) { + switch (attenuation) { + case ADC_ATTEN_DB_0: + return LOG_STR("0 dB"); + case ADC_ATTEN_DB_2_5: + return LOG_STR("2.5 dB"); + case ADC_ATTEN_DB_6: + return LOG_STR("6 dB"); + case ADC_ATTEN_DB_12_COMPAT: + return LOG_STR("12 dB"); + default: + return LOG_STR("Unknown Attenuation"); } } -void ADCSensor::dump_config() { - static const char *const ATTEN_AUTO_STR = "auto"; - static const char *const ATTEN_0DB_STR = "0 db"; - static const char *const ATTEN_2_5DB_STR = "2.5 db"; - static const char *const ATTEN_6DB_STR = "6 db"; - static const char *const ATTEN_12DB_STR = "12 db"; - const char *atten_str = ATTEN_AUTO_STR; +const LogString *adc_unit_to_str(adc_unit_t unit) { + switch (unit) { + case ADC_UNIT_1: + return LOG_STR("ADC1"); + case ADC_UNIT_2: + return LOG_STR("ADC2"); + default: + return LOG_STR("Unknown ADC Unit"); + } +} - LOG_SENSOR("", "ADC Sensor", this); - LOG_PIN(" Pin: ", this->pin_); - - if (!this->autorange_) { - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - atten_str = ATTEN_0DB_STR; - break; - case ADC_ATTEN_DB_2_5: - atten_str = ATTEN_2_5DB_STR; - break; - case ADC_ATTEN_DB_6: - atten_str = ATTEN_6DB_STR; - break; - case ADC_ATTEN_DB_12_COMPAT: - atten_str = ATTEN_12DB_STR; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; +void ADCSensor::setup() { + ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str()); + // Check if another sensor already initialized this ADC unit + if (ADCSensor::shared_adc_handles[this->adc_unit_] == nullptr) { + adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize + init_config.unit_id = this->adc_unit_; + init_config.ulp_mode = ADC_ULP_MODE_DISABLE; +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32H2 + init_config.clk_src = ADC_DIGI_CLK_SRC_DEFAULT; +#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32H2 + esp_err_t err = adc_oneshot_new_unit(&init_config, &ADCSensor::shared_adc_handles[this->adc_unit_]); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error initializing %s: %d", LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), err); + this->mark_failed(); + return; } } + this->adc_handle_ = ADCSensor::shared_adc_handles[this->adc_unit_]; + this->setup_flags_.handle_init_complete = true; + + adc_oneshot_chan_cfg_t config = { + .atten = this->attenuation_, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + esp_err_t err = adc_oneshot_config_channel(this->adc_handle_, this->channel_, &config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error configuring channel: %d", err); + this->mark_failed(); + return; + } + this->setup_flags_.config_complete = true; + + // Initialize ADC calibration + if (this->calibration_handle_ == nullptr) { + adc_cali_handle_t handle = nullptr; + esp_err_t err; + +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + // RISC-V variants and S3 use curve fitting calibration + adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + cali_config.chan = this->channel_; +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + cali_config.unit_id = this->adc_unit_; + cali_config.atten = this->attenuation_; + cali_config.bitwidth = ADC_BITWIDTH_DEFAULT; + + err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); + if (err == ESP_OK) { + this->calibration_handle_ = handle; + this->setup_flags_.calibration_complete = true; + ESP_LOGV(TAG, "Using curve fitting calibration"); + } else { + ESP_LOGW(TAG, "Curve fitting calibration failed with error %d, will use uncalibrated readings", err); + this->setup_flags_.calibration_complete = false; + } +#else // Other ESP32 variants use line fitting calibration + adc_cali_line_fitting_config_t cali_config = { + .unit_id = this->adc_unit_, + .atten = this->attenuation_, + .bitwidth = ADC_BITWIDTH_DEFAULT, +#if !defined(USE_ESP32_VARIANT_ESP32S2) + .default_vref = 1100, // Default reference voltage in mV +#endif // !defined(USE_ESP32_VARIANT_ESP32S2) + }; + err = adc_cali_create_scheme_line_fitting(&cali_config, &handle); + if (err == ESP_OK) { + this->calibration_handle_ = handle; + this->setup_flags_.calibration_complete = true; + ESP_LOGV(TAG, "Using line fitting calibration"); + } else { + ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err); + this->setup_flags_.calibration_complete = false; + } +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C6 || ESP32S3 || ESP32H2 + } + + this->setup_flags_.init_complete = true; +} + +void ADCSensor::dump_config() { + LOG_SENSOR("", "ADC Sensor", this); + LOG_PIN(" Pin: ", this->pin_); ESP_LOGCONFIG(TAG, - " Attenuation: %s\n" - " Samples: %i\n" + " Channel: %d\n" + " Unit: %s\n" + " Attenuation: %s\n" + " Samples: %i\n" " Sampling mode: %s", - atten_str, this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); + this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), + this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_, + LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); + + ESP_LOGCONFIG( + TAG, + " Setup Status:\n" + " Handle Init: %s\n" + " Config: %s\n" + " Calibration: %s\n" + " Overall Init: %s", + this->setup_flags_.handle_init_complete ? "OK" : "FAILED", this->setup_flags_.config_complete ? "OK" : "FAILED", + this->setup_flags_.calibration_complete ? "OK" : "FAILED", this->setup_flags_.init_complete ? "OK" : "FAILED"); + LOG_UPDATE_INTERVAL(this); } float ADCSensor::sample() { - if (!this->autorange_) { - auto aggr = Aggregator(this->sampling_mode_); + if (this->autorange_) { + return this->sample_autorange_(); + } else { + return this->sample_fixed_attenuation_(); + } +} - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - int raw = -1; - if (this->channel1_ != ADC1_CHANNEL_MAX) { - raw = adc1_get_raw(this->channel1_); - } else if (this->channel2_ != ADC2_CHANNEL_MAX) { - adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw); - } - if (raw == -1) { - return NAN; - } +float ADCSensor::sample_fixed_attenuation_() { + auto aggr = Aggregator(this->sampling_mode_); - aggr.add_sample(raw); + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + int raw; + esp_err_t err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw); + + if (err != ESP_OK) { + ESP_LOGW(TAG, "ADC read failed with error %d", err); + continue; } - if (this->output_raw_) { - return aggr.aggregate(); + + if (raw == -1) { + ESP_LOGW(TAG, "Invalid ADC reading"); + continue; } - uint32_t mv = - esp_adc_cal_raw_to_voltage(aggr.aggregate(), &this->cal_characteristics_[(int32_t) this->attenuation_]); - return mv / 1000.0f; + + aggr.add_sample(raw); } - int raw12 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX; + uint32_t final_value = aggr.aggregate(); - if (this->channel1_ != ADC1_CHANNEL_MAX) { - adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_12_COMPAT); - raw12 = adc1_get_raw(this->channel1_); - if (raw12 < ADC_MAX) { - adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_6); - raw6 = adc1_get_raw(this->channel1_); - if (raw6 < ADC_MAX) { - adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_2_5); - raw2 = adc1_get_raw(this->channel1_); - if (raw2 < ADC_MAX) { - adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_0); - raw0 = adc1_get_raw(this->channel1_); - } + if (this->output_raw_) { + return final_value; + } + + if (this->calibration_handle_ != nullptr) { + int voltage_mv; + esp_err_t err = adc_cali_raw_to_voltage(this->calibration_handle_, final_value, &voltage_mv); + if (err == ESP_OK) { + return voltage_mv / 1000.0f; + } else { + ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err); + if (this->calibration_handle_ != nullptr) { +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); +#else // Other ESP32 variants use line fitting calibration + adc_cali_delete_scheme_line_fitting(this->calibration_handle_); +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C6 || ESP32S3 || ESP32H2 + this->calibration_handle_ = nullptr; } } - } else if (this->channel2_ != ADC2_CHANNEL_MAX) { - adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_12_COMPAT); - adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw12); - if (raw12 < ADC_MAX) { - adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_6); - adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6); - if (raw6 < ADC_MAX) { - adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_2_5); - adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2); - if (raw2 < ADC_MAX) { - adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_0); - adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0); - } + } + + return final_value * 3.3f / 4095.0f; +} + +float ADCSensor::sample_autorange_() { + // Auto-range mode + auto read_atten = [this](adc_atten_t atten) -> std::pair { + // First reconfigure the attenuation for this reading + adc_oneshot_chan_cfg_t config = { + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + + esp_err_t err = adc_oneshot_config_channel(this->adc_handle_, this->channel_, &config); + + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error configuring ADC channel for autorange: %d", err); + return {-1, 0.0f}; + } + + // Need to recalibrate for the new attenuation + if (this->calibration_handle_ != nullptr) { + // Delete old calibration handle +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); +#else + adc_cali_delete_scheme_line_fitting(this->calibration_handle_); +#endif + this->calibration_handle_ = nullptr; + } + + // Create new calibration handle for this attenuation + adc_cali_handle_t handle = nullptr; + +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + adc_cali_curve_fitting_config_t cali_config = {}; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + cali_config.chan = this->channel_; +#endif + cali_config.unit_id = this->adc_unit_; + cali_config.atten = atten; + cali_config.bitwidth = ADC_BITWIDTH_DEFAULT; + + err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); +#else + adc_cali_line_fitting_config_t cali_config = { + .unit_id = this->adc_unit_, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, +#if !defined(USE_ESP32_VARIANT_ESP32S2) + .default_vref = 1100, +#endif + }; + err = adc_cali_create_scheme_line_fitting(&cali_config, &handle); +#endif + + int raw; + err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw); + + if (err != ESP_OK) { + ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); + if (handle != nullptr) { +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + adc_cali_delete_scheme_curve_fitting(handle); +#else + adc_cali_delete_scheme_line_fitting(handle); +#endif + } + return {-1, 0.0f}; + } + + float voltage = 0.0f; + if (handle != nullptr) { + int voltage_mv; + err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv); + if (err == ESP_OK) { + voltage = voltage_mv / 1000.0f; + } else { + voltage = raw * 3.3f / 4095.0f; + } + // Clean up calibration handle +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + adc_cali_delete_scheme_curve_fitting(handle); +#else + adc_cali_delete_scheme_line_fitting(handle); +#endif + } else { + voltage = raw * 3.3f / 4095.0f; + } + + return {raw, voltage}; + }; + + auto [raw12, mv12] = read_atten(ADC_ATTEN_DB_12); + if (raw12 == -1) { + ESP_LOGE(TAG, "Failed to read ADC in autorange mode"); + return NAN; + } + + int raw6 = 4095, raw2 = 4095, raw0 = 4095; + float mv6 = 0, mv2 = 0, mv0 = 0; + + if (raw12 < 4095) { + auto [raw6_val, mv6_val] = read_atten(ADC_ATTEN_DB_6); + raw6 = raw6_val; + mv6 = mv6_val; + + if (raw6 < 4095 && raw6 != -1) { + auto [raw2_val, mv2_val] = read_atten(ADC_ATTEN_DB_2_5); + raw2 = raw2_val; + mv2 = mv2_val; + + if (raw2 < 4095 && raw2 != -1) { + auto [raw0_val, mv0_val] = read_atten(ADC_ATTEN_DB_0); + raw0 = raw0_val; + mv0 = mv0_val; } } } @@ -155,19 +318,19 @@ float ADCSensor::sample() { return NAN; } - uint32_t mv12 = esp_adc_cal_raw_to_voltage(raw12, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_12_COMPAT]); - uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]); - uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); - uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); - - uint32_t c12 = std::min(raw12, ADC_HALF); - uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); - uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); - uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); + const int adc_half = 2048; + uint32_t c12 = std::min(raw12, adc_half); + uint32_t c6 = adc_half - std::abs(raw6 - adc_half); + uint32_t c2 = adc_half - std::abs(raw2 - adc_half); + uint32_t c0 = std::min(4095 - raw0, adc_half); uint32_t csum = c12 + c6 + c2 + c0; - uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); - return mv_scaled / (float) (csum * 1000U); + if (csum == 0) { + ESP_LOGE(TAG, "Invalid weight sum in autorange calculation"); + return NAN; + } + + return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum; } } // namespace adc diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 3309bd04c5..01bbaeda15 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -10,13 +10,11 @@ from esphome.const import ( CONF_NUMBER, CONF_PIN, CONF_RAW, - CONF_WIFI, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) from esphome.core import CORE -import esphome.final_validate as fv from . import ( ATTENUATION_MODES, @@ -24,6 +22,7 @@ from . import ( ESP32_VARIANT_ADC2_PIN_TO_CHANNEL, SAMPLING_MODES, adc_ns, + adc_unit_t, validate_adc_pin, ) @@ -57,21 +56,6 @@ def validate_config(config): return config -def final_validate_config(config): - if CORE.is_esp32: - variant = get_esp32_variant() - if ( - CONF_WIFI in fv.full_config.get() - and config[CONF_PIN][CONF_NUMBER] - in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant] - ): - raise cv.Invalid( - f"{variant} doesn't support ADC on this pin when Wi-Fi is configured" - ) - - return config - - ADCSensor = adc_ns.class_( "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler ) @@ -99,8 +83,6 @@ CONFIG_SCHEMA = cv.All( validate_config, ) -FINAL_VALIDATE_SCHEMA = final_validate_config - async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) @@ -119,13 +101,13 @@ async def to_code(config): cg.add(var.set_sample_count(config[CONF_SAMPLES])) cg.add(var.set_sampling_mode(config[CONF_SAMPLING_MODE])) - if attenuation := config.get(CONF_ATTENUATION): - if attenuation == "auto": - cg.add(var.set_autorange(cg.global_ns.true)) - else: - cg.add(var.set_attenuation(attenuation)) - if CORE.is_esp32: + if attenuation := config.get(CONF_ATTENUATION): + if attenuation == "auto": + cg.add(var.set_autorange(cg.global_ns.true)) + else: + cg.add(var.set_attenuation(attenuation)) + variant = get_esp32_variant() pin_num = config[CONF_PIN][CONF_NUMBER] if ( @@ -133,10 +115,10 @@ async def to_code(config): and pin_num in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant] ): chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] - cg.add(var.set_channel1(chan)) + cg.add(var.set_channel(adc_unit_t.ADC_UNIT_1, chan)) elif ( variant in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL and pin_num in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant] ): chan = ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant][pin_num] - cg.add(var.set_channel2(chan)) + cg.add(var.set_channel(adc_unit_t.ADC_UNIT_2, chan))