summaryrefslogtreecommitdiff
path: root/src/drivers
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-04-26 08:49:02 +1000
committerjacqueline <me@jacqueline.id.au>2023-04-26 08:49:02 +1000
commit7972bd4567a99179338259e9e6ce19168c2c0db3 (patch)
treef46642afd36011d3d064e022232e77744b82c6ae /src/drivers
parent4887f3789817f87bf1272af0b52684e3364270c2 (diff)
parent5575378c1c8171cd716b79d3ab89df1e56ceb9d3 (diff)
downloadtangara-fw-7972bd4567a99179338259e9e6ce19168c2c0db3.tar.gz
Merge branch 'main' into leveldb
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/CMakeLists.txt4
-rw-r--r--src/drivers/battery.cpp4
-rw-r--r--src/drivers/dac.cpp341
-rw-r--r--src/drivers/display.cpp43
-rw-r--r--src/drivers/driver_cache.cpp43
-rw-r--r--src/drivers/i2c.cpp11
-rw-r--r--src/drivers/include/dac.hpp127
-rw-r--r--src/drivers/include/display.hpp3
-rw-r--r--src/drivers/include/driver_cache.hpp54
-rw-r--r--src/drivers/include/gpio_expander.hpp76
-rw-r--r--src/drivers/include/i2c.hpp2
-rw-r--r--src/drivers/include/storage.hpp7
-rw-r--r--src/drivers/include/touchwheel.hpp49
-rw-r--r--src/drivers/storage.cpp17
-rw-r--r--src/drivers/touchwheel.cpp95
15 files changed, 704 insertions, 172 deletions
diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt
index c4e4c172..072a8b68 100644
--- a/src/drivers/CMakeLists.txt
+++ b/src/drivers/CMakeLists.txt
@@ -1,6 +1,6 @@
idf_component_register(
- SRCS "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
- "spi.cpp" "display.cpp" "display_init.cpp"
+ SRCS "touchwheel.cpp" "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
+ "spi.cpp" "display.cpp" "display_init.cpp" "driver_cache.cpp"
INCLUDE_DIRS "include"
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/drivers/battery.cpp b/src/drivers/battery.cpp
index 00e7796a..8d747c07 100644
--- a/src/drivers/battery.cpp
+++ b/src/drivers/battery.cpp
@@ -13,8 +13,8 @@ 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
// the max attenuation to properly handle the full range.
static const adc_atten_t kAdcAttenuation = ADC_ATTEN_DB_11;
-// Corresponds to GPIO 34.
-static const adc_channel_t kAdcChannel = ADC_CHANNEL_6;
+// Corresponds to SENSOR_VP.
+static const adc_channel_t kAdcChannel = ADC_CHANNEL_0;
Battery::Battery() {
adc_oneshot_unit_init_cfg_t unit_config = {
diff --git a/src/drivers/dac.cpp b/src/drivers/dac.cpp
index 1ab562f9..e82f0d27 100644
--- a/src/drivers/dac.cpp
+++ b/src/drivers/dac.cpp
@@ -7,36 +7,38 @@
#include "driver/i2c.h"
#include "driver/i2s_common.h"
#include "driver/i2s_std.h"
-#include "driver/i2s_types.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
+#include "hal/gpio_types.h"
#include "hal/i2c_types.h"
#include "gpio_expander.hpp"
#include "hal/i2s_types.h"
#include "i2c.hpp"
+#include "soc/clk_tree_defs.h"
#include "sys/_stdint.h"
namespace drivers {
static const char* kTag = "AUDIODAC";
static const uint8_t kPcm5122Address = 0x4C;
-static const uint8_t kPcm5122Timeout = pdMS_TO_TICKS(100);
static const i2s_port_t kI2SPort = I2S_NUM_0;
-static const AudioDac::SampleRate kDefaultSampleRate =
- AudioDac::SAMPLE_RATE_44_1;
-static const AudioDac::BitsPerSample kDefaultBps = AudioDac::BPS_16;
-
-auto AudioDac::create(GpioExpander* expander)
- -> cpp::result<std::unique_ptr<AudioDac>, Error> {
+auto AudioDac::create(GpioExpander* expander) -> cpp::result<AudioDac*, Error> {
// TODO: tune.
i2s_chan_handle_t i2s_handle;
i2s_chan_config_t channel_config =
I2S_CHANNEL_DEFAULT_CONFIG(kI2SPort, I2S_ROLE_MASTER);
+ // Use the maximum possible DMA buffer size, since a smaller number of large
+ // copies is faster than a large number of small copies.
+ channel_config.dma_frame_num = 1024;
+ // Triple buffering should be enough to keep samples flowing smoothly.
+ // TODO(jacqueline): verify this with 192kHz 32bps.
+ channel_config.dma_desc_num = 4;
+ // channel_config.auto_clear = true;
ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &i2s_handle, NULL));
//
@@ -50,21 +52,22 @@ auto AudioDac::create(GpioExpander* expander)
i2s_std_config_t i2s_config = {
.clk_cfg = dac->clock_config_,
.slot_cfg = dac->slot_config_,
- .gpio_cfg =
- {// TODO: investigate running in three wire mode for less noise
- .mclk = GPIO_NUM_0,
- .bclk = GPIO_NUM_26,
- .ws = GPIO_NUM_27,
- .dout = GPIO_NUM_5,
- .din = I2S_GPIO_UNUSED,
- .invert_flags =
- {
- .mclk_inv = false,
- .bclk_inv = false,
- .ws_inv = false,
- }},
+ .gpio_cfg = {.mclk = GPIO_NUM_0,
+ .bclk = GPIO_NUM_26,
+ .ws = GPIO_NUM_27,
+ .dout = GPIO_NUM_5,
+ .din = I2S_GPIO_UNUSED,
+ .invert_flags =
+ {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = true,
+ }},
};
+ // gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT);
+ // gpio_set_level(GPIO_NUM_0, 0);
+
if (esp_err_t err =
i2s_channel_init_std_mode(i2s_handle, &i2s_config) != ESP_OK) {
ESP_LOGE(kTag, "failed to initialise i2s channel %x", err);
@@ -81,65 +84,64 @@ auto AudioDac::create(GpioExpander* expander)
// The DAC should be booted but in power down mode, but it might not be if we
// didn't shut down cleanly. Reset it to ensure it is in a consistent state.
- dac->WriteRegister(Register::POWER_MODE, 0b10001);
- dac->WriteRegister(Register::POWER_MODE, 1 << 4);
- dac->WriteRegister(Register::RESET, 0b10001);
+ dac->WriteRegister(pcm512x::POWER, 1 << 4);
+ dac->WriteRegister(pcm512x::RESET, 0b10001);
+
+ // Use BCK for the internal PLL.
+ // dac->WriteRegister(Register::PLL_CLOCK_SOURCE, 1 << 4);
+ // dac->WriteRegister(Register::DAC_CLOCK_SOURCE, 0b11 << 5);
+
+ // dac->WriteRegister(Register::PLL_ENABLE, 0);
+ // dac->WriteRegister(Register::DAC_CLOCK_SOURCE, 0b0110000);
+ // dac->WriteRegister(Register::CLOCK_ERRORS, 0b01000001);
+ // dac->WriteRegister(Register::I2S_FORMAT, 0b110000);
+ // dac->WriteRegister(Register::INTERPOLATION, 1 << 4);
+
+ dac->Reconfigure(BPS_16, SAMPLE_RATE_44_1);
// Now configure the DAC for standard auto-clock SCK mode.
- dac->WriteRegister(Register::DAC_CLOCK_SOURCE, 0b11 << 5);
// Enable auto clocking, and do your best to carry on despite errors.
// dac->WriteRegister(Register::CLOCK_ERRORS, 0b1111101);
- i2s_channel_enable(dac->i2s_handle_);
+ // i2s_channel_enable(dac->i2s_handle_);
- dac->WaitForPowerState(
- [](bool booted, PowerState state) { return state == STANDBY; });
+ dac->WaitForPowerState([](bool booted, PowerState state) {
+ return state == RUN || state == STANDBY;
+ });
- return dac;
+ return dac.release();
}
AudioDac::AudioDac(GpioExpander* gpio, i2s_chan_handle_t i2s_handle)
: gpio_(gpio),
i2s_handle_(i2s_handle),
+ i2s_active_(false),
+ active_page_(),
clock_config_(I2S_STD_CLK_DEFAULT_CONFIG(44100)),
slot_config_(I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO)) {
- gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, true);
+ clock_config_.clk_src = I2S_CLK_SRC_PLL_160M;
+ gpio_->set_pin(GpioExpander::AMP_EN, true);
gpio_->Write();
}
AudioDac::~AudioDac() {
i2s_channel_disable(i2s_handle_);
i2s_del_channel(i2s_handle_);
- gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, false);
+ gpio_->set_pin(GpioExpander::AMP_EN, false);
gpio_->Write();
}
void AudioDac::WriteVolume(uint8_t volume) {
- WriteRegister(Register::DIGITAL_VOLUME_L, volume);
- WriteRegister(Register::DIGITAL_VOLUME_R, volume);
+ // Left channel.
+ WriteRegister(pcm512x::DIGITAL_VOLUME_2, volume);
+ // Right channel.
+ WriteRegister(pcm512x::DIGITAL_VOLUME_3, volume);
}
std::pair<bool, AudioDac::PowerState> AudioDac::ReadPowerState() {
- uint8_t result = 0;
-
- I2CTransaction transaction;
- transaction.start()
- .write_addr(kPcm5122Address, I2C_MASTER_WRITE)
- .write_ack(DSP_BOOT_POWER_STATE)
- .start()
- .write_addr(kPcm5122Address, I2C_MASTER_READ)
- .read(&result, I2C_MASTER_NACK)
- .stop();
-
- esp_err_t err = transaction.Execute();
- if (err == ESP_ERR_TIMEOUT) {
- return std::pair(false, POWERDOWN);
- } else {
- }
- ESP_ERROR_CHECK(err);
-
+ uint8_t result = ReadRegister(pcm512x::POWER_STATE);
bool is_booted = result >> 7;
PowerState detail = (PowerState)(result & 0b1111);
return std::pair(is_booted, detail);
@@ -163,13 +165,29 @@ bool AudioDac::WaitForPowerState(
}
auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
- // Disable the current output, if it isn't already stopped.
- WriteRegister(Register::POWER_MODE, 1 << 4);
- i2s_channel_disable(i2s_handle_);
+ if (i2s_active_) {
+ WriteRegister(pcm512x::MUTE, 0b10001);
+ vTaskDelay(1);
+ WriteRegister(pcm512x::POWER, 1 << 4);
+ i2s_channel_disable(i2s_handle_);
+ }
// I2S reconfiguration.
-
- slot_config_.slot_bit_width = (i2s_slot_bit_width_t)bps;
+ uint8_t bps_bits = 0;
+ switch (bps) {
+ case BPS_16:
+ slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
+ bps_bits = 0;
+ break;
+ case BPS_24:
+ slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_24BIT;
+ bps_bits = 0b10;
+ break;
+ case BPS_32:
+ slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT;
+ bps_bits = 0b11;
+ break;
+ }
ESP_ERROR_CHECK(i2s_channel_reconfig_std_slot(i2s_handle_, &slot_config_));
clock_config_.sample_rate_hz = rate;
@@ -181,30 +199,185 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_));
// DAC reconfiguration.
+ // Inspired heavily by https://github.com/tommag/PCM51xx_Arduino (MIT).
+
+ // Check that the bit clock (PLL input) is between 1MHz and 50MHz. It always
+ // should be.
+ uint32_t bckFreq = rate * bps * 2;
+ if (bckFreq < 1000000 || bckFreq > 50000000) {
+ ESP_LOGE(kTag, "bck freq out of range");
+ return;
+ }
+
+ // 24 bits is not supported for 44.1kHz and 48kHz.
+ if ((rate == SAMPLE_RATE_44_1 || rate == SAMPLE_RATE_48) && bps == BPS_24) {
+ // TODO(jacqueline): I think this *can* be implemented, but requires a bunch
+ // of maths.
+ ESP_LOGE(kTag, "sample rate and bps mismatch");
+ return;
+ }
- // TODO: base on BPS
- WriteRegister(Register::I2S_FORMAT, 0);
+ // Initialize system clock from the I2S BCK input
+ // Disable clock autoset and ignore SCK detection
+ WriteRegister(pcm512x::ERROR_DETECT, 0x1A);
+ // Set PLL clock source to BCK
+ WriteRegister(pcm512x::PLL_REF, 0x10);
+ // Set DAC clock source to PLL output
+ WriteRegister(pcm512x::DAC_REF, 0x10);
+
+ // PLL configuration
+ int p, j, d, r;
+
+ // Clock dividers
+ int nmac, ndac, ncp, dosr, idac;
+
+ if (rate == SAMPLE_RATE_11_025 || rate == SAMPLE_RATE_22_05 ||
+ rate == SAMPLE_RATE_44_1) {
+ // 44.1kHz and derivatives.
+ // P = 1, R = 2, D = 0 for all supported combinations.
+ // Set J to have PLL clk = 90.3168 MHz
+ p = 1;
+ r = 2;
+ j = 90316800 / bckFreq / r;
+ d = 0;
+
+ // Derive clocks from the 90.3168MHz PLL
+ nmac = 2;
+ ndac = 16;
+ ncp = 4;
+ dosr = 8;
+ idac = 1024; // DSP clock / sample rate
+ } else {
+ // 8kHz and multiples.
+ // PLL config for a 98.304 MHz PLL clk
+ if (bps == BPS_24 && bckFreq > 1536000) {
+ p = 3;
+ } else if (bckFreq > 12288000) {
+ p = 2;
+ } else {
+ p = 1;
+ }
+
+ r = 2;
+ j = 98304000 / (bckFreq / p) / r;
+ d = 0;
+
+ // Derive clocks from the 98.304MHz PLL
+ switch (rate) {
+ case SAMPLE_RATE_16:
+ nmac = 6;
+ break;
+ case SAMPLE_RATE_32:
+ nmac = 3;
+ break;
+ default:
+ nmac = 2;
+ break;
+ }
+
+ ndac = 16;
+ ncp = 4;
+ dosr = 384000 / rate;
+ idac = 98304000 / nmac / rate; // DSP clock / sample rate
+ }
+
+ // Configure PLL
+ WriteRegister(pcm512x::PLL_COEFF_0, p - 1);
+ WriteRegister(pcm512x::PLL_COEFF_1, j);
+ WriteRegister(pcm512x::PLL_COEFF_2, (d >> 8) & 0x3F);
+ WriteRegister(pcm512x::PLL_COEFF_3, d & 0xFF);
+ WriteRegister(pcm512x::PLL_COEFF_4, r - 1);
+
+ // Clock dividers
+ WriteRegister(pcm512x::DSP_CLKDIV, nmac - 1);
+ WriteRegister(pcm512x::DAC_CLKDIV, ndac - 1);
+ WriteRegister(pcm512x::NCP_CLKDIV, ncp - 1);
+ WriteRegister(pcm512x::OSR_CLKDIV, dosr - 1);
+
+ // IDAC (nb of DSP clock cycles per sample)
+ WriteRegister(pcm512x::IDAC_1, (idac >> 8) & 0xFF);
+ WriteRegister(pcm512x::IDAC_2, idac & 0xFF);
+
+ // FS speed mode
+ int speedMode;
+ if (rate <= SAMPLE_RATE_48) {
+ speedMode = 0;
+ } else if (rate <= SAMPLE_RATE_96) {
+ speedMode = 1;
+ } else if (rate <= SAMPLE_RATE_192) {
+ speedMode = 2;
+ } else {
+ speedMode = 3;
+ }
+ WriteRegister(pcm512x::FS_SPEED_MODE, speedMode);
+
+ WriteRegister(pcm512x::I2S_1, (0b11 << 4) | bps_bits);
+ WriteRegister(pcm512x::I2S_2, 0);
// Configuration is all done, so we can now bring the DAC and I2S stream back
// up. I2S first, since otherwise the DAC will see that there's no clocks and
// shut itself down.
ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_));
- WriteRegister(Register::POWER_MODE, 0);
+ WriteRegister(pcm512x::POWER, 0);
+
+ if (i2s_active_) {
+ vTaskDelay(1);
+ WriteRegister(pcm512x::MUTE, 0);
+ }
+ i2s_active_ = true;
}
-auto AudioDac::WriteData(cpp::span<std::byte> data) -> std::size_t {
+auto AudioDac::WriteData(const cpp::span<const std::byte>& data) -> void {
std::size_t bytes_written = 0;
esp_err_t err = i2s_channel_write(i2s_handle_, data.data(), data.size_bytes(),
- &bytes_written, 0);
+ &bytes_written, portMAX_DELAY);
if (err != ESP_ERR_TIMEOUT) {
ESP_ERROR_CHECK(err);
}
- return bytes_written;
+}
+
+extern "C" IRAM_ATTR auto callback(i2s_chan_handle_t handle,
+ i2s_event_data_t* event,
+ void* user_ctx) -> bool {
+ if (event == nullptr || user_ctx == nullptr) {
+ return false;
+ }
+ if (event->data == nullptr || event->size == 0) {
+ return false;
+ }
+ uint8_t** buf = reinterpret_cast<uint8_t**>(event->data);
+ StreamBufferHandle_t src = reinterpret_cast<StreamBufferHandle_t>(user_ctx);
+ BaseType_t ret = false;
+ std::size_t bytes_received =
+ xStreamBufferReceiveFromISR(src, *buf, event->size, &ret);
+ if (bytes_received < event->size) {
+ memset(*buf + bytes_received, 0, event->size - bytes_received);
+ }
+ return ret;
+}
+
+auto AudioDac::SetSource(StreamBufferHandle_t buffer) -> void {
+ if (i2s_active_) {
+ ESP_ERROR_CHECK(i2s_channel_disable(i2s_handle_));
+ }
+ i2s_event_callbacks_t callbacks{
+ .on_recv = NULL,
+ .on_recv_q_ovf = NULL,
+ .on_sent = NULL,
+ .on_send_q_ovf = NULL,
+ };
+ if (buffer != nullptr) {
+ callbacks.on_sent = &callback;
+ }
+ i2s_channel_register_event_callback(i2s_handle_, &callbacks, buffer);
+ if (i2s_active_) {
+ ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_));
+ }
}
auto AudioDac::Stop() -> void {
LogStatus();
- WriteRegister(Register::POWER_MODE, 1 << 4);
+ WriteRegister(pcm512x::POWER, 1 << 4);
i2s_channel_disable(i2s_handle_);
}
@@ -218,35 +391,47 @@ auto AudioDac::Stop() -> void {
auto AudioDac::LogStatus() -> void {
uint8_t res;
- res = ReadRegister(Register::SAMPLE_RATE_DETECTION);
- ESP_LOGI(kTag, "detected sample rate (want 3): %u", (res >> 4) && 0b111);
+ res = ReadRegister(pcm512x::RATE_DET_1);
+ ESP_LOGI(kTag, "detected sample rate (want 3): %u", (res & 0b01110000) >> 4);
ESP_LOGI(kTag, "detected SCK ratio (want 6): %u", res && 0b1111);
- res = ReadRegister(Register::BCK_DETECTION);
+ res = ReadRegister(pcm512x::RATE_DET_3);
ESP_LOGI(kTag, "detected BCK (want... 16? 32?): %u", res);
- res = ReadRegister(Register::CLOCK_ERROR_STATE);
+ res = ReadRegister(pcm512x::RATE_DET_4);
ESP_LOGI(kTag, "clock errors (want zeroes): ");
ESP_LOGI(kTag, BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(res & 0b1111111));
- res = ReadRegister(Register::CLOCK_STATUS);
+ res = ReadRegister(pcm512x::CLOCK_STATUS);
ESP_LOGI(kTag, "clock status (want zeroes): ");
ESP_LOGI(kTag, BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(res & 0b10111));
- res = ReadRegister(Register::AUTO_MUTE_STATE);
- ESP_LOGI(kTag, "automute status (want 3): %u", res & 0b11);
-
- res = ReadRegister(Register::SOFT_MUTE_STATE);
- ESP_LOGI(kTag, "soft mute pin status (want 3): %u", res & 0b11);
-
- res = ReadRegister(Register::SAMPLE_RATE_STATE);
- ESP_LOGI(kTag, "detected sample speed mode (want 0): %u", res & 0b11);
+ res = ReadRegister(pcm512x::DIGITAL_MUTE_DET);
+ ESP_LOGI(kTag, "automute status (want 0): %u", res & 0b10001);
auto power = ReadPowerState();
ESP_LOGI(kTag, "current power state (want 5): %u", power.second);
}
-void AudioDac::WriteRegister(Register reg, uint8_t val) {
+void AudioDac::WriteRegister(pcm512x::Register r, uint8_t val) {
+ SelectPage(r.page);
+ WriteRegisterRaw(r.reg, val);
+}
+
+uint8_t AudioDac::ReadRegister(pcm512x::Register r) {
+ SelectPage(r.page);
+ return ReadRegisterRaw(r.reg);
+}
+
+void AudioDac::SelectPage(uint8_t page) {
+ if (active_page_ && active_page_ == page) {
+ return;
+ }
+ WriteRegisterRaw(0, page);
+ active_page_ = page;
+}
+
+void AudioDac::WriteRegisterRaw(uint8_t reg, uint8_t val) {
I2CTransaction transaction;
transaction.start()
.write_addr(kPcm5122Address, I2C_MASTER_WRITE)
@@ -256,7 +441,7 @@ void AudioDac::WriteRegister(Register reg, uint8_t val) {
transaction.Execute();
}
-uint8_t AudioDac::ReadRegister(Register reg) {
+uint8_t AudioDac::ReadRegisterRaw(uint8_t reg) {
uint8_t result = 0;
I2CTransaction transaction;
transaction.start()
diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp
index 6ec82787..f8594a5a 100644
--- a/src/drivers/display.cpp
+++ b/src/drivers/display.cpp
@@ -20,6 +20,7 @@
#include "display_init.hpp"
#include "gpio_expander.hpp"
+#include "soc/soc.h"
static const char* kTag = "DISPLAY";
@@ -28,6 +29,10 @@ static const uint8_t kDisplayWidth = 128 + 2;
static const uint8_t kDisplayHeight = 160 + 1;
static const uint8_t kTransactionQueueSize = 10;
+static const gpio_num_t kDisplayDr = GPIO_NUM_33;
+static const gpio_num_t kDisplayLedEn = GPIO_NUM_32;
+static const gpio_num_t kDisplayCs = GPIO_NUM_22;
+
/*
* The size of each of our two display buffers. This is fundamentally a balance
* between performance and memory usage. LVGL docs recommend a buffer 1/10th the
@@ -58,11 +63,28 @@ extern "C" void FlushDataCallback(lv_disp_drv_t* disp_drv,
auto Display::create(GpioExpander* expander,
const displays::InitialisationData& init_data)
- -> std::unique_ptr<Display> {
- // First, turn on the LED backlight.
- expander->set_pin(GpioExpander::DISPLAY_LED, 0);
- expander->set_pin(GpioExpander::DISPLAY_POWER_ENABLE, 1);
- expander->Write();
+ -> Display* {
+ ESP_LOGI(kTag, "Init I/O pins");
+ gpio_config_t dr_config{
+ .pin_bit_mask = 1ULL << kDisplayDr,
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ gpio_config(&dr_config);
+ gpio_set_level(kDisplayDr, 0);
+
+ // TODO: use pwm for the backlight.
+ gpio_config_t led_config{
+ .pin_bit_mask = 1ULL << kDisplayLedEn,
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ gpio_config(&led_config);
+ gpio_set_level(kDisplayLedEn, 1);
// Next, init the SPI device
spi_device_interface_config_t spi_cfg = {
@@ -76,7 +98,7 @@ auto Display::create(GpioExpander* expander,
.cs_ena_posttrans = 0,
.clock_speed_hz = SPI_MASTER_FREQ_40M,
.input_delay_ns = 0,
- .spics_io_num = GPIO_NUM_22,
+ .spics_io_num = kDisplayCs,
.flags = 0,
.queue_size = kTransactionQueueSize,
.pre_cb = NULL,
@@ -103,13 +125,16 @@ auto Display::create(GpioExpander* expander,
display->driver_.draw_buf = &display->buffers_;
display->driver_.hor_res = kDisplayWidth;
display->driver_.ver_res = kDisplayHeight;
+ display->driver_.sw_rotate = 1;
+ display->driver_.rotated = LV_DISP_ROT_270;
+ display->driver_.antialiasing = 0;
display->driver_.flush_cb = &FlushDataCallback;
display->driver_.user_data = display.get();
ESP_LOGI(kTag, "Registering driver");
display->display_ = lv_disp_drv_register(&display->driver_);
- return display;
+ return display.release();
}
Display::Display(GpioExpander* gpio, spi_device_handle_t handle)
@@ -190,9 +215,7 @@ void Display::SendTransaction(TransactionType type,
transaction.tx_buffer = data;
}
- // TODO(jacqueline): Move this to an on-board GPIO for speed.
- gpio_->set_pin(GpioExpander::DISPLAY_DR, type);
- gpio_->Write();
+ gpio_set_level(kDisplayDr, type);
// TODO(jacqueline): Handle these errors.
esp_err_t ret = spi_device_polling_transmit(handle_, &transaction);
diff --git a/src/drivers/driver_cache.cpp b/src/drivers/driver_cache.cpp
new file mode 100644
index 00000000..650e6f16
--- /dev/null
+++ b/src/drivers/driver_cache.cpp
@@ -0,0 +1,43 @@
+#include "driver_cache.hpp"
+
+#include <memory>
+#include <mutex>
+
+#include "display.hpp"
+#include "display_init.hpp"
+#include "storage.hpp"
+#include "touchwheel.hpp"
+
+namespace drivers {
+
+DriverCache::DriverCache() : gpios_(std::make_unique<GpioExpander>()) {}
+DriverCache::~DriverCache() {}
+
+auto DriverCache::AcquireGpios() -> GpioExpander* {
+ return gpios_.get();
+}
+
+auto DriverCache::AcquireDac() -> std::shared_ptr<AudioDac> {
+ return Acquire(dac_, [&]() -> AudioDac* {
+ return AudioDac::create(AcquireGpios()).value_or(nullptr);
+ });
+}
+
+auto DriverCache::AcquireDisplay() -> std::shared_ptr<Display> {
+ return Acquire(display_, [&]() -> Display* {
+ return Display::create(AcquireGpios(), displays::kST7735R);
+ });
+}
+
+auto DriverCache::AcquireStorage() -> std::shared_ptr<SdStorage> {
+ return Acquire(storage_, [&]() -> SdStorage* {
+ return SdStorage::create(AcquireGpios()).value_or(nullptr);
+ });
+}
+
+auto DriverCache::AcquireTouchWheel() -> std::shared_ptr<TouchWheel> {
+ return Acquire(touchwheel_,
+ [&]() -> TouchWheel* { return new TouchWheel(); });
+}
+
+} // namespace drivers
diff --git a/src/drivers/i2c.cpp b/src/drivers/i2c.cpp
index 04a6d7d1..6c6fc407 100644
--- a/src/drivers/i2c.cpp
+++ b/src/drivers/i2c.cpp
@@ -9,8 +9,8 @@
namespace drivers {
static const i2c_port_t kI2CPort = I2C_NUM_0;
-static const gpio_num_t kI2CSdaPin = GPIO_NUM_2;
-static const gpio_num_t kI2CSclPin = GPIO_NUM_4;
+static const gpio_num_t kI2CSdaPin = GPIO_NUM_4;
+static const gpio_num_t kI2CSclPin = GPIO_NUM_2;
static const uint32_t kI2CClkSpeed = 400'000;
static constexpr int kCmdLinkSize = I2C_LINK_RECOMMENDED_SIZE(12);
@@ -36,6 +36,9 @@ esp_err_t init_i2c(void) {
if (esp_err_t err = i2c_driver_install(kI2CPort, config.mode, 0, 0, 0)) {
return err;
}
+ if (esp_err_t err = i2c_set_timeout(kI2CPort, 400000)) {
+ return err;
+ }
// TODO: INT line
@@ -57,8 +60,8 @@ I2CTransaction::~I2CTransaction() {
free(buffer_);
}
-esp_err_t I2CTransaction::Execute() {
- return i2c_master_cmd_begin(I2C_NUM_0, handle_, kI2CTimeout);
+esp_err_t I2CTransaction::Execute(uint8_t port) {
+ return i2c_master_cmd_begin(port, handle_, kI2CTimeout);
}
I2CTransaction& I2CTransaction::start() {
diff --git a/src/drivers/include/dac.hpp b/src/drivers/include/dac.hpp
index 06808a78..4952c992 100644
--- a/src/drivers/include/dac.hpp
+++ b/src/drivers/include/dac.hpp
@@ -12,6 +12,7 @@
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
+#include "freertos/stream_buffer.h"
#include "result.hpp"
#include "span.hpp"
@@ -20,6 +21,91 @@
namespace drivers {
+namespace pcm512x {
+class Register {
+ public:
+ uint8_t page;
+ uint8_t reg;
+
+ constexpr Register(uint8_t p, uint8_t r) : page(p), reg(r) {}
+};
+
+constexpr Register RESET(0, 1);
+constexpr Register POWER(0, 2);
+constexpr Register MUTE(0, 3);
+constexpr Register PLL_EN(0, 4);
+constexpr Register SPI_MISO_FUNCTION(0, 6);
+constexpr Register DSP(0, 7);
+constexpr Register GPIO_EN(0, 8);
+constexpr Register BCLK_LRCLK_CFG(0, 9);
+constexpr Register DSP_GPIO_INPUT(0, 10);
+constexpr Register MASTER_MODE(0, 12);
+constexpr Register PLL_REF(0, 13);
+constexpr Register DAC_REF(0, 14);
+constexpr Register GPIO_DACIN(0, 16);
+constexpr Register GPIO_PLLIN(0, 18);
+constexpr Register SYNCHRONIZE(0, 19);
+constexpr Register PLL_COEFF_0(0, 20);
+constexpr Register PLL_COEFF_1(0, 21);
+constexpr Register PLL_COEFF_2(0, 22);
+constexpr Register PLL_COEFF_3(0, 23);
+constexpr Register PLL_COEFF_4(0, 24);
+constexpr Register DSP_CLKDIV(0, 27);
+constexpr Register DAC_CLKDIV(0, 28);
+constexpr Register NCP_CLKDIV(0, 29);
+constexpr Register OSR_CLKDIV(0, 30);
+constexpr Register MASTER_CLKDIV_1(0, 32);
+constexpr Register MASTER_CLKDIV_2(0, 33);
+constexpr Register FS_SPEED_MODE(0, 34);
+constexpr Register IDAC_1(0, 35);
+constexpr Register IDAC_2(0, 36);
+constexpr Register ERROR_DETECT(0, 37);
+constexpr Register I2S_1(0, 40);
+constexpr Register I2S_2(0, 41);
+constexpr Register DAC_ROUTING(0, 42);
+constexpr Register DSP_PROGRAM(0, 43);
+constexpr Register CLKDET(0, 44);
+constexpr Register AUTO_MUTE(0, 59);
+constexpr Register DIGITAL_VOLUME_1(0, 60);
+constexpr Register DIGITAL_VOLUME_2(0, 61);
+constexpr Register DIGITAL_VOLUME_3(0, 62);
+constexpr Register DIGITAL_MUTE_1(0, 63);
+constexpr Register DIGITAL_MUTE_2(0, 64);
+constexpr Register DIGITAL_MUTE_3(0, 65);
+constexpr Register GPIO_OUTPUT_1(0, 80);
+constexpr Register GPIO_OUTPUT_2(0, 81);
+constexpr Register GPIO_OUTPUT_3(0, 82);
+constexpr Register GPIO_OUTPUT_4(0, 83);
+constexpr Register GPIO_OUTPUT_5(0, 84);
+constexpr Register GPIO_OUTPUT_6(0, 85);
+constexpr Register GPIO_CONTROL_1(0, 86);
+constexpr Register GPIO_CONTROL_2(0, 87);
+constexpr Register OVERFLOW(0, 90);
+constexpr Register RATE_DET_1(0, 91);
+constexpr Register RATE_DET_2(0, 92);
+constexpr Register RATE_DET_3(0, 93);
+constexpr Register RATE_DET_4(0, 94);
+constexpr Register CLOCK_STATUS(0, 95);
+constexpr Register ANALOG_MUTE_DET(0, 108);
+constexpr Register POWER_STATE(0, 118);
+constexpr Register GPIN(0, 119);
+constexpr Register DIGITAL_MUTE_DET(0, 120);
+
+constexpr Register OUTPUT_AMPLITUDE(1, 1);
+constexpr Register ANALOG_GAIN_CTRL(1, 2);
+constexpr Register UNDERVOLTAGE_PROT(1, 5);
+constexpr Register ANALOG_MUTE_CTRL(1, 6);
+constexpr Register ANALOG_GAIN_BOOST(1, 7);
+constexpr Register VCOM_CTRL_1(1, 8);
+constexpr Register VCOM_CTRL_2(1, 9);
+
+constexpr Register CRAM_CTRL(44, 1);
+
+constexpr Register FLEX_A(253, 63);
+constexpr Register FLEX_B(253, 64);
+
+} // namespace pcm512x
+
/**
* Interface for a PCM5122PWR DAC, configured over I2C.
*/
@@ -31,8 +117,7 @@ class AudioDac {
FAILED_TO_INSTALL_I2S,
};
- static auto create(GpioExpander* expander)
- -> cpp::result<std::unique_ptr<AudioDac>, Error>;
+ static auto create(GpioExpander* expander) -> cpp::result<AudioDac*, Error>;
AudioDac(GpioExpander* gpio, i2s_chan_handle_t i2s_handle);
~AudioDac();
@@ -64,14 +149,21 @@ class AudioDac {
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_32 = 32000,
SAMPLE_RATE_44_1 = 44100,
SAMPLE_RATE_48 = 48000,
+ SAMPLE_RATE_96 = 96000,
+ SAMPLE_RATE_192 = 192000,
};
// TODO(jacqueline): worth supporting channels here as well?
auto Reconfigure(BitsPerSample bps, SampleRate rate) -> void;
- auto WriteData(cpp::span<std::byte> data) -> std::size_t;
+ auto WriteData(const cpp::span<const std::byte>& data) -> void;
+ auto SetSource(StreamBufferHandle_t buffer) -> void;
auto Stop() -> void;
auto LogStatus() -> void;
@@ -83,6 +175,8 @@ class AudioDac {
private:
GpioExpander* gpio_;
i2s_chan_handle_t i2s_handle_;
+ bool i2s_active_;
+ std::optional<uint8_t> active_page_;
i2s_std_clk_config_t clock_config_;
i2s_std_slot_config_t slot_config_;
@@ -93,29 +187,12 @@ class AudioDac {
*/
bool WaitForPowerState(std::function<bool(bool, PowerState)> predicate);
- enum Register {
- PAGE_SELECT = 0,
- RESET = 1,
- POWER_MODE = 2,
- DE_EMPHASIS = 7,
- DAC_CLOCK_SOURCE = 14,
- CLOCK_ERRORS = 37,
- I2S_FORMAT = 40,
- DIGITAL_VOLUME_L = 61,
- DIGITAL_VOLUME_R = 62,
-
- SAMPLE_RATE_DETECTION = 91,
- BCK_DETECTION = 93,
- CLOCK_ERROR_STATE = 94,
- CLOCK_STATUS = 95,
- AUTO_MUTE_STATE = 108,
- SOFT_MUTE_STATE = 114,
- SAMPLE_RATE_STATE = 115,
- DSP_BOOT_POWER_STATE = 118,
- };
+ void WriteRegister(pcm512x::Register r, uint8_t val);
+ uint8_t ReadRegister(pcm512x::Register r);
- void WriteRegister(Register reg, uint8_t val);
- uint8_t ReadRegister(Register reg);
+ void SelectPage(uint8_t page);
+ void WriteRegisterRaw(uint8_t reg, uint8_t val);
+ uint8_t ReadRegisterRaw(uint8_t reg);
};
} // namespace drivers
diff --git a/src/drivers/include/display.hpp b/src/drivers/include/display.hpp
index 8157c3a5..9e4a0224 100644
--- a/src/drivers/include/display.hpp
+++ b/src/drivers/include/display.hpp
@@ -23,8 +23,7 @@ class Display {
* us back any kind of signal to tell us we're actually using them correctly.
*/
static auto create(GpioExpander* expander,
- const displays::InitialisationData& init_data)
- -> std::unique_ptr<Display>;
+ const displays::InitialisationData& init_data) -> Display*;
Display(GpioExpander* gpio, spi_device_handle_t handle);
~Display();
diff --git a/src/drivers/include/driver_cache.hpp b/src/drivers/include/driver_cache.hpp
new file mode 100644
index 00000000..c56ebc3f
--- /dev/null
+++ b/src/drivers/include/driver_cache.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+#include "dac.hpp"
+#include "display.hpp"
+#include "gpio_expander.hpp"
+#include "storage.hpp"
+#include "touchwheel.hpp"
+
+namespace drivers {
+
+class DriverCache {
+ private:
+ std::unique_ptr<GpioExpander> gpios_;
+ std::weak_ptr<AudioDac> dac_;
+ std::weak_ptr<Display> display_;
+ std::weak_ptr<SdStorage> storage_;
+ std::weak_ptr<TouchWheel> touchwheel_;
+ // TODO(jacqueline): Haptics, samd
+
+ std::mutex mutex_;
+
+ template <typename T, typename F>
+ auto Acquire(std::weak_ptr<T> ptr, F factory) -> std::shared_ptr<T> {
+ std::shared_ptr<T> acquired = ptr.lock();
+ if (acquired) {
+ return acquired;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+
+ acquired = ptr.lock();
+ if (acquired) {
+ return acquired;
+ }
+ acquired.reset(factory());
+ ptr = acquired;
+ return acquired;
+ }
+
+ public:
+ DriverCache();
+ ~DriverCache();
+
+ auto AcquireGpios() -> GpioExpander*;
+ auto AcquireDac() -> std::shared_ptr<AudioDac>;
+ auto AcquireDisplay() -> std::shared_ptr<Display>;
+ auto AcquireStorage() -> std::shared_ptr<SdStorage>;
+ auto AcquireTouchWheel() -> std::shared_ptr<TouchWheel>;
+};
+
+} // namespace drivers
diff --git a/src/drivers/include/gpio_expander.hpp b/src/drivers/include/gpio_expander.hpp
index a6e96d87..cd3719a0 100644
--- a/src/drivers/include/gpio_expander.hpp
+++ b/src/drivers/include/gpio_expander.hpp
@@ -35,28 +35,28 @@ class GpioExpander {
static const uint8_t kPca8575Timeout = pdMS_TO_TICKS(100);
// Port A:
- // 0 - audio power enable
- // 1 - usb interface power enable (active low)
- // 2 - display power enable
- // 3 - touchpad power enable
- // 4 - sd card power enable
- // 5 - sd mux switch
- // 6 - LDO enable
- // 7 - charge power ok (active low)
- // All power switches low, sd mux pointing away from us, inputs high.
- static const uint8_t kPortADefault = 0b10000010;
+ // 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 - unused
- // 2 - volume up
- // 3 - volume down
- // 4 - lock switch
- // 5 - touchpad interupt
- // 6 - display DR
- // 7 - display LED
- // Inputs all high, all others low.
- static const uint8_t kPortBDefault = 0b00111101;
+ // 0 - trs output enable
+ // 1 - 3.5mm jack detect (active low)
+ // 2 - NC
+ // 3 - NC
+ // 4 - NC
+ // 5 - NC
+ // 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
@@ -99,30 +99,30 @@ class GpioExpander {
/* Maps each pin of the expander to its number in a `pack`ed uint16. */
enum Pin {
// Port A
- AUDIO_POWER_ENABLE = 0,
- USB_INTERFACE_POWER_ENABLE = 1,
- DISPLAY_POWER_ENABLE = 2,
- TOUCHPAD_POWER_ENABLE = 3,
- SD_CARD_POWER_ENABLE = 4,
- SD_MUX_SWITCH = 5,
- LDO_ENABLE = 6,
- CHARGE_POWER_OK = 7, // Active-low input
+ SD_MUX_SWITCH = 0,
+ SD_MUX_EN_ACTIVE_LOW = 1,
+ KEY_UP = 2,
+ KEY_DOWN = 3,
+ KEY_LOCK = 4,
+ DISPLAY_RESET = 5,
+ // UNUSED = 6,
+ SD_CARD_POWER_ENABLE = 7,
// Port B
- PHONE_DETECT = 8, // Active-high input
- // UNUSED = 9,
- VOL_UP = 10,
- VOL_DOWN = 11,
- LOCK = 12,
- TOUCHPAD_INT = 13,
- DISPLAY_DR = 14,
- DISPLAY_LED = 15,
+ AMP_EN = 8,
+ PHONE_DETECT = 9,
+ // UNUSED = 10,
+ // UNUSED = 11,
+ // UNUSED = 12,
+ // UNUSED = 13,
+ // UNUSED = 14,
+ // UNUSED = 15,
};
/* Nicer value names for use with the SD_MUX_SWITCH pin. */
enum SdController {
- SD_MUX_ESP = 1,
- SD_MUX_USB = 0,
+ SD_MUX_ESP = 0,
+ SD_MUX_SAMD = 1,
};
/**
diff --git a/src/drivers/include/i2c.hpp b/src/drivers/include/i2c.hpp
index dbdd8a11..811c9333 100644
--- a/src/drivers/include/i2c.hpp
+++ b/src/drivers/include/i2c.hpp
@@ -35,7 +35,7 @@ class I2CTransaction {
* ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
- esp_err_t Execute();
+ esp_err_t Execute(uint8_t port = I2C_NUM_0);
/*
* Enqueues a start condition. May also be used for repeated start
diff --git a/src/drivers/include/storage.hpp b/src/drivers/include/storage.hpp
index 64ce4782..c19ec935 100644
--- a/src/drivers/include/storage.hpp
+++ b/src/drivers/include/storage.hpp
@@ -25,14 +25,13 @@ class SdStorage {
FAILED_TO_MOUNT,
};
- static auto create(GpioExpander* gpio)
- -> cpp::result<std::shared_ptr<SdStorage>, Error>;
+ static auto create(GpioExpander* gpio) -> cpp::result<SdStorage*, Error>;
SdStorage(GpioExpander* gpio,
esp_err_t (*do_transaction)(sdspi_dev_handle_t, sdmmc_command_t*),
sdspi_dev_handle_t handle_,
- std::unique_ptr<sdmmc_host_t>& host_,
- std::unique_ptr<sdmmc_card_t>& card_,
+ std::unique_ptr<sdmmc_host_t> host_,
+ std::unique_ptr<sdmmc_card_t> card_,
FATFS* fs_);
~SdStorage();
diff --git a/src/drivers/include/touchwheel.hpp b/src/drivers/include/touchwheel.hpp
new file mode 100644
index 00000000..3dfa182b
--- /dev/null
+++ b/src/drivers/include/touchwheel.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <stdint.h>
+#include <functional>
+
+#include "esp_err.h"
+#include "result.hpp"
+
+#include "gpio_expander.hpp"
+
+namespace drivers {
+
+struct TouchWheelData {
+ bool is_touched = false;
+ uint8_t wheel_position = -1;
+};
+
+class TouchWheel {
+ public:
+ TouchWheel();
+ ~TouchWheel();
+
+ // Not copyable or movable.
+ TouchWheel(const TouchWheel&) = delete;
+ TouchWheel& operator=(const TouchWheel&) = delete;
+
+ auto Update() -> void;
+ auto GetTouchWheelData() const -> TouchWheelData;
+
+ private:
+ TouchWheelData data_;
+
+ enum Register {
+ FIRMWARE_VERSION = 0x1,
+ DETECTION_STATUS = 0x2,
+ KEY_STATUS_A = 0x3,
+ KEY_STATUS_B = 0x4,
+ SLIDER_POSITION = 0x5,
+ CALIBRATE = 0x6,
+ RESET = 0x7,
+ LOW_POWER = 0x8,
+ SLIDER_OPTIONS = 0x14,
+ };
+
+ void WriteRegister(uint8_t reg, uint8_t val);
+ uint8_t ReadRegister(uint8_t reg);
+};
+
+} // namespace drivers
diff --git a/src/drivers/storage.cpp b/src/drivers/storage.cpp
index 88159744..d90bd811 100644
--- a/src/drivers/storage.cpp
+++ b/src/drivers/storage.cpp
@@ -49,8 +49,9 @@ static esp_err_t do_transaction(sdspi_dev_handle_t handle,
}
} // namespace callback
-auto SdStorage::create(GpioExpander* gpio)
- -> cpp::result<std::shared_ptr<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_MUX_EN_ACTIVE_LOW, 0);
gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP);
gpio->Write();
@@ -103,16 +104,16 @@ auto SdStorage::create(GpioExpander* gpio)
return cpp::fail(Error::FAILED_TO_MOUNT);
}
- return std::make_unique<SdStorage>(gpio, do_transaction, handle, host, card,
- fs);
+ return new SdStorage(gpio, do_transaction, handle, std::move(host),
+ std::move(card), fs);
}
SdStorage::SdStorage(GpioExpander* gpio,
esp_err_t (*do_transaction)(sdspi_dev_handle_t,
sdmmc_command_t*),
sdspi_dev_handle_t handle,
- std::unique_ptr<sdmmc_host_t>& host,
- std::unique_ptr<sdmmc_card_t>& card,
+ std::unique_ptr<sdmmc_host_t> host,
+ std::unique_ptr<sdmmc_card_t> card,
FATFS* fs)
: gpio_(gpio),
do_transaction_(do_transaction),
@@ -136,6 +137,10 @@ SdStorage::~SdStorage() {
// Uninstall the SPI driver
sdspi_host_remove_device(this->handle_);
sdspi_host_deinit();
+
+ gpio_->set_pin(GpioExpander::SD_CARD_POWER_ENABLE, 0);
+ gpio_->set_pin(GpioExpander::SD_MUX_EN_ACTIVE_LOW, 1);
+ gpio_->Write();
}
auto SdStorage::HandleTransaction(sdspi_dev_handle_t handle,
diff --git a/src/drivers/touchwheel.cpp b/src/drivers/touchwheel.cpp
new file mode 100644
index 00000000..51a67187
--- /dev/null
+++ b/src/drivers/touchwheel.cpp
@@ -0,0 +1,95 @@
+#include "touchwheel.hpp"
+#include <stdint.h>
+
+#include <cstdint>
+
+#include "assert.h"
+#include "driver/gpio.h"
+#include "driver/i2c.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "freertos/projdefs.h"
+#include "hal/gpio_types.h"
+#include "hal/i2c_types.h"
+
+#include "i2c.hpp"
+
+namespace drivers {
+
+static const char* kTag = "TOUCHWHEEL";
+static const uint8_t kTouchWheelAddress = 0x1C;
+static const gpio_num_t kIntPin = GPIO_NUM_25;
+
+TouchWheel::TouchWheel() {
+ gpio_config_t int_config{
+ .pin_bit_mask = 1ULL << kIntPin,
+ .mode = GPIO_MODE_INPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ gpio_config(&int_config);
+
+ WriteRegister(Register::RESET, 1);
+ // TODO(daniel): do we need this? how long does reset take?
+ vTaskDelay(pdMS_TO_TICKS(1));
+ WriteRegister(Register::SLIDER_OPTIONS, 0b11000000);
+ WriteRegister(Register::CALIBRATE, 1);
+}
+
+TouchWheel::~TouchWheel() {}
+
+void TouchWheel::WriteRegister(uint8_t reg, uint8_t val) {
+ // uint8_t maskedReg = reg | kWriteMask;
+ uint8_t maskedReg = reg;
+ I2CTransaction transaction;
+ transaction.start()
+ .write_addr(kTouchWheelAddress, I2C_MASTER_WRITE)
+ .write_ack(maskedReg, val)
+ .stop();
+ transaction.Execute();
+ // TODO(jacqueline): check for errors again when i find where all the ffc
+ // cables went q.q
+ // ESP_ERROR_CHECK(transaction.Execute());
+}
+
+uint8_t TouchWheel::ReadRegister(uint8_t reg) {
+ uint8_t res;
+ I2CTransaction transaction;
+ transaction.start()
+ .write_addr(kTouchWheelAddress, I2C_MASTER_WRITE)
+ .write_ack(reg)
+ .start()
+ .write_addr(kTouchWheelAddress, I2C_MASTER_READ)
+ .read(&res, I2C_MASTER_NACK)
+ .stop();
+ ESP_ERROR_CHECK(transaction.Execute());
+ return res;
+}
+
+void TouchWheel::Update() {
+ // Read data from device into member struct
+ bool has_data = !gpio_get_level(GPIO_NUM_25);
+ if (!has_data) {
+ return;
+ }
+ uint8_t status = ReadRegister(Register::DETECTION_STATUS);
+ if (status & 0b10000000) {
+ // Still calibrating.
+ return;
+ }
+ if (status & 0b10) {
+ // Slider detect.
+ data_.wheel_position = ReadRegister(Register::SLIDER_POSITION);
+ }
+ if (status & 0b1) {
+ // Key detect.
+ // TODO(daniel): implement me
+ }
+}
+
+TouchWheelData TouchWheel::GetTouchWheelData() const {
+ return data_;
+}
+
+} // namespace drivers