This commit is contained in:
Phillip Kühne 2025-02-13 00:54:03 +01:00
parent c130026f00
commit b0068333c8
Signed by: phillip
GPG Key ID: E4C1C4D2F90902AA
6 changed files with 78 additions and 36 deletions

View File

@ -9,7 +9,9 @@ Dezibot::Dezibot() : multiColorLight() {};
void Dezibot::begin(void) void Dezibot::begin(void)
{ {
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();

View File

@ -8,13 +8,31 @@
*/ */
#include "Power.h" #include "Power.h"
static portMUX_TYPE mux;
void vTaskUpdatePowerState(void *pvParameters) {
for (;;) {
ESP_LOGV(TAG, "Updating Power State...");
Power::updatePowerStateHandler();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
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.
ESP_LOGI(TAG, "Initializing Power Management");
if (powerScheduler == nullptr) { if (powerScheduler == nullptr) {
powerScheduler = &PowerScheduler::getPowerScheduler(); ESP_LOGI(TAG, "Creating Power Scheduler");
powerScheduler = &PowerScheduler::getPowerScheduler(
PowerParameters::Battery::CELL_CURRENT_1C_MA,
PowerParameters::Battery::CELL_CURRENT_2C_MA);
Power::recalculateCurrentBudgets();
Power::updatePowerStateHandler();
TaskHandle_t xHandle = NULL;
xTaskCreate(vTaskUpdatePowerState, "vTaskPowerStateUpdate", 4096, NULL,
tskIDLE_PRIORITY, &xHandle);
configASSERT(xHandle);
if (!(powerScheduler->tryAccquireCurrentAllowance( if (!(powerScheduler->tryAccquireCurrentAllowance(
PowerParameters::PowerConsumers::ESP, PowerParameters::PowerConsumers::ESP,
@ -71,20 +89,22 @@ float Power::getConsumerCurrent(PowerParameters::PowerConsumers consumer) {
float Power::getBatteryVoltage() { float Power::getBatteryVoltage() {
// Get the battery voltage from the ADC and convert it to a voltage // Get the battery voltage from the ADC and convert it to a voltage
// using the voltage divider. // using the voltage divider.
portENTER_CRITICAL(&mux); pinMode(PowerParameters::PinConfig::BAT_ADC_EN, OUTPUT);
pinMode(PowerParameters::PinConfig::BAT_ADC, INPUT);
// Enable voltage divider // Enable voltage divider
digitalWrite(PowerParameters::PinConfig::BAT_ADC_EN, HIGH); digitalWrite(PowerParameters::PinConfig::BAT_ADC_EN, HIGH);
// Allow voltage to stabilize
delayMicroseconds(10);
// Returns value between 0 and 4095 mapping to between 0 and 3.3V // Returns value between 0 and 4095 mapping to between 0 and 3.3V
uint16_t batteryAdcValue = uint16_t batteryAdcValue = analogRead(PowerParameters::PinConfig::BAT_ADC);
analogRead(PowerParameters::PinConfig::BAT_ADC) *
(PowerParameters::Battery::BAT_ADC::VOLTAGE_DIVIDER_FACTOR);
// Disable voltage divider // Disable voltage divider
digitalWrite(PowerParameters::PinConfig::BAT_ADC_EN, LOW); digitalWrite(PowerParameters::PinConfig::BAT_ADC_EN, LOW);
portEXIT_CRITICAL(&mux);
// Convert ADC value to voltage // Convert ADC value to voltage
float batteryVoltage = float batteryVoltage =
(batteryAdcValue * 3.3 / 4095) * (batteryAdcValue / 4095.0 * 3.3) *
PowerParameters::Battery::BAT_ADC::VOLTAGE_DIVIDER_FACTOR; PowerParameters::Battery::BAT_ADC::VOLTAGE_DIVIDER_FACTOR;
ESP_LOGD(TAG, "Battery ADC value: %d, Calculated voltage: %.2f",
batteryAdcValue, batteryVoltage);
return batteryVoltage; return batteryVoltage;
} }
@ -168,13 +188,14 @@ void Power::updatePowerStateHandler() {
// Update the available current (changes based on battery state of charge) // Update the available current (changes based on battery state of charge)
powerScheduler->recalculateCurrentBudgets(); powerScheduler->recalculateCurrentBudgets();
ESP_LOGV(TAG, "Current: %f mA, Charge: %f Coulombs, %d %%", currentCurrent,
coloumbsRemaining, percentRemaining);
return; return;
} }
float Power::getMax3V3Current() { float Power::getMax3V3Current() {
float u_bat = getBatteryVoltage(); float u_bat = getBatteryVoltage();
float i_bat = PowerParameters::Battery::CELL_CURRENT_1C; float i_bat = PowerParameters::Battery::CELL_CURRENT_1C_MA;
float eta = PowerParameters::BUCK_BOOST_EFFICIENCY; float eta = PowerParameters::BUCK_BOOST_EFFICIENCY;
constexpr float u_3v3 = 3.3; constexpr float u_3v3 = 3.3;
return (u_bat * i_bat * eta) / u_3v3; return (u_bat * i_bat * eta) / u_3v3;
@ -186,11 +207,19 @@ void Power::addSoCSample(float soc) {
lastSOC[latestSoCIndex] = soc; lastSOC[latestSoCIndex] = soc;
} }
void Power::initPowerState(void) {
// Initialize the power state
lastPowerStateUpdate = xTaskGetTickCount();
// TODO: Get initial battery charge state based on voltage, set coloumbs based
// on that
}
int Power::latestSoCIndex = 0;
float Power::lastSOC[PowerParameters::Battery::AVERAGING_SAMPLES] = {0};
TickType_t Power::lastPowerStateUpdate = 0;
float Power::coloumbsRemaining =
PowerParameters::Battery::CELL_CHARGE_FULL_COLOUMB;
int Power::percentRemaining = 100.0;
PowerScheduler *Power::powerScheduler = nullptr; PowerScheduler *Power::powerScheduler = nullptr;
Power::Power() { Power::Power() {}
// Initialize the power scheduler
powerScheduler = &PowerScheduler::getPowerScheduler();
// Initialize the mutex
mux = portMUX_INITIALIZER_UNLOCKED;
}

View File

@ -88,12 +88,14 @@ public:
/// @return available current in milliamps /// @return available current in milliamps
static float getMax3V3Current(); static float getMax3V3Current();
/// @brief update Power State
/// @note needs to be public for task creation
static void updatePowerStateHandler();
protected: protected:
/// @brief PowerScheduler instance to manage power consumption /// @brief PowerScheduler instance to manage power consumption
static PowerScheduler *powerScheduler; static PowerScheduler *powerScheduler;
/// @brief update Power State
static void updatePowerStateHandler();
/* /*
* Power State * Power State
@ -115,6 +117,9 @@ protected:
/// @brief Add calculated value to circular array, pushing out oldest value /// @brief Add calculated value to circular array, pushing out oldest value
static void addSoCSample(float soc); static void addSoCSample(float soc);
/// @brief initialize the power state
static void initPowerState(void);
}; };
extern Power power; extern Power power;

View File

@ -33,8 +33,8 @@ namespace PowerParameters {
static constexpr float CELL_CHARGE_FULL_COLOUMB = CELL_CAPACITY_MAH * 3.6; static constexpr float CELL_CHARGE_FULL_COLOUMB = CELL_CAPACITY_MAH * 3.6;
static constexpr float CELL_ENERGY_FULL_JOULES = static constexpr float CELL_ENERGY_FULL_JOULES =
CELL_CAPACITY_MAH * CELL_VOLTAGE_NOMINAL * 3.6; CELL_CAPACITY_MAH * CELL_VOLTAGE_NOMINAL * 3.6;
static constexpr float CELL_CURRENT_1C = CELL_CAPACITY_MAH; static constexpr float CELL_CURRENT_1C_MA = CELL_CAPACITY_MAH;
static constexpr float CELL_CURRENT_2C = CELL_CAPACITY_MAH * 2; static constexpr float CELL_CURRENT_2C_MA = CELL_CAPACITY_MAH * 2;
struct BAT_ADC { struct BAT_ADC {
static constexpr float VOLTAGE_DIVIDER_R12 = 27e3; static constexpr float VOLTAGE_DIVIDER_R12 = 27e3;
static constexpr float VOLTAGE_DIVIDER_R13 = 10e3; static constexpr float VOLTAGE_DIVIDER_R13 = 10e3;

View File

@ -14,9 +14,8 @@
#include "Power.h" #include "Power.h"
bool PowerScheduler::tryAccquireCurrentAllowance( bool PowerScheduler::tryAccquireCurrentAllowance(
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent, PowerParameters::PowerConsumers consumer, float neededCurrent,
uint16_t requestedDurationMs) { uint16_t requestedDurationMs) {
portENTER_CRITICAL(&mux);
float existingConsumption = getConsumerCurrent(consumer); float existingConsumption = getConsumerCurrent(consumer);
const bool currentAvailableBelowLimit = const bool currentAvailableBelowLimit =
this->freeLimitCurrentBudget + existingConsumption > 0; this->freeLimitCurrentBudget + existingConsumption > 0;
@ -38,15 +37,19 @@ bool PowerScheduler::tryAccquireCurrentAllowance(
.grantedAt = xTaskGetTickCount(), .grantedAt = xTaskGetTickCount(),
.granted = true}); .granted = true});
this->recalculateCurrentBudgets(); this->recalculateCurrentBudgets();
portEXIT_CRITICAL(&mux);
return true; return true;
} else { } else {
ESP_LOGI(TAG,
"Task %p denied %f mA of power;"
"currently allocated: %f mA;"
"total available (2C): %f mA",
xTaskGetCurrentTaskHandle(), neededCurrent, getCurrentCurrent(),
maximumCurrent);
return false; return false;
} }
} }
void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) { void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) {
portENTER_CRITICAL(&mux);
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) {
@ -55,13 +58,12 @@ void PowerScheduler::releaseCurrent(PowerParameters::PowerConsumers consumer) {
} }
} }
recalculateCurrentBudgets(); recalculateCurrentBudgets();
portEXIT_CRITICAL(&mux);
// Check if there are tasks waiting for power // Check if there are tasks waiting for power
checkWaitingTasks(); checkWaitingTasks();
} }
bool PowerScheduler::waitForCurrentAllowance( bool PowerScheduler::waitForCurrentAllowance(
PowerParameters::PowerConsumers consumer, uint16_t neededCurrent, PowerParameters::PowerConsumers consumer, float neededCurrent,
uint16_t maxSlackTimeMs, uint16_t requestedDurationMs) { uint16_t maxSlackTimeMs, uint16_t requestedDurationMs) {
if (tryAccquireCurrentAllowance(consumer, neededCurrent, if (tryAccquireCurrentAllowance(consumer, neededCurrent,
requestedDurationMs)) { requestedDurationMs)) {
@ -98,7 +100,7 @@ bool PowerScheduler::waitForCurrentAllowance(
const bool currentAvailableBelowMaximum = const bool currentAvailableBelowMaximum =
this->freeMaximumCurrentBudget + existingConsumption >= this->freeMaximumCurrentBudget + existingConsumption >=
neededCurrent; neededCurrent;
const bool currentIsInsignificant = neededCurrent < 0.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 // TODO Check if there is a currently active allowance for this
@ -137,6 +139,12 @@ bool PowerScheduler::waitForCurrentAllowance(
break; break;
} }
} }
ESP_LOGI(TAG,
"Task %p timed out waiting for %f mA of power;"
"currently allocated: %f mA;"
"total available (2C): %f mA",
xTaskGetCurrentTaskHandle(), neededCurrent, getCurrentCurrent(),
maximumCurrent);
return false; return false;
} else { } else {
// Should be impossible to reach // Should be impossible to reach
@ -161,7 +169,9 @@ 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...");
float tempFreeLimitCurrentBudget = Power::getMax3V3Current(); float tempFreeLimitCurrentBudget = Power::getMax3V3Current();
ESP_LOGI(TAG, "Got max 3V3 current: %.2f", tempFreeLimitCurrentBudget);
float tempFreeMaximumCurrentBudget = Power::getMax3V3Current() * 2; float tempFreeMaximumCurrentBudget = Power::getMax3V3Current() * 2;
for (auto &allowance : currentAllowances) { for (auto &allowance : currentAllowances) {
if (allowance.granted) { if (allowance.granted) {
@ -171,6 +181,8 @@ void PowerScheduler::recalculateCurrentBudgets(void) {
} }
this->freeLimitCurrentBudget = tempFreeLimitCurrentBudget; this->freeLimitCurrentBudget = tempFreeLimitCurrentBudget;
this->freeMaximumCurrentBudget = tempFreeMaximumCurrentBudget; this->freeMaximumCurrentBudget = tempFreeMaximumCurrentBudget;
ESP_LOGV(TAG, "Current budgets recalculated: %f mA, %f mA",
this->freeLimitCurrentBudget, this->freeMaximumCurrentBudget);
} }
PowerScheduler::CurrentAllowance * PowerScheduler::CurrentAllowance *
@ -214,11 +226,9 @@ PowerScheduler &PowerScheduler::getPowerScheduler(float i_limit_ma,
if (powerSchedulerInstance == nullptr) { if (powerSchedulerInstance == nullptr) {
// Double check locking // Double check locking
// https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf // https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
taskENTER_CRITICAL(&mux);
if (powerSchedulerInstance == nullptr) { if (powerSchedulerInstance == nullptr) {
powerSchedulerInstance = new PowerScheduler(i_limit_ma, i_max_ma); powerSchedulerInstance = new PowerScheduler(i_limit_ma, i_max_ma);
} }
taskEXIT_CRITICAL(&mux);
} }
return *powerSchedulerInstance; return *powerSchedulerInstance;
} }
@ -256,9 +266,6 @@ 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>();
mux = portMUX_INITIALIZER_UNLOCKED;
} }
portMUX_TYPE PowerScheduler::mux = portMUX_INITIALIZER_UNLOCKED;
PowerScheduler::~PowerScheduler() {} PowerScheduler::~PowerScheduler() {}

View File

@ -49,7 +49,7 @@ public:
/// so requesting the same or less power will always succeed. Also, small amounts /// so requesting the same or less power will always succeed. Also, small amounts
/// of power (below 1 mA) will always be granted. /// of power (below 1 mA) will always be granted.
bool tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer, bool tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer,
uint16_t neededcurrent, float neededcurrent,
uint16_t requestedDurationMs = 0); uint16_t requestedDurationMs = 0);
/// @brief "Return" the current currently allocated to a consumer /// @brief "Return" the current currently allocated to a consumer
/// @param consumer the active consumer to release the current for /// @param consumer the active consumer to release the current for
@ -64,7 +64,7 @@ public:
/// available /// available
/// @return whether the power could be successfully allocatedy /// @return whether the power could be successfully allocatedy
bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer, bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
uint16_t neededCurrent, float neededCurrent,
uint16_t maxSlackTimeMs = DEFAULT_SLACK_TIME_MS, uint16_t maxSlackTimeMs = DEFAULT_SLACK_TIME_MS,
uint16_t requestedDurationMs = 0); uint16_t requestedDurationMs = 0);
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up /// @brief Put the ESP32 into deep sleep mode, without a method to wake up
@ -81,7 +81,7 @@ public:
uint16_t maxSlackTimeMs; uint16_t maxSlackTimeMs;
uint16_t requestedDurationMs; uint16_t requestedDurationMs;
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
uint16_t neededCurrent; float neededCurrent;
TickType_t requestedAt; TickType_t requestedAt;
TickType_t grantedAt; TickType_t grantedAt;
bool granted; bool granted;
@ -122,7 +122,6 @@ protected:
void checkWaitingTasks(void); void checkWaitingTasks(void);
// @brief Mutex to protect the power scheduler from concurrent access // @brief Mutex to protect the power scheduler from concurrent access
static portMUX_TYPE mux;
std::vector<PowerScheduler::CurrentAllowance> currentAllowances; std::vector<PowerScheduler::CurrentAllowance> currentAllowances;
}; };