diff --git a/src/app_console/app_console.cpp b/src/app_console/app_console.cpp index e9bd7cdc..f6b23215 100644 --- a/src/app_console/app_console.cpp +++ b/src/app_console/app_console.cpp @@ -496,7 +496,7 @@ int CmdSamd(int argc, char** argv) { AppConsole::sSamd->ResetToFlashSamd(); } else if (cmd == "charge") { - auto res = AppConsole::sSamd->ReadChargeStatus(); + auto res = AppConsole::sSamd->GetChargeStatus(); if (res) { switch (res.value()) { case drivers::Samd::ChargeStatus::kNoBattery: diff --git a/src/drivers/gpios.cpp b/src/drivers/gpios.cpp index f6293697..412c091f 100644 --- a/src/drivers/gpios.cpp +++ b/src/drivers/gpios.cpp @@ -58,7 +58,9 @@ constexpr std::pair unpack(uint16_t ba) { return std::pair((uint8_t)ba, (uint8_t)(ba >> 8)); } -void interrupt_isr(void* arg) { +SemaphoreHandle_t Gpios::sReadPending; + +static void interrupt_isr(void* arg) { SemaphoreHandle_t sem = reinterpret_cast(arg); xSemaphoreGive(sem); } @@ -73,10 +75,7 @@ auto Gpios::Create() -> Gpios* { return instance; } -Gpios::Gpios() - : ports_(pack(kPortADefault, kPortBDefault)), - inputs_(0), - read_pending_(xSemaphoreCreateBinary()) { +Gpios::Gpios() : ports_(pack(kPortADefault, kPortBDefault)), inputs_(0) { gpio_config_t config{ .pin_bit_mask = static_cast(1) << GPIO_NUM_34, .mode = GPIO_MODE_INPUT, @@ -87,6 +86,7 @@ Gpios::Gpios() gpio_config(&config); gpio_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM); + gpio_isr_handler_add(GPIO_NUM_34, &interrupt_isr, sReadPending); } Gpios::~Gpios() { @@ -141,8 +141,9 @@ auto Gpios::Read() -> bool { return true; } -auto Gpios::InstallReadPendingISR() -> void { - gpio_isr_handler_add(GPIO_NUM_34, &interrupt_isr, read_pending_); +auto Gpios::CreateReadPending() -> SemaphoreHandle_t { + sReadPending = xSemaphoreCreateBinary(); + return sReadPending; } } // namespace drivers diff --git a/src/drivers/include/gpios.hpp b/src/drivers/include/gpios.hpp index 5ac475bf..fe330e3b 100644 --- a/src/drivers/include/gpios.hpp +++ b/src/drivers/include/gpios.hpp @@ -107,8 +107,7 @@ class Gpios : public IGpios { */ auto Read(void) -> bool; - auto InstallReadPendingISR() -> void; - auto IsReadPending() -> SemaphoreHandle_t { return read_pending_; } + static auto CreateReadPending() -> SemaphoreHandle_t; // Not copyable or movable. There should usually only ever be once instance // of this class, and that instance will likely have a static lifetime. @@ -121,7 +120,7 @@ class Gpios : public IGpios { std::atomic ports_; std::atomic inputs_; - SemaphoreHandle_t read_pending_; + static SemaphoreHandle_t sReadPending; }; } // namespace drivers diff --git a/src/drivers/include/samd.hpp b/src/drivers/include/samd.hpp index 1560b590..4a31a577 100644 --- a/src/drivers/include/samd.hpp +++ b/src/drivers/include/samd.hpp @@ -8,6 +8,9 @@ #include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + namespace drivers { class Samd { @@ -32,7 +35,8 @@ class Samd { kFullCharge, }; - auto ReadChargeStatus() -> std::optional; + auto GetChargeStatus() -> std::optional; + auto UpdateChargeStatus() -> void; enum class UsbStatus { // There is no compatible usb host attached. @@ -44,9 +48,23 @@ class Samd { kAttachedMounted, }; - auto ReadUsbStatus() -> UsbStatus; + auto GetUsbStatus() -> UsbStatus; + auto UpdateUsbStatus() -> void; auto ResetToFlashSamd() -> void; + + static auto CreateReadPending() -> SemaphoreHandle_t; + + // Not copyable or movable. There should usually only ever be once instance + // of this class, and that instance will likely have a static lifetime. + Samd(const Samd&) = delete; + Samd& operator=(const Samd&) = delete; + + private: + std::optional charge_status_; + UsbStatus usb_status_; + + static SemaphoreHandle_t sReadPending; }; } // namespace drivers diff --git a/src/drivers/samd.cpp b/src/drivers/samd.cpp index e7bfbb6b..455e9ce5 100644 --- a/src/drivers/samd.cpp +++ b/src/drivers/samd.cpp @@ -26,7 +26,24 @@ static const char kTag[] = "SAMD"; namespace drivers { +SemaphoreHandle_t Samd::sReadPending; + +static void interrupt_isr(void* arg) { + SemaphoreHandle_t sem = reinterpret_cast(arg); + xSemaphoreGive(sem); +} + Samd::Samd() { + gpio_config_t config{ + .pin_bit_mask = static_cast(1) << GPIO_NUM_35, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_NEGEDGE, + }; + gpio_config(&config); + gpio_isr_handler_add(GPIO_NUM_35, &interrupt_isr, sReadPending); + // Being able to interface with the SAMD properly is critical. To ensure we // will be able to, we begin by checking the I2C protocol version is // compatible, and throw if it's not. @@ -41,10 +58,17 @@ Samd::Samd() { .stop(); ESP_ERROR_CHECK(transaction.Execute()); ESP_LOGI(kTag, "samd firmware rev: %u", raw_res); + + UpdateChargeStatus(); + UpdateUsbStatus(); } Samd::~Samd() {} -auto Samd::ReadChargeStatus() -> std::optional { +auto Samd::GetChargeStatus() -> std::optional { + return charge_status_; +} + +auto Samd::UpdateChargeStatus() -> void { uint8_t raw_res; I2CTransaction transaction; transaction.start() @@ -54,31 +78,39 @@ auto Samd::ReadChargeStatus() -> std::optional { .write_addr(kAddress, I2C_MASTER_READ) .read(&raw_res, I2C_MASTER_NACK) .stop(); - ESP_LOGI(kTag, "checking charge status"); ESP_ERROR_CHECK(transaction.Execute()); - ESP_LOGI(kTag, "raw charge status: 0x%x", raw_res); uint8_t usb_state = raw_res & 0b11; uint8_t charge_state = (raw_res >> 2) & 0b111; switch (charge_state) { case 0b000: case 0b011: - return ChargeStatus::kNoBattery; + charge_status_ = ChargeStatus::kNoBattery; + break; case 0b001: - return usb_state == 1 ? ChargeStatus::kChargingRegular - : ChargeStatus::kChargingFast; + charge_status_ = usb_state == 1 ? ChargeStatus::kChargingRegular + : ChargeStatus::kChargingFast; + break; case 0b010: - return ChargeStatus::kFullCharge; + charge_status_ = ChargeStatus::kFullCharge; + break; case 0b100: - return ChargeStatus::kBatteryCritical; + charge_status_ = ChargeStatus::kBatteryCritical; + break; case 0b101: - return ChargeStatus::kDischarging; + charge_status_ = ChargeStatus::kDischarging; + break; default: - return {}; + charge_status_ = {}; + break; } } -auto Samd::ReadUsbStatus() -> UsbStatus { +auto Samd::GetUsbStatus() -> UsbStatus { + return usb_status_; +} + +auto Samd::UpdateUsbStatus() -> void { uint8_t raw_res; I2CTransaction transaction; transaction.start() @@ -88,15 +120,13 @@ auto Samd::ReadUsbStatus() -> UsbStatus { .write_addr(kAddress, I2C_MASTER_READ) .read(&raw_res, I2C_MASTER_NACK) .stop(); - ESP_LOGI(kTag, "checking usb status"); ESP_ERROR_CHECK(transaction.Execute()); - ESP_LOGI(kTag, "raw usb status: 0x%x", raw_res); if (!(raw_res & 0b1)) { - return UsbStatus::kDetached; + usb_status_ = UsbStatus::kDetached; } - return (raw_res & 0b10) ? UsbStatus::kAttachedMounted - : UsbStatus::kAttachedIdle; + usb_status_ = + (raw_res & 0b10) ? UsbStatus::kAttachedMounted : UsbStatus::kAttachedIdle; } auto Samd::ResetToFlashSamd() -> void { @@ -108,4 +138,9 @@ auto Samd::ResetToFlashSamd() -> void { ESP_ERROR_CHECK(transaction.Execute()); } +auto Samd::CreateReadPending() -> SemaphoreHandle_t { + sReadPending = xSemaphoreCreateBinary(); + return sReadPending; +} + } // namespace drivers diff --git a/src/main/main.cpp b/src/main/main.cpp index 685e1fc1..f6aba812 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -18,7 +18,8 @@ extern "C" void app_main(void) { ESP_ERROR_CHECK(drivers::init_i2c()); - drivers::Gpios* gpios = system_fsm::SystemState::early_init_gpios(); + SemaphoreHandle_t gpios_semphr = drivers::Gpios::CreateReadPending(); + SemaphoreHandle_t samd_semphr = drivers::Samd::CreateReadPending(); // Semaphores must be empty before being added to a queue set. Hence all this // weird early init stuff; by being explicit about initialisation order, we're @@ -27,7 +28,8 @@ extern "C" void app_main(void) { QueueSetHandle_t set = xQueueCreateSet(2); auto* event_queue = events::queues::SystemAndAudio(); xQueueAddToSet(event_queue->has_events(), set); - xQueueAddToSet(gpios->IsReadPending(), set); + xQueueAddToSet(gpios_semphr, set); + xQueueAddToSet(samd_semphr, set); tinyfsm::FsmList::start(); @@ -36,9 +38,12 @@ extern "C" void app_main(void) { QueueSetMemberHandle_t member = xQueueSelectFromSet(set, portMAX_DELAY); if (member == event_queue->has_events()) { event_queue->Service(0); - } else if (member == gpios->IsReadPending()) { + } else if (member == gpios_semphr) { xSemaphoreTake(member, 0); events::System().Dispatch(system_fsm::internal::GpioInterrupt{}); + } else if (member == samd_semphr) { + xSemaphoreTake(member, 0); + events::System().Dispatch(system_fsm::internal::SamdInterrupt{}); } } } diff --git a/src/system_fsm/booting.cpp b/src/system_fsm/booting.cpp index 2a4d5f5c..6720fb4a 100644 --- a/src/system_fsm/booting.cpp +++ b/src/system_fsm/booting.cpp @@ -39,7 +39,7 @@ auto Booting::entry() -> void { // I2C and SPI are both always needed. We can't even power down or show an // error without these. ESP_ERROR_CHECK(drivers::init_spi()); - sGpios->InstallReadPendingISR(); + sGpios.reset(drivers::Gpios::Create()); // Start bringing up LVGL now, since we have all of its prerequisites. sTrackQueue.reset(new audio::TrackQueue()); @@ -97,7 +97,7 @@ auto Booting::react(const BootComplete& ev) -> void { // It's possible that the SAMD is currently exposing the SD card as a USB // device. Make sure we don't immediately try to claim it. if (sSamd && - sSamd->ReadUsbStatus() == drivers::Samd::UsbStatus::kAttachedMounted) { + sSamd->GetUsbStatus() == drivers::Samd::UsbStatus::kAttachedMounted) { transit(); } transit(); diff --git a/src/system_fsm/include/system_events.hpp b/src/system_fsm/include/system_events.hpp index 0903cb77..87461f0b 100644 --- a/src/system_fsm/include/system_events.hpp +++ b/src/system_fsm/include/system_events.hpp @@ -60,6 +60,8 @@ struct HasPhonesChanged : tinyfsm::Event { bool falling; }; +struct ChargingStatusChanged : tinyfsm::Event {}; + namespace internal { /* @@ -68,11 +70,8 @@ namespace internal { */ struct ReadyToUnmount : tinyfsm::Event {}; -/* - * Sent when the actual unmount operation should be performed. Always dispatched - * by SysState in response to StoragePrepareToUnmount. - */ struct GpioInterrupt : tinyfsm::Event {}; +struct SamdInterrupt : tinyfsm::Event {}; } // namespace internal diff --git a/src/system_fsm/include/system_fsm.hpp b/src/system_fsm/include/system_fsm.hpp index 3d513666..b31cc6b7 100644 --- a/src/system_fsm/include/system_fsm.hpp +++ b/src/system_fsm/include/system_fsm.hpp @@ -35,8 +35,6 @@ class SystemState : public tinyfsm::Fsm { public: virtual ~SystemState() {} - static auto early_init_gpios() -> drivers::Gpios*; - virtual void entry() {} virtual void exit() {} @@ -45,6 +43,7 @@ class SystemState : public tinyfsm::Fsm { void react(const FatalError&); void react(const internal::GpioInterrupt&); + void react(const internal::SamdInterrupt&); 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 96e806f4..24f65a4f 100644 --- a/src/system_fsm/system_fsm.cpp +++ b/src/system_fsm/system_fsm.cpp @@ -13,6 +13,8 @@ #include "tag_parser.hpp" #include "track_queue.hpp" +static const char kTag[] = "system"; + namespace system_fsm { std::shared_ptr SystemState::sGpios; @@ -33,11 +35,6 @@ std::shared_ptr SystemState::sTrackQueue; console::AppConsole* SystemState::sAppConsole; -auto SystemState::early_init_gpios() -> drivers::Gpios* { - sGpios.reset(drivers::Gpios::Create()); - return sGpios.get(); -} - void SystemState::react(const FatalError& err) { if (!is_in_state()) { transit(); @@ -78,6 +75,26 @@ void SystemState::react(const internal::GpioInterrupt&) { } } +void SystemState::react(const internal::SamdInterrupt&) { + auto prev_charge_status = sSamd->GetChargeStatus(); + auto prev_usb_status = sSamd->GetUsbStatus(); + + sSamd->UpdateChargeStatus(); + sSamd->UpdateUsbStatus(); + + auto charge_status = sSamd->GetChargeStatus(); + auto usb_status = sSamd->GetUsbStatus(); + + if (charge_status != prev_charge_status) { + ChargingStatusChanged ev{}; + events::System().Dispatch(ev); + events::Ui().Dispatch(ev); + } + if (usb_status != prev_usb_status) { + ESP_LOGI(kTag, "usb status changed"); + } +} + } // namespace system_fsm FSM_INITIAL_STATE(system_fsm::SystemState, system_fsm::states::Booting)