diff --git a/components/Monitoring/Monitoring/AdcSampler.cpp b/components/Monitoring/Monitoring/AdcSampler.cpp new file mode 100644 index 0000000..3696b5b --- /dev/null +++ b/components/Monitoring/Monitoring/AdcSampler.cpp @@ -0,0 +1,160 @@ +#include "AdcSampler.hpp" + +#if defined(CONFIG_IDF_TARGET_ESP32S3) +#include +#include + +static const char *TAG_ADC = "[AdcSampler]"; + +adc_oneshot_unit_handle_t AdcSampler::shared_unit_ = nullptr; + +AdcSampler::~AdcSampler() +{ + if (cali_handle_) + { + adc_cali_delete_scheme_curve_fitting(cali_handle_); + cali_handle_ = nullptr; + } +} + +bool AdcSampler::init(int gpio, adc_atten_t atten, adc_bitwidth_t bitwidth, size_t window_size) +{ + if (window_size == 0) + { + window_size = 1; + } + samples_.assign(window_size, 0); + sample_sum_ = 0; + sample_idx_ = 0; + sample_count_ = 0; + + atten_ = atten; + bitwidth_ = bitwidth; + + if (!map_gpio_to_channel(gpio, unit_, channel_)) + { + ESP_LOGW(TAG_ADC, "GPIO %d may not be ADC-capable on ESP32-S3", gpio); + } + + if (!ensure_unit()) + { + return false; + } + + if (!configure_channel(gpio, atten, bitwidth)) + { + return false; + } + + // Calibration using curve fitting if available + adc_cali_curve_fitting_config_t cal_cfg = { + .unit_id = unit_, + .chan = channel_, + .atten = atten_, + .bitwidth = bitwidth_, + }; + if (adc_cali_create_scheme_curve_fitting(&cal_cfg, &cali_handle_) == ESP_OK) + { + cali_inited_ = true; + ESP_LOGI(TAG_ADC, "ADC calibration initialized (curve fitting)"); + } + else + { + cali_inited_ = false; + ESP_LOGW(TAG_ADC, "ADC calibration not available; using raw-to-mV approximation"); + } + + return true; +} + +bool AdcSampler::sampleOnce() +{ + if (!shared_unit_) + { + return false; + } + + int raw = 0; + if (adc_oneshot_read(shared_unit_, channel_, &raw) != ESP_OK) + { + return false; + } + + int mv = 0; + if (cali_inited_) + { + if (adc_cali_raw_to_voltage(cali_handle_, raw, &mv) != ESP_OK) + { + mv = 0; + } + } + else + { + // Approximation for 11dB attenuation + mv = (raw * 2450) / 4095; + } + + // Moving average + sample_sum_ -= samples_[sample_idx_]; + samples_[sample_idx_] = mv; + sample_sum_ += mv; + sample_idx_ = (sample_idx_ + 1) % samples_.size(); + if (sample_count_ < samples_.size()) + { + sample_count_++; + } + filtered_mv_ = sample_sum_ / static_cast(sample_count_ > 0 ? sample_count_ : 1); + + return true; +} + +bool AdcSampler::ensure_unit() +{ + if (shared_unit_) + { + return true; + } + + adc_oneshot_unit_init_cfg_t unit_cfg = { + .unit_id = ADC_UNIT_1, + .clk_src = ADC_RTC_CLK_SRC_DEFAULT, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + esp_err_t err = adc_oneshot_new_unit(&unit_cfg, &shared_unit_); + if (err != ESP_OK) + { + ESP_LOGE(TAG_ADC, "adc_oneshot_new_unit failed: %s", esp_err_to_name(err)); + shared_unit_ = nullptr; + return false; + } + return true; +} + +bool AdcSampler::configure_channel(int gpio, adc_atten_t atten, adc_bitwidth_t bitwidth) +{ + adc_oneshot_chan_cfg_t chan_cfg = { + .atten = atten, + .bitwidth = bitwidth, + }; + esp_err_t err = adc_oneshot_config_channel(shared_unit_, channel_, &chan_cfg); + if (err != ESP_OK) + { + ESP_LOGE(TAG_ADC, "adc_oneshot_config_channel failed (GPIO %d, CH %d): %s", gpio, channel_, esp_err_to_name(err)); + return false; + } + return true; +} + +bool AdcSampler::map_gpio_to_channel(int gpio, adc_unit_t &unit, adc_channel_t &channel) +{ + unit = ADC_UNIT_1; + if (gpio >= 1 && gpio <= 10) + { + channel = static_cast(gpio - 1); + return true; + } + channel = ADC_CHANNEL_0; + return false; +} + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/components/Monitoring/Monitoring/AdcSampler.hpp b/components/Monitoring/Monitoring/AdcSampler.hpp new file mode 100644 index 0000000..e68763f --- /dev/null +++ b/components/Monitoring/Monitoring/AdcSampler.hpp @@ -0,0 +1,58 @@ +#pragma once +#include +#include +#include "sdkconfig.h" + +#if defined(CONFIG_IDF_TARGET_ESP32S3) +#include "esp_adc/adc_oneshot.h" +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" +#include + +class AdcSampler +{ +public: + AdcSampler() = default; + ~AdcSampler(); + + // Initialize the ADC channel on the shared ADC1 oneshot unit. + // window_size: moving-average window (>=1). + bool init(int gpio, adc_atten_t atten = ADC_ATTEN_DB_12, adc_bitwidth_t bitwidth = ADC_BITWIDTH_DEFAULT, size_t window_size = 1); + + // Perform one conversion, update filtered value. Returns false on failure. + bool sampleOnce(); + + int getFilteredMilliVolts() const { return filtered_mv_; } + +private: + bool ensure_unit(); + bool configure_channel(int gpio, adc_atten_t atten, adc_bitwidth_t bitwidth); + bool map_gpio_to_channel(int gpio, adc_unit_t &unit, adc_channel_t &channel); + + // Shared ADC1 oneshot handle and calibration mutex-less state (single-threaded use here). + static adc_oneshot_unit_handle_t shared_unit_; + + adc_cali_handle_t cali_handle_{nullptr}; + bool cali_inited_{false}; + adc_channel_t channel_{ADC_CHANNEL_0}; + adc_unit_t unit_{ADC_UNIT_1}; + adc_atten_t atten_{ADC_ATTEN_DB_12}; + adc_bitwidth_t bitwidth_{ADC_BITWIDTH_DEFAULT}; + + std::vector samples_{}; + int sample_sum_{0}; + size_t sample_idx_{0}; + size_t sample_count_{0}; + int filtered_mv_{0}; +}; + +#else +// Stub for non-ESP32-S3 targets to keep interfaces consistent. +class AdcSampler +{ +public: + bool init(int /*gpio*/, int /*atten*/ = 0, int /*bitwidth*/ = 0, size_t /*window_size*/ = 1) { return false; } + bool sampleOnce() { return false; } + int getFilteredMilliVolts() const { return 0; } +}; +#endif