mirror of
https://gitlab.dit.htwk-leipzig.de/phillip.kuehne/dezibot.git
synced 2025-05-19 19:11:48 +02:00
Add Power Scheduler
This commit is contained in:
parent
893234ed24
commit
b44538b473
@ -10,41 +10,106 @@
|
|||||||
#include "Power.h"
|
#include "Power.h"
|
||||||
static portMUX_TYPE mux;
|
static portMUX_TYPE mux;
|
||||||
|
|
||||||
void Power::begin()
|
void Power::begin() {
|
||||||
{
|
|
||||||
// Check if another instance of us already initialized the power scheduler,
|
// Check if another instance of us already initialized the power scheduler,
|
||||||
// if not, we will do it.
|
// if not, we will do it.
|
||||||
if (powerScheduler == nullptr)
|
if (powerScheduler == nullptr) {
|
||||||
{
|
powerScheduler = &PowerScheduler::getPowerScheduler();
|
||||||
powerScheduler = PowerScheduler::getPowerScheduler();
|
|
||||||
if (!(powerScheduler->tryAccquirePowerAllowance(CONSUMPTION_ESP_BASE)))
|
if (!(powerScheduler->tryAccquireCurrentAllowance(
|
||||||
{
|
PowerParameters::PowerConsumers::ESP,
|
||||||
Serial.println("Alledgedly not enough power available to reserve the ESP32s base power consumption. Something is wrong.");
|
PowerParameters::CurrentConsumptions::CURRENT_ESP_AVG))) {
|
||||||
|
Serial.println("Alledgedly not enough power available to reserve the "
|
||||||
|
"ESP32s base power consumption. Something is wrong.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t Power::getFreePowerBudget(void)
|
uint16_t Power::getFreeCurrentBudget(void) {
|
||||||
{
|
return powerScheduler->getFreeCurrentBudget();
|
||||||
return powerScheduler->getFreePowerBudget();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Power::tryAccquirePowerAllowance(uint16_t neededPower)
|
bool Power::tryAccquireCurrentAllowance(
|
||||||
{
|
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
|
||||||
return powerScheduler->tryAccquirePowerAllowance(neededPower);
|
uint16_t requestedDurationMs) {
|
||||||
|
return powerScheduler->tryAccquireCurrentAllowance(consumer, neededCurrent,
|
||||||
|
requestedDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Power::waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait)
|
bool Power::waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
||||||
{
|
uint16_t neededCurrent,
|
||||||
return powerScheduler->waitForPowerAllowance(neededPower, TicksToWait);
|
TickType_t TicksToWait,
|
||||||
|
uint16_t requestedDurationMs) {
|
||||||
|
return powerScheduler->waitForCurrentAllowance(
|
||||||
|
consumer, neededCurrent, TicksToWait, requestedDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::beginPermanentDeepSleep(void)
|
void Power::beginPermanentDeepSleep(void) {
|
||||||
{
|
|
||||||
return powerScheduler->beginPermanentDeepSleep();
|
return powerScheduler->beginPermanentDeepSleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::releasePower(uint16_t power)
|
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() {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerScheduler* Power::powerScheduler = nullptr;
|
||||||
|
|
||||||
|
Power::Power() {
|
||||||
|
// Initialize the power scheduler
|
||||||
|
powerScheduler = &PowerScheduler::getPowerScheduler();
|
||||||
|
// Initialize the mutex
|
||||||
|
mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
}
|
}
|
@ -13,38 +13,77 @@
|
|||||||
|
|
||||||
#define TOTAL_POWER_MILLIWATTS POWER_BUDGET
|
#define TOTAL_POWER_MILLIWATTS POWER_BUDGET
|
||||||
|
|
||||||
enum TaskResumptionReason
|
enum TaskResumptionReason { POWER_AVAILABLE, TIMEOUT };
|
||||||
{
|
|
||||||
POWER_AVAILABLE,
|
|
||||||
TIMEOUT
|
|
||||||
};
|
|
||||||
|
|
||||||
class Power
|
class Power {
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void begin();
|
static void begin(void);
|
||||||
Power();
|
Power();
|
||||||
uint16_t getFreePowerBudget(void);
|
uint16_t getFreeCurrentBudget(void);
|
||||||
/// @brief Request an allowance of a certain number of milliwatts from the power scheduler
|
/// @brief Request an allowance of a certain number of milliwatts from the
|
||||||
|
/// power scheduler
|
||||||
/// @param neededPower the amount of power we want to be accounted for (in mW)
|
/// @param neededPower the amount of power we want to be accounted for (in mW)
|
||||||
/// @return whether the power could be successfully allocated
|
/// @return whether the power could be successfully allocated
|
||||||
bool tryAccquirePowerAllowance(uint16_t neededPower);
|
static bool
|
||||||
/// @brief "Return" a certain amount of power when it is no longer needed
|
tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
||||||
|
uint16_t neededCurrent,
|
||||||
|
uint16_t requestedDurationMs);
|
||||||
|
/// @brief "Return" the power consumed by an active consumer
|
||||||
/// @param neededPower the amount of power to return (in mW)
|
/// @param neededPower the amount of power to return (in mW)
|
||||||
/// @return whether the power
|
/// @return whether the power
|
||||||
void releasePower(uint16_t power);
|
static void releaseCurrent(PowerParameters::PowerConsumers consumer);
|
||||||
|
|
||||||
/// @brief Wait for a certain amount of power to be available
|
/// @brief Wait for a certain amount of power to be available
|
||||||
/// @param neededPower the amount of power we want to be accounted for (in mW)
|
/// @param neededPower the amount of power we want to be accounted for (in mW)
|
||||||
/// @param TicksToWait the amount of time to wait for the power to become available
|
/// @param TicksToWait the amount of time to wait for the power to become
|
||||||
|
/// available
|
||||||
/// @return whether the power could be successfully allocatedy
|
/// @return whether the power could be successfully allocatedy
|
||||||
bool waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait);
|
static bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
||||||
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown.
|
uint16_t neededCurrent,
|
||||||
void beginPermanentDeepSleep(void);
|
TickType_t TicksToWait,
|
||||||
|
uint16_t requestedDurationMs);
|
||||||
|
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up
|
||||||
|
/// again. Basically this is a shutdown.
|
||||||
|
static void beginPermanentDeepSleep(void);
|
||||||
|
|
||||||
|
/// @brief Get battery voltage measurement.
|
||||||
|
/// @return Battery Terminal Voltage in Volts
|
||||||
|
static float getBatteryVoltage();
|
||||||
|
|
||||||
|
/// @brief Get estimated battery charge state as percentage
|
||||||
|
/// @return Battery charge state in percent
|
||||||
|
static int getBatteryChargePercent();
|
||||||
|
|
||||||
|
/// @brief Get estimated battery charge state as coulombs
|
||||||
|
/// @return Battery charge state in coulombs
|
||||||
|
static float getBatteryChargeCoulombs();
|
||||||
|
|
||||||
|
/// @brief get available current (after voltage conversion and efficiency
|
||||||
|
/// losses, referencing 1C discharge)
|
||||||
|
/// @return available current in milliamps
|
||||||
|
static float getAvailableCurrent();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/// @brief PowerScheduler instance to manage power consumption
|
||||||
static PowerScheduler *powerScheduler;
|
static PowerScheduler *powerScheduler;
|
||||||
|
|
||||||
|
/// @brief update Power State
|
||||||
|
static void updatePowerState();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Power State
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// @brief remaining Charge in coulombs
|
||||||
|
static int coloumbsRemaining;
|
||||||
|
|
||||||
|
/// @brief remaining Charge in percent
|
||||||
|
static int percentRemaining;
|
||||||
|
|
||||||
|
friend class PowerScheduler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern Power power;
|
||||||
|
|
||||||
#endif // Power
|
#endif // Power
|
@ -99,7 +99,7 @@ namespace PowerParameters {
|
|||||||
PT_DL_FRONT,
|
PT_DL_FRONT,
|
||||||
PT_DL_BOTTOM,
|
PT_DL_BOTTOM,
|
||||||
LED_UV,
|
LED_UV,
|
||||||
DISPLAY,
|
DISPLAY_OLED,
|
||||||
MOTOR_LEFT,
|
MOTOR_LEFT,
|
||||||
MOTOR_RIGHT,
|
MOTOR_RIGHT,
|
||||||
IMU
|
IMU
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @file PowerScheduler.cpp
|
* @file PowerScheduler.cpp
|
||||||
* @author Phillip Kühne
|
* @author Phillip Kühne
|
||||||
* @brief The actual power scheduler class, which keeps track of the power budget and allocates power to different components.
|
* @brief The actual power scheduler class, which keeps track of the power
|
||||||
|
* budget and allocates power to different components.
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
* @date 2024-12-21
|
* @date 2024-12-21
|
||||||
*
|
*
|
||||||
@ -11,99 +12,186 @@
|
|||||||
|
|
||||||
#include "PowerScheduler.h"
|
#include "PowerScheduler.h"
|
||||||
|
|
||||||
bool PowerScheduler::tryAccquirePowerAllowance(uint16_t neededPower)
|
bool PowerScheduler::tryAccquireCurrentAllowance(
|
||||||
{
|
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
|
||||||
if (this->freePowerBudget >= neededPower)
|
uint16_t requestedDurationMs) {
|
||||||
{
|
portENTER_CRITICAL(&mux);
|
||||||
this->freePowerBudget -= neededPower;
|
if (this->freeLimitCurrentBudget > 0 &&
|
||||||
|
this->freeMaximumCurrentBudget >= neededCurrent) {
|
||||||
|
this->currentAllowances.push_back(
|
||||||
|
{.consumer = consumer,
|
||||||
|
.maxSlackTimeMs = 0,
|
||||||
|
.requestedDurationMs = requestedDurationMs,
|
||||||
|
.taskHandle = xTaskGetCurrentTaskHandle(),
|
||||||
|
.neededCurrent = neededCurrent,
|
||||||
|
.requestedAt = xTaskGetTickCount(),
|
||||||
|
.granted = false});
|
||||||
|
this->recalculateCurrentBudgets();
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
return true;
|
return true;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PowerScheduler::releasePower(uint16_t power)
|
void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) {
|
||||||
{
|
portENTER_CRITICAL(&mux);
|
||||||
if (this->freePowerBudget + power <= this->totalPowerBudget)
|
for (auto it = currentAllowances.begin(); it != currentAllowances.end();
|
||||||
{
|
++it) {
|
||||||
this->freePowerBudget += power;
|
if (it->consumer == consumer && it->granted) {
|
||||||
|
currentAllowances.erase(it);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else // TODO: Maybe we should actually throw an error here, since obviouslsy someone used us wrong.
|
|
||||||
{
|
|
||||||
this->freePowerBudget = this->totalPowerBudget;
|
|
||||||
}
|
}
|
||||||
|
recalculateCurrentBudgets();
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
// Check if there are tasks waiting for power
|
// Check if there are tasks waiting for power
|
||||||
checkWaitingTasks();
|
checkWaitingTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PowerScheduler::waitForPowerAllowance(uint16_t neededPower, TickType_t ticksToWait)
|
bool PowerScheduler::waitForCurrentAllowance(
|
||||||
{
|
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
|
||||||
if (tryAccquirePowerAllowance(neededPower))
|
uint16_t maxSlackTimeMs, uint16_t requestedDurationMs) {
|
||||||
{
|
if (tryAccquireCurrentAllowance(consumer, neededCurrent,
|
||||||
|
requestedDurationMs)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Suspend the task while waiting for power to be available
|
// Suspend the task while waiting for power to be available
|
||||||
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
|
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
|
||||||
TickType_t initialTickCount = xTaskGetTickCount();
|
TickType_t initialTickCount = xTaskGetTickCount();
|
||||||
waitingTasks.push(currentTask);
|
PowerScheduler::CurrentAllowance newAllowance = {
|
||||||
|
.consumer = consumer,
|
||||||
|
.maxSlackTimeMs = maxSlackTimeMs,
|
||||||
|
.requestedDurationMs = requestedDurationMs,
|
||||||
|
.taskHandle = currentTask,
|
||||||
|
.neededCurrent = neededCurrent,
|
||||||
|
.requestedAt = initialTickCount,
|
||||||
|
.granted = false};
|
||||||
|
|
||||||
|
this->currentAllowances.push_back(newAllowance);
|
||||||
uint32_t notificationValue;
|
uint32_t notificationValue;
|
||||||
BaseType_t notificationStatus = xTaskNotifyWait(0, 0, ¬ificationValue, ticksToWait);
|
BaseType_t notificationStatus = xTaskNotifyWait(
|
||||||
|
0, 0, ¬ificationValue, pdMS_TO_TICKS(maxSlackTimeMs));
|
||||||
// Code below will be executed after the task is woken up
|
// Code below will be executed after the task is woken up
|
||||||
while (notificationStatus == pdPASS)
|
while (notificationStatus == pdPASS) {
|
||||||
{
|
if (notificationValue ==
|
||||||
if (notificationValue == 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 (tryAccquirePowerAllowance(neededPower))
|
if (this->freeLimitCurrentBudget > 0 &&
|
||||||
{
|
this->freeMaximumCurrentBudget >= neededCurrent) {
|
||||||
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.
|
||||||
xTaskNotifyWait(0, 0, ¬ificationValue, ticksToWait - (xTaskGetTickCount() - initialTickCount));
|
xTaskNotifyWait(0, 0, ¬ificationValue,
|
||||||
|
pdMS_TO_TICKS(maxSlackTimeMs) -
|
||||||
|
(xTaskGetTickCount() - initialTickCount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (notificationStatus == pdFALSE)
|
if (notificationStatus == pdFALSE) {
|
||||||
{
|
|
||||||
// We waited long enough...
|
// We waited long enough...
|
||||||
return false;
|
// Remove the task from the list of waiting tasks
|
||||||
|
for (auto it = currentAllowances.begin(); it != currentAllowances.end();
|
||||||
|
++it) {
|
||||||
|
if (it->consumer == consumer &&
|
||||||
|
it->requestedAt == initialTickCount) {
|
||||||
|
currentAllowances.erase(it);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
return false;
|
||||||
|
} else {
|
||||||
// Should be impossible to reach
|
// Should be impossible to reach
|
||||||
throw "Reached impossible state";
|
throw "Reached impossible state";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PowerScheduler::checkWaitingTasks(void)
|
void PowerScheduler::checkWaitingTasks(void) {
|
||||||
{
|
// If there are requested allowances, try to grant the one expiring next
|
||||||
// Check if there are tasks waiting for power
|
if (this->currentAllowances.size() > 0) {
|
||||||
if (!waitingTasks.empty())
|
|
||||||
{
|
PowerScheduler::CurrentAllowance* nextAllowance =
|
||||||
TaskHandle_t task = waitingTasks.front();
|
getNextExpiringAllowance();
|
||||||
waitingTasks.pop();
|
if (nextAllowance != nullptr) {
|
||||||
xTaskNotify(task, POWER_AVAILABLE, eSetValueWithOverwrite);
|
xTaskNotify(nextAllowance->taskHandle,
|
||||||
|
PowerScheduler::PowerWakeupReasons::POWER_AVAILABLE,
|
||||||
|
eSetValueWithOverwrite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PowerScheduler *PowerScheduler::getPowerScheduler()
|
void PowerScheduler::recalculateCurrentBudgets(void) {
|
||||||
{
|
// TODO: replace with actual current modeling
|
||||||
if (powerSchedulerInstance == nullptr)
|
this->freeLimitCurrentBudget = this->limitCurrent;
|
||||||
{
|
this->freeMaximumCurrentBudget = this->maximumCurrent * 2;
|
||||||
// Double check locking https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
|
for (auto &allowance : currentAllowances) {
|
||||||
|
if (allowance.granted) {
|
||||||
|
this->freeLimitCurrentBudget -= allowance.neededCurrent;
|
||||||
|
this->freeMaximumCurrentBudget -= allowance.neededCurrent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerScheduler::CurrentAllowance*
|
||||||
|
PowerScheduler::getCurrentAllowance(PowerParameters::PowerConsumers consumer) {
|
||||||
|
for (auto &allowance : currentAllowances) {
|
||||||
|
if (allowance.consumer == consumer) {
|
||||||
|
return &allowance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
PowerScheduler::CurrentAllowance*
|
||||||
|
PowerScheduler::getCurrentAllowance(TaskHandle_t taskHandle) {
|
||||||
|
for (auto &allowance : currentAllowances) {
|
||||||
|
if (allowance.taskHandle == taskHandle) {
|
||||||
|
return &allowance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
PowerScheduler::CurrentAllowance*
|
||||||
|
PowerScheduler::getNextExpiringAllowance(void) {
|
||||||
|
TickType_t minTicks = UINT32_MAX;
|
||||||
|
CurrentAllowance *nextAllowance = nullptr;
|
||||||
|
for (auto &allowance : currentAllowances) {
|
||||||
|
if (!(allowance.granted)) {
|
||||||
|
TickType_t ticks =
|
||||||
|
allowance.requestedAt + pdMS_TO_TICKS(allowance.maxSlackTimeMs);
|
||||||
|
if (ticks < minTicks) {
|
||||||
|
minTicks = ticks;
|
||||||
|
nextAllowance = &allowance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Will be nullptr if no allowance was found
|
||||||
|
return nextAllowance;
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerScheduler& PowerScheduler::getPowerScheduler(float i_limit_ma,
|
||||||
|
float i_max_ma) {
|
||||||
|
if (powerSchedulerInstance == nullptr) {
|
||||||
|
// Double check locking
|
||||||
|
// https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
|
||||||
taskENTER_CRITICAL(&mux);
|
taskENTER_CRITICAL(&mux);
|
||||||
if (powerSchedulerInstance == nullptr)
|
if (powerSchedulerInstance == nullptr) {
|
||||||
{
|
powerSchedulerInstance = new PowerScheduler(i_limit_ma, i_max_ma);
|
||||||
powerSchedulerInstance = new PowerScheduler();
|
|
||||||
}
|
}
|
||||||
taskEXIT_CRITICAL(&mux);
|
taskEXIT_CRITICAL(&mux);
|
||||||
}
|
}
|
||||||
return powerSchedulerInstance;
|
return *powerSchedulerInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PowerScheduler::PowerScheduler(float i_limit_ma, float i_max_ma) {
|
||||||
|
this->limitCurrent = i_limit_ma;
|
||||||
|
this->maximumCurrent = i_max_ma;
|
||||||
|
this->currentAllowances = std::vector<CurrentAllowance>();
|
||||||
|
mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
portMUX_TYPE PowerScheduler::mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
|
||||||
|
PowerScheduler::~PowerScheduler() {}
|
@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @file PowerScheduler.hpp
|
* @file PowerScheduler.hpp
|
||||||
* @author Phillip Kühne
|
* @author Phillip Kühne
|
||||||
* @brief The actual power scheduler class, which keeps track of the power budget and allocates power to different components.
|
* @brief The actual power scheduler class, which keeps track of the power
|
||||||
|
* budget and allocates power to different components.
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
* @date 2024-12-21
|
* @date 2024-12-21
|
||||||
*
|
*
|
||||||
@ -9,62 +10,114 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <queue>
|
#include "PowerParameters.h"
|
||||||
#include <Arduino.h>
|
|
||||||
#include "Consumptions.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifndef PowerScheduler_h
|
#ifndef PowerScheduler_h
|
||||||
#define PowerScheduler_h
|
#define PowerScheduler_h
|
||||||
|
|
||||||
#define TOTAL_POWER_MILLIWATTS POWER_BUDGET
|
class PowerScheduler {
|
||||||
|
|
||||||
class PowerScheduler
|
|
||||||
{
|
|
||||||
private:
|
private:
|
||||||
/* data */
|
static constexpr uint16_t DEFAULT_SLACK_TIME_MS = 100;
|
||||||
|
PowerScheduler(float i_limit_ma, float i_max_ma);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PowerScheduler();
|
|
||||||
~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();
|
static PowerScheduler& getPowerScheduler(float i_limit_ma=0, float i_max_ma=0);
|
||||||
uint16_t getFreePowerBudget(void);
|
/// @brief Get the current free current budget (to C1 discharge)
|
||||||
/// @brief Request an allowance of a certain number of milliwatts from the power scheduler
|
/// @return the amount of power that is currently available (in mA)
|
||||||
/// @param neededPower the amount of power we want to be accounted for (in mW)
|
uint16_t getFreeCurrentBudget(void);
|
||||||
/// @return whether the power could be successfully allocated
|
/// @brief Get the current hard maximum free current (to C2 discharge)
|
||||||
bool tryAccquirePowerAllowance(uint16_t neededPower);
|
/// @return the maximum amount of power that can be allocated (in mA)
|
||||||
/// @brief "Return" a certain amount of power when it is no longer needed
|
uint16_t getFreeHardMaxCurrent(void);
|
||||||
/// @param neededPower the amount of power to return (in mW)
|
|
||||||
/// @return whether the power
|
|
||||||
void releasePower(uint16_t power);
|
|
||||||
|
|
||||||
/// @brief Wait for a certain amount of power to be available
|
/// @brief Request an allowance of a certain number of milliamperes from the
|
||||||
/// @param neededPower the amount of power we want to be accounted for (in mW)
|
/// power scheduler without waiting for it (meaning it will not be scheduled
|
||||||
/// @param TicksToWait the amount of time to wait for the power to become available
|
/// for future allocation). Only one can be active per consumer.
|
||||||
|
/// @param neededCurrent the amount of current we want to be accounted for (in
|
||||||
|
/// mA)
|
||||||
|
/// @return whether the current could be successfully allocated
|
||||||
|
bool tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
||||||
|
uint16_t neededcurrent,
|
||||||
|
uint16_t requestedDurationMs = 0);
|
||||||
|
/// @brief "Return" the current currently allocated to a consumer
|
||||||
|
/// @param consumer the active consumer to release the current for
|
||||||
|
void releaseCurrent(PowerParameters::PowerConsumers consumer);
|
||||||
|
|
||||||
|
/// @brief Wait for a certain amount of current to be available. This will
|
||||||
|
/// "reseve a spot in the queue". Only one can be active per consumer.
|
||||||
|
/// @param neededCurrent the amount of power we want to be accounted for (in
|
||||||
|
/// mW)
|
||||||
|
/// @param TicksToWait the amount of time to wait for the power to become
|
||||||
|
/// available
|
||||||
/// @return whether the power could be successfully allocatedy
|
/// @return whether the power could be successfully allocatedy
|
||||||
bool waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait);
|
bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
||||||
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown.
|
uint16_t neededCurrent,
|
||||||
|
uint16_t maxSlackTimeMs = DEFAULT_SLACK_TIME_MS,
|
||||||
|
uint16_t requestedDurationMs = 0);
|
||||||
|
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up
|
||||||
|
/// again. Basically this is a shutdown.
|
||||||
void beginPermanentDeepSleep(void);
|
void beginPermanentDeepSleep(void);
|
||||||
|
|
||||||
protected:
|
//// @brief Get currently granted power
|
||||||
static const uint16_t totalPowerBudget = TOTAL_POWER_MILLIWATTS;
|
/// @return the amount of power that is currently allocated (in mA)
|
||||||
uint16_t freePowerBudget;
|
uint16_t getCurrentCurrent(void);
|
||||||
std::queue<TaskHandle_t> waitingTasks;
|
|
||||||
void checkWaitingTasks(void);
|
/// @brief Power consumer data structure
|
||||||
bool takePowerIfAvailable(uint16_t neededPower);
|
struct CurrentAllowance {
|
||||||
|
PowerParameters::PowerConsumers consumer;
|
||||||
|
uint16_t maxSlackTimeMs;
|
||||||
|
uint16_t requestedDurationMs;
|
||||||
|
TaskHandle_t taskHandle;
|
||||||
|
uint16_t neededCurrent;
|
||||||
|
TickType_t requestedAt;
|
||||||
|
TickType_t startedAt;
|
||||||
|
bool granted;
|
||||||
|
};
|
||||||
|
|
||||||
|
// @brief waiting task wakeup reasons
|
||||||
|
enum PowerWakeupReasons {
|
||||||
|
POWER_AVAILABLE = 1,
|
||||||
|
POWER_EXPIRED = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Current above which there will be no new scheduling
|
||||||
|
uint16_t limitCurrent;
|
||||||
|
// Absolute maximum current that can be allocated
|
||||||
|
uint16_t maximumCurrent;
|
||||||
|
|
||||||
|
// Current budget that is currently available to limitCurrent
|
||||||
|
int16_t freeLimitCurrentBudget;
|
||||||
|
// Current budget that is currently available to maximumCurrent
|
||||||
|
int16_t freeMaximumCurrentBudget;
|
||||||
|
|
||||||
|
// @brief Responsible for selecting the next task to be granted power
|
||||||
|
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
|
||||||
|
static portMUX_TYPE mux;
|
||||||
|
|
||||||
|
std::vector<PowerScheduler::CurrentAllowance> currentAllowances;
|
||||||
};
|
};
|
||||||
|
|
||||||
PowerScheduler::PowerScheduler(/* args */)
|
|
||||||
{
|
|
||||||
// TODO: Create wrappper around all this which handles single-instancing so we look like a normal arduino library from the outside.
|
|
||||||
this->freePowerBudget = TOTAL_POWER_MILLIWATTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
PowerScheduler::~PowerScheduler()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static PowerScheduler *powerSchedulerInstance;
|
static PowerScheduler *powerSchedulerInstance;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user