diff --git a/src/drivers/battery.cpp b/src/drivers/battery.cpp index 445e9c23..1755fd64 100644 --- a/src/drivers/battery.cpp +++ b/src/drivers/battery.cpp @@ -14,6 +14,21 @@ namespace drivers { +/* + * Battery voltage, in millivolts, at which the battery charger IC will stop + * charging. + */ +static const uint32_t kFullChargeMilliVolts = 4200; + +/* + * Battery voltage, in millivolts, at which *we* will consider the battery to + * be completely discharged. This is intentionally higher than the charger IC + * cut-off and the protection on the battery itself; we want to make sure we + * finish up and have everything unmounted and snoozing before the BMS cuts us + * off. + */ +static const uint32_t kEmptyChargeMilliVolts = 3200; // BMS limit is 3100. + static const adc_bitwidth_t kAdcBitWidth = ADC_BITWIDTH_12; static const adc_unit_t kAdcUnit = ADC_UNIT_1; // Max battery voltage should be a little over 2V due to our divider, so we need @@ -44,6 +59,8 @@ Battery::Battery() { }; ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting( &calibration_config, &adc_calibration_handle_)); + + UpdatePercent(); } Battery::~Battery() { @@ -60,7 +77,21 @@ auto Battery::Millivolts() -> uint32_t { ESP_ERROR_CHECK( adc_cali_raw_to_voltage(adc_calibration_handle_, raw, &voltage)); - return voltage; + // Voltage divider halves the battery voltage to get it into the ADC's range. + return voltage * 2; +} + +auto Battery::UpdatePercent() -> bool { + auto old_percent = percent_; + // FIXME: So what we *should* do here is measure the actual real-life + // time from full battery -> empty battery, store it in NVS, then rely on + // that. If someone could please do this, it would be lovely. Thanks! + uint32_t mV = std::max(Millivolts(), kEmptyChargeMilliVolts); + percent_ = static_cast(std::min( + std::max(0.0, mV - kEmptyChargeMilliVolts) / + (kFullChargeMilliVolts - kEmptyChargeMilliVolts) * 100.0, + 100.0)); + return old_percent != percent_; } } // namespace drivers diff --git a/src/drivers/include/battery.hpp b/src/drivers/include/battery.hpp index f0b8c8b3..64a00135 100644 --- a/src/drivers/include/battery.hpp +++ b/src/drivers/include/battery.hpp @@ -26,9 +26,14 @@ class Battery { */ auto Millivolts() -> uint32_t; + auto UpdatePercent() -> bool; + auto Percent() -> uint_fast8_t { return percent_; } + private: adc_oneshot_unit_handle_t adc_handle_; adc_cali_handle_t adc_calibration_handle_; + + uint_fast8_t percent_; }; } // namespace drivers diff --git a/src/system_fsm/booting.cpp b/src/system_fsm/booting.cpp index 27f2b9c9..78653592 100644 --- a/src/system_fsm/booting.cpp +++ b/src/system_fsm/booting.cpp @@ -4,6 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-only */ +#include + #include "assert.h" #include "audio_fsm.hpp" #include "bluetooth.hpp" @@ -12,6 +14,10 @@ #include "esp_err.h" #include "esp_log.h" #include "event_queue.hpp" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/projdefs.h" +#include "freertos/timers.h" #include "gpios.hpp" #include "lvgl/lvgl.h" #include "nvs.hpp" @@ -32,6 +38,12 @@ namespace states { static const char kTag[] = "BOOT"; +static const TickType_t kBatteryCheckPeriod = pdMS_TO_TICKS(60 * 1000); + +static void battery_timer_cb(TimerHandle_t timer) { + events::System().Dispatch(internal::BatteryTimerFired{}); +} + auto Booting::entry() -> void { ESP_LOGI(kTag, "beginning tangara boot"); ESP_LOGI(kTag, "installing early drivers"); @@ -62,6 +74,12 @@ auto Booting::entry() -> void { return; } + ESP_LOGI(kTag, "battery is at %u%% (= %lu mV)", sBattery->Percent(), + sBattery->Millivolts()); + TimerHandle_t battery_timer = xTimerCreate("battery", kBatteryCheckPeriod, + true, NULL, battery_timer_cb); + xTimerStart(battery_timer, portMAX_DELAY); + ESP_LOGI(kTag, "starting bluetooth"); sBluetooth.reset(new drivers::Bluetooth(sNvs.get())); // sBluetooth->Enable(); diff --git a/src/system_fsm/idle.cpp b/src/system_fsm/idle.cpp index caba3954..91075fc6 100644 --- a/src/system_fsm/idle.cpp +++ b/src/system_fsm/idle.cpp @@ -72,7 +72,6 @@ void Idle::react(const internal::IdleTimeout& ev) { sGpios->WriteBuffered(drivers::IGpios::Pin::kSdMuxSwitch, true); sGpios->WriteBuffered(drivers::IGpios::Pin::kSdMuxDisable, true); - // Pull down to prevent sourcing current uselessly from input pins. sGpios->WriteBuffered(drivers::IGpios::Pin::kSdCardDetect, false); sGpios->WriteBuffered(drivers::IGpios::Pin::kKeyUp, false); diff --git a/src/system_fsm/include/system_events.hpp b/src/system_fsm/include/system_events.hpp index f62da801..8a3ba5ec 100644 --- a/src/system_fsm/include/system_events.hpp +++ b/src/system_fsm/include/system_events.hpp @@ -53,6 +53,7 @@ struct HasPhonesChanged : tinyfsm::Event { }; struct ChargingStatusChanged : tinyfsm::Event {}; +struct BatteryPercentChanged : tinyfsm::Event {}; namespace internal { @@ -61,6 +62,8 @@ struct SamdInterrupt : tinyfsm::Event {}; struct IdleTimeout : tinyfsm::Event {}; +struct BatteryTimerFired : tinyfsm::Event {}; + } // namespace internal } // namespace system_fsm diff --git a/src/system_fsm/include/system_fsm.hpp b/src/system_fsm/include/system_fsm.hpp index 0698be9b..6baceca1 100644 --- a/src/system_fsm/include/system_fsm.hpp +++ b/src/system_fsm/include/system_fsm.hpp @@ -47,6 +47,7 @@ class SystemState : public tinyfsm::Fsm { void react(const FatalError&); void react(const internal::GpioInterrupt&); void react(const internal::SamdInterrupt&); + void react(const internal::BatteryTimerFired&); virtual void react(const DisplayReady&) {} virtual void react(const BootComplete&) {} diff --git a/src/system_fsm/system_fsm.cpp b/src/system_fsm/system_fsm.cpp index 24f65a4f..78c4c53e 100644 --- a/src/system_fsm/system_fsm.cpp +++ b/src/system_fsm/system_fsm.cpp @@ -95,6 +95,16 @@ void SystemState::react(const internal::SamdInterrupt&) { } } +void SystemState::react(const internal::BatteryTimerFired&) { + ESP_LOGI(kTag, "checking battery"); + if (sBattery->UpdatePercent()) { + ESP_LOGI(kTag, "battery now at %u%%", sBattery->Percent()); + BatteryPercentChanged ev{}; + events::Ui().Dispatch(ev); + events::System().Dispatch(ev); + } +} + } // namespace system_fsm FSM_INITIAL_STATE(system_fsm::SystemState, system_fsm::states::Booting)