summaryrefslogtreecommitdiff
path: root/src/drivers
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-08-08 20:25:42 +1000
committerjacqueline <me@jacqueline.id.au>2023-08-08 20:25:42 +1000
commite1181fbe59a835ea9c93d6e067e9757e8c522d3c (patch)
tree2fd61bb93713de8c2205b7b6d0a8c84c49832e93 /src/drivers
parentc3f40a8cc37114365ef3ec6f2888df64e5206b39 (diff)
parent592f231627843bc44ebaaa4506aec26da1f56499 (diff)
downloadtangara-fw-e1181fbe59a835ea9c93d6e067e9757e8c522d3c.tar.gz
Merge branch 'main' into opus
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/CMakeLists.txt3
-rw-r--r--src/drivers/bluetooth.cpp253
-rw-r--r--src/drivers/i2s_dac.cpp1
-rw-r--r--src/drivers/include/bluetooth.hpp108
-rw-r--r--src/drivers/include/i2s_dac.hpp6
-rw-r--r--src/drivers/include/nvs.hpp27
-rw-r--r--src/drivers/nvs.cpp73
-rw-r--r--src/drivers/storage.cpp18
8 files changed, 481 insertions, 8 deletions
diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt
index 40cd0c4f..a64495f0 100644
--- a/src/drivers/CMakeLists.txt
+++ b/src/drivers/CMakeLists.txt
@@ -5,6 +5,7 @@
idf_component_register(
SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
"spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" "relative_wheel.cpp" "wm8523.cpp"
+ "nvs.cpp" "bluetooth.cpp"
INCLUDE_DIRS "include"
- REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks")
+ REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "bt" "tinyfsm")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp
new file mode 100644
index 00000000..f9ab4e95
--- /dev/null
+++ b/src/drivers/bluetooth.cpp
@@ -0,0 +1,253 @@
+#include "bluetooth.hpp"
+
+#include <stdint.h>
+
+#include <atomic>
+#include <ostream>
+#include <sstream>
+
+#include "esp_a2dp_api.h"
+#include "esp_avrc_api.h"
+#include "esp_bt.h"
+#include "esp_bt_device.h"
+#include "esp_bt_main.h"
+#include "esp_gap_bt_api.h"
+#include "esp_log.h"
+#include "esp_mac.h"
+#include "tinyfsm/include/tinyfsm.hpp"
+
+namespace drivers {
+
+static constexpr char kTag[] = "bluetooth";
+
+static std::atomic<StreamBufferHandle_t> sStream;
+
+auto gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t* param) -> void {
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::internal::Gap{.type = event, .param = param});
+}
+
+auto avrcp_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t* param)
+ -> void {
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::internal::Avrc{.type = event, .param = param});
+}
+
+auto a2dp_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* param) -> void {
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::internal::A2dp{.type = event, .param = param});
+}
+
+auto a2dp_data_cb(uint8_t* buf, int32_t buf_size) -> int32_t {
+ if (buf == nullptr || buf_size <= 0) {
+ return 0;
+ }
+ StreamBufferHandle_t stream = sStream.load();
+ if (stream == nullptr) {
+ return 0;
+ }
+ return xStreamBufferReceive(stream, buf, buf_size, 0);
+}
+
+Bluetooth::Bluetooth() {
+ tinyfsm::FsmList<bluetooth::BluetoothState>::start();
+}
+
+auto Bluetooth::Enable() -> bool {
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::Enable{});
+
+ return !bluetooth::BluetoothState::is_in_state<bluetooth::Disabled>();
+}
+
+auto Bluetooth::Disable() -> void {
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::Disable{});
+}
+
+auto DeviceName() -> std::string {
+ uint8_t mac[8]{0};
+ esp_efuse_mac_get_default(mac);
+ std::ostringstream name;
+ name << "TANGARA " << std::hex << mac[0] << mac[1];
+ return name.str();
+}
+
+namespace bluetooth {
+
+static bool sIsFirstEntry = true;
+
+void Disabled::entry() {
+ if (sIsFirstEntry) {
+ // We only use BT Classic, to claw back ~60KiB from the BLE firmware.
+ esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
+ sIsFirstEntry = false;
+ return;
+ }
+
+ esp_bluedroid_disable();
+ esp_bluedroid_deinit();
+ esp_bt_controller_disable();
+}
+
+void Disabled::react(const events::Enable&) {
+ esp_bt_controller_config_t config = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+ if (esp_bt_controller_init(&config) != ESP_OK) {
+ ESP_LOGE(kTag, "initialize controller failed");
+ return;
+ }
+
+ if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
+ ESP_LOGE(kTag, "enable controller failed");
+ return;
+ }
+
+ if (esp_bluedroid_init() != ESP_OK) {
+ ESP_LOGE(kTag, "initialize bluedroid failed");
+ return;
+ }
+
+ if (esp_bluedroid_enable() != ESP_OK) {
+ ESP_LOGE(kTag, "enable bluedroid failed");
+ return;
+ }
+
+ // Enable Secure Simple Pairing
+ esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
+ esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
+ esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
+
+ // Set a reasonable name for the device.
+ std::string name = DeviceName();
+ esp_bt_dev_set_device_name(name.c_str());
+
+ // Initialise GAP. This controls advertising our device, and scanning for
+ // other devices.
+ esp_bt_gap_register_callback(gap_cb);
+
+ // Initialise AVRCP. This handles playback controls; play/pause/volume/etc.
+ // esp_avrc_ct_init();
+ // esp_avrc_ct_register_callback(avrcp_cb);
+
+ // Initialise A2DP. This handles streaming audio. Currently ESP-IDF's SBC
+ // encoder only supports 2 channels of interleaved 16 bit samples, at
+ // 44.1kHz, so there is no additional configuration to be done for the
+ // stream itself.
+ esp_a2d_source_init();
+ esp_a2d_register_callback(a2dp_cb);
+ esp_a2d_source_register_data_callback(a2dp_data_cb);
+
+ // Don't let anyone interact with us before we're ready.
+ esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
+
+ transit<Scanning>();
+}
+
+static constexpr uint8_t kDiscoveryTimeSeconds = 10;
+static constexpr uint8_t kDiscoveryMaxResults = 0;
+
+void Scanning::entry() {
+ ESP_LOGI(kTag, "scanning for devices");
+ esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY,
+ kDiscoveryTimeSeconds, kDiscoveryMaxResults);
+}
+
+void Scanning::exit() {
+ esp_bt_gap_cancel_discovery();
+}
+
+auto OnDeviceDiscovered(esp_bt_gap_cb_param_t* param) -> void {
+ ESP_LOGI(kTag, "device discovered");
+}
+
+void Scanning::react(const events::internal::Gap& ev) {
+ switch (ev.type) {
+ case ESP_BT_GAP_DISC_RES_EVT:
+ OnDeviceDiscovered(ev.param);
+ break;
+ case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
+ if (ev.param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
+ ESP_LOGI(kTag, "still scanning");
+ esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY,
+ kDiscoveryTimeSeconds, kDiscoveryMaxResults);
+ }
+ break;
+ case ESP_BT_GAP_MODE_CHG_EVT:
+ // todo: mode change. is this important?
+ ESP_LOGI(kTag, "GAP mode changed");
+ break;
+ default:
+ ESP_LOGW(kTag, "unhandled GAP event: %u", ev.type);
+ }
+}
+
+void Connecting::entry() {
+ ESP_LOGI(kTag, "connecting to device");
+ esp_a2d_source_connect(nullptr);
+}
+
+void Connecting::exit() {}
+
+void Connecting::react(const events::internal::Gap& ev) {
+ switch (ev.type) {
+ case ESP_BT_GAP_AUTH_CMPL_EVT:
+ // todo: auth completed. check if we succeeded.
+ break;
+ case ESP_BT_GAP_PIN_REQ_EVT:
+ // todo: device needs a pin to connect.
+ break;
+ case ESP_BT_GAP_CFM_REQ_EVT:
+ // todo: device needs user to click okay.
+ break;
+ case ESP_BT_GAP_KEY_NOTIF_EVT:
+ // todo: device is telling us a password?
+ break;
+ case ESP_BT_GAP_KEY_REQ_EVT:
+ // todo: device needs a password
+ break;
+ case ESP_BT_GAP_MODE_CHG_EVT:
+ // todo: mode change. is this important?
+ break;
+ default:
+ ESP_LOGW(kTag, "unhandled GAP event: %u", ev.type);
+ }
+}
+
+void Connecting::react(const events::internal::A2dp& ev) {
+ switch (ev.type) {
+ case ESP_A2D_CONNECTION_STATE_EVT:
+ // todo: connection state changed. we might be connected!
+ break;
+ default:
+ ESP_LOGW(kTag, "unhandled A2DP event: %u", ev.type);
+ }
+}
+
+void Connected::react(const events::internal::A2dp& ev) {
+ switch (ev.type) {
+ case ESP_A2D_CONNECTION_STATE_EVT:
+ // todo: connection state changed. we might have dropped
+ break;
+ case ESP_A2D_AUDIO_STATE_EVT:
+ // todo: audio state changed. who knows, dude.
+ break;
+ default:
+ ESP_LOGW(kTag, "unhandled A2DP event: %u", ev.type);
+ }
+}
+
+void Connected::react(const events::internal::Avrc& ev) {
+ switch (ev.type) {
+ case ESP_AVRC_CT_CONNECTION_STATE_EVT:
+ // todo: avrc connected. send our capabilities.
+ default:
+ ESP_LOGW(kTag, "unhandled AVRC event: %u", ev.type);
+ }
+}
+
+} // namespace bluetooth
+
+} // namespace drivers
+
+FSM_INITIAL_STATE(drivers::bluetooth::BluetoothState,
+ drivers::bluetooth::Disabled)
diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp
index c835fb1f..885321d1 100644
--- a/src/drivers/i2s_dac.cpp
+++ b/src/drivers/i2s_dac.cpp
@@ -161,7 +161,6 @@ auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
word_length = 0b10;
break;
case BPS_32:
- // TODO(jacqueline): Error on this? It's not supported anymore.
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT;
slot_config_.ws_width = 32;
word_length = 0b11;
diff --git a/src/drivers/include/bluetooth.hpp b/src/drivers/include/bluetooth.hpp
new file mode 100644
index 00000000..2b5e6a8d
--- /dev/null
+++ b/src/drivers/include/bluetooth.hpp
@@ -0,0 +1,108 @@
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/stream_buffer.h>
+#include "esp_a2dp_api.h"
+#include "esp_avrc_api.h"
+#include "esp_gap_bt_api.h"
+#include "tinyfsm.hpp"
+#include "tinyfsm/include/tinyfsm.hpp"
+
+namespace drivers {
+
+/*
+ * A handle used to interact with the bluetooth state machine.
+ */
+class Bluetooth {
+ public:
+ Bluetooth();
+
+ auto Enable() -> bool;
+ auto Disable() -> void;
+
+ auto SetSource(StreamBufferHandle_t) -> void;
+};
+
+namespace bluetooth {
+
+namespace events {
+struct Enable : public tinyfsm::Event {};
+struct Disable : public tinyfsm::Event {};
+
+namespace internal {
+struct Gap : public tinyfsm::Event {
+ esp_bt_gap_cb_event_t type;
+ esp_bt_gap_cb_param_t* param;
+};
+struct A2dp : public tinyfsm::Event {
+ esp_a2d_cb_event_t type;
+ esp_a2d_cb_param_t* param;
+};
+struct Avrc : public tinyfsm::Event {
+ esp_avrc_ct_cb_event_t type;
+ esp_avrc_ct_cb_param_t* param;
+};
+} // namespace internal
+} // namespace events
+
+class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
+ public:
+ virtual ~BluetoothState(){};
+
+ virtual void entry() {}
+ virtual void exit() {}
+
+ virtual void react(const events::Enable& ev){};
+ virtual void react(const events::Disable& ev) = 0;
+
+ virtual void react(const events::internal::Gap& ev) = 0;
+ virtual void react(const events::internal::A2dp& ev) = 0;
+ virtual void react(const events::internal::Avrc& ev){};
+};
+
+class Disabled : public BluetoothState {
+ void entry() override;
+
+ void react(const events::Enable& ev) override;
+ void react(const events::Disable& ev) override{};
+
+ void react(const events::internal::Gap& ev) override {}
+ void react(const events::internal::A2dp& ev) override {}
+};
+
+class Scanning : public BluetoothState {
+ void entry() override;
+ void exit() override;
+
+ void react(const events::Disable& ev) override;
+
+ void react(const events::internal::Gap& ev) override;
+ void react(const events::internal::A2dp& ev) override;
+};
+
+class Connecting : public BluetoothState {
+ void entry() override;
+ void exit() override;
+
+ void react(const events::Disable& ev) override;
+ void react(const events::internal::Gap& ev) override;
+ void react(const events::internal::A2dp& ev) override;
+};
+
+class Connected : public BluetoothState {
+ void entry() override;
+ void exit() override;
+
+ void react(const events::Disable& ev) override;
+ void react(const events::internal::Gap& ev) override;
+ void react(const events::internal::A2dp& ev) override;
+ void react(const events::internal::Avrc& ev) override;
+};
+
+} // namespace bluetooth
+
+} // namespace drivers
diff --git a/src/drivers/include/i2s_dac.hpp b/src/drivers/include/i2s_dac.hpp
index 06c0dc16..889ba68c 100644
--- a/src/drivers/include/i2s_dac.hpp
+++ b/src/drivers/include/i2s_dac.hpp
@@ -51,14 +51,12 @@ class I2SDac {
BPS_32 = I2S_DATA_BIT_WIDTH_32BIT,
};
enum SampleRate {
- SAMPLE_RATE_11_025 = 11025,
- SAMPLE_RATE_16 = 16000,
- SAMPLE_RATE_22_05 = 22050,
+ SAMPLE_RATE_8 = 8000,
SAMPLE_RATE_32 = 32000,
SAMPLE_RATE_44_1 = 44100,
SAMPLE_RATE_48 = 48000,
+ SAMPLE_RATE_88_2 = 88200,
SAMPLE_RATE_96 = 96000,
- SAMPLE_RATE_192 = 192000,
};
auto Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void;
diff --git a/src/drivers/include/nvs.hpp b/src/drivers/include/nvs.hpp
new file mode 100644
index 00000000..be783583
--- /dev/null
+++ b/src/drivers/include/nvs.hpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include "esp_err.h"
+#include "nvs.h"
+
+namespace drivers {
+
+class NvsStorage {
+ public:
+ static auto Open() -> NvsStorage*;
+
+ auto SchemaVersion() -> uint8_t;
+
+ explicit NvsStorage(nvs_handle_t);
+ ~NvsStorage();
+
+ private:
+ nvs_handle_t handle_;
+};
+
+} // namespace drivers \ No newline at end of file
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
new file mode 100644
index 00000000..a2de9518
--- /dev/null
+++ b/src/drivers/nvs.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "nvs.hpp"
+#include <stdint.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "esp_log.h"
+#include "nvs.h"
+#include "nvs_flash.h"
+
+namespace drivers {
+
+static constexpr char kTag[] = "nvm";
+static constexpr uint8_t kSchemaVersion = 1;
+
+static constexpr char kKeyVersion[] = "ver";
+
+auto NvsStorage::Open() -> NvsStorage* {
+ esp_err_t err = nvs_flash_init();
+ if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
+ ESP_LOGW(kTag, "partition needs initialisation");
+ nvs_flash_erase();
+ err = nvs_flash_init();
+ }
+ if (err != ESP_OK) {
+ ESP_LOGE(kTag, "failed to init nvm");
+ return nullptr;
+ }
+
+ nvs_handle_t handle;
+ if ((err = nvs_open("tangara", NVS_READWRITE, &handle)) != ESP_OK) {
+ ESP_LOGE(kTag, "failed to open nvs namespace");
+ return nullptr;
+ }
+
+ std::unique_ptr<NvsStorage> instance = std::make_unique<NvsStorage>(handle);
+ if (instance->SchemaVersion() < kSchemaVersion) {
+ ESP_LOGW(kTag, "namespace needs downgrading");
+ nvs_erase_all(handle);
+ nvs_set_u8(handle, kKeyVersion, kSchemaVersion);
+ err = nvs_commit(handle);
+ if (err != ESP_OK) {
+ ESP_LOGW(kTag, "failed to init namespace");
+ return nullptr;
+ }
+ }
+
+ ESP_LOGI(kTag, "nvm storage initialised okay");
+ return instance.release();
+}
+
+NvsStorage::NvsStorage(nvs_handle_t handle) : handle_(handle) {}
+
+NvsStorage::~NvsStorage() {
+ nvs_close(handle_);
+ nvs_flash_deinit();
+}
+
+auto NvsStorage::SchemaVersion() -> uint8_t {
+ uint8_t ret;
+ if (nvs_get_u8(handle_, kKeyVersion, &ret) != ESP_OK) {
+ return UINT8_MAX;
+ }
+ return ret;
+}
+
+} // namespace drivers
diff --git a/src/drivers/storage.cpp b/src/drivers/storage.cpp
index db257dee..f253a79a 100644
--- a/src/drivers/storage.cpp
+++ b/src/drivers/storage.cpp
@@ -63,7 +63,7 @@ auto SdStorage::Create(IGpios* gpio) -> cpp::result<SdStorage*, Error> {
// Will return ESP_ERR_INVALID_RESPONSE if there is no card
esp_err_t err = sdmmc_card_init(host.get(), card.get());
if (err != ESP_OK) {
- ESP_LOGW(kTag, "Failed to read, err: %d", err);
+ ESP_LOGW(kTag, "Failed to read, err: %s", esp_err_to_name(err));
return cpp::fail(Error::FAILED_TO_READ);
}
@@ -74,7 +74,21 @@ auto SdStorage::Create(IGpios* gpio) -> cpp::result<SdStorage*, Error> {
// Mount right now, not on first operation.
FRESULT ferr = f_mount(fs, "", 1);
if (ferr != FR_OK) {
- ESP_LOGW(kTag, "Failed to mount, err: %d", ferr);
+ std::string err_str;
+ switch (ferr) {
+ case FR_DISK_ERR:
+ err_str = "FR_DISK_ERR";
+ break;
+ case FR_NOT_READY:
+ err_str = "FR_NOT_READY";
+ break;
+ case FR_NO_FILESYSTEM:
+ err_str = "FR_NO_FILESYSTEM";
+ break;
+ default:
+ err_str = std::to_string(ferr);
+ }
+ ESP_LOGW(kTag, "Failed to mount, err: %s", err_str.c_str());
return cpp::fail(Error::FAILED_TO_MOUNT);
}