From e79f7ce29016a68df3cc2b685dd8ef4dbf3081d0 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 20 Oct 2021 19:44:51 +0200 Subject: [PATCH] [ESP32] ADC auto-range setting (#2541) --- esphome/components/adc/adc_sensor.cpp | 160 ++++++++++++++++---------- esphome/components/adc/adc_sensor.h | 4 + esphome/components/adc/sensor.py | 6 +- 3 files changed, 110 insertions(+), 60 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c8f8b0e0f6..0c24c615f3 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -1,5 +1,6 @@ #include "adc_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC @@ -16,8 +17,6 @@ namespace adc { static const char *const TAG = "adc"; #ifdef USE_ESP32 -void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } - inline adc1_channel_t gpio_to_adc1(uint8_t pin) { #if CONFIG_IDF_TARGET_ESP32 switch (pin) { @@ -57,6 +56,8 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) { } #endif } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_autorange(bool autorange) { this->autorange_ = autorange; } #endif void ADCSensor::setup() { @@ -66,6 +67,8 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 + if (this->autorange_) + this->attenuation_ = ADC_ATTEN_DB_11; adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 @@ -84,22 +87,25 @@ void ADCSensor::dump_config() { #endif #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); - break; - case ADC_ATTEN_DB_2_5: - ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); - break; - case ADC_ATTEN_DB_6: - ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); - break; - case ADC_ATTEN_DB_11: - ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } + if (autorange_) + ESP_LOGCONFIG(TAG, " Attenuation: auto"); + else + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); + break; + case ADC_ATTEN_DB_2_5: + ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); + break; + case ADC_ATTEN_DB_6: + ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); + break; + case ADC_ATTEN_DB_11: + ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); + break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; + } #endif LOG_UPDATE_INTERVAL(this); } @@ -109,56 +115,92 @@ void ADCSensor::update() { ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } -float ADCSensor::sample() { +uint16_t ADCSensor::read_raw_() { #ifdef USE_ESP32 - int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin())); - float value_v = raw / 4095.0f; -#if CONFIG_IDF_TARGET_ESP32 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - value_v *= 1.1; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.5; - break; - case ADC_ATTEN_DB_6: - value_v *= 2.2; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.9; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - value_v *= 0.84; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.13; - break; - case ADC_ATTEN_DB_6: - value_v *= 1.56; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.0; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#endif - return value_v; + return adc1_get_raw(gpio_to_adc1(pin_->get_pin())); #endif #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) + return ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT + return analogRead(this->pin_->get_pin()); // NOLINT #endif #endif } +uint32_t ADCSensor::raw_to_microvolts_(uint16_t raw) { +#ifdef USE_ESP32 +#if CONFIG_IDF_TARGET_ESP32 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 269; // 1e6 * 1.1 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 366; // 1e6 * 1.5 / 4095 + case ADC_ATTEN_DB_6: + return raw * 537; // 1e6 * 2.2 / 4095 + case ADC_ATTEN_DB_11: + return raw * 952; // 1e6 * 3.9 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 205; // 1e6 * 0.84 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 276; // 1e6 * 1.13 / 4095 + case ADC_ATTEN_DB_6: + return raw * 381; // 1e6 * 1.56 / 4095 + case ADC_ATTEN_DB_11: + return raw * 733; // 1e6 * 3.0 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#endif +#endif + +#ifdef USE_ESP8266 + return raw * 977; // 1e6 / 1024 +#endif +} +float ADCSensor::sample() { + int raw = this->read_raw_(); + uint32_t v = this->raw_to_microvolts_(raw); +#ifdef USE_ESP32 + if (autorange_) { + int raw11 = raw, raw6 = 4095, raw2 = 4095, raw0 = 4095; + uint32_t v11 = v, v6 = 0, v2 = 0, v0 = 0; + if (raw11 < 4095) { // Progressively read all attenuation ranges + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_6); + raw6 = this->read_raw_(); + v6 = this->raw_to_microvolts_(raw6); + if (raw6 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_2_5); + raw2 = this->read_raw_(); + v2 = this->raw_to_microvolts_(raw2); + if (raw2 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_0); + raw0 = this->read_raw_(); + v0 = this->raw_to_microvolts_(raw0); + } + } + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_11); + } // Contribution coefficients (normalized to 2048) + uint16_t c11 = clamp(raw11, 0, 2048); // high 1, middle 1, low 0 + uint16_t c6 = (2048 - abs(raw6 - 2048)); // high 0, middle 1, low 0 + uint16_t c2 = (2048 - abs(raw2 - 2048)); // high 0, middle 1, low 0 + uint16_t c0 = clamp(4095 - raw0, 0, 2048); // high 0, middle 1, low 1 + uint32_t csum = c11 + c6 + c2 + c0; // sum to normalize the final result + if (csum > 0) + v = (v11 * c11) + (v6 * c6) + (v2 * c2) + (v0 * c0); + else // in case of error, this keeps the 11db output (v) + csum = 1; + csum *= 1e6; // include the 1e6 microvolts->volts conversion factor + return (float) v / (float) csum; // normalize, convert & return + } +#endif + return v / (float) 1e6; // convert from microvolts to volts +} #ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index b8c702be4e..fafb0d5ca0 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -18,6 +18,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. void set_attenuation(adc_atten_t attenuation); + void set_autorange(bool autorange); #endif /// Update adc values. @@ -36,9 +37,12 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage protected: InternalGPIOPin *pin_; + uint16_t read_raw_(); + uint32_t raw_to_microvolts_(uint16_t raw); #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + bool autorange_{false}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 26ef504c1a..0265f52d31 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -21,6 +21,7 @@ ATTENUATION_MODES = { "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, "6db": cg.global_ns.ADC_ATTEN_DB_6, "11db": cg.global_ns.ADC_ATTEN_DB_11, + "auto": "auto", } @@ -92,4 +93,7 @@ async def to_code(config): cg.add(var.set_pin(pin)) if CONF_ATTENUATION in config: - cg.add(var.set_attenuation(config[CONF_ATTENUATION])) + if config[CONF_ATTENUATION] == "auto": + cg.add(var.set_autorange(cg.global_ns.true)) + else: + cg.add(var.set_attenuation(config[CONF_ATTENUATION]))