mirror of
https://gitlab.dit.htwk-leipzig.de/phillip.kuehne/dezibot.git
synced 2025-05-19 11:01:46 +02:00
Add Power Scheduler
This commit is contained in:
parent
893234ed24
commit
b44538b473
@ -10,41 +10,106 @@
|
||||
#include "Power.h"
|
||||
static portMUX_TYPE mux;
|
||||
|
||||
void Power::begin()
|
||||
{
|
||||
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->tryAccquirePowerAllowance(CONSUMPTION_ESP_BASE)))
|
||||
{
|
||||
Serial.println("Alledgedly not enough power available to reserve the ESP32s base power consumption. Something is wrong.");
|
||||
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::getFreePowerBudget(void)
|
||||
{
|
||||
return powerScheduler->getFreePowerBudget();
|
||||
uint16_t Power::getFreeCurrentBudget(void) {
|
||||
return powerScheduler->getFreeCurrentBudget();
|
||||
}
|
||||
|
||||
bool Power::tryAccquirePowerAllowance(uint16_t neededPower)
|
||||
{
|
||||
return powerScheduler->tryAccquirePowerAllowance(neededPower);
|
||||
bool Power::tryAccquireCurrentAllowance(
|
||||
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
|
||||
uint16_t requestedDurationMs) {
|
||||
return powerScheduler->tryAccquireCurrentAllowance(consumer, neededCurrent,
|
||||
requestedDurationMs);
|
||||
}
|
||||
|
||||
bool Power::waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait)
|
||||
{
|
||||
return powerScheduler->waitForPowerAllowance(neededPower, TicksToWait);
|
||||
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)
|
||||
{
|
||||
void Power::beginPermanentDeepSleep(void) {
|
||||
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
|
||||
|
||||
enum TaskResumptionReason
|
||||
{
|
||||
POWER_AVAILABLE,
|
||||
TIMEOUT
|
||||
};
|
||||
enum TaskResumptionReason { POWER_AVAILABLE, TIMEOUT };
|
||||
|
||||
class Power
|
||||
{
|
||||
class Power {
|
||||
|
||||
public:
|
||||
static void begin();
|
||||
static void begin(void);
|
||||
Power();
|
||||
uint16_t getFreePowerBudget(void);
|
||||
/// @brief Request an allowance of a certain number of milliwatts from the power scheduler
|
||||
uint16_t getFreeCurrentBudget(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
|
||||
static bool
|
||||
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)
|
||||
/// @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
|
||||
/// @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
|
||||
bool waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait);
|
||||
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown.
|
||||
void beginPermanentDeepSleep(void);
|
||||
static bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
||||
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:
|
||||
/// @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
|
@ -99,7 +99,7 @@ namespace PowerParameters {
|
||||
PT_DL_FRONT,
|
||||
PT_DL_BOTTOM,
|
||||
LED_UV,
|
||||
DISPLAY,
|
||||
DISPLAY_OLED,
|
||||
MOTOR_LEFT,
|
||||
MOTOR_RIGHT,
|
||||
IMU
|
||||
|
@ -1,7 +1,8 @@
|
||||
/**
|
||||
* @file PowerScheduler.cpp
|
||||
* @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
|
||||
* @date 2024-12-21
|
||||
*
|
||||
@ -11,99 +12,186 @@
|
||||
|
||||
#include "PowerScheduler.h"
|
||||
|
||||
bool PowerScheduler::tryAccquirePowerAllowance(uint16_t neededPower)
|
||||
{
|
||||
if (this->freePowerBudget >= neededPower)
|
||||
{
|
||||
this->freePowerBudget -= neededPower;
|
||||
bool PowerScheduler::tryAccquireCurrentAllowance(
|
||||
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
|
||||
uint16_t requestedDurationMs) {
|
||||
portENTER_CRITICAL(&mux);
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PowerScheduler::releasePower(uint16_t power)
|
||||
{
|
||||
if (this->freePowerBudget + power <= this->totalPowerBudget)
|
||||
{
|
||||
this->freePowerBudget += power;
|
||||
void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) {
|
||||
portENTER_CRITICAL(&mux);
|
||||
for (auto it = currentAllowances.begin(); it != currentAllowances.end();
|
||||
++it) {
|
||||
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
|
||||
checkWaitingTasks();
|
||||
}
|
||||
|
||||
bool PowerScheduler::waitForPowerAllowance(uint16_t neededPower, TickType_t ticksToWait)
|
||||
{
|
||||
if (tryAccquirePowerAllowance(neededPower))
|
||||
{
|
||||
bool PowerScheduler::waitForCurrentAllowance(
|
||||
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent,
|
||||
uint16_t maxSlackTimeMs, uint16_t requestedDurationMs) {
|
||||
if (tryAccquireCurrentAllowance(consumer, neededCurrent,
|
||||
requestedDurationMs)) {
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
|
||||
// Suspend the task while waiting for power to be available
|
||||
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
|
||||
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;
|
||||
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
|
||||
while (notificationStatus == pdPASS)
|
||||
{
|
||||
if (notificationValue == POWER_AVAILABLE)
|
||||
{
|
||||
// We were woken up because new power is available, check if it is enough
|
||||
if (tryAccquirePowerAllowance(neededPower))
|
||||
{
|
||||
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
|
||||
{
|
||||
} else {
|
||||
// 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...
|
||||
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
|
||||
throw "Reached impossible state";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PowerScheduler::checkWaitingTasks(void)
|
||||
{
|
||||
// Check if there are tasks waiting for power
|
||||
if (!waitingTasks.empty())
|
||||
{
|
||||
TaskHandle_t task = waitingTasks.front();
|
||||
waitingTasks.pop();
|
||||
xTaskNotify(task, POWER_AVAILABLE, eSetValueWithOverwrite);
|
||||
void PowerScheduler::checkWaitingTasks(void) {
|
||||
// If there are requested allowances, try to grant the one expiring next
|
||||
if (this->currentAllowances.size() > 0) {
|
||||
|
||||
PowerScheduler::CurrentAllowance* nextAllowance =
|
||||
getNextExpiringAllowance();
|
||||
if (nextAllowance != nullptr) {
|
||||
xTaskNotify(nextAllowance->taskHandle,
|
||||
PowerScheduler::PowerWakeupReasons::POWER_AVAILABLE,
|
||||
eSetValueWithOverwrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PowerScheduler *PowerScheduler::getPowerScheduler()
|
||||
{
|
||||
if (powerSchedulerInstance == nullptr)
|
||||
{
|
||||
// Double check locking https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
|
||||
void PowerScheduler::recalculateCurrentBudgets(void) {
|
||||
// TODO: replace with actual current modeling
|
||||
this->freeLimitCurrentBudget = this->limitCurrent;
|
||||
this->freeMaximumCurrentBudget = this->maximumCurrent * 2;
|
||||
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);
|
||||
if (powerSchedulerInstance == nullptr)
|
||||
{
|
||||
powerSchedulerInstance = new PowerScheduler();
|
||||
if (powerSchedulerInstance == nullptr) {
|
||||
powerSchedulerInstance = new PowerScheduler(i_limit_ma, i_max_ma);
|
||||
}
|
||||
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
|
||||
* @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
|
||||
* @date 2024-12-21
|
||||
*
|
||||
@ -9,62 +10,114 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <queue>
|
||||
#include <Arduino.h>
|
||||
#include "Consumptions.h"
|
||||
#include "PowerParameters.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
|
||||
#ifndef PowerScheduler_h
|
||||
#define PowerScheduler_h
|
||||
|
||||
#define TOTAL_POWER_MILLIWATTS POWER_BUDGET
|
||||
|
||||
class PowerScheduler
|
||||
{
|
||||
class PowerScheduler {
|
||||
private:
|
||||
/* data */
|
||||
static constexpr uint16_t DEFAULT_SLACK_TIME_MS = 100;
|
||||
PowerScheduler(float i_limit_ma, float i_max_ma);
|
||||
|
||||
public:
|
||||
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);
|
||||
static PowerScheduler& getPowerScheduler(float i_limit_ma=0, float i_max_ma=0);
|
||||
/// @brief Get the current free current budget (to C1 discharge)
|
||||
/// @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 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 TicksToWait the amount of time to wait for the power to become available
|
||||
/// @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 waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait);
|
||||
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown.
|
||||
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);
|
||||
|
||||
protected:
|
||||
static const uint16_t totalPowerBudget = TOTAL_POWER_MILLIWATTS;
|
||||
uint16_t freePowerBudget;
|
||||
std::queue<TaskHandle_t> waitingTasks;
|
||||
void checkWaitingTasks(void);
|
||||
bool takePowerIfAvailable(uint16_t neededPower);
|
||||
//// @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:
|
||||
// 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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user