1
0
mirror of https://github.com/esphome/esphome.git synced 2025-02-12 16:08:19 +00:00

Add ADC sampling method option (#8131)

Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com>
This commit is contained in:
Igor Novgorodov 2025-02-10 05:32:54 +01:00 committed by GitHub
parent 0d13e2040d
commit 1ab1768b6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 132 additions and 30 deletions

View File

@ -36,6 +36,14 @@ ATTENUATION_MODES = {
"auto": "auto", "auto": "auto",
} }
sampling_mode = adc_ns.enum("SamplingMode", is_class=True)
SAMPLING_MODES = {
"avg": sampling_mode.AVG,
"min": sampling_mode.MIN,
"max": sampling_mode.MAX,
}
adc1_channel_t = cg.global_ns.enum("adc1_channel_t") adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
adc2_channel_t = cg.global_ns.enum("adc2_channel_t") adc2_channel_t = cg.global_ns.enum("adc2_channel_t")

View File

@ -28,6 +28,21 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11;
#endif #endif
#endif // USE_ESP32 #endif // USE_ESP32
enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 };
const LogString *sampling_mode_to_str(SamplingMode mode);
class Aggregator {
public:
void add_sample(uint32_t value);
uint32_t aggregate();
Aggregator(SamplingMode mode);
protected:
SamplingMode mode_{SamplingMode::AVG};
uint32_t aggr_{0};
uint32_t samples_{0};
};
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public: public:
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -54,6 +69,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; } void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; }
void set_sample_count(uint8_t sample_count); void set_sample_count(uint8_t sample_count);
void set_sampling_mode(SamplingMode sampling_mode);
float sample() override; float sample() override;
#ifdef USE_ESP8266 #ifdef USE_ESP8266
@ -68,6 +84,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
InternalGPIOPin *pin_; InternalGPIOPin *pin_;
bool output_raw_{false}; bool output_raw_{false};
uint8_t sample_count_{1}; uint8_t sample_count_{1};
SamplingMode sampling_mode_{SamplingMode::AVG};
#ifdef USE_RP2040 #ifdef USE_RP2040
bool is_temperature_{false}; bool is_temperature_{false};

View File

@ -6,6 +6,59 @@ namespace adc {
static const char *const TAG = "adc.common"; static const char *const TAG = "adc.common";
const LogString *sampling_mode_to_str(SamplingMode mode) {
switch (mode) {
case SamplingMode::AVG:
return LOG_STR("average");
case SamplingMode::MIN:
return LOG_STR("minimum");
case SamplingMode::MAX:
return LOG_STR("maximum");
}
return LOG_STR("unknown");
}
Aggregator::Aggregator(SamplingMode mode) {
this->mode_ = mode;
// set to max uint if mode is "min"
if (mode == SamplingMode::MIN) {
this->aggr_ = UINT32_MAX;
}
}
void Aggregator::add_sample(uint32_t value) {
this->samples_ += 1;
switch (this->mode_) {
case SamplingMode::AVG:
this->aggr_ += value;
break;
case SamplingMode::MIN:
if (value < this->aggr_) {
this->aggr_ = value;
}
break;
case SamplingMode::MAX:
if (value > this->aggr_) {
this->aggr_ = value;
}
}
}
uint32_t Aggregator::aggregate() {
if (this->mode_ == SamplingMode::AVG) {
if (this->samples_ == 0) {
return this->aggr_;
}
return (this->aggr_ + (this->samples_ >> 1)) / this->samples_; // NOLINT(clang-analyzer-core.DivideZero)
}
return this->aggr_;
}
void ADCSensor::update() { void ADCSensor::update() {
float value_v = this->sample(); float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
@ -18,6 +71,8 @@ void ADCSensor::set_sample_count(uint8_t sample_count) {
} }
} }
void ADCSensor::set_sampling_mode(SamplingMode sampling_mode) { this->sampling_mode_ = sampling_mode; }
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace adc } // namespace adc

View File

@ -78,12 +78,14 @@ void ADCSensor::dump_config() {
} }
} }
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
float ADCSensor::sample() { float ADCSensor::sample() {
if (!this->autorange_) { if (!this->autorange_) {
uint32_t sum = 0; auto aggr = Aggregator(this->sampling_mode_);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
int raw = -1; int raw = -1;
if (this->channel1_ != ADC1_CHANNEL_MAX) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
@ -94,13 +96,14 @@ float ADCSensor::sample() {
if (raw == -1) { if (raw == -1) {
return NAN; return NAN;
} }
sum += raw;
aggr.add_sample(raw);
} }
sum = (sum + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (this->output_raw_) { if (this->output_raw_) {
return sum; return aggr.aggregate();
} }
uint32_t mv = esp_adc_cal_raw_to_voltage(sum, &this->cal_characteristics_[(int32_t) this->attenuation_]); uint32_t mv =
esp_adc_cal_raw_to_voltage(aggr.aggregate(), &this->cal_characteristics_[(int32_t) this->attenuation_]);
return mv / 1000.0f; return mv / 1000.0f;
} }

View File

@ -31,23 +31,27 @@ void ADCSensor::dump_config() {
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC #endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
float ADCSensor::sample() { float ADCSensor::sample() {
uint32_t raw = 0; auto aggr = Aggregator(this->sampling_mode_);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
uint32_t raw = 0;
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else #else
raw += analogRead(this->pin_->get_pin()); // NOLINT raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif // USE_ADC_SENSOR_VCC #endif // USE_ADC_SENSOR_VCC
aggr.add_sample(raw);
} }
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (this->output_raw_) { if (this->output_raw_) {
return raw; return aggr.aggregate();
} }
return raw / 1024.0f; return aggr.aggregate() / 1024.0f;
} }
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }

