Add Power Scheduler

This commit is contained in:
Phillip Kühne 2025-02-11 23:33:19 +01:00
parent 893234ed24
commit b44538b473
Signed by: phillip
GPG Key ID: E4C1C4D2F90902AA
5 changed files with 425 additions and 180 deletions

View File

@ -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->tryAccquireCurrentAllowance(
if (!(powerScheduler->tryAccquirePowerAllowance(CONSUMPTION_ESP_BASE))) 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."); Serial.println("Alledgedly not enough power available to reserve the "
return; "ESP32s base power consumption. Something is wrong.");
} 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;
} }

View File

@ -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
/// @param neededPower the amount of power we want to be accounted for (in mW) /// power scheduler
/// @return whether the power could be successfully allocated /// @param neededPower the amount of power we want to be accounted for (in mW)
bool tryAccquirePowerAllowance(uint16_t neededPower); /// @return whether the power could be successfully allocated
/// @brief "Return" a certain amount of power when it is no longer needed static bool
/// @param neededPower the amount of power to return (in mW) tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer,
/// @return whether the power uint16_t neededCurrent,
void releasePower(uint16_t power); uint16_t requestedDurationMs);
/// @brief "Return" the power consumed by an active consumer
/// @param neededPower the amount of power to return (in mW)
/// @return whether the 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
/// @return whether the power could be successfully allocatedy /// available
bool waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait); /// @return whether the power could be successfully allocatedy
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown. static bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
void beginPermanentDeepSleep(void); uint16_t neededCurrent,
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:
static PowerScheduler *powerScheduler; /// @brief PowerScheduler instance to manage power consumption
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

View File

@ -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

View File

@ -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 &&
return true; this->freeMaximumCurrentBudget >= neededCurrent) {
} this->currentAllowances.push_back(
else {.consumer = consumer,
{ .maxSlackTimeMs = 0,
return false; .requestedDurationMs = requestedDurationMs,
} .taskHandle = xTaskGetCurrentTaskHandle(),
.neededCurrent = neededCurrent,
.requestedAt = xTaskGetTickCount(),
.granted = false});
this->recalculateCurrentBudgets();
portEXIT_CRITICAL(&mux);
return true;
} else {
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. }
{ recalculateCurrentBudgets();
this->freePowerBudget = this->totalPowerBudget; 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,
return true; requestedDurationMs)) {
return true;
} else {
// Suspend the task while waiting for power to be available
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
TickType_t initialTickCount = xTaskGetTickCount();
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;
BaseType_t notificationStatus = xTaskNotifyWait(
0, 0, &notificationValue, pdMS_TO_TICKS(maxSlackTimeMs));
// Code below will be executed after the task is woken up
while (notificationStatus == pdPASS) {
if (notificationValue ==
PowerScheduler::PowerWakeupReasons::POWER_AVAILABLE) {
// We were woken up because new power is available, check if it is
// enough
if (this->freeLimitCurrentBudget > 0 &&
this->freeMaximumCurrentBudget >= neededCurrent) {
return true;
} else {
// Still not enough power available for us. Wait the remaining ticks.
xTaskNotifyWait(0, 0, &notificationValue,
pdMS_TO_TICKS(maxSlackTimeMs) -
(xTaskGetTickCount() - initialTickCount));
}
}
} }
else if (notificationStatus == pdFALSE) {
{ // We waited long enough...
// Suspend the task while waiting for power to be available // Remove the task from the list of waiting tasks
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle(); for (auto it = currentAllowances.begin(); it != currentAllowances.end();
TickType_t initialTickCount = xTaskGetTickCount(); ++it) {
waitingTasks.push(currentTask); if (it->consumer == consumer &&
uint32_t notificationValue; it->requestedAt == initialTickCount) {
BaseType_t notificationStatus = xTaskNotifyWait(0, 0, &notificationValue, ticksToWait); currentAllowances.erase(it);
// Code below will be executed after the task is woken up break;
while (notificationStatus == pdPASS)
{
if (notificationValue == POWER_AVAILABLE)
{
// We were woken up because new power is available, check if it is enough
if (tryAccquirePowerAllowance(neededPower))
{
return true;
}
else
{
// Still not enough power available for us. Wait the remaining ticks.
xTaskNotifyWait(0, 0, &notificationValue, ticksToWait - (xTaskGetTickCount() - initialTickCount));
}
}
}
if (notificationStatus == pdFALSE)
{
// We waited long enough...
return false;
}
else
{
// Should be impossible to reach
throw "Reached impossible state";
} }
}
return false;
} else {
// Should be impossible to reach
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) {
taskENTER_CRITICAL(&mux); if (allowance.granted) {
if (powerSchedulerInstance == nullptr) this->freeLimitCurrentBudget -= allowance.neededCurrent;
{ this->freeMaximumCurrentBudget -= allowance.neededCurrent;
powerSchedulerInstance = new PowerScheduler();
}
taskEXIT_CRITICAL(&mux);
} }
return powerSchedulerInstance; }
} }
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);
if (powerSchedulerInstance == nullptr) {
powerSchedulerInstance = new PowerScheduler(i_limit_ma, i_max_ma);
}
taskEXIT_CRITICAL(&mux);
}
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() {}

View File

@ -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;
public: PowerScheduler(float i_limit_ma, float i_max_ma);
PowerScheduler();
~PowerScheduler();
/// @brief Initialize the singleton instance of the power manager
/// @return reference to the power manager
static PowerScheduler *getPowerScheduler();
uint16_t getFreePowerBudget(void);
/// @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)
/// @return whether the power could be successfully allocated
bool tryAccquirePowerAllowance(uint16_t neededPower);
/// @brief "Return" a certain amount of power when it is no longer needed
/// @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 public:
/// @param neededPower the amount of power we want to be accounted for (in mW) ~PowerScheduler();
/// @param TicksToWait the amount of time to wait for the power to become available /// @brief Initialize the singleton instance of the power manager
/// @return whether the power could be successfully allocatedy /// @return reference to the power manager
bool waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait); static PowerScheduler& getPowerScheduler(float i_limit_ma=0, float i_max_ma=0);
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown. /// @brief Get the current free current budget (to C1 discharge)
void beginPermanentDeepSleep(void); /// @return the amount of power that is currently available (in mA)
uint16_t getFreeCurrentBudget(void);
/// @brief Get the current hard maximum free current (to C2 discharge)
/// @return the maximum amount of power that can be allocated (in mA)
uint16_t getFreeHardMaxCurrent(void);
/// @brief Request an allowance of a certain number of milliamperes from the
/// power scheduler without waiting for it (meaning it will not be scheduled
/// 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
bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
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);
//// @brief Get currently granted power
/// @return the amount of power that is currently allocated (in mA)
uint16_t getCurrentCurrent(void);
/// @brief Power consumer data structure
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: protected:
static const uint16_t totalPowerBudget = TOTAL_POWER_MILLIWATTS; // Current above which there will be no new scheduling
uint16_t freePowerBudget; uint16_t limitCurrent;
std::queue<TaskHandle_t> waitingTasks; // Absolute maximum current that can be allocated
void checkWaitingTasks(void); uint16_t maximumCurrent;
bool takePowerIfAvailable(uint16_t neededPower);
// 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;