Mostly working pipeline, including proper EOF signalling

custom
jacqueline 2 years ago
parent cabfd4b75e
commit 61c91b3cdb
  1. 12
      src/audio/audio_decoder.cpp
  2. 8
      src/audio/audio_playback.cpp
  3. 10
      src/audio/audio_task.cpp
  4. 11
      src/audio/chunk.cpp
  5. 10
      src/audio/fatfs_audio_input.cpp
  6. 107
      src/audio/i2s_audio_output.cpp
  7. 1
      src/audio/include/audio_decoder.hpp
  8. 2
      src/audio/include/audio_element.hpp
  9. 4
      src/audio/include/audio_task.hpp
  10. 5
      src/audio/include/chunk.hpp
  11. 1
      src/audio/include/fatfs_audio_input.hpp
  12. 12
      src/audio/include/i2s_audio_output.hpp
  13. 2
      src/audio/include/stream_event.hpp
  14. 11
      src/audio/stream_event.cpp
  15. 64
      src/drivers/dac.cpp
  16. 8
      src/drivers/display.cpp
  17. 2
      src/drivers/display_init.cpp
  18. 10
      src/drivers/include/dac.hpp
  19. 8
      src/main/main.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());
}
}

@ -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)

@ -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);
}

@ -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

@ -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>(

@ -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

@ -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;

@ -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

@ -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>;

@ -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;

@ -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;

@ -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

@ -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 {

@ -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;
}

@ -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) {

@ -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(

@ -96,7 +96,7 @@ static const uint8_t kST7735RCommonFooter[]{
const InitialisationData kST7735R = {
.num_sequences = 3,
.sequences = {kST7735RCommonHeader, kST7735RCommonRed,
.sequences = {kST7735RCommonHeader, kST7735RCommonGreen,
kST7735RCommonFooter}};
} // namespace displays

@ -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.

@ -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");

Loading…
Cancel
Save