add ringbuffer for battery percentage smoothing

This commit is contained in:
Dave ID 2026-02-03 15:48:49 +01:00
parent f67c544e16
commit ee806390f3
4 changed files with 104 additions and 4 deletions

40
src/Battery.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "Battery.h"
void BatteryPercentageRingBuffer::init(uint16_t v) {
for (size_t i = 0; i < MAX_SAMPLES; i++) {
buf[i] = v;
}
sum = v * MAX_SAMPLES;
prev_val = v;
head = 0;
}
void BatteryPercentageRingBuffer::update(uint16_t v) {
// Previous percentage is set to 161 only if buffer was constructed but not initialized yet
if (!prev_val) {
init(v);
}
// Recalculate rolling sum
sum -= buf[head];
buf[head] = v;
sum += v;
// Shift head
head++;
if (head >= MAX_SAMPLES) head = 0;
}
uint16_t BatteryPercentageRingBuffer::evaluate() {
// We 'round' (only works for pos numbers but oh well good enough for battery percentages) to an int
float avg = (sum / MAX_SAMPLES) + 0.5;
uint16_t new_val = (int)avg;
// Battery percentage should not increse when not charging so we just cap it to be lower than the last value
if (new_val < prev_val) {
prev_val = new_val;
return new_val;
} else {
return prev_val;
}
}

View File

@ -1,6 +1,21 @@
#pragma once #pragma once
#include <BatteryMonitor.h> #include <BatteryMonitor.h>
#include <cstddef>
#define BAT_GPIO0 0 // Battery voltage #define BAT_GPIO0 0 // Battery voltage
static BatteryMonitor battery(BAT_GPIO0); static BatteryMonitor battery(BAT_GPIO0);
struct BatteryPercentageRingBuffer {
static constexpr uint16_t MAX_SAMPLES = 10;
uint16_t buf[MAX_SAMPLES];
uint16_t head = 0;
uint16_t sum = 0;
uint16_t prev_val = 0;
void init(uint16_t value);
void update(uint16_t value);
uint16_t evaluate();
};

View File

@ -1,5 +1,6 @@
#include "ScreenComponents.h" #include "ScreenComponents.h"
#include <Arduino.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <cstdint> #include <cstdint>
@ -8,10 +9,24 @@
#include "Battery.h" #include "Battery.h"
#include "fontIds.h" #include "fontIds.h"
BatteryPercentageRingBuffer ScreenComponents::batteryBuffer;
void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top, void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top,
const bool showPercentage) { const bool showPercentage) {
const bool charging = (digitalRead(20) == HIGH);
// Left aligned battery icon and percentage // Left aligned battery icon and percentage
const uint16_t percentage = battery.readPercentage(); uint16_t percentage = battery.readPercentage();
if (charging) {
// If charging reinitialize buffer with current percentage and display this value
batteryBuffer.init(percentage);
} else {
// Else update buffer with new percentage and return smoothed validated percentage to display
batteryBuffer.update(percentage);
percentage = batteryBuffer.evaluate();
}
const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : ""; const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : "";
renderer.drawText(SMALL_FONT_ID, left + 20, top, percentageText.c_str()); renderer.drawText(SMALL_FONT_ID, left + 20, top, percentageText.c_str());
@ -34,12 +49,38 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left,
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5); renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5);
// The +1 is to round up, so that we always fill at least one pixel // The +1 is to round up, so that we always fill at least one pixel
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1; constexpr int maxFillWidth = batteryWidth - 5;
if (filledWidth > batteryWidth - 5) { int filledWidth = percentage * maxFillWidth / 100 + 1;
filledWidth = batteryWidth - 5; // Ensure we don't overflow if (filledWidth > maxFillWidth) {
filledWidth = maxFillWidth;
}
// When charging, ensure minimum fill so lightning bolt is fully visible
constexpr int minFillForBolt = 8; // Bolt extends 6px wide, needs padding
if (charging && filledWidth < minFillForBolt) {
filledWidth = minFillForBolt;
} }
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
// Draw lightning bolt when charging (white/inverted on black fill for visibility)
if (charging) {
// Lightning bolt: 6px wide, 8px tall, centered in battery
const int boltX = x + 4;
const int boltY = y + 2;
// Draw bolt in white (state=false) for visibility on black fill
// Upper diagonal pointing right
renderer.drawLine(boltX + 4, boltY + 0, boltX + 5, boltY + 0, false);
renderer.drawLine(boltX + 3, boltY + 1, boltX + 4, boltY + 1, false);
renderer.drawLine(boltX + 2, boltY + 2, boltX + 5, boltY + 2, false); // Wide middle
renderer.drawLine(boltX + 3, boltY + 3, boltX + 4, boltY + 3, false);
// Lower diagonal pointing left
renderer.drawLine(boltX + 2, boltY + 4, boltX + 3, boltY + 4, false);
renderer.drawLine(boltX + 1, boltY + 5, boltX + 4, boltY + 5, false); // Wide middle
renderer.drawLine(boltX + 2, boltY + 6, boltX + 3, boltY + 6, false);
renderer.drawLine(boltX + 1, boltY + 7, boltX + 2, boltY + 7, false);
}
} }
ScreenComponents::PopupLayout ScreenComponents::drawPopup(const GfxRenderer& renderer, const char* message) { ScreenComponents::PopupLayout ScreenComponents::drawPopup(const GfxRenderer& renderer, const char* message) {

View File

@ -4,6 +4,8 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "Battery.h"
class GfxRenderer; class GfxRenderer;
struct TabInfo { struct TabInfo {
@ -15,6 +17,8 @@ class ScreenComponents {
public: public:
static const int BOOK_PROGRESS_BAR_HEIGHT = 4; static const int BOOK_PROGRESS_BAR_HEIGHT = 4;
static BatteryPercentageRingBuffer batteryBuffer;
struct PopupLayout { struct PopupLayout {
int x; int x;
int y; int y;