Refactor BatterySensor and Display classes to integrate MAX17048 and SH110X support

This commit is contained in:
Joshua King
2026-02-21 22:09:13 -05:00
parent 63061bdab2
commit df00d77ce1
5 changed files with 45 additions and 99 deletions

View File

@@ -7,9 +7,10 @@ monitor_speed = 115200
lib_deps =
adafruit/Adafruit GFX Library
adafruit/Adafruit SSD1306
adafruit/Adafruit SH110X
adafruit/Adafruit VEML7700 Library
adafruit/Adafruit MPU6050
adafruit/Adafruit MAX1704X
bblanchon/ArduinoJson
; board_build.filesystem = spiffs

View File

@@ -1,109 +1,64 @@
#include "BatterySensor.h"
#include <math.h>
void BatterySensor::begin() {
pinMode(PIN_BATTERY_ADC, INPUT);
pinMode(PIN_LBO, INPUT);
pinMode(PIN_CHG, INPUT_PULLUP); // CHG is LOW when charging
_ok = _maxlipo.begin();
if (!_ok) {
Serial.println("[Battery] MAX17048 not detected");
return;
}
analogReadResolution(12); // 12-bit ADC (0-4095)
Serial.println("[Battery] Initialized");
Serial.println("[Battery] ADC on GPIO 2, LBO on GPIO 5, CHG on GPIO 4");
// Initial reading
_voltage = readBatteryVoltage();
_percent = voltageToPercent(_voltage);
_isLow = (_voltage < VOLTAGE_LOW);
Serial.println("[Battery] MAX17048 initialized on I2C");
refresh();
Serial.print("[Battery] Initial voltage: ");
Serial.print(_voltage);
Serial.print(_voltage, 2);
Serial.print("V, ");
Serial.print(_percent);
Serial.println("%");
}
void BatterySensor::loop() {
if (!_ok) return;
unsigned long now = millis();
if (now - _lastCheckMs < CHECK_INTERVAL_MS) return;
_lastCheckMs = now;
float prevVoltage = _voltage;
int prevPercent = _percent;
bool wasLow = _isLow;
bool wasCharging = _isCharging;
// Read battery voltage via ADC
_voltage = readBatteryVoltage();
_percent = voltageToPercent(_voltage);
refresh();
// Check CHG pin (LOW when charging)
bool chgLow = !digitalRead(PIN_CHG);
_isCharging = chgLow;
// Check LBO pin as backup confirmation
bool lboHigh = digitalRead(PIN_LBO);
bool lboIndicatesLow = !lboHigh;
// Battery is low if either voltage is low OR LBO pin indicates low (and not charging)
_isLow = ((_voltage < VOLTAGE_LOW) || lboIndicatesLow) && !_isCharging;
// Log significant changes
if (abs(_percent - prevPercent) >= 5 || _isLow != wasLow || _isCharging != wasCharging) {
if (abs(_percent - prevPercent) >= 2 || _isLow != wasLow || _isCharging != wasCharging) {
Serial.print("[Battery] Voltage: ");
Serial.print(_voltage, 2);
Serial.print("V (");
Serial.print(_percent);
Serial.print("%) ");
Serial.print("%)");
if (_isCharging) {
Serial.print("CHARGING ");
}
Serial.print("LBO: ");
Serial.println(lboHigh ? "OK" : "LOW");
if (_isLow && !wasLow && !_isCharging) {
Serial.println("[Battery] ⚠️ LOW BATTERY WARNING - Please recharge!");
}
if (_isCharging && !wasCharging) {
Serial.println("[Battery] 🔌 Charging started");
} else if (!_isCharging && wasCharging) {
Serial.println("[Battery] ✓ Charging complete or disconnected");
}
if (_isCharging) Serial.print(" CHARGING");
Serial.println();
}
}
float BatterySensor::readBatteryVoltage() {
// Take multiple samples and average them for stability
long sum = 0;
for (int i = 0; i < SAMPLES; i++) {
sum += analogRead(PIN_BATTERY_ADC);
delay(5);
void BatterySensor::refresh() {
float v = _maxlipo.cellVoltage();
float p = _maxlipo.cellPercent();
float rate = _maxlipo.chargeRate();
if (!isnan(v) && isfinite(v) && v > 0.0f) _voltage = v;
if (!isnan(p) && isfinite(p)) {
int pct = (int)lroundf(p);
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
_percent = pct;
}
int avgRaw = sum / SAMPLES;
// Convert ADC value to voltage at the pin
// ESP32-C3 ADC: 12-bit (0-4095) maps to 0-3.3V
float adcVoltage = (avgRaw / 4095.0) * 3.3;
// Multiply by divider ratio to get actual battery voltage
float batteryVoltage = adcVoltage * DIVIDER_RATIO;
return batteryVoltage;
}
int BatterySensor::voltageToPercent(float voltage) {
// Clamp voltage to valid range
if (voltage >= VOLTAGE_MAX) return 100;
if (voltage <= VOLTAGE_MIN) return 0;
// Linear mapping from voltage to percentage
// This is a simplified model; LiPo discharge curves are non-linear
// For better accuracy, you could use a lookup table
float percent = ((voltage - VOLTAGE_MIN) / (VOLTAGE_MAX - VOLTAGE_MIN)) * 100.0;
return constrain((int)percent, 0, 100);
_isLow = (_voltage < VOLTAGE_LOW) || (_percent < 15);
_isCharging = (!isnan(rate) && isfinite(rate) && rate > CHARGE_RATE_MIN_PCT_PER_HR);
}
bool BatterySensor::shouldBlink() const {
@@ -112,7 +67,5 @@ bool BatterySensor::shouldBlink() const {
}
int BatterySensor::iconFillWidth(int maxWidth) const {
// Calculate fill width for battery icon
// Returns 0 to maxWidth based on percentage
return (_percent * maxWidth) / 100;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <Arduino.h>
#include <Adafruit_MAX1704X.h>
class BatterySensor {
public:
@@ -17,27 +18,18 @@ public:
int iconFillWidth(int maxWidth) const;
private:
static constexpr int PIN_BATTERY_ADC = 2; // Voltage divider input
static constexpr int PIN_LBO = 5; // PowerBoost LBO pin
static constexpr int PIN_CHG = 4; // PowerBoost CHG pin (LOW when charging)
static constexpr unsigned long CHECK_INTERVAL_MS = 2000;
static constexpr int SAMPLES = 10; // Number of ADC samples to average
// LiPo voltage thresholds
static constexpr float VOLTAGE_MIN = 3.0; // 0%
static constexpr float VOLTAGE_MAX = 4.2; // 100%
static constexpr float VOLTAGE_LOW = 3.2; // Low battery threshold
// Voltage divider: R1=100k, R2=100k (2:1 ratio)
static constexpr float DIVIDER_RATIO = 2.0;
static constexpr float CHARGE_RATE_MIN_PCT_PER_HR = 0.2f; // heuristic
int _percent = 100;
float _voltage = 3.7;
bool _isLow = false;
bool _isCharging = false;
unsigned long _lastCheckMs = 0;
bool _ok = false;
Adafruit_MAX17048 _maxlipo;
float readBatteryVoltage();
int voltageToPercent(float voltage);
void refresh();
};

View File

@@ -2,7 +2,7 @@
void Display::begin() {
Wire.begin(PIN_SDA, PIN_SCL);
_ok = _oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR, true, false);
_ok = _oled.begin(OLED_ADDR, true);
if (!_ok) return;
_displayEnabled = true;
setContrast(_contrast);
@@ -25,8 +25,8 @@ void Display::showStatus(const String& line1, const String& line2) {
void Display::setContrast(uint8_t contrast) {
if (!_ok) return;
_contrast = contrast;
_oled.ssd1306_command(SSD1306_SETCONTRAST);
_oled.ssd1306_command(_contrast);
_oled.oled_command(SH110X_SETCONTRAST);
_oled.oled_command(_contrast);
}
void Display::setDisplayEnabled(bool enabled) {
@@ -35,12 +35,12 @@ void Display::setDisplayEnabled(bool enabled) {
_displayEnabled = enabled;
if (enabled) {
_oled.ssd1306_command(SSD1306_DISPLAYON);
_oled.oled_command(SH110X_DISPLAYON);
setContrast(_contrast);
} else {
_oled.clearDisplay();
_oled.display();
_oled.ssd1306_command(SSD1306_DISPLAYOFF);
_oled.oled_command(SH110X_DISPLAYOFF);
}
}

View File

@@ -2,12 +2,12 @@
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_SH110X.h>
class Display {
public:
void begin();
Adafruit_SSD1306& oled() { return _oled; }
Adafruit_SH1106G& oled() { return _oled; }
bool ok() const { return _ok; }
void showStatus(const String& line1, const String& line2);
@@ -21,7 +21,7 @@ private:
static constexpr int PIN_SDA = 6;
static constexpr int PIN_SCL = 7;
static constexpr uint8_t OLED_ADDR = 0x3C; // try 0x3D if blank
Adafruit_SSD1306 _oled = Adafruit_SSD1306(128, 64, &Wire, -1);
Adafruit_SH1106G _oled = Adafruit_SH1106G(128, 64, &Wire, -1);
bool _ok = false;
bool _displayEnabled = true;
uint8_t _contrast = 0xCF;