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:
parent
0d13e2040d
commit
1ab1768b6a
@ -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")
|
||||||
|
|
||||||
|
@ -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};
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"; }
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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":
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user