From 5a4e4f645463d638885a68795e3ed411f6febce0 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 10 Dec 2024 00:18:24 +0100 Subject: [PATCH] Use esp_adc/adc_cali.h for IDF v5 This add compatibility to ESP-IDF v5 to the ADC component, preventing the warning message related to the deprecated libraries. --- esphome/components/adc/adc_sensor.h | 156 ++++++++++++-- esphome/components/adc/adc_sensor_common.cpp | 1 - ...nsor_esp32.cpp => adc_sensor_esp32_v4.cpp} | 11 +- .../components/adc/adc_sensor_esp32_v5.cpp | 203 ++++++++++++++++++ esphome/components/adc/adc_sensor_esp8266.cpp | 1 - esphome/components/adc/adc_sensor_rp2040.cpp | 1 - 6 files changed, 344 insertions(+), 29 deletions(-) rename esphome/components/adc/{adc_sensor_esp32.cpp => adc_sensor_esp32_v4.cpp} (96%) create mode 100644 esphome/components/adc/adc_sensor_esp32_v5.cpp diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 7a3e1c8da7..6be0241f9d 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -3,17 +3,50 @@ #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" +#include "esphome/core/log.h" #ifdef USE_ESP32 -#include -#include "driver/adc.h" + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + #include "esp_adc/adc_oneshot.h" + #include "esp_adc/adc_cali.h" + #include "esp_adc/adc_cali_scheme.h" + #else + #include + #include "driver/adc.h" + #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #endif // USE_ESP32 namespace esphome { namespace adc { #ifdef USE_ESP32 + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + // Map old channel names to new ones for compatibility + #define ADC1_CHANNEL_0 ADC_CHANNEL_0 + #define ADC1_CHANNEL_1 ADC_CHANNEL_1 + #define ADC1_CHANNEL_2 ADC_CHANNEL_2 + #define ADC1_CHANNEL_3 ADC_CHANNEL_3 + #define ADC1_CHANNEL_4 ADC_CHANNEL_4 + #define ADC1_CHANNEL_5 ADC_CHANNEL_5 + #define ADC1_CHANNEL_6 ADC_CHANNEL_6 + #define ADC1_CHANNEL_7 ADC_CHANNEL_7 + #define ADC1_CHANNEL_MAX ADC_CHANNEL_MAX + + #define ADC2_CHANNEL_0 ADC_CHANNEL_0 + #define ADC2_CHANNEL_1 ADC_CHANNEL_1 + #define ADC2_CHANNEL_2 ADC_CHANNEL_2 + #define ADC2_CHANNEL_3 ADC_CHANNEL_3 + #define ADC2_CHANNEL_4 ADC_CHANNEL_4 + #define ADC2_CHANNEL_5 ADC_CHANNEL_5 + #define ADC2_CHANNEL_6 ADC_CHANNEL_6 + #define ADC2_CHANNEL_7 ADC_CHANNEL_7 + #define ADC2_CHANNEL_8 ADC_CHANNEL_8 + #define ADC2_CHANNEL_9 ADC_CHANNEL_9 + #define ADC2_CHANNEL_MAX ADC_CHANNEL_MAX + #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + // clang-format off #if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 7)) || \ (ESP_IDF_VERSION_MAJOR == 5 && \ @@ -25,37 +58,106 @@ namespace adc { static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_12; #else static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11; -#endif +#endif // ESP_IDF_VERSION check for ADC_ATTEN_DB_12_COMPAT #endif // USE_ESP32 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); + + /// 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. +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + /// 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; + this->do_setup_ = true; + } + + /// 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_channel1(adc_channel_t channel) { + this->channel_ = channel; + this->is_adc1_ = true; + this->do_setup_ = true; + } + + /// Configure the ADC to use a specific channel on ADC2. + /// This sets the channel for single-shot or continuous ADC measurements. + /// ADC2 is shared with other peripherals, so care must be taken to avoid conflicts. + /// @param channel The ADC2 channel to configure, such as ADC_CHANNEL_0, ADC_CHANNEL_3, etc. + void set_channel2(adc_channel_t channel) { + this->channel_ = channel; + this->is_adc1_ = false; + this->do_setup_ = true; + } +#else + /// 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; } + + /// Configure the ADC to use a specific channel on ADC1. + /// This sets the ADC1 channel for measurement and disables ADC2 channel usage. + /// @param channel The ADC1 channel to configure (e.g., ADC1_CHANNEL_0, ADC1_CHANNEL_3). void set_channel1(adc1_channel_t channel) { this->channel1_ = channel; this->channel2_ = ADC2_CHANNEL_MAX; } + + /// Configure the ADC to use a specific channel on ADC2. + /// This sets the ADC2 channel for measurement and disables ADC1 channel usage. + /// Note: ADC2 may have limitations due to shared resources with Wi-Fi or other peripherals. + /// @param channel The ADC2 channel to configure (e.g., ADC2_CHANNEL_0, ADC2_CHANNEL_3). void set_channel2(adc2_channel_t channel) { this->channel2_ = channel; this->channel1_ = ADC1_CHANNEL_MAX; } +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + /// 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); - float sample() override; - #ifdef USE_ESP8266 std::string unique_id() override; #endif // USE_ESP8266 @@ -69,21 +171,31 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage bool output_raw_{false}; uint8_t sample_count_{1}; -#ifdef USE_RP2040 - bool is_temperature_{false}; -#endif // USE_RP2040 - #ifdef USE_ESP32 + bool autorange_{false}; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + adc_oneshot_unit_handle_t adc1_handle_{nullptr}; + adc_oneshot_unit_handle_t adc2_handle_{nullptr}; + adc_cali_handle_t calibration_handle_{nullptr}; + adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + adc_channel_t channel_; + bool is_adc1_{true}; + bool do_setup_{false}; +#else 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}; #if ESP_IDF_VERSION_MAJOR >= 5 esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; #else esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; -#endif // ESP_IDF_VERSION_MAJOR +#endif // ESP_IDF_VERSION_MAJOR >= 5 +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #endif // USE_ESP32 + +#ifdef USE_RP2040 + bool is_temperature_{false}; +#endif // USE_RP2040 }; } // namespace adc diff --git a/esphome/components/adc/adc_sensor_common.cpp b/esphome/components/adc/adc_sensor_common.cpp index 2dccd55fcd..1d6eb8ddb9 100644 --- a/esphome/components/adc/adc_sensor_common.cpp +++ b/esphome/components/adc/adc_sensor_common.cpp @@ -1,5 +1,4 @@ #include "adc_sensor.h" -#include "esphome/core/log.h" namespace esphome { namespace adc { diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32_v4.cpp similarity index 96% rename from esphome/components/adc/adc_sensor_esp32.cpp rename to esphome/components/adc/adc_sensor_esp32_v4.cpp index 24e3750091..ea29259553 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32_v4.cpp @@ -1,12 +1,13 @@ #ifdef USE_ESP32 #include "adc_sensor.h" -#include "esphome/core/log.h" + +#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) namespace esphome { namespace adc { -static const char *const TAG = "adc.esp32"; +static const char *const TAG = "adc.esp32.v4"; static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast(ADC_WIDTH_MAX - 1); @@ -73,7 +74,7 @@ void ADCSensor::dump_config() { case ADC_ATTEN_DB_12_COMPAT: ESP_LOGCONFIG(TAG, " Attenuation: 12db"); break; - default: // This is to satisfy the unused ADC_ATTEN_MAX + default: break; } } @@ -160,4 +161,6 @@ float ADCSensor::sample() { } // namespace adc } // namespace esphome -#endif // USE_ESP32 +#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + +#endif // USE_ESP32 \ No newline at end of file diff --git a/esphome/components/adc/adc_sensor_esp32_v5.cpp b/esphome/components/adc/adc_sensor_esp32_v5.cpp new file mode 100644 index 0000000000..ad53292ba6 --- /dev/null +++ b/esphome/components/adc/adc_sensor_esp32_v5.cpp @@ -0,0 +1,203 @@ +#ifdef USE_ESP32 + +#include "adc_sensor.h" + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.esp32.v5"; + +void ADCSensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); + + if (!this->do_setup_) { + return; + } + + if (this->is_adc1_) { + if (this->adc1_handle_ == nullptr) { + adc_oneshot_unit_init_cfg_t init_config1 = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &this->adc1_handle_)); + } + + adc_oneshot_chan_cfg_t config = { + .atten = this->attenuation_, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(this->adc1_handle_, this->channel_, &config)); + + } else { + if (this->adc2_handle_ == nullptr) { + adc_oneshot_unit_init_cfg_t init_config2 = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config2, &this->adc2_handle_)); + } + + adc_oneshot_chan_cfg_t config = { + .atten = this->attenuation_, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(this->adc2_handle_, this->channel_, &config)); + } + + // Initialize ADC calibration + if (this->calibration_handle_ == nullptr) { + adc_cali_handle_t handle = nullptr; + adc_unit_t unit_id = this->is_adc1_ ? ADC_UNIT_1 : ADC_UNIT_2; + adc_cali_line_fitting_config_t cali_config = { + .unit_id = unit_id, + .atten = this->attenuation_, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + + if (adc_cali_create_scheme_line_fitting(&cali_config, &handle) == ESP_OK) { + this->calibration_handle_ = handle; + ESP_LOGV(TAG, "Using line fitting for calibration"); + } + } + + this->do_setup_ = false; +} + +void ADCSensor::dump_config() { + LOG_SENSOR("", "ADC Sensor", this); + LOG_PIN(" Pin: ", this->pin_); + if (this->autorange_) { + ESP_LOGCONFIG(TAG, " Attenuation: auto"); + } else { + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + ESP_LOGCONFIG(TAG, " Attenuation: 0db"); + break; + case ADC_ATTEN_DB_2_5: + ESP_LOGCONFIG(TAG, " Attenuation: 2.5db"); + break; + case ADC_ATTEN_DB_6: + ESP_LOGCONFIG(TAG, " Attenuation: 6db"); + break; + case ADC_ATTEN_DB_12: + ESP_LOGCONFIG(TAG, " Attenuation: 12db"); + break; + default: + break; + } + } + ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); + LOG_UPDATE_INTERVAL(this); +} + +float ADCSensor::sample() { + if (!this->autorange_) { + uint32_t sum = 0; + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + int raw = -1; + if (this->is_adc1_) { + ESP_ERROR_CHECK(adc_oneshot_read(this->adc1_handle_, this->channel_, &raw)); + } else { + ESP_ERROR_CHECK(adc_oneshot_read(this->adc2_handle_, this->channel_, &raw)); + } + if (raw == -1) { + return NAN; + } + sum += raw; + } + sum = (sum + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + + if (this->output_raw_) { + return sum; + } + + if (this->calibration_handle_ != nullptr) { + int voltage_mv; + ESP_ERROR_CHECK(adc_cali_raw_to_voltage(this->calibration_handle_, sum, &voltage_mv)); + return voltage_mv / 1000.0f; + } + return sum * 3.3f / 4095.0f; // Fallback if no calibration + } + + // Auto-range mode + int raw12 = 4095, raw6 = 4095, raw2 = 4095, raw0 = 4095; + float mv12 = 0, mv6 = 0, mv2 = 0, mv0 = 0; + + // Helper lambda for reading with different attenuations + auto read_atten = [this](adc_atten_t atten) -> std::pair { + if (this->is_adc1_) { + adc_oneshot_chan_cfg_t config = { + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(this->adc1_handle_, this->channel_, &config)); + int raw; + ESP_ERROR_CHECK(adc_oneshot_read(this->adc1_handle_, this->channel_, &raw)); + if (this->calibration_handle_ != nullptr) { + int voltage_mv; + ESP_ERROR_CHECK(adc_cali_raw_to_voltage(this->calibration_handle_, raw, &voltage_mv)); + return {raw, voltage_mv / 1000.0f}; + } + return {raw, raw * 3.3f / 4095.0f}; + } else { + adc_oneshot_chan_cfg_t config = { + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(this->adc2_handle_, this->channel_, &config)); + int raw; + ESP_ERROR_CHECK(adc_oneshot_read(this->adc2_handle_, this->channel_, &raw)); + if (this->calibration_handle_ != nullptr) { + int voltage_mv; + ESP_ERROR_CHECK(adc_cali_raw_to_voltage(this->calibration_handle_, raw, &voltage_mv)); + return {raw, voltage_mv / 1000.0f}; + } + return {raw, raw * 3.3f / 4095.0f}; + } + }; + + auto [raw12_val, mv12_val] = read_atten(ADC_ATTEN_DB_12); + raw12 = raw12_val; + mv12 = mv12_val; + + if (raw12 < 4095) { + auto [raw6_val, mv6_val] = read_atten(ADC_ATTEN_DB_6); + raw6 = raw6_val; + mv6 = mv6_val; + + if (raw6 < 4095) { + auto [raw2_val, mv2_val] = read_atten(ADC_ATTEN_DB_2_5); + raw2 = raw2_val; + mv2 = mv2_val; + + if (raw2 < 4095) { + auto [raw0_val, mv0_val] = read_atten(ADC_ATTEN_DB_0); + raw0 = raw0_val; + mv0 = mv0_val; + } + } + } + + if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw12 == -1) { + return NAN; + } + + 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; + + return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum; +} + +} // namespace adc +} // namespace esphome + +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + +#endif // USE_ESP32 diff --git a/esphome/components/adc/adc_sensor_esp8266.cpp b/esphome/components/adc/adc_sensor_esp8266.cpp index c9b6f8b652..6530f14c21 100644 --- a/esphome/components/adc/adc_sensor_esp8266.cpp +++ b/esphome/components/adc/adc_sensor_esp8266.cpp @@ -2,7 +2,6 @@ #include "adc_sensor.h" #include "esphome/core/helpers.h" -#include "esphome/core/log.h" #ifdef USE_ADC_SENSOR_VCC #include diff --git a/esphome/components/adc/adc_sensor_rp2040.cpp b/esphome/components/adc/adc_sensor_rp2040.cpp index 520ff3bacc..7d22f10a4e 100644 --- a/esphome/components/adc/adc_sensor_rp2040.cpp +++ b/esphome/components/adc/adc_sensor_rp2040.cpp @@ -1,7 +1,6 @@ #ifdef USE_RP2040 #include "adc_sensor.h" -#include "esphome/core/log.h" #ifdef CYW43_USES_VSYS_PIN #include "pico/cyw43_arch.h"