Reduce battery-reading noise with a trimmed-mean ADC sample
Sometimes a single reading came in noticeably off from the real trend (a regulator/RF transient during sampling), and the next normal reading would then look like a big jump relative to that bad one -- server-side, enough to misfire the recharge-cycle heuristic (see the paired server commit). Went from 8 raw-averaged samples to 16, sorted, with the 3 extreme samples on each end dropped before averaging the remaining 10 -- a handful of outliers can no longer skew the result the way a plain average let them.
This commit is contained in:
+30
-7
@@ -1,3 +1,5 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "esp_adc/adc_cali_scheme.h"
|
#include "esp_adc/adc_cali_scheme.h"
|
||||||
#include "esp_adc/adc_oneshot.h"
|
#include "esp_adc/adc_oneshot.h"
|
||||||
@@ -11,7 +13,13 @@ static const char *TAG = "battery";
|
|||||||
#if CONFIG_FRAME_BATTERY_ADC_GPIO >= 0
|
#if CONFIG_FRAME_BATTERY_ADC_GPIO >= 0
|
||||||
|
|
||||||
#define BATTERY_ADC_GPIO CONFIG_FRAME_BATTERY_ADC_GPIO
|
#define BATTERY_ADC_GPIO CONFIG_FRAME_BATTERY_ADC_GPIO
|
||||||
#define BATTERY_SAMPLES 8
|
#define BATTERY_SAMPLES 16
|
||||||
|
/* Trimmed mean: the extreme BATTERY_TRIM samples on each end (regulator/
|
||||||
|
* RF transients, not the true resting voltage) are dropped before
|
||||||
|
* averaging the rest -- a plain average lets even one or two of those
|
||||||
|
* skew the result enough to read as a real percent change downstream
|
||||||
|
* (see the recharge-jump handling in routers/device.py). */
|
||||||
|
#define BATTERY_TRIM 3
|
||||||
/* The external divider halves the battery voltage (2x200k, per the
|
/* The external divider halves the battery voltage (2x200k, per the
|
||||||
* Seeed-documented XIAO wiring) so a full 4.2V cell reads ~2.1V at the
|
* Seeed-documented XIAO wiring) so a full 4.2V cell reads ~2.1V at the
|
||||||
* pin, inside the 12dB-attenuation ADC range. */
|
* pin, inside the 12dB-attenuation ADC range. */
|
||||||
@@ -34,6 +42,11 @@ static const struct {
|
|||||||
{ 3300, 5 }, { 3000, 0 },
|
{ 3300, 5 }, { 3000, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int int_cmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return *(const int *)a - *(const int *)b;
|
||||||
|
}
|
||||||
|
|
||||||
static int mv_to_percent(int mv)
|
static int mv_to_percent(int mv)
|
||||||
{
|
{
|
||||||
int n = sizeof(LIPO_CURVE) / sizeof(LIPO_CURVE[0]);
|
int n = sizeof(LIPO_CURVE) / sizeof(LIPO_CURVE[0]);
|
||||||
@@ -139,19 +152,17 @@ int battery_read_percent(void)
|
|||||||
ESP_LOGW(TAG, "ADC calibration unavailable, using nominal scaling");
|
ESP_LOGW(TAG, "ADC calibration unavailable, using nominal scaling");
|
||||||
}
|
}
|
||||||
|
|
||||||
int mv_sum = 0;
|
int mv_samples[BATTERY_SAMPLES];
|
||||||
int samples = 0;
|
int samples = 0;
|
||||||
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
||||||
int value;
|
int value;
|
||||||
if (calibrated) {
|
if (calibrated) {
|
||||||
if (adc_oneshot_get_calibrated_result(adc, cali, channel, &value) == ESP_OK) {
|
if (adc_oneshot_get_calibrated_result(adc, cali, channel, &value) == ESP_OK) {
|
||||||
mv_sum += value;
|
mv_samples[samples++] = value;
|
||||||
samples++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (adc_oneshot_read(adc, channel, &value) == ESP_OK) {
|
if (adc_oneshot_read(adc, channel, &value) == ESP_OK) {
|
||||||
mv_sum += value * 3300 / 4095; /* nominal 12-bit full scale at 12dB */
|
mv_samples[samples++] = value * 3300 / 4095; /* nominal 12-bit full scale at 12dB */
|
||||||
samples++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +178,19 @@ int battery_read_percent(void)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int battery_mv = (mv_sum / samples) * BATTERY_DIVIDER_RATIO;
|
/* Only trim if there's enough left afterward to still be a
|
||||||
|
* meaningful average -- falls back to a plain average of whatever
|
||||||
|
* came in on a wake where most reads failed. */
|
||||||
|
qsort(mv_samples, samples, sizeof(int), int_cmp);
|
||||||
|
int trim = (samples > 2 * BATTERY_TRIM) ? BATTERY_TRIM : 0;
|
||||||
|
int mv_sum = 0;
|
||||||
|
int kept = 0;
|
||||||
|
for (int i = trim; i < samples - trim; i++) {
|
||||||
|
mv_sum += mv_samples[i];
|
||||||
|
kept++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int battery_mv = (mv_sum / kept) * BATTERY_DIVIDER_RATIO;
|
||||||
if (battery_mv < BATTERY_MV_MIN || battery_mv > BATTERY_MV_MAX) {
|
if (battery_mv < BATTERY_MV_MIN || battery_mv > BATTERY_MV_MAX) {
|
||||||
ESP_LOGI(TAG, "Reading %dmV outside plausible battery range, ignoring", battery_mv);
|
ESP_LOGI(TAG, "Reading %dmV outside plausible battery range, ignoring", battery_mv);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
Reference in New Issue
Block a user