From 81e70925c47b0fe1252c51b2a2797fa9bce4f413 Mon Sep 17 00:00:00 2001 From: Erwin Repolust Date: Wed, 8 Mar 2023 03:24:16 +0100 Subject: [PATCH 1/5] Changed to running average to improve accuracy --- usermods/Battery/usermod_v2_Battery.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 2dc85424..05532d9b 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -126,6 +126,8 @@ class UsermodBattery : public Usermod if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; + //initialize voltage again with analog read as that will give us faster precision + voltage = (analogReadMilliVolts(batteryPin) / 1000.0f + calibration / 2.0f) * 2.0f; } if (!success) { @@ -178,9 +180,9 @@ class UsermodBattery : public Usermod #ifdef ARDUINO_ARCH_ESP32 // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) - rawValue = analogReadMilliVolts(batteryPin); - // calculate the voltage - voltage = (rawValue / 1000.0f) + calibration; + rawValue = analogReadMilliVolts(batteryPin) / 1000.0f + calibration / 2.0f; + // calculate the voltage with a 1/20th weighted running average + voltage = ((voltage / 2.0f) * 19.0f + rawValue) / 20.0f; // usually a voltage divider (50%) is used on ESP32, so we need to multiply by 2 voltage *= 2.0f; #else @@ -191,8 +193,8 @@ class UsermodBattery : public Usermod voltage = ((rawValue / getAdcPrecision()) * maxBatteryVoltage) + calibration; #endif // check if voltage is within specified voltage range, allow 10% over/under voltage - voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; - + //voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; + // translate battery voltage into percentage /* the standard "map" function doesn't work From e00e778bce2b4bb905d3efa7d74642ba5b6b7633 Mon Sep 17 00:00:00 2001 From: Erwin Repolust Date: Wed, 8 Mar 2023 03:54:48 +0100 Subject: [PATCH 2/5] Less operations and better readable --- usermods/Battery/usermod_v2_Battery.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 05532d9b..0e9fc563 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -126,8 +126,8 @@ class UsermodBattery : public Usermod if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; - //initialize voltage again with analog read as that will give us faster precision - voltage = (analogReadMilliVolts(batteryPin) / 1000.0f + calibration / 2.0f) * 2.0f; + //initialize voltage with analog read + voltage = analogReadMilliVolts(batteryPin) / 500.0f + calibration; } if (!success) { @@ -179,12 +179,10 @@ class UsermodBattery : public Usermod initializing = false; #ifdef ARDUINO_ARCH_ESP32 - // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) - rawValue = analogReadMilliVolts(batteryPin) / 1000.0f + calibration / 2.0f; - // calculate the voltage with a 1/20th weighted running average - voltage = ((voltage / 2.0f) * 19.0f + rawValue) / 20.0f; - // usually a voltage divider (50%) is used on ESP32, so we need to multiply by 2 - voltage *= 2.0f; + // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) and divide by 500 to fix to the right decimal place and multiply by 2 to account for the voltage divider + rawValue = analogReadMilliVolts(batteryPin) / 500.0f + calibration; + // calculate the voltage with a weighted running average because ADC in ESP32 is fluctuating too much for a good single readout + voltage = (voltage * 19.0f + rawValue) / 20.0f; #else // read battery raw input rawValue = analogRead(batteryPin); @@ -192,9 +190,9 @@ class UsermodBattery : public Usermod // calculate the voltage voltage = ((rawValue / getAdcPrecision()) * maxBatteryVoltage) + calibration; #endif - // check if voltage is within specified voltage range, allow 10% over/under voltage + // check if voltage is within specified voltage range, allow 10% over/under voltage - removed cause this just makes it hard for people to troubleshoot as the voltage in the web gui will say invalid instead of displaying a voltage //voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; - + // translate battery voltage into percentage /* the standard "map" function doesn't work From 8b61b9ebfea84b2a4f1780747365fbb483b34e5c Mon Sep 17 00:00:00 2001 From: Erwin Repolust Date: Fri, 10 Mar 2023 01:28:04 +0100 Subject: [PATCH 3/5] Added code for esp8266 --- usermods/Battery/usermod_v2_Battery.h | 44 +++++++++++---------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 0e9fc563..60e90019 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -29,6 +29,10 @@ class UsermodBattery : public Usermod float rawValue = 0.0f; // calculated voltage float voltage = maxBatteryVoltage; + // between 0 and 1, to control strength of voltage smoothing filter + float alpha = 0.05f; + // multiplyer for the voltage divider that is in place between ADC pin and battery, default will be 2 but might be adapted to readout voltages over ~5v ESP32 or ~6.6v ESP8266 + float voltageMultiplyer = 2.0f; // mapped battery level based on voltage int8_t batteryLevel = 100; // offset or calibration value to fine tune the calculated voltage @@ -126,8 +130,8 @@ class UsermodBattery : public Usermod if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; - //initialize voltage with analog read - voltage = analogReadMilliVolts(batteryPin) / 500.0f + calibration; + //use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default) and divide by 1000 to get from milivolts to volts and multiply by voltage divider and apply calibration value + voltage = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplyer + calibration; } if (!success) { @@ -137,8 +141,9 @@ class UsermodBattery : public Usermod pinMode(batteryPin, INPUT); } #else //ESP8266 boards have only one analog input pin A0 - pinMode(batteryPin, INPUT); + //use analog read on esp8266 ( 150 mV ~ 3000mV no attenuation options) and divide by ADC precision 1023 and multiply by 3v ADC max voltage and apply calibration value + voltage = (analogRead(batteryPin) / 1023.0f) * 3.3f * voltageMultiplyer + calibration; #endif nextReadTime = millis() + readingInterval; @@ -179,16 +184,15 @@ class UsermodBattery : public Usermod initializing = false; #ifdef ARDUINO_ARCH_ESP32 - // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) and divide by 500 to fix to the right decimal place and multiply by 2 to account for the voltage divider - rawValue = analogReadMilliVolts(batteryPin) / 500.0f + calibration; - // calculate the voltage with a weighted running average because ADC in ESP32 is fluctuating too much for a good single readout - voltage = (voltage * 19.0f + rawValue) / 20.0f; + // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default) and divide by 1000 to get from milivolts to volts and multiply by voltage divider and apply calibration value + rawValue = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplyer + calibration; + // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout + voltage = voltage + alpha * (rawValue - voltage); #else - // read battery raw input - rawValue = analogRead(batteryPin); - - // calculate the voltage - voltage = ((rawValue / getAdcPrecision()) * maxBatteryVoltage) + calibration; + // use analog read on esp8266 ( 150 mV ~ 3000mV no attenuation options) and divide by ADC precision 1023 and multiply by 3v ADC max voltage and apply calibration value + rawValue = (analogRead(batteryPin) / 1023.0f) * 3.0f * voltageMultiplyer + calibration; + // filter with exponential smoothing + voltage = voltage + alpha * (rawValue - voltage); #endif // check if voltage is within specified voltage range, allow 10% over/under voltage - removed cause this just makes it hard for people to troubleshoot as the voltage in the web gui will say invalid instead of displaying a voltage //voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; @@ -595,21 +599,7 @@ class UsermodBattery : public Usermod totalBatteryCapacity = capacity; } - /* - * Get the choosen adc precision - * esp8266 = 10bit resolution = 1024.0f - * esp32 = 12bit resolution = 4095.0f - */ - float getAdcPrecision() - { - #ifdef ARDUINO_ARCH_ESP32 - // esp32 - return 4096.0f; - #else - // esp8266 - return 1024.0f; - #endif - } + /* * Get the calculated voltage From ec08432f9256e250aa9dbadfd9804948419be4ef Mon Sep 17 00:00:00 2001 From: Erwin Repolust Date: Tue, 14 Mar 2023 01:44:41 +0100 Subject: [PATCH 4/5] added voltage multiplier to gui and set defaults --- usermods/Battery/battery_defaults.h | 9 +++++++ usermods/Battery/usermod_v2_Battery.h | 35 +++++++++++++++++++++------ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index c682cb45..958bfe52 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -26,6 +26,15 @@ #endif #endif +//the default ratio for the voltage divider +#ifndef USERMOD_BATTERY_VOLTAGE_MULTIPLIER + #ifdef ARDUINO_ARCH_ESP32 + #define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 2.0f + #else //ESP8266 boards + #define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 4.2f + #endif +#endif + #ifndef USERMOD_BATTERY_MAX_VOLTAGE #define USERMOD_BATTERY_MAX_VOLTAGE 4.2f #endif diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 60e90019..5fbfd6a7 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -31,8 +31,8 @@ class UsermodBattery : public Usermod float voltage = maxBatteryVoltage; // between 0 and 1, to control strength of voltage smoothing filter float alpha = 0.05f; - // multiplyer for the voltage divider that is in place between ADC pin and battery, default will be 2 but might be adapted to readout voltages over ~5v ESP32 or ~6.6v ESP8266 - float voltageMultiplyer = 2.0f; + // multiplier for the voltage divider that is in place between ADC pin and battery, default will be 2 but might be adapted to readout voltages over ~5v ESP32 or ~6.6v ESP8266 + float voltageMultiplier = USERMOD_BATTERY_VOLTAGE_MULTIPLIER; // mapped battery level based on voltage int8_t batteryLevel = 100; // offset or calibration value to fine tune the calculated voltage @@ -130,8 +130,8 @@ class UsermodBattery : public Usermod if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; - //use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default) and divide by 1000 to get from milivolts to volts and multiply by voltage divider and apply calibration value - voltage = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplyer + calibration; + //use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default) and divide by 1000 to get from milivolts to volts and multiply by voltage divider ratio and apply calibration value + voltage = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; } if (!success) { @@ -142,8 +142,8 @@ class UsermodBattery : public Usermod } #else //ESP8266 boards have only one analog input pin A0 pinMode(batteryPin, INPUT); - //use analog read on esp8266 ( 150 mV ~ 3000mV no attenuation options) and divide by ADC precision 1023 and multiply by 3v ADC max voltage and apply calibration value - voltage = (analogRead(batteryPin) / 1023.0f) * 3.3f * voltageMultiplyer + calibration; + //use analog read on esp8266 ( 150 mV ~ 3000mV no attenuation options) and divide by ADC precision 1023 and multiply by voltage divider ratio and apply calibration value + voltage = (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; #endif nextReadTime = millis() + readingInterval; @@ -185,12 +185,12 @@ class UsermodBattery : public Usermod #ifdef ARDUINO_ARCH_ESP32 // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default) and divide by 1000 to get from milivolts to volts and multiply by voltage divider and apply calibration value - rawValue = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplyer + calibration; + rawValue = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout voltage = voltage + alpha * (rawValue - voltage); #else // use analog read on esp8266 ( 150 mV ~ 3000mV no attenuation options) and divide by ADC precision 1023 and multiply by 3v ADC max voltage and apply calibration value - rawValue = (analogRead(batteryPin) / 1023.0f) * 3.0f * voltageMultiplyer + calibration; + rawValue = (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; // filter with exponential smoothing voltage = voltage + alpha * (rawValue - voltage); #endif @@ -367,6 +367,7 @@ class UsermodBattery : public Usermod battery[F("max-voltage")] = maxBatteryVoltage; battery[F("capacity")] = totalBatteryCapacity; battery[F("calibration")] = calibration; + battery[F("voltage-multiplier")] = voltageMultiplier; battery[FPSTR(_readInterval)] = readingInterval; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section @@ -443,6 +444,7 @@ class UsermodBattery : public Usermod setMaxBatteryVoltage(battery[F("max-voltage")] | maxBatteryVoltage); setTotalBatteryCapacity(battery[F("capacity")] | totalBatteryCapacity); setCalibration(battery[F("calibration")] | calibration); + setVoltageMultiplier(battery[F("voltage-multiplier")] | voltageMultiplier); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); JsonObject ao = battery[F("auto-off")]; @@ -637,6 +639,23 @@ class UsermodBattery : public Usermod calibration = offset; } + /* + * Set the voltage multiplier value + * A multiplier that may need adjusting for different voltage divider setups + */ + void setVoltageMultiplier(float multiplier) + { + voltageMultiplier = multiplier; + } + + /* + * Get the voltage multiplier value + * A multiplier that may need adjusting for different voltage divider setups + */ + float getVoltageMultiplier() + { + return voltageMultiplier; + } /* * Get auto-off feature enabled status From 2c3fa0fd8f3b2f4d4e74d3b15d01eea6dd6bee49 Mon Sep 17 00:00:00 2001 From: Erwin Repolust Date: Thu, 16 Mar 2023 01:33:57 +0100 Subject: [PATCH 5/5] added function for voltage reads --- usermods/Battery/usermod_v2_Battery.h | 31 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 5fbfd6a7..d3e52e00 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -114,6 +114,17 @@ class UsermodBattery : public Usermod } } + float readVoltage() + { + #ifdef ARDUINO_ARCH_ESP32 + // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value + return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; + #else + // use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value + return (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; + #endif + } + public: //Functions called by WLED @@ -130,8 +141,7 @@ class UsermodBattery : public Usermod if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; - //use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default) and divide by 1000 to get from milivolts to volts and multiply by voltage divider ratio and apply calibration value - voltage = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; + voltage = readVoltage(); } if (!success) { @@ -142,8 +152,7 @@ class UsermodBattery : public Usermod } #else //ESP8266 boards have only one analog input pin A0 pinMode(batteryPin, INPUT); - //use analog read on esp8266 ( 150 mV ~ 3000mV no attenuation options) and divide by ADC precision 1023 and multiply by voltage divider ratio and apply calibration value - voltage = (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; + voltage = readVoltage(); #endif nextReadTime = millis() + readingInterval; @@ -183,17 +192,10 @@ class UsermodBattery : public Usermod initializing = false; -#ifdef ARDUINO_ARCH_ESP32 - // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default) and divide by 1000 to get from milivolts to volts and multiply by voltage divider and apply calibration value - rawValue = (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; + rawValue = readVoltage(); // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout voltage = voltage + alpha * (rawValue - voltage); -#else - // use analog read on esp8266 ( 150 mV ~ 3000mV no attenuation options) and divide by ADC precision 1023 and multiply by 3v ADC max voltage and apply calibration value - rawValue = (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; - // filter with exponential smoothing - voltage = voltage + alpha * (rawValue - voltage); -#endif + // check if voltage is within specified voltage range, allow 10% over/under voltage - removed cause this just makes it hard for people to troubleshoot as the voltage in the web gui will say invalid instead of displaying a voltage //voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; @@ -380,6 +382,9 @@ class UsermodBattery : public Usermod lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold; lp[FPSTR(_duration)] = lowPowerIndicatorDuration; + // read voltage in case calibration or voltage multiplier changed to see immediate effect + voltage = readVoltage() + DEBUG_PRINTLN(F("Battery config saved.")); }