View File

@ -23,23 +23,28 @@ void ADCSensor::dump_config() {
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC #endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
float ADCSensor::sample() { float ADCSensor::sample() {
uint32_t raw = 0; uint32_t raw = 0;
auto aggr = Aggregator(this->sampling_mode_);
if (this->output_raw_) { if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogRead(this->pin_->get_pin()); // NOLINT raw = analogRead(this->pin_->get_pin()); // NOLINT
aggr.add_sample(raw);
} }
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) return aggr.aggregate();
return raw;
} }
for (uint8_t sample = 0; sample < this->sample_count_; sample++) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT raw = analogReadVoltage(this->pin_->get_pin()); // NOLINT
aggr.add_sample(raw);
} }
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw / 1000.0f; return aggr.aggregate() / 1000.0f;
} }
} // namespace adc } // namespace adc

View File

@ -34,24 +34,28 @@ void ADCSensor::dump_config() {
#endif // USE_ADC_SENSOR_VCC #endif // USE_ADC_SENSOR_VCC
} }
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
float ADCSensor::sample() { float ADCSensor::sample() {
uint32_t raw = 0;
auto aggr = Aggregator(this->sampling_mode_);
if (this->is_temperature_) { if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true); adc_set_temp_sensor_enabled(true);
delay(1); delay(1);
adc_select_input(4); adc_select_input(4);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read(); raw = adc_read();
aggr.add_sample(raw);
} }
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
adc_set_temp_sensor_enabled(false); adc_set_temp_sensor_enabled(false);
if (this->output_raw_) { if (this->output_raw_) {
return raw; return aggr.aggregate();
} }
return raw * 3.3f / 4096.0f; return aggr.aggregate() * 3.3f / 4096.0f;
} }
uint8_t pin = this->pin_->get_pin(); uint8_t pin = this->pin_->get_pin();
@ -68,11 +72,10 @@ float ADCSensor::sample() {
adc_gpio_init(pin); adc_gpio_init(pin);
adc_select_input(pin - 26); adc_select_input(pin - 26);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read(); raw = adc_read();
aggr.add_sample(raw);
} }
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
#ifdef CYW43_USES_VSYS_PIN #ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) { if (pin == PICO_VSYS_PIN) {
@ -81,10 +84,10 @@ float ADCSensor::sample() {
#endif // CYW43_USES_VSYS_PIN #endif // CYW43_USES_VSYS_PIN
if (this->output_raw_) { if (this->output_raw_) {
return raw; return aggr.aggregate();
} }
float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f; float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f;
return raw * 3.3f / 4096.0f * coeff; return aggr.aggregate() * 3.3f / 4096.0f * coeff;
} }
} // namespace adc } // namespace adc

View File

@ -1,11 +1,9 @@
import logging import logging
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.core import CORE
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ATTENUATION, CONF_ATTENUATION,
CONF_ID, CONF_ID,
@ -17,10 +15,14 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE
import esphome.final_validate as fv
from . import ( from . import (
ATTENUATION_MODES, ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL, ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
SAMPLING_MODES,
adc_ns, adc_ns,
validate_adc_pin, validate_adc_pin,
) )
@ -30,9 +32,11 @@ _LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["voltage_sampler"] AUTO_LOAD = ["voltage_sampler"]
CONF_SAMPLES = "samples" CONF_SAMPLES = "samples"
CONF_SAMPLING_MODE = "sampling_mode"
_attenuation = cv.enum(ATTENUATION_MODES, lower=True) _attenuation = cv.enum(ATTENUATION_MODES, lower=True)
_sampling_mode = cv.enum(SAMPLING_MODES, lower=True)
def validate_config(config): def validate_config(config):
@ -88,6 +92,7 @@ CONFIG_SCHEMA = cv.All(
cv.only_on_esp32, _attenuation cv.only_on_esp32, _attenuation
), ),
cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255), cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_SAMPLING_MODE, default="avg"): _sampling_mode,
} }
) )
.extend(cv.polling_component_schema("60s")), .extend(cv.polling_component_schema("60s")),
@ -112,6 +117,7 @@ async def to_code(config):
cg.add(var.set_output_raw(config[CONF_RAW])) cg.add(var.set_output_raw(config[CONF_RAW]))
cg.add(var.set_sample_count(config[CONF_SAMPLES])) 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 := config.get(CONF_ATTENUATION):
if attenuation == "auto": if attenuation == "auto":

View File

@ -10,5 +10,6 @@ sensor:
pin: A0 pin: A0
id: s_1 id: s_1
name: test s1 name: test s1
sampling_mode: min
update_interval: 60s update_interval: 60s
device_class: voltage device_class: voltage