mirror of
https://gitlab.dit.htwk-leipzig.de/phillip.kuehne/dezibot.git
synced 2025-05-19 11:01:46 +02:00
Add RAII-wrapped Mutexes for access to state-tracking variables
This commit is contained in:
parent
0d6977e148
commit
264e37c983
@ -11,7 +11,6 @@ void Dezibot::begin(void)
|
|||||||
{
|
{
|
||||||
ESP_LOGI("Dezibot", "Initializing Dezibot");
|
ESP_LOGI("Dezibot", "Initializing Dezibot");
|
||||||
power.begin();
|
power.begin();
|
||||||
delay(10);
|
|
||||||
Wire.begin(SDA_PIN, SCL_PIN);
|
Wire.begin(SDA_PIN, SCL_PIN);
|
||||||
infraredLight.begin();
|
infraredLight.begin();
|
||||||
lightDetection.begin();
|
lightDetection.begin();
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
#include "Power.h"
|
#include "Power.h"
|
||||||
|
|
||||||
|
SemaphoreHandle_t Power::powerMutex = NULL;
|
||||||
|
|
||||||
void vTaskUpdatePowerState(void *pvParameters) {
|
void vTaskUpdatePowerState(void *pvParameters) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ESP_LOGV(TAG, "Updating Power State...");
|
ESP_LOGV(TAG, "Updating Power State...");
|
||||||
@ -22,6 +24,15 @@ void Power::begin() {
|
|||||||
// if not, we will do it.
|
// if not, we will do it.
|
||||||
ESP_LOGI(TAG, "Initializing Power Management");
|
ESP_LOGI(TAG, "Initializing Power Management");
|
||||||
|
|
||||||
|
// Create mutex if it doesn't exist
|
||||||
|
if (powerMutex == NULL) {
|
||||||
|
powerMutex = xSemaphoreCreateMutex();
|
||||||
|
if (powerMutex == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create power mutex");
|
||||||
|
Serial.println("Failed to create power mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (powerScheduler == nullptr) {
|
if (powerScheduler == nullptr) {
|
||||||
ESP_LOGI(TAG, "Creating Power Scheduler");
|
ESP_LOGI(TAG, "Creating Power Scheduler");
|
||||||
powerScheduler = &PowerScheduler::getPowerScheduler(
|
powerScheduler = &PowerScheduler::getPowerScheduler(
|
||||||
@ -149,18 +160,22 @@ void Power::updatePowerStateHandler() {
|
|||||||
int referenceCurrentMa =
|
int referenceCurrentMa =
|
||||||
PowerParameters::Battery::DISCHARGE_CURVE::REFERENCE_CURRENT_A * 1000;
|
PowerParameters::Battery::DISCHARGE_CURVE::REFERENCE_CURRENT_A * 1000;
|
||||||
|
|
||||||
|
float coloumbsConsumedSinceLastUpdate;
|
||||||
|
|
||||||
// Calculate remaining battery charge in Coulombs based on current and time
|
// Calculate remaining battery charge in Coulombs based on current and time
|
||||||
|
if (!busPowered) {
|
||||||
float coloumbsConsumedSinceLastUpdate =
|
float coloumbsConsumedSinceLastUpdate =
|
||||||
(currentCurrent / 1000) *
|
(currentCurrent / 1000) *
|
||||||
((pdTICKS_TO_MS(xTaskGetTickCount() - lastPowerStateUpdate)) / 1000.0);
|
((pdTICKS_TO_MS(xTaskGetTickCount() - lastPowerStateUpdate)) / 1000.0);
|
||||||
|
|
||||||
// Update coloumbs remaining
|
// Update coloumbs remaining
|
||||||
coloumbsRemaining -= coloumbsConsumedSinceLastUpdate;
|
coloumbsRemaining -= coloumbsConsumedSinceLastUpdate;
|
||||||
|
}
|
||||||
float chargeState;
|
float chargeState;
|
||||||
|
|
||||||
// If current flow is close enough to reference, get battery charge state via
|
// If current flow is close enough to reference, get battery charge state via
|
||||||
// voltage curve
|
// voltage curve
|
||||||
|
if (!busPowered) {
|
||||||
if ((currentCurrent > (referenceCurrentMa * 0.6)) &&
|
if ((currentCurrent > (referenceCurrentMa * 0.6)) &&
|
||||||
(currentCurrent < (referenceCurrentMa * 1.4))) {
|
(currentCurrent < (referenceCurrentMa * 1.4))) {
|
||||||
// Get battery charge state from voltage curve
|
// Get battery charge state from voltage curve
|
||||||
@ -168,11 +183,16 @@ void Power::updatePowerStateHandler() {
|
|||||||
} else {
|
} else {
|
||||||
// Estimate battery charge state from charge consumption
|
// Estimate battery charge state from charge consumption
|
||||||
float oldChargeState = lastSOC[latestSoCIndex];
|
float oldChargeState = lastSOC[latestSoCIndex];
|
||||||
chargeState =
|
chargeState = oldChargeState -
|
||||||
oldChargeState - ((coloumbsConsumedSinceLastUpdate /
|
((coloumbsConsumedSinceLastUpdate /
|
||||||
PowerParameters::Battery::CELL_CHARGE_FULL_COLOUMB) *
|
PowerParameters::Battery::CELL_CHARGE_FULL_COLOUMB) *
|
||||||
100);
|
100);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If we are charging, we can't estimate the charge state based on current
|
||||||
|
// consumption
|
||||||
|
chargeState = getBatteryVoltageChargePercent();
|
||||||
|
}
|
||||||
|
|
||||||
addSoCSample(chargeState);
|
addSoCSample(chargeState);
|
||||||
|
|
||||||
@ -190,10 +210,16 @@ void Power::updatePowerStateHandler() {
|
|||||||
powerScheduler->recalculateCurrentBudgets();
|
powerScheduler->recalculateCurrentBudgets();
|
||||||
ESP_LOGV(TAG, "Current: %f mA, Charge: %f Coulombs, %d %%", currentCurrent,
|
ESP_LOGV(TAG, "Current: %f mA, Charge: %f Coulombs, %d %%", currentCurrent,
|
||||||
coloumbsRemaining, percentRemaining);
|
coloumbsRemaining, percentRemaining);
|
||||||
|
|
||||||
|
// Update supply and charge state flags
|
||||||
|
busPowered = digitalRead(PowerParameters::PinConfig::VUSB_SENS);
|
||||||
|
chargingState = digitalRead(PowerParameters::PinConfig::BAT_CHG_STAT);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Power::getMax3V3Current() {
|
float Power::getMax3V3Current() {
|
||||||
|
// Conversion from Thesis
|
||||||
float u_bat = getBatteryVoltage();
|
float u_bat = getBatteryVoltage();
|
||||||
float i_bat = PowerParameters::Battery::CELL_CURRENT_1C_MA;
|
float i_bat = PowerParameters::Battery::CELL_CURRENT_1C_MA;
|
||||||
float eta = PowerParameters::BUCK_BOOST_EFFICIENCY;
|
float eta = PowerParameters::BUCK_BOOST_EFFICIENCY;
|
||||||
@ -202,6 +228,11 @@ float Power::getMax3V3Current() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Power::addSoCSample(float soc) {
|
void Power::addSoCSample(float soc) {
|
||||||
|
PowerMutex lock(powerMutex);
|
||||||
|
if (!lock.isLocked()) {
|
||||||
|
ESP_LOGE(TAG, "Could not take power to add SoC sample");
|
||||||
|
return;
|
||||||
|
}
|
||||||
latestSoCIndex =
|
latestSoCIndex =
|
||||||
(latestSoCIndex + 1) % PowerParameters::Battery::AVERAGING_SAMPLES;
|
(latestSoCIndex + 1) % PowerParameters::Battery::AVERAGING_SAMPLES;
|
||||||
lastSOC[latestSoCIndex] = soc;
|
lastSOC[latestSoCIndex] = soc;
|
||||||
@ -221,6 +252,11 @@ void Power::initPowerState(void) {
|
|||||||
constexpr float fullColoumbs =
|
constexpr float fullColoumbs =
|
||||||
PowerParameters::Battery::CELL_CHARGE_FULL_COLOUMB;
|
PowerParameters::Battery::CELL_CHARGE_FULL_COLOUMB;
|
||||||
percentRemaining = initialChargePercentages;
|
percentRemaining = initialChargePercentages;
|
||||||
|
// Set up flags and pins for them
|
||||||
|
pinMode(PowerParameters::PinConfig::VUSB_SENS, INPUT);
|
||||||
|
pinMode(PowerParameters::PinConfig::BAT_CHG_STAT, INPUT_PULLUP);
|
||||||
|
busPowered = digitalRead(PowerParameters::PinConfig::VUSB_SENS);
|
||||||
|
chargingState = digitalRead(PowerParameters::PinConfig::BAT_CHG_STAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::dumpPowerStatistics() {
|
void Power::dumpPowerStatistics() {
|
||||||
@ -228,31 +264,69 @@ void Power::dumpPowerStatistics() {
|
|||||||
Serial.printf("Current: %f mA\r\n", Power::getCurrentCurrent());
|
Serial.printf("Current: %f mA\r\n", Power::getCurrentCurrent());
|
||||||
Serial.printf("Battery Voltage: %f V\r\n", Power::getBatteryVoltage());
|
Serial.printf("Battery Voltage: %f V\r\n", Power::getBatteryVoltage());
|
||||||
Serial.printf("Battery Charge: %f %%\r\n", Power::getBatteryChargePercent());
|
Serial.printf("Battery Charge: %f %%\r\n", Power::getBatteryChargePercent());
|
||||||
Serial.printf("Battery Charge: %f Coulombs\r\n", Power::getBatteryChargeCoulombs());
|
Serial.printf("Battery Charge: %f Coulombs\r\n",
|
||||||
Serial.printf("Max 3.3V Current in this state: %f mA\r\n", Power::getMax3V3Current());
|
Power::getBatteryChargeCoulombs());
|
||||||
|
Serial.printf("Max 3.3V Current in this state (1C, 2C): %f mA, %f mA \r\n",
|
||||||
|
Power::getMax3V3Current(), Power::getMax3V3Current() * 2);
|
||||||
Serial.printf("=========================================\r\n");
|
Serial.printf("=========================================\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::dumpConsumerStatistics() {
|
void Power::dumpConsumerStatistics() {
|
||||||
Serial.printf("======== Dezibot Consumer Statistics ========\r\n");
|
Serial.printf("======== Dezibot Consumer Statistics ========\r\n");
|
||||||
Serial.printf("ESP: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::ESP));
|
Serial.printf("ESP: %f mA\r\n", Power::getConsumerCurrent(
|
||||||
Serial.printf("WIFI: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::WIFI));
|
PowerParameters::PowerConsumers::ESP));
|
||||||
Serial.printf("LED_RGB_TOP_LEFT: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_RGB_TOP_LEFT));
|
Serial.printf("WIFI: %f mA\r\n", Power::getConsumerCurrent(
|
||||||
Serial.printf("LED_RGB_TOP_RIGHT: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_RGB_TOP_RIGHT));
|
PowerParameters::PowerConsumers::WIFI));
|
||||||
Serial.printf("LED_RGB_BOTTOM: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_RGB_BOTTOM));
|
Serial.printf("LED_RGB_TOP_LEFT: %f mA\r\n",
|
||||||
Serial.printf("RGBW_SENSOR: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::RGBW_SENSOR));
|
Power::getConsumerCurrent(
|
||||||
Serial.printf("LED_IR_BOTTOM: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_IR_BOTTOM));
|
PowerParameters::PowerConsumers::LED_RGB_TOP_LEFT));
|
||||||
Serial.printf("LED_IR_FRONT: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_IR_FRONT));
|
Serial.printf("LED_RGB_TOP_RIGHT: %f mA\r\n",
|
||||||
Serial.printf("PT_IR: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::PT_IR));
|
Power::getConsumerCurrent(
|
||||||
Serial.printf("PT_DL: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::PT_DL));
|
PowerParameters::PowerConsumers::LED_RGB_TOP_RIGHT));
|
||||||
Serial.printf("LED_UV: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_UV));
|
Serial.printf("LED_RGB_BOTTOM: %f mA\r\n",
|
||||||
Serial.printf("DISPLAY_OLED: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::DISPLAY_OLED));
|
Power::getConsumerCurrent(
|
||||||
Serial.printf("MOTOR_LEFT: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::MOTOR_LEFT));
|
PowerParameters::PowerConsumers::LED_RGB_BOTTOM));
|
||||||
Serial.printf("MOTOR_RIGHT: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::MOTOR_RIGHT));
|
Serial.printf(
|
||||||
Serial.printf("IMU: %f mA\r\n", Power::getConsumerCurrent(PowerParameters::PowerConsumers::IMU));
|
"RGBW_SENSOR: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::RGBW_SENSOR));
|
||||||
|
Serial.printf("LED_IR_BOTTOM: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(
|
||||||
|
PowerParameters::PowerConsumers::LED_IR_BOTTOM));
|
||||||
|
Serial.printf(
|
||||||
|
"LED_IR_FRONT: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_IR_FRONT));
|
||||||
|
Serial.printf(
|
||||||
|
"PT_IR: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::PT_IR));
|
||||||
|
Serial.printf(
|
||||||
|
"PT_DL: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::PT_DL));
|
||||||
|
Serial.printf(
|
||||||
|
"LED_UV: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::LED_UV));
|
||||||
|
Serial.printf(
|
||||||
|
"DISPLAY_OLED: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::DISPLAY_OLED));
|
||||||
|
Serial.printf(
|
||||||
|
"MOTOR_LEFT: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::MOTOR_LEFT));
|
||||||
|
Serial.printf(
|
||||||
|
"MOTOR_RIGHT: %f mA\r\n",
|
||||||
|
Power::getConsumerCurrent(PowerParameters::PowerConsumers::MOTOR_RIGHT));
|
||||||
|
Serial.printf("IMU: %f mA\r\n", Power::getConsumerCurrent(
|
||||||
|
PowerParameters::PowerConsumers::IMU));
|
||||||
Serial.printf("=============================================\r\n");
|
Serial.printf("=============================================\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Power::isUSBPowered() { return busPowered; }
|
||||||
|
|
||||||
|
bool Power::isBatteryPowered() { return !busPowered; }
|
||||||
|
|
||||||
|
bool Power::isBatteryCharging() { return chargingState && busPowered; }
|
||||||
|
|
||||||
|
bool Power::isBatteryDischarging() { return !chargingState && !busPowered; }
|
||||||
|
|
||||||
|
bool Power::isBatteryFullyCharged() { return !chargingState && busPowered; }
|
||||||
int Power::latestSoCIndex = 0;
|
int Power::latestSoCIndex = 0;
|
||||||
float Power::lastSOC[PowerParameters::Battery::AVERAGING_SAMPLES] = {0};
|
float Power::lastSOC[PowerParameters::Battery::AVERAGING_SAMPLES] = {0};
|
||||||
TickType_t Power::lastPowerStateUpdate = 0;
|
TickType_t Power::lastPowerStateUpdate = 0;
|
||||||
@ -261,4 +335,10 @@ float Power::coloumbsRemaining =
|
|||||||
float Power::percentRemaining = 100.0;
|
float Power::percentRemaining = 100.0;
|
||||||
PowerScheduler *Power::powerScheduler = nullptr;
|
PowerScheduler *Power::powerScheduler = nullptr;
|
||||||
|
|
||||||
|
bool Power::busPowered = false;
|
||||||
|
|
||||||
|
bool Power::chargingState = false;
|
||||||
|
|
||||||
Power::Power() {}
|
Power::Power() {}
|
||||||
|
|
||||||
|
Power::~Power() {}
|
@ -8,6 +8,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PowerScheduler.h"
|
#include "PowerScheduler.h"
|
||||||
|
#include "esp_adc/adc_cali.h"
|
||||||
|
#include "esp_adc/adc_cali_scheme.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/adc.h"
|
||||||
|
#include "esp_adc/adc_oneshot.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
#ifndef Power_h
|
#ifndef Power_h
|
||||||
#define Power_h
|
#define Power_h
|
||||||
|
|
||||||
@ -17,6 +23,33 @@ enum TaskResumptionReason { POWER_AVAILABLE, TIMEOUT };
|
|||||||
|
|
||||||
class Power {
|
class Power {
|
||||||
|
|
||||||
|
private:
|
||||||
|
static SemaphoreHandle_t powerMutex;
|
||||||
|
static constexpr uint16_t MUTEX_TIMEOUT_MS = 1;
|
||||||
|
|
||||||
|
// RAII for mutex
|
||||||
|
class PowerMutex {
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t &mutex;
|
||||||
|
bool locked;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PowerMutex(SemaphoreHandle_t &mutex) : mutex(mutex), locked(false) {
|
||||||
|
locked =
|
||||||
|
(xSemaphoreTake(mutex, pdMS_TO_TICKS(MUTEX_TIMEOUT_MS)) == pdTRUE);
|
||||||
|
if (!locked) {
|
||||||
|
ESP_LOGW(TAG, "Could not take power mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~PowerMutex() {
|
||||||
|
if (locked) {
|
||||||
|
xSemaphoreGive(mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool isLocked() { return locked; }
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// @brief PowerScheduler instance to manage power consumption
|
/// @brief PowerScheduler instance to manage power consumption
|
||||||
static PowerScheduler *powerScheduler;
|
static PowerScheduler *powerScheduler;
|
||||||
@ -34,6 +67,10 @@ protected:
|
|||||||
/// @brief remaining Charge in percent
|
/// @brief remaining Charge in percent
|
||||||
static float percentRemaining;
|
static float percentRemaining;
|
||||||
|
|
||||||
|
static bool busPowered;
|
||||||
|
|
||||||
|
static bool chargingState;
|
||||||
|
|
||||||
/// @brief Circular array of last calculated values for current state of
|
/// @brief Circular array of last calculated values for current state of
|
||||||
/// charge
|
/// charge
|
||||||
static float lastSOC[PowerParameters::Battery::AVERAGING_SAMPLES];
|
static float lastSOC[PowerParameters::Battery::AVERAGING_SAMPLES];
|
||||||
@ -48,6 +85,7 @@ protected:
|
|||||||
public:
|
public:
|
||||||
static void begin(void);
|
static void begin(void);
|
||||||
Power();
|
Power();
|
||||||
|
~Power();
|
||||||
/// @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)
|
||||||
static float getFreeLimitCurrentBudget(void);
|
static float getFreeLimitCurrentBudget(void);
|
||||||
@ -88,7 +126,8 @@ public:
|
|||||||
/// @return the amount of power that is currently allocated (in mA)
|
/// @return the amount of power that is currently allocated (in mA)
|
||||||
static float getCurrentCurrent(void);
|
static float getCurrentCurrent(void);
|
||||||
|
|
||||||
// @brief Responsible for recalculating the current budgets
|
/// @brief Responsible for recalculating the current budgets
|
||||||
|
/// @note these change based on the current state of charge
|
||||||
static void recalculateCurrentBudgets(void);
|
static void recalculateCurrentBudgets(void);
|
||||||
|
|
||||||
// @brief Get current consumption of a consumer
|
// @brief Get current consumption of a consumer
|
||||||
@ -125,6 +164,27 @@ public:
|
|||||||
|
|
||||||
/// @brief dump consumer statistics to serial
|
/// @brief dump consumer statistics to serial
|
||||||
static void dumpConsumerStatistics();
|
static void dumpConsumerStatistics();
|
||||||
|
|
||||||
|
/// @brief get wether power is supplied via USB
|
||||||
|
/// @return true if power is supplied via USB
|
||||||
|
static bool isUSBPowered();
|
||||||
|
|
||||||
|
/// @brief get wether power is supplied via battery
|
||||||
|
/// @return true if power is supplied via battery
|
||||||
|
static bool isBatteryPowered();
|
||||||
|
|
||||||
|
/// @brief get wether the battery is currently charging
|
||||||
|
/// @return true if the battery is charging
|
||||||
|
static bool isBatteryCharging();
|
||||||
|
|
||||||
|
/// @brief get wether the battery is currently discharging
|
||||||
|
/// @return true if the battery is discharging
|
||||||
|
static bool isBatteryDischarging();
|
||||||
|
|
||||||
|
/// @brief get wether the battery is currently fully charged
|
||||||
|
/// @return true if the battery is fully charged
|
||||||
|
static bool isBatteryFullyCharged();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Power power;
|
extern Power power;
|
||||||
|
@ -27,6 +27,11 @@ bool PowerScheduler::tryAccquireCurrentAllowance(
|
|||||||
if (existingConsumption > 0) {
|
if (existingConsumption > 0) {
|
||||||
releaseCurrent(consumer);
|
releaseCurrent(consumer);
|
||||||
}
|
}
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
ESP_LOGE(TAG, "Failed to Acquire PowerScheduler Mutex during Current Allocation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this->currentAllowances.push_back(PowerScheduler::CurrentAllowance{
|
this->currentAllowances.push_back(PowerScheduler::CurrentAllowance{
|
||||||
.consumer = consumer,
|
.consumer = consumer,
|
||||||
.maxSlackTimeMs = 0,
|
.maxSlackTimeMs = 0,
|
||||||
@ -37,6 +42,7 @@ bool PowerScheduler::tryAccquireCurrentAllowance(
|
|||||||
.grantedAt = xTaskGetTickCount(),
|
.grantedAt = xTaskGetTickCount(),
|
||||||
.granted = true});
|
.granted = true});
|
||||||
this->recalculateCurrentBudgets();
|
this->recalculateCurrentBudgets();
|
||||||
|
lock.unlock();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
@ -50,6 +56,10 @@ bool PowerScheduler::tryAccquireCurrentAllowance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) {
|
void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) {
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
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) {
|
||||||
@ -57,6 +67,7 @@ void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lock.unlock();
|
||||||
recalculateCurrentBudgets();
|
recalculateCurrentBudgets();
|
||||||
// Check if there are tasks waiting for power
|
// Check if there are tasks waiting for power
|
||||||
checkWaitingTasks();
|
checkWaitingTasks();
|
||||||
@ -81,8 +92,12 @@ bool PowerScheduler::waitForCurrentAllowance(
|
|||||||
.neededCurrent = neededCurrent,
|
.neededCurrent = neededCurrent,
|
||||||
.requestedAt = initialTickCount,
|
.requestedAt = initialTickCount,
|
||||||
.granted = false};
|
.granted = false};
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this->currentAllowances.push_back(newAllowance);
|
this->currentAllowances.push_back(newAllowance);
|
||||||
|
lock.unlock();
|
||||||
uint32_t notificationValue;
|
uint32_t notificationValue;
|
||||||
BaseType_t notificationStatus = xTaskNotifyWait(
|
BaseType_t notificationStatus = xTaskNotifyWait(
|
||||||
0, 0, ¬ificationValue, pdMS_TO_TICKS(maxSlackTimeMs));
|
0, 0, ¬ificationValue, pdMS_TO_TICKS(maxSlackTimeMs));
|
||||||
@ -103,8 +118,10 @@ bool PowerScheduler::waitForCurrentAllowance(
|
|||||||
const bool currentIsInsignificant = neededCurrent < 1;
|
const bool currentIsInsignificant = neededCurrent < 1;
|
||||||
if (currentIsInsignificant ||
|
if (currentIsInsignificant ||
|
||||||
(currentAvailableBelowLimit && currentAvailableBelowMaximum)) {
|
(currentAvailableBelowLimit && currentAvailableBelowMaximum)) {
|
||||||
// TODO Check if there is a currently active allowance for this
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
// consumer and if so, replace it with the new one
|
if (lock.isLocked() == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (existingConsumption > 0) {
|
if (existingConsumption > 0) {
|
||||||
releaseCurrent(consumer);
|
releaseCurrent(consumer);
|
||||||
}
|
}
|
||||||
@ -132,6 +149,10 @@ bool PowerScheduler::waitForCurrentAllowance(
|
|||||||
if (notificationStatus == pdFALSE) {
|
if (notificationStatus == pdFALSE) {
|
||||||
// We waited long enough...
|
// We waited long enough...
|
||||||
// Remove the task from the list of waiting tasks
|
// Remove the task from the list of waiting tasks
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (auto it = currentAllowances.begin(); it != currentAllowances.end();
|
for (auto it = currentAllowances.begin(); it != currentAllowances.end();
|
||||||
++it) {
|
++it) {
|
||||||
if (it->consumer == consumer && it->requestedAt == initialTickCount) {
|
if (it->consumer == consumer && it->requestedAt == initialTickCount) {
|
||||||
@ -168,11 +189,16 @@ void PowerScheduler::checkWaitingTasks(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PowerScheduler::recalculateCurrentBudgets(void) {
|
void PowerScheduler::recalculateCurrentBudgets(void) {
|
||||||
|
|
||||||
// Get the respective maximums and subtract currently flowing currents
|
// Get the respective maximums and subtract currently flowing currents
|
||||||
ESP_LOGI(TAG, "Recalculating current budgets...");
|
ESP_LOGI(TAG, "Recalculating current budgets...");
|
||||||
float tempFreeLimitCurrentBudget = Power::getMax3V3Current();
|
float tempFreeLimitCurrentBudget = Power::getMax3V3Current();
|
||||||
ESP_LOGI(TAG, "Got max 3V3 current: %.2f", tempFreeLimitCurrentBudget);
|
ESP_LOGI(TAG, "Got max 3V3 current: %.2f", tempFreeLimitCurrentBudget);
|
||||||
float tempFreeMaximumCurrentBudget = Power::getMax3V3Current() * 2;
|
float tempFreeMaximumCurrentBudget = Power::getMax3V3Current() * 2;
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (auto &allowance : currentAllowances) {
|
for (auto &allowance : currentAllowances) {
|
||||||
if (allowance.granted) {
|
if (allowance.granted) {
|
||||||
tempFreeLimitCurrentBudget -= allowance.neededCurrent;
|
tempFreeLimitCurrentBudget -= allowance.neededCurrent;
|
||||||
@ -187,6 +213,10 @@ void PowerScheduler::recalculateCurrentBudgets(void) {
|
|||||||
|
|
||||||
PowerScheduler::CurrentAllowance *
|
PowerScheduler::CurrentAllowance *
|
||||||
PowerScheduler::getCurrentAllowance(PowerParameters::PowerConsumers consumer) {
|
PowerScheduler::getCurrentAllowance(PowerParameters::PowerConsumers consumer) {
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
for (auto &allowance : currentAllowances) {
|
for (auto &allowance : currentAllowances) {
|
||||||
if (allowance.consumer == consumer) {
|
if (allowance.consumer == consumer) {
|
||||||
return &allowance;
|
return &allowance;
|
||||||
@ -196,6 +226,10 @@ PowerScheduler::getCurrentAllowance(PowerParameters::PowerConsumers consumer) {
|
|||||||
}
|
}
|
||||||
PowerScheduler::CurrentAllowance *
|
PowerScheduler::CurrentAllowance *
|
||||||
PowerScheduler::getCurrentAllowance(TaskHandle_t taskHandle) {
|
PowerScheduler::getCurrentAllowance(TaskHandle_t taskHandle) {
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
for (auto &allowance : currentAllowances) {
|
for (auto &allowance : currentAllowances) {
|
||||||
if (allowance.taskHandle == taskHandle) {
|
if (allowance.taskHandle == taskHandle) {
|
||||||
return &allowance;
|
return &allowance;
|
||||||
@ -205,8 +239,12 @@ PowerScheduler::getCurrentAllowance(TaskHandle_t taskHandle) {
|
|||||||
}
|
}
|
||||||
PowerScheduler::CurrentAllowance *
|
PowerScheduler::CurrentAllowance *
|
||||||
PowerScheduler::getNextExpiringAllowance(void) {
|
PowerScheduler::getNextExpiringAllowance(void) {
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
TickType_t minTicks = UINT32_MAX;
|
TickType_t minTicks = UINT32_MAX;
|
||||||
CurrentAllowance *nextAllowance = nullptr;
|
CurrentAllowance *nextAllowance = nullptr;
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
for (auto &allowance : currentAllowances) {
|
for (auto &allowance : currentAllowances) {
|
||||||
if (!(allowance.granted)) {
|
if (!(allowance.granted)) {
|
||||||
TickType_t ticks =
|
TickType_t ticks =
|
||||||
@ -223,18 +261,18 @@ PowerScheduler::getNextExpiringAllowance(void) {
|
|||||||
|
|
||||||
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) {
|
|
||||||
// Double check locking
|
|
||||||
// https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
|
|
||||||
if (powerSchedulerInstance == nullptr) {
|
if (powerSchedulerInstance == nullptr) {
|
||||||
powerSchedulerInstance = new PowerScheduler(i_limit_ma, i_max_ma);
|
powerSchedulerInstance = new PowerScheduler(i_limit_ma, i_max_ma);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return *powerSchedulerInstance;
|
return *powerSchedulerInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
float PowerScheduler::getCurrentCurrent(void) {
|
float PowerScheduler::getCurrentCurrent(void) {
|
||||||
float currentSum = 0;
|
float currentSum = 0;
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (auto &allowance : currentAllowances) {
|
for (auto &allowance : currentAllowances) {
|
||||||
if (allowance.granted) {
|
if (allowance.granted) {
|
||||||
currentSum += allowance.neededCurrent;
|
currentSum += allowance.neededCurrent;
|
||||||
@ -253,6 +291,10 @@ float PowerScheduler::getFreeMaximumCurrentBudget(void) {
|
|||||||
|
|
||||||
float PowerScheduler::getConsumerCurrent(
|
float PowerScheduler::getConsumerCurrent(
|
||||||
PowerParameters::PowerConsumers consumer) {
|
PowerParameters::PowerConsumers consumer) {
|
||||||
|
PowerSchedulerMutex lock(powerSchedulerMutex);
|
||||||
|
if (lock.isLocked() == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
float currentSum = 0;
|
float currentSum = 0;
|
||||||
for (auto &allowance : currentAllowances) {
|
for (auto &allowance : currentAllowances) {
|
||||||
if (allowance.consumer == consumer && allowance.granted) {
|
if (allowance.consumer == consumer && allowance.granted) {
|
||||||
@ -266,6 +308,11 @@ 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;
|
||||||
this->currentAllowances = std::vector<CurrentAllowance>();
|
this->currentAllowances = std::vector<CurrentAllowance>();
|
||||||
|
this->powerSchedulerMutex = xSemaphoreCreateMutex();
|
||||||
}
|
}
|
||||||
|
|
||||||
PowerScheduler::~PowerScheduler() {}
|
PowerScheduler::~PowerScheduler() {
|
||||||
|
if (powerSchedulerMutex != NULL) {
|
||||||
|
vSemaphoreDelete(powerSchedulerMutex);
|
||||||
|
}
|
||||||
|
}
|
@ -24,8 +24,50 @@
|
|||||||
class PowerScheduler {
|
class PowerScheduler {
|
||||||
private:
|
private:
|
||||||
static constexpr uint16_t DEFAULT_SLACK_TIME_MS = 100;
|
static constexpr uint16_t DEFAULT_SLACK_TIME_MS = 100;
|
||||||
|
static constexpr uint16_t MUTEX_TIMEOUT_MS = 10;
|
||||||
|
SemaphoreHandle_t powerSchedulerMutex;
|
||||||
PowerScheduler(float i_limit_ma, float i_max_ma);
|
PowerScheduler(float i_limit_ma, float i_max_ma);
|
||||||
|
|
||||||
|
// RAII for mutex
|
||||||
|
class PowerSchedulerMutex {
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t &mutex;
|
||||||
|
bool locked;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PowerSchedulerMutex(SemaphoreHandle_t &mutex) : mutex(mutex), locked(false) {
|
||||||
|
locked =
|
||||||
|
(xSemaphoreTake(mutex, pdMS_TO_TICKS(MUTEX_TIMEOUT_MS)) == pdTRUE);
|
||||||
|
if (!locked) {
|
||||||
|
ESP_LOGW(TAG, "Could not take powerScheduler mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
if (locked) {
|
||||||
|
xSemaphoreGive(mutex);
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock() {
|
||||||
|
if (!locked) {
|
||||||
|
locked = (xSemaphoreTake(mutex, pdMS_TO_TICKS(MUTEX_TIMEOUT_MS)) ==
|
||||||
|
pdTRUE);
|
||||||
|
if (!locked) {
|
||||||
|
ESP_LOGW(TAG, "Could not take power mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~PowerSchedulerMutex() {
|
||||||
|
if (locked) {
|
||||||
|
xSemaphoreGive(mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool isLocked() { return locked; }
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~PowerScheduler();
|
~PowerScheduler();
|
||||||
/// @brief Initialize the singleton instance of the power manager
|
/// @brief Initialize the singleton instance of the power manager
|
||||||
@ -45,9 +87,9 @@ public:
|
|||||||
/// @param neededCurrent the amount of current we want to be accounted for (in
|
/// @param neededCurrent the amount of current we want to be accounted for (in
|
||||||
/// mA)
|
/// mA)
|
||||||
/// @return whether the current could be successfully allocated
|
/// @return whether the current could be successfully allocated
|
||||||
/// @note This takes existing power consumption by the same consumer into account,
|
/// @note This takes existing power consumption by the same consumer into
|
||||||
/// so requesting the same or less power will always succeed. Also, small amounts
|
/// account, so requesting the same or less power will always succeed. Also,
|
||||||
/// of power (below 1 mA) will always be granted.
|
/// small amounts of power (below 1 mA) will always be granted.
|
||||||
bool tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
bool tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer,
|
||||||
float neededcurrent,
|
float neededcurrent,
|
||||||
uint16_t requestedDurationMs = 0);
|
uint16_t requestedDurationMs = 0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user