Add power modeling and scheduling based on thesis

This commit is contained in:
Phillip Kühne 2025-02-12 16:09:40 +01:00
parent b44538b473
commit c63935a413
Signed by: phillip
GPG Key ID: E4C1C4D2F90902AA
5 changed files with 195 additions and 54 deletions

View File

@ -74,6 +74,16 @@ float Power::getBatteryVoltage() {
} }
int Power::getBatteryChargePercent() { 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 // Get the battery voltage and calculate the charge state based on the
// discharge curve. // discharge curve.
float batteryVoltage = getBatteryVoltage(); float batteryVoltage = getBatteryVoltage();
@ -105,7 +115,69 @@ int Power::getBatteryChargePercent() {
} }
} }
PowerScheduler* Power::powerScheduler = nullptr; 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() { Power::Power() {
// Initialize the power scheduler // Initialize the power scheduler

View File

@ -11,8 +11,6 @@
#ifndef Power_h #ifndef Power_h
#define Power_h #define Power_h
#define TOTAL_POWER_MILLIWATTS POWER_BUDGET
enum TaskResumptionReason { POWER_AVAILABLE, TIMEOUT }; enum TaskResumptionReason { POWER_AVAILABLE, TIMEOUT };
class Power { class Power {
@ -55,6 +53,11 @@ public:
/// @return Battery charge state in percent /// @return Battery charge state in percent
static int getBatteryChargePercent(); static int getBatteryChargePercent();
/// @brief Get estimated battery charge state as percentage based on
// voltage directly
/// @return Battery charge state in percent
static int getBatteryVoltageChargePercent();
/// @brief Get estimated battery charge state as coulombs /// @brief Get estimated battery charge state as coulombs
/// @return Battery charge state in coulombs /// @return Battery charge state in coulombs
static float getBatteryChargeCoulombs(); static float getBatteryChargeCoulombs();
@ -62,26 +65,35 @@ public:
/// @brief get available current (after voltage conversion and efficiency /// @brief get available current (after voltage conversion and efficiency
/// losses, referencing 1C discharge) /// losses, referencing 1C discharge)
/// @return available current in milliamps /// @return available current in milliamps
static float getAvailableCurrent(); static float getMax3V3Current();
protected: protected:
/// @brief PowerScheduler instance to manage power consumption /// @brief PowerScheduler instance to manage power consumption
static PowerScheduler *powerScheduler; static PowerScheduler *powerScheduler;
/// @brief update Power State /// @brief update Power State
static void updatePowerState(); static void updatePowerStateHandler();
/* /*
* Power State * Power State
*/ */
/// @brief last time of power state update
static TickType_t lastPowerStateUpdate;
/// @brief remaining Charge in coulombs /// @brief remaining Charge in coulombs
static int coloumbsRemaining; static float coloumbsRemaining;
/// @brief remaining Charge in percent /// @brief remaining Charge in percent
static int percentRemaining; static int percentRemaining;
friend class PowerScheduler; /// @brief Circular array of last calculated values for current state of
/// charge
static float lastSOC[PowerParameters::Battery::AVERAGING_SAMPLES];
static int latestSoCIndex;
/// @brief Add calculated value to circular array, pushing out oldest value
static void addSoCSample(float soc);
}; };
extern Power power; extern Power power;

View File

@ -41,6 +41,9 @@ namespace PowerParameters {
static constexpr float VOLTAGE_DIVIDER_FACTOR = static constexpr float VOLTAGE_DIVIDER_FACTOR =
(VOLTAGE_DIVIDER_R12 + VOLTAGE_DIVIDER_R13) / VOLTAGE_DIVIDER_R13; (VOLTAGE_DIVIDER_R12 + VOLTAGE_DIVIDER_R13) / VOLTAGE_DIVIDER_R13;
}; };
// Configuration
static constexpr int AVERAGING_SAMPLES = 10;
}; };
// Factors concerning Buck-Boost-Converter // Factors concerning Buck-Boost-Converter

View File

