summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-02-10 15:32:21 +1100
committerjacqueline <me@jacqueline.id.au>2023-02-10 15:32:21 +1100
commit61c91b3cdb2c9dd655f3adf0f461f5cefb3b2e9b (patch)
tree8d0bb288781f86455f02219d0b7ac8cee493eeb3 /src
parentcabfd4b75ecc733bdf36997606a686c4d2bc277d (diff)
downloadtangara-fw-61c91b3cdb2c9dd655f3adf0f461f5cefb3b2e9b.tar.gz
Mostly working pipeline, including proper EOF signalling
Diffstat (limited to 'src')
-rw-r--r--src/audio/audio_decoder.cpp12
-rw-r--r--src/audio/audio_playback.cpp8
-rw-r--r--src/audio/audio_task.cpp10
-rw-r--r--src/audio/chunk.cpp11
-rw-r--r--src/audio/fatfs_audio_input.cpp10
-rw-r--r--src/audio/i2s_audio_output.cpp107
-rw-r--r--src/audio/include/audio_decoder.hpp1
-rw-r--r--src/audio/include/audio_element.hpp2
-rw-r--r--src/audio/include/audio_task.hpp4
-rw-r--r--src/audio/include/chunk.hpp5
-rw-r--r--src/audio/include/fatfs_audio_input.hpp1
-rw-r--r--src/audio/include/i2s_audio_output.hpp12
-rw-r--r--src/audio/include/stream_event.hpp2
-rw-r--r--src/audio/stream_event.cpp11
-rw-r--r--src/drivers/dac.cpp64
-rw-r--r--src/drivers/display.cpp8
-rw-r--r--src/drivers/display_init.cpp2
-rw-r--r--src/drivers/include/dac.hpp10
-rw-r--r--src/main/main.cpp8
19 files changed, 244 insertions, 44 deletions
diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp
index 27b9cca3..d90ca496 100644
--- a/src/audio/audio_decoder.cpp
+++ b/src/audio/audio_decoder.cpp
@@ -84,6 +84,16 @@ auto AudioDecoder::ProcessChunk(const cpp::span<std::byte>& chunk)
return {};
}
+auto AudioDecoder::ProcessEndOfStream() -> void {
+ has_samples_to_send_ = false;
+ needs_more_input_ = true;
+ current_codec_.reset();
+
+ SendOrBufferEvent(
+ std::unique_ptr<StreamEvent>(
+ StreamEvent::CreateEndOfStream(input_events_)));
+}
+
auto AudioDecoder::Process() -> cpp::result<void, AudioProcessingError> {
if (has_samples_to_send_) {
ESP_LOGI(kTag, "sending samples");
@@ -132,7 +142,7 @@ auto AudioDecoder::Process() -> cpp::result<void, AudioProcessingError> {
has_samples_to_send_ = true;
if (needs_more_input_) {
- chunk_reader_->HandleLeftovers(current_codec_->GetInputPosition());
+ chunk_reader_->HandleBytesUsed(current_codec_->GetInputPosition());
}
}
diff --git a/src/audio/audio_playback.cpp b/src/audio/audio_playback.cpp
index 4df598c5..504a2a4e 100644
--- a/src/audio/audio_playback.cpp
+++ b/src/audio/audio_playback.cpp
@@ -38,9 +38,9 @@ auto AudioPlayback::create(drivers::GpioExpander* expander,
playback->ConnectElements(codec.get(), sink.get());
// Launch!
- playback->element_handles_.push_back(StartAudioTask("src", source));
- playback->element_handles_.push_back(StartAudioTask("dec", codec));
- playback->element_handles_.push_back(StartAudioTask("sink", sink));
+ playback->element_handles_.push_back(StartAudioTask("src", {}, source));
+ playback->element_handles_.push_back(StartAudioTask("dec", {}, codec));
+ playback->element_handles_.push_back(StartAudioTask("sink", 0, sink));
playback->input_handle_ = source->InputEventQueue();
@@ -60,6 +60,8 @@ auto AudioPlayback::Play(const std::string& filename) -> void {
info.path = filename;
auto event = StreamEvent::CreateStreamInfo(input_handle_, info);
xQueueSend(input_handle_, &event, portMAX_DELAY);
+ event = StreamEvent::CreateEndOfStream(input_handle_);
+ xQueueSend(input_handle_, &event, portMAX_DELAY);
}
auto AudioPlayback::ConnectElements(IAudioElement* src, IAudioElement* sink)
diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp
index 47b115cc..078aa461 100644
--- a/src/audio/audio_task.cpp
+++ b/src/audio/audio_task.cpp
@@ -28,6 +28,7 @@ namespace audio {
static const char* kTag = "task";
auto StartAudioTask(const std::string& name,
+ std::optional<BaseType_t> core_id,
std::shared_ptr<IAudioElement> element)
-> std::unique_ptr<AudioElementHandle> {
auto task_handle = std::make_unique<TaskHandle_t>();
@@ -36,8 +37,13 @@ auto StartAudioTask(const std::string& name,
AudioTaskArgs* args = new AudioTaskArgs{.element = element};
ESP_LOGI(kTag, "starting audio task %s", name.c_str());
- xTaskCreate(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args,
- kTaskPriorityAudio, task_handle.get());
+ if (core_id) {
+ xTaskCreatePinnedToCore(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args,
+ kTaskPriorityAudio, task_handle.get(), *core_id);
+ } else {
+ xTaskCreate(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args,
+ kTaskPriorityAudio, task_handle.get());
+ }
return std::make_unique<AudioElementHandle>(std::move(task_handle), element);
}
diff --git a/src/audio/chunk.cpp b/src/audio/chunk.cpp
index 61c0dc2b..01760a84 100644
--- a/src/audio/chunk.cpp
+++ b/src/audio/chunk.cpp
@@ -41,8 +41,11 @@ auto ChunkReader::HandleNewData(cpp::span<std::byte> data)
return last_data_in_working_buffer_;
}
-auto ChunkReader::HandleLeftovers(std::size_t bytes_used) -> void {
- leftover_bytes_ = last_data_in_working_buffer_.size() - bytes_used;
+auto ChunkReader::HandleBytesUsed(std::size_t bytes_used) -> void {
+ HandleBytesLeftOver(last_data_in_working_buffer_.size() - bytes_used);
+}
+auto ChunkReader::HandleBytesLeftOver(std::size_t bytes_left) -> void {
+ leftover_bytes_ = bytes_left;
// Ensure that we don't have more than a chunk of leftever bytes. This is
// bad, because we probably won't have enough data to store the next chunk.
@@ -55,4 +58,8 @@ auto ChunkReader::HandleLeftovers(std::size_t bytes_used) -> void {
}
}
+auto ChunkReader::GetLeftovers() -> cpp::span<std::byte> {
+ return working_buffer_.first(leftover_bytes_);
+}
+
} // namespace audio
diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp
index 08f39347..8990bf4f 100644
--- a/src/audio/fatfs_audio_input.cpp
+++ b/src/audio/fatfs_audio_input.cpp
@@ -69,6 +69,16 @@ auto FatfsAudioInput::ProcessChunk(const cpp::span<std::byte>& chunk)
return cpp::fail(UNSUPPORTED_STREAM);
}
+auto FatfsAudioInput::ProcessEndOfStream() -> void {
+ if (is_file_open_) {
+ f_close(&current_file_);
+ is_file_open_ = false;
+ SendOrBufferEvent(
+ std::unique_ptr<StreamEvent>(
+ StreamEvent::CreateEndOfStream(input_events_)));
+ }
+}
+
auto FatfsAudioInput::Process() -> cpp::result<void, AudioProcessingError> {
if (is_file_open_) {
auto dest_event = std::unique_ptr<StreamEvent>(
diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp
index b00e31d3..3bed15b5 100644
--- a/src/audio/i2s_audio_output.cpp
+++ b/src/audio/i2s_audio_output.cpp
@@ -7,6 +7,7 @@
#include "audio_element.hpp"
#include "dac.hpp"
+#include "freertos/projdefs.h"
#include "gpio_expander.hpp"
#include "result.hpp"
@@ -15,6 +16,8 @@ static const char* kTag = "I2SOUT";
namespace audio {
+static const std::size_t kDmaQueueLength = 8;
+
auto I2SAudioOutput::create(drivers::GpioExpander* expander)
-> cpp::result<std::shared_ptr<I2SAudioOutput>, Error> {
// First, we need to perform initial configuration of the DAC chip.
@@ -38,12 +41,26 @@ I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander,
: expander_(expander),
dac_(std::move(dac)),
volume_(255),
- is_soft_muted_(false) {}
+ is_soft_muted_(false),
+ chunk_reader_(),
+ latest_chunk_(),
+ dma_size_(),
+ dma_queue_(nullptr) {}
I2SAudioOutput::~I2SAudioOutput() {
+ if (dma_queue_ != nullptr) {
+ ClearDmaQueue();
+ }
// TODO: power down the DAC.
}
+auto I2SAudioOutput::HasUnprocessedInput() -> bool {
+ if (dma_queue_ == nullptr || !dma_size_) {
+ return false;
+ }
+ return latest_chunk_.size() >= *dma_size_;
+}
+
auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> {
// TODO(jacqueline): probs do something with the channel hey
@@ -53,6 +70,12 @@ auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info)
return cpp::fail(UNSUPPORTED_STREAM);
}
+ if (!info.chunk_size) {
+ ESP_LOGE(kTag, "audio stream missing chunk size");
+ return cpp::fail(UNSUPPORTED_STREAM);
+ }
+ chunk_reader_.emplace(*info.chunk_size);
+
ESP_LOGI(kTag, "incoming audio stream: %u bpp @ %u Hz", *info.bits_per_sample,
*info.sample_rate);
@@ -85,23 +108,78 @@ auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info)
return cpp::fail(UNSUPPORTED_STREAM);
}
- dac_->Reconfigure(bps, sample_rate);
+ QueueHandle_t new_dma_queue =
+ xQueueCreate(kDmaQueueLength, sizeof(std::byte*));
+
+ dma_size_ = dac_->Reconfigure(bps, sample_rate, new_dma_queue);
+
+ if (dma_queue_ != nullptr) {
+ ClearDmaQueue();
+ }
+ dma_queue_ = new_dma_queue;
return {};
}
auto I2SAudioOutput::ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> {
- ESP_LOGI(kTag, "playing samples");
- SetSoftMute(false);
- // TODO(jacqueline): write smaller parts with a small delay so that we can
- // be responsive to pause and seek commands.
- dac_->WriteData(chunk, portMAX_DELAY);
+ ESP_LOGI(kTag, "received new samples");
+ latest_chunk_ = chunk_reader_->HandleNewData(chunk);
return 0;
}
+auto I2SAudioOutput::ProcessEndOfStream() -> void {
+ if (chunk_reader_ && dma_size_) {
+ auto leftovers = chunk_reader_->GetLeftovers();
+ if (leftovers.size() > 0 && leftovers.size() < *dma_size_) {
+ std::byte* dest = static_cast<std::byte*>(malloc(*dma_size_));
+ cpp::span dest_span(dest, *dma_size_);
+
+ std::copy(leftovers.begin(), leftovers.end(), dest_span.begin());
+ std::fill(dest_span.begin() + leftovers.size(), dest_span.end(), static_cast<std::byte>(0));
+
+ xQueueSend(dma_queue_, &dest, portMAX_DELAY);
+ }
+ }
+
+ SendOrBufferEvent(
+ std::unique_ptr<StreamEvent>(
+ StreamEvent::CreateEndOfStream(input_events_)));
+
+ chunk_reader_.reset();
+ dma_size_.reset();
+}
+
auto I2SAudioOutput::Process() -> cpp::result<void, AudioProcessingError> {
- // TODO(jacqueline): Play the stream in smaller sections
+ std::size_t spaces_available = uxQueueSpacesAvailable(dma_queue_);
+ if (spaces_available == 0) {
+ // TODO: think about this more. can this just be the output event queue?
+ vTaskDelay(pdMS_TO_TICKS(100));
+ return {};
+ }
+
+ // Fill the queue as much as possible, since we need to be able to stream
+ // FAST.
+ while (latest_chunk_.size() >= *dma_size_ && spaces_available > 0) {
+ // TODO: small memory arena for this?
+ std::byte* dest = static_cast<std::byte*>(malloc(*dma_size_));
+ cpp::span dest_span(dest, *dma_size_);
+ cpp::span src_span = latest_chunk_.first(*dma_size_);
+ std::copy(src_span.begin(), src_span.end(), dest_span.begin());
+ if (!xQueueSend(dma_queue_, &dest, 0)) {
+ // TODO: calculate how often we expect this to happen.
+ free(dest);
+ break;
+ }
+ latest_chunk_ = latest_chunk_.subspan(*dma_size_);
+ ESP_LOGI(kTag, "wrote dma buffer of size %u", *dma_size_);
+ }
+ if (latest_chunk_.size() < *dma_size_) {
+ // TODO: if this is the end of the stream, then we should be sending this
+ // with zero padding. hmm. i guess we need an explicit EOF event?
+ chunk_reader_->HandleBytesLeftOver(latest_chunk_.size());
+ ESP_LOGI(kTag, "not enough samples for dma buffer");
+ }
return {};
}
@@ -124,4 +202,17 @@ auto I2SAudioOutput::SetSoftMute(bool enabled) -> void {
}
}
+auto I2SAudioOutput::ClearDmaQueue() -> void {
+ // Ensure we don't leak any memory from events leftover in the queue.
+ while (uxQueueSpacesAvailable(dma_queue_) < kDmaQueueLength) {
+ std::byte* data = nullptr;
+ if (xQueueReceive(input_events_, &data, 0)) {
+ free(data);
+ } else {
+ break;
+ }
+ }
+ vQueueDelete(dma_queue_);
+}
+
} // namespace audio
diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp
index d0f7469b..aa83825f 100644
--- a/src/audio/include/audio_decoder.hpp
+++ b/src/audio/include/audio_decoder.hpp
@@ -37,6 +37,7 @@ class AudioDecoder : public IAudioElement {
-> cpp::result<void, AudioProcessingError> override;
auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> override;
+ auto ProcessEndOfStream() -> void override;
auto Process() -> cpp::result<void, AudioProcessingError> override;
AudioDecoder(const AudioDecoder&) = delete;
diff --git a/src/audio/include/audio_element.hpp b/src/audio/include/audio_element.hpp
index c6453d4d..0c80524c 100644
--- a/src/audio/include/audio_element.hpp
+++ b/src/audio/include/audio_element.hpp
@@ -105,6 +105,8 @@ class IAudioElement {
virtual auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> = 0;
+ virtual auto ProcessEndOfStream() -> void = 0;
+
/*
* Called when there has been no data received over the input buffer for some
* time. This could be used to synthesize output, or to save memory by
diff --git a/src/audio/include/audio_task.hpp b/src/audio/include/audio_task.hpp
index 9a76ea7e..399ad679 100644
--- a/src/audio/include/audio_task.hpp
+++ b/src/audio/include/audio_task.hpp
@@ -1,9 +1,12 @@
#pragma once
#include <memory>
+#include <string>
+#include <optional>
#include "audio_element.hpp"
#include "audio_element_handle.hpp"
+#include "freertos/portmacro.h"
namespace audio {
@@ -12,6 +15,7 @@ struct AudioTaskArgs {
};
auto StartAudioTask(const std::string& name,
+ std::optional<BaseType_t> core_id,
std::shared_ptr<IAudioElement> element)
-> std::unique_ptr<AudioElementHandle>;
diff --git a/src/audio/include/chunk.hpp b/src/audio/include/chunk.hpp
index 0ece1ed6..31e1f969 100644
--- a/src/audio/include/chunk.hpp
+++ b/src/audio/include/chunk.hpp
@@ -27,7 +27,8 @@ class ChunkReader {
explicit ChunkReader(std::size_t chunk_size);
~ChunkReader();
- auto HandleLeftovers(std::size_t bytes_used) -> void;
+ auto HandleBytesLeftOver(std::size_t bytes_left) -> void;
+ auto HandleBytesUsed(std::size_t bytes_used) -> void;
/*
* Reads chunks of data from the given input stream, and invokes the given
@@ -43,6 +44,8 @@ class ChunkReader {
*/
auto HandleNewData(cpp::span<std::byte> data) -> cpp::span<std::byte>;
+ auto GetLeftovers() -> cpp::span<std::byte>;
+
ChunkReader(const ChunkReader&) = delete;
ChunkReader& operator=(const ChunkReader&) = delete;
diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp
index 5625d941..883441c2 100644
--- a/src/audio/include/fatfs_audio_input.hpp
+++ b/src/audio/include/fatfs_audio_input.hpp
@@ -28,6 +28,7 @@ class FatfsAudioInput : public IAudioElement {
-> cpp::result<void, AudioProcessingError> override;
auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> override;
+ auto ProcessEndOfStream() -> void override;
auto Process() -> cpp::result<void, AudioProcessingError> override;
FatfsAudioInput(const FatfsAudioInput&) = delete;
diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp
index 02ac7a16..b4fd4c59 100644
--- a/src/audio/include/i2s_audio_output.hpp
+++ b/src/audio/include/i2s_audio_output.hpp
@@ -4,6 +4,7 @@
#include <memory>
#include "audio_element.hpp"
+#include "chunk.hpp"
#include "result.hpp"
#include "dac.hpp"
@@ -21,13 +22,13 @@ class I2SAudioOutput : public IAudioElement {
std::unique_ptr<drivers::AudioDac> dac);
~I2SAudioOutput();
- // TODO.
- auto HasUnprocessedInput() -> bool override { return false; }
+ auto HasUnprocessedInput() -> bool override;
auto ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> override;
auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> override;
+ auto ProcessEndOfStream() -> void override;
auto Process() -> cpp::result<void, AudioProcessingError> override;
I2SAudioOutput(const I2SAudioOutput&) = delete;
@@ -37,11 +38,18 @@ class I2SAudioOutput : public IAudioElement {
auto SetVolume(uint8_t volume) -> void;
auto SetSoftMute(bool enabled) -> void;
+ auto ClearDmaQueue() -> void;
+
drivers::GpioExpander* expander_;
std::unique_ptr<drivers::AudioDac> dac_;
uint8_t volume_;
bool is_soft_muted_;
+
+ std::optional<ChunkReader> chunk_reader_;
+ cpp::span<std::byte> latest_chunk_;
+ std::optional<std::size_t> dma_size_;
+ QueueHandle_t dma_queue_;
};
} // namespace audio
diff --git a/src/audio/include/stream_event.hpp b/src/audio/include/stream_event.hpp
index 4f441fa5..e84c8388 100644
--- a/src/audio/include/stream_event.hpp
+++ b/src/audio/include/stream_event.hpp
@@ -16,6 +16,7 @@ struct StreamEvent {
static auto CreateChunkData(QueueHandle_t source, std::size_t chunk_size)
-> StreamEvent*;
static auto CreateChunkNotification(QueueHandle_t source) -> StreamEvent*;
+ static auto CreateEndOfStream(QueueHandle_t source) -> StreamEvent*;
StreamEvent();
~StreamEvent();
@@ -28,6 +29,7 @@ struct StreamEvent {
STREAM_INFO,
CHUNK_DATA,
CHUNK_NOTIFICATION,
+ END_OF_STREAM,
} tag;
union {
diff --git a/src/audio/stream_event.cpp b/src/audio/stream_event.cpp
index e3228680..6efebbca 100644
--- a/src/audio/stream_event.cpp
+++ b/src/audio/stream_event.cpp
@@ -37,6 +37,13 @@ auto StreamEvent::CreateChunkNotification(QueueHandle_t source)
return event;
}
+auto StreamEvent::CreateEndOfStream(QueueHandle_t source) -> StreamEvent* {
+ auto event = new StreamEvent;
+ event->tag = StreamEvent::END_OF_STREAM;
+ event->source = source;
+ return event;
+}
+
StreamEvent::StreamEvent() : tag(StreamEvent::UNINITIALISED) {}
StreamEvent::~StreamEvent() {
@@ -51,6 +58,8 @@ StreamEvent::~StreamEvent() {
break;
case CHUNK_NOTIFICATION:
break;
+ case END_OF_STREAM:
+ break;
}
}
@@ -70,6 +79,8 @@ StreamEvent::StreamEvent(StreamEvent&& other) {
break;
case CHUNK_NOTIFICATION:
break;
+ case END_OF_STREAM:
+ break;
}
other.tag = StreamEvent::UNINITIALISED;
}
diff --git a/src/drivers/dac.cpp b/src/drivers/dac.cpp
index 214feb28..eec1959f 100644
--- a/src/drivers/dac.cpp
+++ b/src/drivers/dac.cpp
@@ -10,6 +10,7 @@
#include "driver/i2s_types.h"
#include "esp_err.h"
#include "esp_log.h"
+#include "freertos/portmacro.h"
#include "hal/i2c_types.h"
#include "gpio_expander.hpp"
@@ -27,14 +28,22 @@ static const AudioDac::SampleRate kDefaultSampleRate =
AudioDac::SAMPLE_RATE_44_1;
static const AudioDac::BitsPerSample kDefaultBps = AudioDac::BPS_16;
+extern "C" {
+bool dma_callback(i2s_chan_handle_t handle,
+ i2s_event_data_t* event,
+ void* user_ctx) {
+ AudioDac* dac = static_cast<AudioDac*>(user_ctx);
+ return dac->WriteDataFromISR(static_cast<std::byte*>(event->data),
+ event->size);
+}
+}
+
auto AudioDac::create(GpioExpander* expander)
-> cpp::result<std::unique_ptr<AudioDac>, Error> {
// TODO: tune.
i2s_chan_handle_t i2s_handle;
i2s_chan_config_t channel_config =
I2S_CHANNEL_DEFAULT_CONFIG(kI2SPort, I2S_ROLE_MASTER);
- // Auto clear to trigger soft-mute when we run out of data.
- channel_config.auto_clear = true;
ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &i2s_handle, NULL));
//
// First, instantiate the instance so it can do all of its power on
@@ -99,7 +108,8 @@ AudioDac::AudioDac(GpioExpander* gpio, i2s_chan_handle_t i2s_handle)
i2s_handle_(i2s_handle),
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)) {
+ I2S_SLOT_MODE_STEREO)),
+ dma_queue_(nullptr) {
gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, true);
gpio_->Write();
}
@@ -157,7 +167,9 @@ bool AudioDac::WaitForPowerState(
return has_matched;
}
-auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> bool {
+auto AudioDac::Reconfigure(BitsPerSample bps,
+ SampleRate rate,
+ QueueHandle_t dma_queue) -> std::size_t {
// TODO(jacqueline): investigate how reliable the auto-clocking of the dac
// is. We might need to explicit reconfigure the dac here as well if it's not
// good enough.
@@ -171,20 +183,44 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> bool {
bps == BPS_24 ? I2S_MCLK_MULTIPLE_384 : I2S_MCLK_MULTIPLE_256;
ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_));
+ dma_queue_ = dma_queue;
+
+ // TODO: less spooky action here plz.
+ // dma_buffer_size = dma_frame_num (channel config) * slot_num (always 2?) *
+ // slot_bit_width / 8
+ //size_t dma_size = 240 * 2 * slot_config_.slot_bit_width / 8;
+ size_t dma_size = 960;
+ ESP_LOGI(kTag, "new dma size: %u bytes", dma_size);
+
+ i2s_event_callbacks_t callbacks = {
+ .on_recv = NULL,
+ .on_recv_q_ovf = NULL,
+ .on_sent = &dma_callback,
+ .on_send_q_ovf = NULL,
+ };
+ ESP_ERROR_CHECK(
+ i2s_channel_register_event_callback(i2s_handle_, &callbacks, this));
+
ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_));
- return true;
+
+ return dma_size;
}
-auto AudioDac::WriteData(const cpp::span<std::byte>& data, TickType_t max_wait)
- -> std::size_t {
- std::size_t res = 0;
- esp_err_t err =
- i2s_channel_write(i2s_handle_, data.data(), data.size(), &res, max_wait);
- if (err == ESP_ERR_TIMEOUT) {
- return res;
+auto AudioDac::WriteDataFromISR(std::byte* data, std::size_t size) -> bool {
+ std::byte* new_data;
+ BaseType_t high_priority_task_awoken = pdFALSE;
+
+ if (xQueueReceiveFromISR(dma_queue_, &new_data, &high_priority_task_awoken)) {
+ // Item was received. Copy it into the DMA buffer.
+ memcpy(data, new_data, size);
+ free(new_data);
+ ESP_DRAM_LOGI(kTag, "wrote dma");
+ } else {
+ // No item was received. Write empty data.
+ memset(data, 0, size);
}
- ESP_ERROR_CHECK(err);
- return res;
+
+ return high_priority_task_awoken;
}
void AudioDac::WriteRegister(Register reg, uint8_t val) {
diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp
index 2ada4a3e..8d62cda2 100644
--- a/src/drivers/display.cpp
+++ b/src/drivers/display.cpp
@@ -49,7 +49,7 @@ namespace drivers {
namespace callback {
static std::atomic<Display*> instance = nullptr;
-static void flush_cb(lv_disp_drv_t* disp_drv,
+extern "C" void flush_cb(lv_disp_drv_t* disp_drv,
const lv_area_t* area,
lv_color_t* color_map) {
auto instance_unwrapped = instance.load();
@@ -74,8 +74,9 @@ static void IRAM_ATTR post_cb(spi_transaction_t* transaction) {
auto Display::create(GpioExpander* expander,
const displays::InitialisationData& init_data)
-> cpp::result<std::unique_ptr<Display>, Error> {
- expander->with(
- [&](auto& gpio) { gpio.set_pin(GpioExpander::DISPLAY_LED, 1); });
+ expander->set_pin(GpioExpander::DISPLAY_LED, 0);
+ expander->set_pin(GpioExpander::DISPLAY_POWER_ENABLE, 1);
+ expander->Write();
// Next, init the SPI device
spi_device_interface_config_t spi_cfg = {
@@ -182,6 +183,7 @@ void Display::SendTransaction(TransactionType type,
if (length == 0) {
return;
}
+ ESP_LOGI(kTag, "lvgl transaction");
// TODO: Use a memory pool for these.
spi_transaction_t* transaction = (spi_transaction_t*)heap_caps_calloc(
diff --git a/src/drivers/display_init.cpp b/src/drivers/display_init.cpp
index e66d6a47..293ea5a2 100644
--- a/src/drivers/display_init.cpp
+++ b/src/drivers/display_init.cpp
@@ -96,7 +96,7 @@ static const uint8_t kST7735RCommonFooter[]{
const InitialisationData kST7735R = {
.num_sequences = 3,
- .sequences = {kST7735RCommonHeader, kST7735RCommonRed,
+ .sequences = {kST7735RCommonHeader, kST7735RCommonGreen,
kST7735RCommonFooter}};
} // namespace displays
diff --git a/src/drivers/include/dac.hpp b/src/drivers/include/dac.hpp
index aec89c72..698019b7 100644
--- a/src/drivers/include/dac.hpp
+++ b/src/drivers/include/dac.hpp
@@ -4,6 +4,7 @@
#include <functional>
#include <memory>
+#include <optional>
#include <utility>
#include "driver/i2s_std.h"
@@ -66,11 +67,11 @@ class AudioDac {
};
// TODO(jacqueline): worth supporting channels here as well?
- auto Reconfigure(BitsPerSample bps, SampleRate rate) -> bool;
-
- auto WriteData(const cpp::span<std::byte>& data, TickType_t max_wait)
+ auto Reconfigure(BitsPerSample bps, SampleRate rate, QueueHandle_t dma_queue)
-> std::size_t;
+ auto WriteDataFromISR(std::byte* data, std::size_t size) -> bool;
+
// Not copyable or movable.
AudioDac(const AudioDac&) = delete;
AudioDac& operator=(const AudioDac&) = delete;
@@ -82,6 +83,9 @@ class AudioDac {
i2s_std_clk_config_t clock_config_;
i2s_std_slot_config_t slot_config_;
+ // TODO: volatile?
+ volatile QueueHandle_t dma_queue_;
+
/*
* Pools the power state for up to 10ms, waiting for the given predicate to
* be true.
diff --git a/src/main/main.cpp b/src/main/main.cpp
index fde416ce..08b31cd3 100644
--- a/src/main/main.cpp
+++ b/src/main/main.cpp
@@ -48,7 +48,7 @@ struct LvglArgs {
drivers::GpioExpander* gpio_expander;
};
-void lvgl_main(void* voidArgs) {
+extern "C" void lvgl_main(void* voidArgs) {
ESP_LOGI(TAG, "starting LVGL task");
LvglArgs* args = (LvglArgs*)voidArgs;
drivers::GpioExpander* gpio_expander = args->gpio_expander;
@@ -97,10 +97,10 @@ extern "C" void app_main(void) {
ESP_LOGI(TAG, "Enable power rails for development");
expander->with([&](auto& gpio) {
- gpio.set_pin(drivers::GpioExpander::AUDIO_POWER_ENABLE, 1);
- gpio.set_pin(drivers::GpioExpander::SD_CARD_POWER_ENABLE, 1);
+ gpio.set_pin(drivers::GpioExpander::USB_INTERFACE_POWER_ENABLE, 0);
+ gpio.set_pin(drivers::GpioExpander::SD_CARD_POWER_ENABLE, 0);
gpio.set_pin(drivers::GpioExpander::SD_MUX_SWITCH,
- drivers::GpioExpander::SD_MUX_ESP);
+ drivers::GpioExpander::SD_MUX_USB);
});
ESP_LOGI(TAG, "Init battery measurement");