From ce8ac61d4223e54f200644e6151d9aa8cb6ec9ee Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 14 Sep 2025 15:42:10 +0200 Subject: [PATCH] Implement CardputerPower and improve EstimatedPower driver (#335) --- Boards/M5stackCardputer/CMakeLists.txt | 2 +- .../Source/M5stackCardputer.cpp | 4 +- .../Source/devices/CardputerPower.cpp | 85 +++++++++++++++++++ .../Source/devices/CardputerPower.h | 28 ++++++ .../Source/ChargeFromAdcVoltage.cpp | 17 ++-- .../Source/ChargeFromAdcVoltage.h | 12 +-- .../Source/ChargeFromVoltage.cpp | 16 ++++ .../EstimatedPower/Source/ChargeFromVoltage.h | 22 +++++ Tactility/Source/app/power/Power.cpp | 2 - 9 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 Boards/M5stackCardputer/Source/devices/CardputerPower.cpp create mode 100644 Boards/M5stackCardputer/Source/devices/CardputerPower.h create mode 100644 Drivers/EstimatedPower/Source/ChargeFromVoltage.cpp create mode 100644 Drivers/EstimatedPower/Source/ChargeFromVoltage.h diff --git a/Boards/M5stackCardputer/CMakeLists.txt b/Boards/M5stackCardputer/CMakeLists.txt index 4cd90a0d..1e525df3 100644 --- a/Boards/M5stackCardputer/CMakeLists.txt +++ b/Boards/M5stackCardputer/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver vfs fatfs + REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver esp_adc EstimatedPower vfs fatfs ) diff --git a/Boards/M5stackCardputer/Source/M5stackCardputer.cpp b/Boards/M5stackCardputer/Source/M5stackCardputer.cpp index 227f30a6..e22fe4bb 100644 --- a/Boards/M5stackCardputer/Source/M5stackCardputer.cpp +++ b/Boards/M5stackCardputer/Source/M5stackCardputer.cpp @@ -4,6 +4,7 @@ #include "devices/SdCard.h" #include "devices/CardputerEncoder.h" #include "devices/CardputerKeyboard.h" +#include "devices/CardputerPower.h" #include #include @@ -17,7 +18,8 @@ static DeviceVector createDevices() { createSdCard(), createDisplay(), std::make_shared(), - std::make_shared() + std::make_shared(), + std::make_shared() }; } diff --git a/Boards/M5stackCardputer/Source/devices/CardputerPower.cpp b/Boards/M5stackCardputer/Source/devices/CardputerPower.cpp new file mode 100644 index 00000000..ebd3b65d --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/CardputerPower.cpp @@ -0,0 +1,85 @@ +#include "CardputerPower.h" + +#include +#include + +constexpr auto* TAG = "CardputerPower"; + +bool CardputerPower::adcInitCalibration() { + bool calibrated = false; + + esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT); + if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) { + TT_LOG_W(TAG, "Calibration scheme not supported, skip software calibration"); + } else if (efuse_read_result == ESP_ERR_INVALID_VERSION) { + TT_LOG_W(TAG, "eFuse not burnt, skip software calibration"); + } else if (efuse_read_result == ESP_OK) { + calibrated = true; + TT_LOG_I(TAG, "Calibration success"); + esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics); + } else { + TT_LOG_W(TAG, "eFuse read failed, skipping calibration"); + } + + return calibrated; +} + +uint32_t CardputerPower::adcReadValue() const { + int adc_raw = adc1_get_raw(ADC1_CHANNEL_9); + TT_LOG_D(TAG, "Raw data: %d", adc_raw); + float voltage; + if (calibrated) { + voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics); + TT_LOG_D(TAG, "Calibrated data: %d mV", voltage); + } else { + voltage = 0.0f; + } + return voltage; +} + +bool CardputerPower::ensureInitialized() { + if (!initialized) { + calibrated = adcInitCalibration(); + + if (adc1_config_width(static_cast(ADC_WIDTH_BIT_DEFAULT)) != ESP_OK) { + TT_LOG_E(TAG, "ADC1 config width failed"); + return false; + } + if (adc1_config_channel_atten(ADC1_CHANNEL_9, ADC_ATTEN_DB_11) != ESP_OK) { + TT_LOG_E(TAG, "ADC1 config attenuation failed"); + return false; + } + + initialized = true; + } + + return true; +} + +bool CardputerPower::supportsMetric(MetricType type) const { + switch (type) { + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: + return true; + default: + return false; + } +} + +bool CardputerPower::getMetric(MetricType type, MetricData& data) { + if (!ensureInitialized()) { + return false; + } + + switch (type) { + case MetricType::BatteryVoltage: + data.valueAsUint32 = adcReadValue() * 2; + return true; + case MetricType::ChargeLevel: + data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2); + return true; + default: + return false; + } +} diff --git a/Boards/M5stackCardputer/Source/devices/CardputerPower.h b/Boards/M5stackCardputer/Source/devices/CardputerPower.h new file mode 100644 index 00000000..837c49b4 --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/CardputerPower.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +using tt::hal::power::PowerDevice; + +class CardputerPower final : public PowerDevice { + + ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f); + bool initialized = false; + esp_adc_cal_characteristics_t adcCharacteristics; + bool calibrated = false; + + bool adcInitCalibration(); + uint32_t adcReadValue() const; + + bool ensureInitialized(); + +public: + + std::string getName() const override { return "Cardputer Power"; } + std::string getDescription() const override { return "Power measurement via ADC"; } + + bool supportsMetric(MetricType type) const override; + bool getMetric(MetricType type, MetricData& data) override; +}; diff --git a/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.cpp b/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.cpp index 46dee343..c4318c8a 100644 --- a/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.cpp +++ b/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.cpp @@ -2,19 +2,14 @@ #include #include -constexpr auto TAG = "EstimatePower"; +constexpr auto TAG = "ChargeFromAdcV"; constexpr auto MAX_VOLTAGE_SAMPLES = 15; -uint8_t ChargeFromAdcVoltage::estimateChargeLevelFromVoltage(uint32_t milliVolt) const { - const float volts = std::min((float)milliVolt / 1000.f, configuration.batteryVoltageMax); - const float voltage_percentage = (volts - configuration.batteryVoltageMin) / (configuration.batteryVoltageMax - configuration.batteryVoltageMin); - const float voltage_factor = std::min(1.0f, voltage_percentage); - const auto charge_level = (uint8_t) (voltage_factor * 100.f); - TT_LOG_V(TAG, "mV = %lu, scaled = %.2f, factor = %.2f, result = %d", milliVolt, volts, voltage_factor, charge_level); - return charge_level; -} - -ChargeFromAdcVoltage::ChargeFromAdcVoltage(const Configuration& configuration) : configuration(configuration) { +ChargeFromAdcVoltage::ChargeFromAdcVoltage( + const Configuration& configuration, + float voltageMin, + float voltageMax +) : configuration(configuration), chargeFromVoltage(voltageMin, voltageMax) { if (adc_oneshot_new_unit(&configuration.adcConfig, &adcHandle) != ESP_OK) { TT_LOG_E(TAG, "ADC config failed"); return; diff --git a/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.h b/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.h index 2023a2f3..3e96f373 100644 --- a/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.h +++ b/Drivers/EstimatedPower/Source/ChargeFromAdcVoltage.h @@ -1,5 +1,6 @@ #pragma once +#include #include class ChargeFromAdcVoltage { @@ -7,11 +8,9 @@ class ChargeFromAdcVoltage { public: struct Configuration { - adc_channel_t adcChannel = ADC_CHANNEL_3; float adcMultiplier = 1.0f; float adcRefVoltage = 3.3f; - float batteryVoltageMin = 3.2f; - float batteryVoltageMax = 4.2f; + adc_channel_t adcChannel = ADC_CHANNEL_3; adc_oneshot_unit_init_cfg_t adcConfig = { .unit_id = ADC_UNIT_1, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, @@ -27,16 +26,17 @@ private: adc_oneshot_unit_handle_t adcHandle = nullptr; Configuration configuration; + ChargeFromVoltage chargeFromVoltage; public: - explicit ChargeFromAdcVoltage(const Configuration& configuration); + explicit ChargeFromAdcVoltage(const Configuration& configuration, float voltageMin = 3.2f, float voltageMax = 4.2f); ~ChargeFromAdcVoltage(); - uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) const; - bool readBatteryVoltageSampled(uint32_t& output) const; bool readBatteryVoltageOnce(uint32_t& output) const; + + uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) const { return chargeFromVoltage.estimateCharge(milliVolt); } }; diff --git a/Drivers/EstimatedPower/Source/ChargeFromVoltage.cpp b/Drivers/EstimatedPower/Source/ChargeFromVoltage.cpp new file mode 100644 index 00000000..cd558f83 --- /dev/null +++ b/Drivers/EstimatedPower/Source/ChargeFromVoltage.cpp @@ -0,0 +1,16 @@ +#include "ChargeFromVoltage.h" +#include + +constexpr auto* TAG = "ChargeFromVoltage"; + +uint8_t ChargeFromVoltage::estimateCharge(uint32_t milliVolt) const { + const float volts = std::min((float)milliVolt / 1000.f, batteryVoltageMax); + if (volts < batteryVoltageMin) { + return 0; + } + const float voltage_percentage = (volts - batteryVoltageMin) / (batteryVoltageMax - batteryVoltageMin); + const float voltage_factor = std::min(1.0f, voltage_percentage); + const auto charge_level = (uint8_t) (voltage_factor * 100.f); + TT_LOG_D(TAG, "mV = %lu, scaled = %.2f, factor = %.2f, result = %d", milliVolt, volts, voltage_factor, charge_level); + return charge_level; +} diff --git a/Drivers/EstimatedPower/Source/ChargeFromVoltage.h b/Drivers/EstimatedPower/Source/ChargeFromVoltage.h new file mode 100644 index 00000000..262c1a6a --- /dev/null +++ b/Drivers/EstimatedPower/Source/ChargeFromVoltage.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +class ChargeFromVoltage { + + float batteryVoltageMin; + float batteryVoltageMax; + +public: + + explicit ChargeFromVoltage(float voltageMin = 3.2f, float voltageMax = 4.2f) : + batteryVoltageMin(voltageMin), + batteryVoltageMax(voltageMax) + {} + + /** + * @param milliVolt + * @return a value in the rage of [0, 100] which represents [0%, 100%] charge + */ + uint8_t estimateCharge(uint32_t milliVolt) const; +}; diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index af541599..e5511a6c 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -31,8 +31,6 @@ std::shared_ptr _Nullable optApp() { class PowerApp : public App { -private: - Timer update_timer = Timer(Timer::Type::Periodic, []() { onTimer(); }); std::shared_ptr power;