@ -11,21 +11,27 @@
*/ */
#include "PowerScheduler.h" #include "PowerScheduler.h"
#include "Power.h"
bool PowerScheduler::tryAccquireCurrentAllowance( bool PowerScheduler::tryAccquireCurrentAllowance(
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent, PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
uint16_t requestedDurationMs) { uint16_t requestedDurationMs) {
portENTER_CRITICAL(&mux); portENTER_CRITICAL(&mux);
if (this->freeLimitCurrentBudget > 0 && float existingConsumption = getConsumerCurrent(consumer);
this->freeMaximumCurrentBudget >= neededCurrent) { if ((this->freeLimitCurrentBudget + existingConsumption) > 0 &&
this->currentAllowances.push_back( (this->freeMaximumCurrentBudget + existingConsumption) >= neededCurrent) {
{.consumer = consumer, if (existingConsumption > 0) {
releaseCurrent(consumer);
}
this->currentAllowances.push_back(PowerScheduler::CurrentAllowance{
.consumer = consumer,
.maxSlackTimeMs = 0, .maxSlackTimeMs = 0,
.requestedDurationMs = requestedDurationMs, .requestedDurationMs = requestedDurationMs,
.taskHandle = xTaskGetCurrentTaskHandle(), .taskHandle = xTaskGetCurrentTaskHandle(),
.neededCurrent = neededCurrent, .neededCurrent = neededCurrent,
.requestedAt = xTaskGetTickCount(), .requestedAt = xTaskGetTickCount(),
.granted = false}); .grantedAt = xTaskGetTickCount(),
.granted = true});
this->recalculateCurrentBudgets(); this->recalculateCurrentBudgets();
portEXIT_CRITICAL(&mux); portEXIT_CRITICAL(&mux);
return true; return true;
@ -79,8 +85,24 @@ bool PowerScheduler::waitForCurrentAllowance(
PowerScheduler::PowerWakeupReasons::POWER_AVAILABLE) { PowerScheduler::PowerWakeupReasons::POWER_AVAILABLE) {
// We were woken up because new power is available, check if it is // We were woken up because new power is available, check if it is
// enough // enough
if (this->freeLimitCurrentBudget > 0 && float existingConsumption = getConsumerCurrent(consumer);
this->freeMaximumCurrentBudget >= neededCurrent) { if ((this->freeLimitCurrentBudget + existingConsumption) > 0 &&
(this->freeMaximumCurrentBudget + existingConsumption) >=
neededCurrent) {
// TODO Check if there is a currently active allowance for this
// consumer and if so, replace it with the new one
if (existingConsumption > 0) {
releaseCurrent(consumer);
}
this->currentAllowances.push_back(
{.consumer = consumer,
.maxSlackTimeMs = 0,
.requestedDurationMs = requestedDurationMs,
.taskHandle = xTaskGetCurrentTaskHandle(),
.neededCurrent = neededCurrent,
.requestedAt = initialTickCount,
.grantedAt = xTaskGetTickCount(),
.granted = true});
return true; return true;
} else { } else {
// Still not enough power available for us. Wait the remaining ticks. // Still not enough power available for us. Wait the remaining ticks.
@ -95,8 +117,7 @@ bool PowerScheduler::waitForCurrentAllowance(
// Remove the task from the list of waiting tasks // Remove the task from the list of waiting tasks
for (auto it = currentAllowances.begin(); it != currentAllowances.end(); for (auto it = currentAllowances.begin(); it != currentAllowances.end();
++it) { ++it) {
if (it->consumer == consumer && if (it->consumer == consumer && it->requestedAt == initialTickCount) {
it->requestedAt == initialTickCount) {
currentAllowances.erase(it); currentAllowances.erase(it);
break; break;
} }
@ -113,7 +134,7 @@ void PowerScheduler::checkWaitingTasks(void) {
// If there are requested allowances, try to grant the one expiring next // If there are requested allowances, try to grant the one expiring next
if (this->currentAllowances.size() > 0) { if (this->currentAllowances.size() > 0) {
PowerScheduler::CurrentAllowance* nextAllowance = PowerScheduler::CurrentAllowance *nextAllowance =
getNextExpiringAllowance(); getNextExpiringAllowance();
if (nextAllowance != nullptr) { if (nextAllowance != nullptr) {
xTaskNotify(nextAllowance->taskHandle, xTaskNotify(nextAllowance->taskHandle,
@ -124,18 +145,20 @@ void PowerScheduler::checkWaitingTasks(void) {
} }
void PowerScheduler::recalculateCurrentBudgets(void) { void PowerScheduler::recalculateCurrentBudgets(void) {
// TODO: replace with actual current modeling // Get the respective maximums and subtract currently flowing currents
this->freeLimitCurrentBudget = this->limitCurrent; float tempFreeLimitCurrentBudget = Power::getMax3V3Current();
this->freeMaximumCurrentBudget = this->maximumCurrent * 2; float tempFreeMaximumCurrentBudget = Power::getMax3V3Current() * 2;
for (auto &allowance : currentAllowances) { for (auto &allowance : currentAllowances) {
if (allowance.granted) { if (allowance.granted) {
this->freeLimitCurrentBudget -= allowance.neededCurrent; tempFreeLimitCurrentBudget -= allowance.neededCurrent;
this->freeMaximumCurrentBudget -= allowance.neededCurrent; tempFreeMaximumCurrentBudget -= allowance.neededCurrent;
} }
} }
this->freeLimitCurrentBudget = tempFreeLimitCurrentBudget;
this->freeMaximumCurrentBudget = tempFreeMaximumCurrentBudget;
} }
PowerScheduler::CurrentAllowance* PowerScheduler::CurrentAllowance *
PowerScheduler::getCurrentAllowance(PowerParameters::PowerConsumers consumer) { PowerScheduler::getCurrentAllowance(PowerParameters::PowerConsumers consumer) {
for (auto &allowance : currentAllowances) { for (auto &allowance : currentAllowances) {
if (allowance.consumer == consumer) { if (allowance.consumer == consumer) {
@ -144,7 +167,7 @@ PowerScheduler::getCurrentAllowance(PowerParameters::PowerConsumers consumer) {
} }
return nullptr; return nullptr;
} }
PowerScheduler::CurrentAllowance* PowerScheduler::CurrentAllowance *
PowerScheduler::getCurrentAllowance(TaskHandle_t taskHandle) { PowerScheduler::getCurrentAllowance(TaskHandle_t taskHandle) {
for (auto &allowance : currentAllowances) { for (auto &allowance : currentAllowances) {
if (allowance.taskHandle == taskHandle) { if (allowance.taskHandle == taskHandle) {
@ -153,7 +176,7 @@ PowerScheduler::getCurrentAllowance(TaskHandle_t taskHandle) {
} }
return nullptr; return nullptr;
} }
PowerScheduler::CurrentAllowance* PowerScheduler::CurrentAllowance *
PowerScheduler::getNextExpiringAllowance(void) { PowerScheduler::getNextExpiringAllowance(void) {
TickType_t minTicks = UINT32_MAX; TickType_t minTicks = UINT32_MAX;
CurrentAllowance *nextAllowance = nullptr; CurrentAllowance *nextAllowance = nullptr;
@ -171,7 +194,7 @@ PowerScheduler::getNextExpiringAllowance(void) {
return nextAllowance; return nextAllowance;
} }
PowerScheduler& PowerScheduler::getPowerScheduler(float i_limit_ma, PowerScheduler &PowerScheduler::getPowerScheduler(float i_limit_ma,
float i_max_ma) { float i_max_ma) {
if (powerSchedulerInstance == nullptr) { if (powerSchedulerInstance == nullptr) {
// Double check locking // Double check locking
@ -185,6 +208,35 @@ PowerScheduler& PowerScheduler::getPowerScheduler(float i_limit_ma,
return *powerSchedulerInstance; return *powerSchedulerInstance;
} }
float PowerScheduler::getCurrentCurrent(void) {
float currentSum = 0;
for (auto &allowance : currentAllowances) {
if (allowance.granted) {
currentSum += allowance.neededCurrent;
}
}
return currentSum;
}
float PowerScheduler::getFreeCurrentBudget(void) {
return this->freeLimitCurrentBudget;
}
float PowerScheduler::getFreeHardMaxCurrent(void) {
return this->freeMaximumCurrentBudget;
}
float PowerScheduler::getConsumerCurrent(
PowerParameters::PowerConsumers consumer) {
float currentSum = 0;
for (auto &allowance : currentAllowances) {
if (allowance.consumer == consumer && allowance.granted) {
currentSum += allowance.neededCurrent;
}
}
return currentSum;
}
PowerScheduler::PowerScheduler(float i_limit_ma, float i_max_ma) { PowerScheduler::PowerScheduler(float i_limit_ma, float i_max_ma) {
this->limitCurrent = i_limit_ma; this->limitCurrent = i_limit_ma;
this->maximumCurrent = i_max_ma; this->maximumCurrent = i_max_ma;

View File

@ -24,17 +24,18 @@ private:
static constexpr uint16_t DEFAULT_SLACK_TIME_MS = 100; static constexpr uint16_t DEFAULT_SLACK_TIME_MS = 100;
PowerScheduler(float i_limit_ma, float i_max_ma); PowerScheduler(float i_limit_ma, float i_max_ma);
public: public:
~PowerScheduler(); ~PowerScheduler();
/// @brief Initialize the singleton instance of the power manager /// @brief Initialize the singleton instance of the power manager
/// @return reference to the power manager /// @return reference to the power manager
static PowerScheduler& getPowerScheduler(float i_limit_ma=0, float i_max_ma=0); static PowerScheduler &getPowerScheduler(float i_limit_ma = 0,
float i_max_ma = 0);
/// @brief Get the current free current budget (to C1 discharge) /// @brief Get the current free current budget (to C1 discharge)
/// @return the amount of power that is currently available (in mA) /// @return the amount of power that is currently available (in mA)
uint16_t getFreeCurrentBudget(void); float getFreeCurrentBudget(void);
/// @brief Get the current hard maximum free current (to C2 discharge) /// @brief Get the current hard maximum free current (to C2 discharge)
/// @return the maximum amount of power that can be allocated (in mA) /// @return the maximum amount of power that can be allocated (in mA)
uint16_t getFreeHardMaxCurrent(void); float getFreeHardMaxCurrent(void);
/// @brief Request an allowance of a certain number of milliamperes from the /// @brief Request an allowance of a certain number of milliamperes from the
/// power scheduler without waiting for it (meaning it will not be scheduled /// power scheduler without waiting for it (meaning it will not be scheduled
@ -66,7 +67,7 @@ private:
//// @brief Get currently granted power //// @brief Get currently granted power
/// @return the amount of power that is currently allocated (in mA) /// @return the amount of power that is currently allocated (in mA)
uint16_t getCurrentCurrent(void); float getCurrentCurrent(void);
/// @brief Power consumer data structure /// @brief Power consumer data structure
struct CurrentAllowance { struct CurrentAllowance {
@ -76,7 +77,7 @@ private:
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
uint16_t neededCurrent; uint16_t neededCurrent;
TickType_t requestedAt; TickType_t requestedAt;
TickType_t startedAt; TickType_t grantedAt;
bool granted; bool granted;
}; };
@ -85,6 +86,20 @@ private:
POWER_AVAILABLE = 1, POWER_AVAILABLE = 1,
POWER_EXPIRED = 2, POWER_EXPIRED = 2,
}; };
// @brief Responsible for recalculating the current budgets
void recalculateCurrentBudgets(void);
// @brief Get current consumption of a consumer
float getConsumerCurrent(PowerParameters::PowerConsumers consumer);
// @brief Retrieve the current allowance for a given consumer
CurrentAllowance *
getCurrentAllowance(PowerParameters::PowerConsumers consumer);
// @brief Retrieve the current allowance for a given task
CurrentAllowance *getCurrentAllowance(TaskHandle_t taskHandle);
// @brief Retrieve the allowance that will expire next
CurrentAllowance *getNextExpiringAllowance(void);
protected: protected:
// Current above which there will be no new scheduling // Current above which there will be no new scheduling
@ -100,25 +115,12 @@ protected:
// @brief Responsible for selecting the next task to be granted power // @brief Responsible for selecting the next task to be granted power
void checkWaitingTasks(void); void checkWaitingTasks(void);
// @brief Responsible for recalculating the current budgets
void recalculateCurrentBudgets(void);
// @brief Retrieve the current allowance for a given consumer
CurrentAllowance *
getCurrentAllowance(PowerParameters::PowerConsumers consumer);
// @brief Retrieve the current allowance for a given task
CurrentAllowance *getCurrentAllowance(TaskHandle_t taskHandle);
// @brief Retrieve the allowance that will expire next
CurrentAllowance *getNextExpiringAllowance(void);
// @brief Mutex to protect the power scheduler from concurrent access // @brief Mutex to protect the power scheduler from concurrent access
static portMUX_TYPE mux; static portMUX_TYPE mux;
std::vector<PowerScheduler::CurrentAllowance> currentAllowances; std::vector<PowerScheduler::CurrentAllowance> currentAllowances;
}; };
static PowerScheduler *powerSchedulerInstance; static PowerScheduler *powerSchedulerInstance;
#endif // PowerScheduler_h #endif // PowerScheduler_h