Start on converting gpio expander interupts to fsm events

custom
jacqueline 2 years ago
parent 9763cc955c
commit 0347555d5b
  1. 13
      src/audio/audio_fsm.cpp
  2. 10
      src/audio/i2s_audio_output.cpp
  3. 3
      src/audio/include/audio_events.hpp
  4. 3
      src/audio/include/audio_fsm.hpp
  5. 4
      src/audio/include/audio_sink.hpp
  6. 4
      src/audio/include/i2s_audio_output.hpp
  7. 101
      src/drivers/gpio_expander.cpp
  8. 72
      src/drivers/include/gpio_expander.hpp
  9. 4
      src/drivers/storage.cpp
  10. 11
      src/events/include/event_queue.hpp
  11. 4
      src/system_fsm/booting.cpp
  12. 19
      src/system_fsm/include/system_events.hpp
  13. 1
      src/system_fsm/include/system_fsm.hpp
  14. 38
      src/system_fsm/system_fsm.cpp
  15. 4
      src/ui/include/ui_fsm.hpp
  16. 4
      src/ui/ui_fsm.cpp

@ -11,6 +11,7 @@
#include "audio_decoder.hpp" #include "audio_decoder.hpp"
#include "audio_events.hpp" #include "audio_events.hpp"
#include "audio_task.hpp" #include "audio_task.hpp"
#include "event_queue.hpp"
#include "fatfs_audio_input.hpp" #include "fatfs_audio_input.hpp"
#include "i2s_audio_output.hpp" #include "i2s_audio_output.hpp"
#include "i2s_dac.hpp" #include "i2s_dac.hpp"
@ -65,6 +66,18 @@ void AudioState::react(const system_fsm::StorageMounted& ev) {
sDatabase = ev.db; sDatabase = ev.db;
} }
void AudioState::react(const system_fsm::KeyUpChanged& ev) {
if (ev.falling && sI2SOutput->AdjustVolumeUp()) {
events::Dispatch<VolumeChanged, ui::UiState>({});
}
}
void AudioState::react(const system_fsm::KeyDownChanged& ev) {
if (ev.falling && sI2SOutput->AdjustVolumeDown()) {
events::Dispatch<VolumeChanged, ui::UiState>({});
}
}
namespace states { namespace states {
void Uninitialised::react(const system_fsm::BootComplete&) { void Uninitialised::react(const system_fsm::BootComplete&) {

@ -100,20 +100,22 @@ auto I2SAudioOutput::GetAdjustedMaxAttenuation() -> int_fast8_t {
return adjusted_max; return adjusted_max;
} }
auto I2SAudioOutput::AdjustVolumeUp() -> void { auto I2SAudioOutput::AdjustVolumeUp() -> bool {
if (attenuation_ + left_difference_ <= pots_->GetMinAttenuation()) { if (attenuation_ + left_difference_ <= pots_->GetMinAttenuation()) {
return; return false;
} }
attenuation_--; attenuation_--;
pots_->SetRelative(-1); pots_->SetRelative(-1);
return true;
} }
auto I2SAudioOutput::AdjustVolumeDown() -> void { auto I2SAudioOutput::AdjustVolumeDown() -> bool {
if (attenuation_ - left_difference_ >= pots_->GetMaxAttenuation()) { if (attenuation_ - left_difference_ >= pots_->GetMaxAttenuation()) {
return; return false;
} }
attenuation_++; attenuation_++;
pots_->SetRelative(1); pots_->SetRelative(1);
return true;
} }
auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool { auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool {

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <cstdint>
#include <string> #include <string>
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
@ -32,4 +33,6 @@ struct InputFileOpened : tinyfsm::Event {};
struct InputFileFinished : tinyfsm::Event {}; struct InputFileFinished : tinyfsm::Event {};
struct AudioPipelineIdle : tinyfsm::Event {}; struct AudioPipelineIdle : tinyfsm::Event {};
struct VolumeChanged : tinyfsm::Event {};
} // namespace audio } // namespace audio

@ -40,6 +40,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
void react(const system_fsm::StorageMounted&); 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 system_fsm::BootComplete&) {}
virtual void react(const PlayTrack&) {} virtual void react(const PlayTrack&) {}
virtual void react(const PlayFile&) {} virtual void react(const PlayFile&) {}

@ -45,8 +45,8 @@ class IAudioSink {
virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0; virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0;
virtual auto SetVolume(uint_fast8_t percent) -> void = 0; virtual auto SetVolume(uint_fast8_t percent) -> void = 0;
virtual auto GetVolume() -> uint_fast8_t = 0; virtual auto GetVolume() -> uint_fast8_t = 0;
virtual auto AdjustVolumeUp() -> void = 0; virtual auto AdjustVolumeUp() -> bool = 0;
virtual auto AdjustVolumeDown() -> void = 0; virtual auto AdjustVolumeDown() -> bool = 0;
virtual auto Configure(const StreamInfo::Format& format) -> bool = 0; virtual auto Configure(const StreamInfo::Format& format) -> bool = 0;
virtual auto Send(const cpp::span<std::byte>& data) -> void = 0; virtual auto Send(const cpp::span<std::byte>& data) -> void = 0;

@ -34,8 +34,8 @@ class I2SAudioOutput : public IAudioSink {
auto SetVolumeImbalance(int_fast8_t balance) -> void override; auto SetVolumeImbalance(int_fast8_t balance) -> void override;
auto SetVolume(uint_fast8_t percent) -> void override; auto SetVolume(uint_fast8_t percent) -> void override;
auto GetVolume() -> uint_fast8_t override; auto GetVolume() -> uint_fast8_t override;
auto AdjustVolumeUp() -> void override; auto AdjustVolumeUp() -> bool override;
auto AdjustVolumeDown() -> void override; auto AdjustVolumeDown() -> bool override;
auto Configure(const StreamInfo::Format& format) -> bool override; auto Configure(const StreamInfo::Format& format) -> bool override;
auto Send(const cpp::span<std::byte>& data) -> void override; auto Send(const cpp::span<std::byte>& data) -> void override;

@ -5,35 +5,97 @@
*/ */
#include "gpio_expander.hpp" #include "gpio_expander.hpp"
#include <stdint.h>
#include <cstdint> #include <cstdint>
#include "driver/gpio.h"
#include "hal/gpio_types.h"
#include "i2c.hpp" #include "i2c.hpp"
namespace drivers { namespace drivers {
GpioExpander::GpioExpander() { static const uint8_t kPca8575Address = 0x20;
ports_ = pack(kPortADefault, kPortBDefault);
// Read and write initial values on initialisation so that we do not have a // Port A:
// strange partially-initialised state. // 0 - sd card mux switch
// TODO: log or abort if these error; it's really bad! // 1 - sd card mux enable (active low)
Write(); // 2 - key up
Read(); // 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<uint8_t, uint8_t> unpack(uint16_t ba) {
return std::pair((uint8_t)ba, (uint8_t)(ba >> 8));
}
void GpioExpander::with(std::function<void(GpioExpander&)> f) { void interrupt_isr(void* arg) {
f(*this); GpioExpander* instance = reinterpret_cast<GpioExpander*>(arg);
Write(); auto listener = instance->listener().lock();
if (listener) {
std::invoke(*listener);
}
} }
esp_err_t GpioExpander::Write() { auto GpioExpander::Create() -> GpioExpander* {
i2c_cmd_handle_t handle = i2c_cmd_link_create(); GpioExpander* instance = new GpioExpander();
if (handle == NULL) { // Read and write initial values on initialisation so that we do not have a
return ESP_ERR_NO_MEM; // 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<uint64_t>(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<uint8_t, uint8_t> ports_ab = unpack(ports()); std::pair<uint8_t, uint8_t> ports_ab = unpack(ports());
I2CTransaction transaction; I2CTransaction transaction;
@ -42,10 +104,10 @@ esp_err_t GpioExpander::Write() {
.write_ack(ports_ab.first, ports_ab.second) .write_ack(ports_ab.first, ports_ab.second)
.stop(); .stop();
return transaction.Execute(); return transaction.Execute() == ESP_OK;
} }
esp_err_t GpioExpander::Read() { bool GpioExpander::Read() {
uint8_t input_a, input_b; uint8_t input_a, input_b;
I2CTransaction transaction; I2CTransaction transaction;
@ -56,8 +118,11 @@ esp_err_t GpioExpander::Read() {
.stop(); .stop();
esp_err_t ret = transaction.Execute(); esp_err_t ret = transaction.Execute();
if (ret != ESP_OK) {
return false;
}
inputs_ = pack(input_a, input_b); inputs_ = pack(input_a, input_b);
return ret; return true;
} }
void GpioExpander::set_pin(Pin pin, bool value) { void GpioExpander::set_pin(Pin pin, bool value) {

@ -10,6 +10,7 @@
#include <atomic> #include <atomic>
#include <functional> #include <functional>
#include <memory>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <tuple> #include <tuple>
@ -35,52 +36,9 @@ namespace drivers {
*/ */
class GpioExpander { class GpioExpander {
public: public:
static auto Create() -> GpioExpander* { return new GpioExpander(); } static auto Create() -> GpioExpander*;
GpioExpander();
~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<uint8_t, uint8_t> unpack(uint16_t ba) {
return std::pair((uint8_t)ba, (uint8_t)(ba >> 8));
}
/* /*
* Convenience function for running some arbitrary pin writing code, then * Convenience function for running some arbitrary pin writing code, then
* flushing a `Write()` to the expander. Example usage: * flushing a `Write()` to the expander. Example usage:
@ -91,19 +49,23 @@ class GpioExpander {
* }); * });
* ``` * ```
*/ */
void with(std::function<void(GpioExpander&)> f); template <typename F>
auto with(F fn) -> void {
std::invoke(fn);
Write();
}
/** /**
* Sets the ports on the GPIO expander to the values currently represented * Sets the ports on the GPIO expander to the values currently represented
* in `ports`. * in `ports`.
*/ */
esp_err_t Write(void); auto Write(void) -> bool;
/** /**
* Reads from the GPIO expander, populating `inputs` with the most recent * Reads from the GPIO expander, populating `inputs` with the most recent
* values. * values.
*/ */
esp_err_t Read(void); auto Read(void) -> bool;
/* Maps each pin of the expander to its number in a `pack`ed uint16. */ /* Maps each pin of the expander to its number in a `pack`ed uint16. */
enum Pin { enum Pin {
@ -113,9 +75,9 @@ class GpioExpander {
KEY_UP = 2, KEY_UP = 2,
KEY_DOWN = 3, KEY_DOWN = 3,
KEY_LOCK = 4, KEY_LOCK = 4,
DISPLAY_RESET = 5, DISPLAY_RESET_ACTIVE_LOW = 5,
// UNUSED = 6, // UNUSED = 6,
SD_CARD_POWER_ENABLE = 7, SD_CARD_POWER_ENABLE_ACTIVE_LOW = 7,
// Port B // Port B
PHONE_DETECT = 8, PHONE_DETECT = 8,
@ -161,14 +123,26 @@ class GpioExpander {
*/ */
bool get_input(Pin pin) const; bool get_input(Pin pin) const;
auto listener() -> std::weak_ptr<std::function<void(void)>>& {
return listener_;
}
auto set_listener(const std::weak_ptr<std::function<void(void)>>& l) -> void {
listener_ = l;
}
// Not copyable or movable. There should usually only ever be once instance // Not copyable or movable. There should usually only ever be once instance
// of this class, and that instance will likely have a static lifetime. // of this class, and that instance will likely have a static lifetime.
GpioExpander(const GpioExpander&) = delete; GpioExpander(const GpioExpander&) = delete;
GpioExpander& operator=(const GpioExpander&) = delete; GpioExpander& operator=(const GpioExpander&) = delete;
private: private:
GpioExpander();
std::atomic<uint16_t> ports_; std::atomic<uint16_t> ports_;
std::atomic<uint16_t> inputs_; std::atomic<uint16_t> inputs_;
std::weak_ptr<std::function<void(void)>> listener_;
}; };
} // namespace drivers } // namespace drivers

@ -56,7 +56,7 @@ static esp_err_t do_transaction(sdspi_dev_handle_t handle,
} // namespace callback } // namespace callback
auto SdStorage::Create(GpioExpander* gpio) -> cpp::result<SdStorage*, Error> { auto SdStorage::Create(GpioExpander* gpio) -> cpp::result<SdStorage*, Error> {
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_EN_ACTIVE_LOW, 0);
gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP); gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP);
gpio->Write(); gpio->Write();
@ -144,7 +144,7 @@ SdStorage::~SdStorage() {
sdspi_host_remove_device(this->handle_); sdspi_host_remove_device(this->handle_);
sdspi_host_deinit(); 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_->set_pin(GpioExpander::SD_MUX_EN_ACTIVE_LOW, 1);
gpio_->Write(); gpio_->Write();
} }

@ -13,6 +13,7 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#include "system_fsm.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
#include "ui_fsm.hpp" #include "ui_fsm.hpp"
@ -33,6 +34,16 @@ class EventQueue {
return instance; return instance;
} }
template <typename Event>
auto DispatchFromISR(const Event& ev) -> bool {
WorkItem* item = new WorkItem([=]() {
tinyfsm::FsmList<system_fsm::SystemState>::template dispatch<Event>(ev);
});
BaseType_t ret;
xQueueSendFromISR(system_handle_, &item, &ret);
return ret;
}
template <typename Event, typename Machine, typename... Machines> template <typename Event, typename Machine, typename... Machines>
auto Dispatch(const Event& ev) -> void { auto Dispatch(const Event& ev) -> void {
WorkItem* item = new WorkItem( WorkItem* item = new WorkItem(

@ -27,6 +27,10 @@ namespace states {
static const char kTag[] = "BOOT"; static const char kTag[] = "BOOT";
static std::function<void(void)> sGpiosCallback = []() {
events::EventQueue::GetInstance().DispatchFromISR(internal::GpioInterrupt{});
};
auto Booting::entry() -> void { auto Booting::entry() -> void {
ESP_LOGI(kTag, "beginning tangara boot"); ESP_LOGI(kTag, "beginning tangara boot");
ESP_LOGI(kTag, "installing early drivers"); ESP_LOGI(kTag, "installing early drivers");

@ -47,6 +47,19 @@ struct StorageMounted : tinyfsm::Event {
struct StorageError : 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 { namespace internal {
/* /*
@ -55,6 +68,12 @@ namespace internal {
*/ */
struct ReadyToUnmount : tinyfsm::Event {}; 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 internal
} // namespace system_fsm } // namespace system_fsm

@ -38,6 +38,7 @@ class SystemState : public tinyfsm::Fsm<SystemState> {
void react(const tinyfsm::Event& ev) {} void react(const tinyfsm::Event& ev) {}
void react(const FatalError&); void react(const FatalError&);
void react(const internal::GpioInterrupt&);
virtual void react(const DisplayReady&) {} virtual void react(const DisplayReady&) {}
virtual void react(const BootComplete&) {} virtual void react(const BootComplete&) {}

@ -5,6 +5,8 @@
*/ */
#include "system_fsm.hpp" #include "system_fsm.hpp"
#include "audio_fsm.hpp"
#include "event_queue.hpp"
#include "relative_wheel.hpp" #include "relative_wheel.hpp"
#include "system_events.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<KeyUpChanged, audio::AudioState, ui::UiState>(
{.falling = prev_key_up});
}
if (key_down != prev_key_down) {
events::Dispatch<KeyDownChanged, audio::AudioState, ui::UiState>(
{.falling = prev_key_down});
}
if (key_lock != prev_key_lock) {
events::Dispatch<KeyLockChanged, SystemState, ui::UiState>(
{.falling = prev_key_lock});
}
if (has_headphones != prev_has_headphones) {
events::Dispatch<HasPhonesChanged, audio::AudioState>(
{.falling = prev_has_headphones});
}
}
} // namespace system_fsm } // namespace system_fsm
FSM_INITIAL_STATE(system_fsm::SystemState, system_fsm::states::Booting) FSM_INITIAL_STATE(system_fsm::SystemState, system_fsm::states::Booting)

@ -35,6 +35,8 @@ class UiState : public tinyfsm::Fsm<UiState> {
/* Fallback event handler. Does nothing. */ /* Fallback event handler. Does nothing. */
void react(const tinyfsm::Event& ev) {} 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::DisplayReady&) {}
virtual void react(const system_fsm::BootComplete&) {} virtual void react(const system_fsm::BootComplete&) {}
@ -58,6 +60,8 @@ class Splash : public UiState {
class Interactive : public UiState { class Interactive : public UiState {
void entry() override; void entry() override;
void react(const system_fsm::KeyLockChanged&) override;
}; };
class FatalError : public UiState {}; class FatalError : public UiState {};

@ -68,6 +68,10 @@ void Interactive::entry() {
sCurrentScreen.reset(new screens::Menu()); sCurrentScreen.reset(new screens::Menu());
} }
void Interactive::react(const system_fsm::KeyLockChanged& ev) {
sDisplay->SetDisplayOn(ev.falling);
}
} // namespace states } // namespace states
} // namespace ui } // namespace ui

Loading…
Cancel
Save