diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index ffd0d5c3..1f4f1f44 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -11,6 +11,7 @@ #include "audio_decoder.hpp" #include "audio_events.hpp" #include "audio_task.hpp" +#include "event_queue.hpp" #include "fatfs_audio_input.hpp" #include "i2s_audio_output.hpp" #include "i2s_dac.hpp" @@ -65,6 +66,18 @@ void AudioState::react(const system_fsm::StorageMounted& ev) { sDatabase = ev.db; } +void AudioState::react(const system_fsm::KeyUpChanged& ev) { + if (ev.falling && sI2SOutput->AdjustVolumeUp()) { + events::Dispatch({}); + } +} + +void AudioState::react(const system_fsm::KeyDownChanged& ev) { + if (ev.falling && sI2SOutput->AdjustVolumeDown()) { + events::Dispatch({}); + } +} + namespace states { void Uninitialised::react(const system_fsm::BootComplete&) { diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp index b61259ad..77de7b43 100644 --- a/src/audio/i2s_audio_output.cpp +++ b/src/audio/i2s_audio_output.cpp @@ -100,20 +100,22 @@ auto I2SAudioOutput::GetAdjustedMaxAttenuation() -> int_fast8_t { return adjusted_max; } -auto I2SAudioOutput::AdjustVolumeUp() -> void { +auto I2SAudioOutput::AdjustVolumeUp() -> bool { if (attenuation_ + left_difference_ <= pots_->GetMinAttenuation()) { - return; + return false; } attenuation_--; pots_->SetRelative(-1); + return true; } -auto I2SAudioOutput::AdjustVolumeDown() -> void { +auto I2SAudioOutput::AdjustVolumeDown() -> bool { if (attenuation_ - left_difference_ >= pots_->GetMaxAttenuation()) { - return; + return false; } attenuation_++; pots_->SetRelative(1); + return true; } auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool { diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index 7359e8ac..019b65a2 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include "tinyfsm.hpp" @@ -32,4 +33,6 @@ struct InputFileOpened : tinyfsm::Event {}; struct InputFileFinished : tinyfsm::Event {}; struct AudioPipelineIdle : tinyfsm::Event {}; +struct VolumeChanged : tinyfsm::Event {}; + } // namespace audio diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index 1f3b1dbd..7e84785f 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -40,6 +40,9 @@ class AudioState : public tinyfsm::Fsm { void react(const system_fsm::StorageMounted&); + void react(const system_fsm::KeyUpChanged&); + void react(const system_fsm::KeyDownChanged&); + virtual void react(const system_fsm::BootComplete&) {} virtual void react(const PlayTrack&) {} virtual void react(const PlayFile&) {} diff --git a/src/audio/include/audio_sink.hpp b/src/audio/include/audio_sink.hpp index e6538bda..ac007bf8 100644 --- a/src/audio/include/audio_sink.hpp +++ b/src/audio/include/audio_sink.hpp @@ -45,8 +45,8 @@ class IAudioSink { virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0; virtual auto SetVolume(uint_fast8_t percent) -> void = 0; virtual auto GetVolume() -> uint_fast8_t = 0; - virtual auto AdjustVolumeUp() -> void = 0; - virtual auto AdjustVolumeDown() -> void = 0; + virtual auto AdjustVolumeUp() -> bool = 0; + virtual auto AdjustVolumeDown() -> bool = 0; virtual auto Configure(const StreamInfo::Format& format) -> bool = 0; virtual auto Send(const cpp::span& data) -> void = 0; diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp index 7c125476..5dd6cc27 100644 --- a/src/audio/include/i2s_audio_output.hpp +++ b/src/audio/include/i2s_audio_output.hpp @@ -34,8 +34,8 @@ class I2SAudioOutput : public IAudioSink { auto SetVolumeImbalance(int_fast8_t balance) -> void override; auto SetVolume(uint_fast8_t percent) -> void override; auto GetVolume() -> uint_fast8_t override; - auto AdjustVolumeUp() -> void override; - auto AdjustVolumeDown() -> void override; + auto AdjustVolumeUp() -> bool override; + auto AdjustVolumeDown() -> bool override; auto Configure(const StreamInfo::Format& format) -> bool override; auto Send(const cpp::span& data) -> void override; diff --git a/src/drivers/gpio_expander.cpp b/src/drivers/gpio_expander.cpp index 87c5d038..5a9bb83e 100644 --- a/src/drivers/gpio_expander.cpp +++ b/src/drivers/gpio_expander.cpp @@ -5,35 +5,97 @@ */ #include "gpio_expander.hpp" +#include #include +#include "driver/gpio.h" +#include "hal/gpio_types.h" #include "i2c.hpp" namespace drivers { -GpioExpander::GpioExpander() { - ports_ = pack(kPortADefault, kPortBDefault); - // Read and write initial values on initialisation so that we do not have a - // strange partially-initialised state. - // TODO: log or abort if these error; it's really bad! - Write(); - Read(); +static const uint8_t kPca8575Address = 0x20; + +// Port A: +// 0 - sd card mux switch +// 1 - sd card mux enable (active low) +// 2 - key up +// 3 - key down +// 4 - key lock +// 5 - display reset (active low) +// 6 - NC +// 7 - sd card power (active low) +// Default to SD card off, inputs high. +static const uint8_t kPortADefault = 0b10111110; + +// Port B: +// 0 - 3.5mm jack detect (active low) +// 1 - headphone amp power enable +// 2 - volume zero-cross detection +// 3 - volume direction +// 4 - volume left channel +// 5 - volume right channel +// 6 - NC +// 7 - NC +// Default input high, trs output low +static const uint8_t kPortBDefault = 0b00000011; + +/* + * Convenience mehod for packing the port a and b bytes into a single 16 bit + * value. + */ +constexpr uint16_t pack(uint8_t a, uint8_t b) { + return ((uint16_t)b) << 8 | a; } -GpioExpander::~GpioExpander() {} +/* + * Convenience mehod for unpacking the result of `pack` back into two single + * byte port datas. + */ +constexpr std::pair unpack(uint16_t ba) { + return std::pair((uint8_t)ba, (uint8_t)(ba >> 8)); +} -void GpioExpander::with(std::function f) { - f(*this); - Write(); +void interrupt_isr(void* arg) { + GpioExpander* instance = reinterpret_cast(arg); + auto listener = instance->listener().lock(); + if (listener) { + std::invoke(*listener); + } } -esp_err_t GpioExpander::Write() { - i2c_cmd_handle_t handle = i2c_cmd_link_create(); - if (handle == NULL) { - return ESP_ERR_NO_MEM; +auto GpioExpander::Create() -> GpioExpander* { + GpioExpander* instance = new GpioExpander(); + // Read and write initial values on initialisation so that we do not have a + // strange partially-initialised state. + if (!instance->Write() || !instance->Read()) { + return nullptr; } + return instance; +} + +GpioExpander::GpioExpander() + : ports_(pack(kPortADefault, kPortBDefault)), inputs_(0), listener_() { + gpio_config_t config{ + .pin_bit_mask = static_cast(1) << GPIO_NUM_34, + .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_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED | + ESP_INTR_FLAG_IRAM); + gpio_isr_handler_add(GPIO_NUM_34, &interrupt_isr, this); +} + +GpioExpander::~GpioExpander() { + gpio_isr_handler_remove(GPIO_NUM_34); + gpio_uninstall_isr_service(); +} +bool GpioExpander::Write() { std::pair ports_ab = unpack(ports()); I2CTransaction transaction; @@ -42,10 +104,10 @@ esp_err_t GpioExpander::Write() { .write_ack(ports_ab.first, ports_ab.second) .stop(); - return transaction.Execute(); + return transaction.Execute() == ESP_OK; } -esp_err_t GpioExpander::Read() { +bool GpioExpander::Read() { uint8_t input_a, input_b; I2CTransaction transaction; @@ -56,8 +118,11 @@ esp_err_t GpioExpander::Read() { .stop(); esp_err_t ret = transaction.Execute(); + if (ret != ESP_OK) { + return false; + } inputs_ = pack(input_a, input_b); - return ret; + return true; } void GpioExpander::set_pin(Pin pin, bool value) { diff --git a/src/drivers/include/gpio_expander.hpp b/src/drivers/include/gpio_expander.hpp index 8231e140..8108d176 100644 --- a/src/drivers/include/gpio_expander.hpp +++ b/src/drivers/include/gpio_expander.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -35,52 +36,9 @@ namespace drivers { */ class GpioExpander { public: - static auto Create() -> GpioExpander* { return new GpioExpander(); } - - GpioExpander(); + static auto Create() -> GpioExpander*; ~GpioExpander(); - static const uint8_t kPca8575Address = 0x20; - static const uint8_t kPca8575Timeout = pdMS_TO_TICKS(100); - - // Port A: - // 0 - sd card mux switch - // 1 - sd card mux enable (active low) - // 2 - key up - // 3 - key down - // 4 - key lock - // 5 - display reset - // 6 - NC - // 7 - sd card power (active low) - // Default to SD card off, inputs high. - static const uint8_t kPortADefault = 0b10111110; - - // Port B: - // 0 - 3.5mm jack detect (active low) - // 1 - trs output enable - // 2 - volume zero-cross detection - // 3 - volume direction - // 4 - volume left channel - // 5 - volume right channel - // 6 - NC - // 7 - NC - // Default input high, trs output low - static const uint8_t kPortBDefault = 0b00000010; - - /* - * Convenience mehod for packing the port a and b bytes into a single 16 bit - * value. - */ - static uint16_t pack(uint8_t a, uint8_t b) { return ((uint16_t)b) << 8 | a; } - - /* - * Convenience mehod for unpacking the result of `pack` back into two single - * byte port datas. - */ - static std::pair unpack(uint16_t ba) { - return std::pair((uint8_t)ba, (uint8_t)(ba >> 8)); - } - /* * Convenience function for running some arbitrary pin writing code, then * flushing a `Write()` to the expander. Example usage: @@ -91,19 +49,23 @@ class GpioExpander { * }); * ``` */ - void with(std::function f); + template + auto with(F fn) -> void { + std::invoke(fn); + Write(); + } /** * Sets the ports on the GPIO expander to the values currently represented * in `ports`. */ - esp_err_t Write(void); + auto Write(void) -> bool; /** * Reads from the GPIO expander, populating `inputs` with the most recent * values. */ - esp_err_t Read(void); + auto Read(void) -> bool; /* Maps each pin of the expander to its number in a `pack`ed uint16. */ enum Pin { @@ -113,9 +75,9 @@ class GpioExpander { KEY_UP = 2, KEY_DOWN = 3, KEY_LOCK = 4, - DISPLAY_RESET = 5, + DISPLAY_RESET_ACTIVE_LOW = 5, // UNUSED = 6, - SD_CARD_POWER_ENABLE = 7, + SD_CARD_POWER_ENABLE_ACTIVE_LOW = 7, // Port B PHONE_DETECT = 8, @@ -161,14 +123,26 @@ class GpioExpander { */ bool get_input(Pin pin) const; + auto listener() -> std::weak_ptr>& { + return listener_; + } + + auto set_listener(const std::weak_ptr>& l) -> void { + listener_ = l; + } + // Not copyable or movable. There should usually only ever be once instance // of this class, and that instance will likely have a static lifetime. GpioExpander(const GpioExpander&) = delete; GpioExpander& operator=(const GpioExpander&) = delete; private: + GpioExpander(); + std::atomic ports_; std::atomic inputs_; + + std::weak_ptr> listener_; }; } // namespace drivers diff --git a/src/drivers/storage.cpp b/src/drivers/storage.cpp index 211b1484..4f16f1d1 100644 --- a/src/drivers/storage.cpp +++ b/src/drivers/storage.cpp @@ -56,7 +56,7 @@ static esp_err_t do_transaction(sdspi_dev_handle_t handle, } // namespace callback auto SdStorage::Create(GpioExpander* gpio) -> cpp::result { - gpio->set_pin(GpioExpander::SD_CARD_POWER_ENABLE, 0); + gpio->set_pin(GpioExpander::SD_CARD_POWER_ENABLE_ACTIVE_LOW, 0); gpio->set_pin(GpioExpander::SD_MUX_EN_ACTIVE_LOW, 0); gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP); gpio->Write(); @@ -144,7 +144,7 @@ SdStorage::~SdStorage() { sdspi_host_remove_device(this->handle_); sdspi_host_deinit(); - gpio_->set_pin(GpioExpander::SD_CARD_POWER_ENABLE, 0); + gpio_->set_pin(GpioExpander::SD_CARD_POWER_ENABLE_ACTIVE_LOW, 1); gpio_->set_pin(GpioExpander::SD_MUX_EN_ACTIVE_LOW, 1); gpio_->Write(); } diff --git a/src/events/include/event_queue.hpp b/src/events/include/event_queue.hpp index eb8dd0a0..01f37896 100644 --- a/src/events/include/event_queue.hpp +++ b/src/events/include/event_queue.hpp @@ -13,6 +13,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/queue.h" +#include "system_fsm.hpp" #include "tinyfsm.hpp" #include "ui_fsm.hpp" @@ -33,6 +34,16 @@ class EventQueue { return instance; } + template + auto DispatchFromISR(const Event& ev) -> bool { + WorkItem* item = new WorkItem([=]() { + tinyfsm::FsmList::template dispatch(ev); + }); + BaseType_t ret; + xQueueSendFromISR(system_handle_, &item, &ret); + return ret; + } + template auto Dispatch(const Event& ev) -> void { WorkItem* item = new WorkItem( diff --git a/src/system_fsm/booting.cpp b/src/system_fsm/booting.cpp index 1e1b2959..e7505f11 100644 --- a/src/system_fsm/booting.cpp +++ b/src/system_fsm/booting.cpp @@ -27,6 +27,10 @@ namespace states { static const char kTag[] = "BOOT"; +static std::function sGpiosCallback = []() { + events::EventQueue::GetInstance().DispatchFromISR(internal::GpioInterrupt{}); +}; + auto Booting::entry() -> void { ESP_LOGI(kTag, "beginning tangara boot"); ESP_LOGI(kTag, "installing early drivers"); diff --git a/src/system_fsm/include/system_events.hpp b/src/system_fsm/include/system_events.hpp index ec202c69..0903cb77 100644 --- a/src/system_fsm/include/system_events.hpp +++ b/src/system_fsm/include/system_events.hpp @@ -47,6 +47,19 @@ struct StorageMounted : tinyfsm::Event { struct StorageError : tinyfsm::Event {}; +struct KeyUpChanged : tinyfsm::Event { + bool falling; +}; +struct KeyDownChanged : tinyfsm::Event { + bool falling; +}; +struct KeyLockChanged : tinyfsm::Event { + bool falling; +}; +struct HasPhonesChanged : tinyfsm::Event { + bool falling; +}; + namespace internal { /* @@ -55,6 +68,12 @@ 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 {}; + } // namespace internal } // namespace system_fsm diff --git a/src/system_fsm/include/system_fsm.hpp b/src/system_fsm/include/system_fsm.hpp index 037c0a0e..725f2f50 100644 --- a/src/system_fsm/include/system_fsm.hpp +++ b/src/system_fsm/include/system_fsm.hpp @@ -38,6 +38,7 @@ class SystemState : public tinyfsm::Fsm { void react(const tinyfsm::Event& ev) {} void react(const FatalError&); + void react(const internal::GpioInterrupt&); 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 c59c0908..dd5161ed 100644 --- a/src/system_fsm/system_fsm.cpp +++ b/src/system_fsm/system_fsm.cpp @@ -5,6 +5,8 @@ */ #include "system_fsm.hpp" +#include "audio_fsm.hpp" +#include "event_queue.hpp" #include "relative_wheel.hpp" #include "system_events.hpp" @@ -28,6 +30,42 @@ void SystemState::react(const FatalError& err) { } } +void SystemState::react(const internal::GpioInterrupt& ev) { + ESP_LOGI("sys", "gpios changed"); + bool prev_key_up = sGpioExpander->get_input(drivers::GpioExpander::KEY_UP); + bool prev_key_down = + sGpioExpander->get_input(drivers::GpioExpander::KEY_DOWN); + bool prev_key_lock = + sGpioExpander->get_input(drivers::GpioExpander::KEY_LOCK); + bool prev_has_headphones = + sGpioExpander->get_input(drivers::GpioExpander::PHONE_DETECT); + + sGpioExpander->Read(); + + bool key_up = sGpioExpander->get_input(drivers::GpioExpander::KEY_UP); + bool key_down = sGpioExpander->get_input(drivers::GpioExpander::KEY_DOWN); + bool key_lock = sGpioExpander->get_input(drivers::GpioExpander::KEY_LOCK); + bool has_headphones = + sGpioExpander->get_input(drivers::GpioExpander::PHONE_DETECT); + + if (key_up != prev_key_up) { + events::Dispatch( + {.falling = prev_key_up}); + } + if (key_down != prev_key_down) { + events::Dispatch( + {.falling = prev_key_down}); + } + if (key_lock != prev_key_lock) { + events::Dispatch( + {.falling = prev_key_lock}); + } + if (has_headphones != prev_has_headphones) { + events::Dispatch( + {.falling = prev_has_headphones}); + } +} + } // namespace system_fsm FSM_INITIAL_STATE(system_fsm::SystemState, system_fsm::states::Booting) diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index 8b80f162..d0bb7b2f 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -35,6 +35,8 @@ class UiState : public tinyfsm::Fsm { /* Fallback event handler. Does nothing. */ void react(const tinyfsm::Event& ev) {} + virtual void react(const system_fsm::KeyLockChanged&){}; + virtual void react(const system_fsm::DisplayReady&) {} virtual void react(const system_fsm::BootComplete&) {} @@ -58,6 +60,8 @@ class Splash : public UiState { class Interactive : public UiState { void entry() override; + + void react(const system_fsm::KeyLockChanged&) override; }; class FatalError : public UiState {}; diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index bdc1f89f..c2ac6d8b 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -68,6 +68,10 @@ void Interactive::entry() { sCurrentScreen.reset(new screens::Menu()); } +void Interactive::react(const system_fsm::KeyLockChanged& ev) { + sDisplay->SetDisplayOn(ev.falling); +} + } // namespace states } // namespace ui