Files
dezibot/src/power/Power.cpp

187 lines
6.3 KiB
C++

/**
* @file Power.h
* @author Phillip Kühne
* @brief This component provides utilities for keeping track of power usage
* consumption.
* @version 0.1
* @date 2024-11-23
*/
#include "Power.h"
static portMUX_TYPE mux;
void Power::begin() {
// Check if another instance of us already initialized the power scheduler,
// if not, we will do it.
if (powerScheduler == nullptr) {
powerScheduler = &PowerScheduler::getPowerScheduler();
if (!(powerScheduler->tryAccquireCurrentAllowance(
PowerParameters::PowerConsumers::ESP,
PowerParameters::CurrentConsumptions::CURRENT_ESP_AVG))) {
Serial.println("Alledgedly not enough power available to reserve the "
"ESP32s base power consumption. Something is wrong.");
return;
}
}
}
uint16_t Power::getFreeCurrentBudget(void) {
return powerScheduler->getFreeCurrentBudget();
}
bool Power::tryAccquireCurrentAllowance(
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
uint16_t requestedDurationMs) {
return powerScheduler->tryAccquireCurrentAllowance(consumer, neededCurrent,
requestedDurationMs);
}
bool Power::waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
uint16_t neededCurrent,
TickType_t TicksToWait,
uint16_t requestedDurationMs) {
return powerScheduler->waitForCurrentAllowance(
consumer, neededCurrent, TicksToWait, requestedDurationMs);
}
void Power::beginPermanentDeepSleep(void) {
return powerScheduler->beginPermanentDeepSleep();
}
void Power::releaseCurrent(PowerParameters::PowerConsumers consumer) {
powerScheduler->releaseCurrent(consumer);
}
float Power::getBatteryVoltage() {
// Get the battery voltage from the ADC and convert it to a voltage
// using the voltage divider.
portENTER_CRITICAL(&mux);
// Enable voltage divider
digitalWrite(PowerParameters::PinConfig::BAT_ADC_EN, HIGH);
// Returns value between 0 and 4095 mapping to between 0 and 3.3V
uint16_t batteryAdcValue =
analogRead(PowerParameters::PinConfig::BAT_ADC) *
(PowerParameters::Battery::BAT_ADC::VOLTAGE_DIVIDER_FACTOR);
// Disable voltage divider
digitalWrite(PowerParameters::PinConfig::BAT_ADC_EN, LOW);
portEXIT_CRITICAL(&mux);
// Convert ADC value to voltage
float batteryVoltage =
(batteryAdcValue * 3.3 / 4095) *
PowerParameters::Battery::BAT_ADC::VOLTAGE_DIVIDER_FACTOR;
return batteryVoltage;
}
int Power::getBatteryChargePercent() {
return percentRemaining;
}
float Power::getBatteryChargeCoulombs() {
return coloumbsRemaining;
}
int Power::getBatteryVoltageChargePercent() {
// Get the battery voltage and calculate the charge state based on the
// discharge curve.
float batteryVoltage = getBatteryVoltage();
float chargeState = 0;
// Clamp edge cases
if (batteryVoltage >=
PowerParameters::Battery::DISCHARGE_CURVE::VOLTAGES[0]) {
return PowerParameters::Battery::DISCHARGE_CURVE::CHARGE_STATES[0];
}
if (batteryVoltage <=
PowerParameters::Battery::DISCHARGE_CURVE::VOLTAGES
[PowerParameters::Battery::DISCHARGE_CURVE::NUM_POINTS - 1]) {
return PowerParameters::Battery::DISCHARGE_CURVE::CHARGE_STATES
[PowerParameters::Battery::DISCHARGE_CURVE::NUM_POINTS - 1];
}
float p1_x, p1_y, p2_x, p2_y;
for (int i = 0; i < PowerParameters::Battery::DISCHARGE_CURVE::NUM_POINTS;
i++) {
if (batteryVoltage >=
PowerParameters::Battery::DISCHARGE_CURVE::VOLTAGES[i]) {
p1_y = PowerParameters::Battery::DISCHARGE_CURVE::CHARGE_STATES[i];
p1_x = PowerParameters::Battery::DISCHARGE_CURVE::VOLTAGES[i];
p2_y = PowerParameters::Battery::DISCHARGE_CURVE::CHARGE_STATES[i + 1];
p2_x = PowerParameters::Battery::DISCHARGE_CURVE::VOLTAGES[i + 1];
chargeState =
((p2_y - p1_y) / (p2_x - p1_x)) * (batteryVoltage - p1_x) + p1_y;
return chargeState;
}
}
}
void Power::updatePowerStateHandler() {
float currentCurrent = powerScheduler->getCurrentCurrent();
int referenceCurrentMa =
PowerParameters::Battery::DISCHARGE_CURVE::REFERENCE_CURRENT_A * 1000;
// Calculate remaining battery charge in Coulombs based on current and time
float coloumbsConsumedSinceLastUpdate =
(currentCurrent / 1000) *
((pdTICKS_TO_MS(xTaskGetTickCount() - lastPowerStateUpdate)) / 1000.0);
// Update coloumbs remaining
coloumbsRemaining -= coloumbsConsumedSinceLastUpdate;
float chargeState;
// If current flow is close enough to reference, get battery charge state via
// voltage curve
if ((currentCurrent > (referenceCurrentMa * 0.6)) &&
(currentCurrent < (referenceCurrentMa * 1.4))) {
// Get battery charge state from voltage curve
chargeState = getBatteryVoltageChargePercent();
} else {
// Calculate battery charge state from Charge consumption
float oldChargeState = lastSOC[latestSoCIndex];
float chargeState =
oldChargeState - ((coloumbsConsumedSinceLastUpdate /
PowerParameters::Battery::CELL_CHARGE_FULL_COLOUMB) *
100);
}
addSoCSample(chargeState);
// Update percentage remaining based on charge state average
float sampleSum = 0;
for (int i = 0; i < PowerParameters::Battery::AVERAGING_SAMPLES; i++) {
sampleSum += lastSOC[i];
}
percentRemaining = sampleSum / PowerParameters::Battery::AVERAGING_SAMPLES;
// Update last update time
lastPowerStateUpdate = xTaskGetTickCount();
// Update the available current (changes based on battery state of charge)
powerScheduler->recalculateCurrentBudgets();
return;
}
float Power::getMax3V3Current() {
float u_bat = getBatteryVoltage();
float i_bat = PowerParameters::Battery::CELL_CURRENT_1C;
float eta = PowerParameters::BUCK_BOOST_EFFICIENCY;
constexpr float u_3v3 = 3.3;
return (u_bat * i_bat * eta) / u_3v3;
}
void Power::addSoCSample(float soc) {
latestSoCIndex = (latestSoCIndex + 1) % PowerParameters::Battery::AVERAGING_SAMPLES;
lastSOC[latestSoCIndex] = soc;
}
PowerScheduler *Power::powerScheduler = nullptr;
Power::Power() {
// Initialize the power scheduler
powerScheduler = &PowerScheduler::getPowerScheduler();
// Initialize the mutex
mux = portMUX_INITIALIZER_UNLOCKED;
}