From a64fbc1cf513820fd83d0a7e80ca5acf0d7df795 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 11 Oct 2022 10:50:55 +1100 Subject: [PATCH] the music plays! --- main/CMakeLists.txt | 2 +- main/dac.cpp | 223 ++++++++----------------------------------- main/dac.h | 73 +++++--------- main/gay-ipod-fw.cpp | 136 ++++++++++++++++++++++---- main/gpio-expander.h | 2 +- main/storage.h | 2 +- 6 files changed, 186 insertions(+), 252 deletions(-) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b9409cb4..223c8ca2 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( SRCS "gay-ipod-fw.cpp" "dac.cpp" "gpio-expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" INCLUDE_DIRS "." - REQUIRES "esp_adc_cal" "fatfs") + REQUIRES "esp_adc_cal" "fatfs" "audio_pipeline" "audio_stream") diff --git a/main/dac.cpp b/main/dac.cpp index cf1ad4b9..513793db 100644 --- a/main/dac.cpp +++ b/main/dac.cpp @@ -1,5 +1,6 @@ #include "dac.h" +#include "esp_err.h" #include "i2c.h" #include "esp_log.h" #include "assert.h" @@ -18,175 +19,29 @@ AudioDac::AudioDac(GpioExpander *gpio) { AudioDac::~AudioDac() {}; -void AudioDac::Start(SampleRate sample_rate, BitDepth bit_depth) { - // First check that the arguments look okay. - - // Check that the bit clock (PLL input) is between 1MHz and 50MHz - uint32_t bckFreq = sample_rate * bit_depth * 2; - if (bckFreq < 1000000 || bckFreq > 50000000) { - return; - } - - // 24 bits is not supported for 44.1kHz and 48kHz. - if ((sample_rate == SAMPLE_RATE_44_1K || sample_rate == SAMPLE_RATE_48K) - && bit_depth == BIT_DEPTH_24) { - return; - } - - // Next, wait for the IC to boot up before we try configuring it. - bool is_booted = false; - for (int i=0; i<10; i++) { - uint8_t result = ReadPowerState(); - is_booted = result >> 7; - if (is_booted) { - break; - } else { - ESP_LOGI(TAG, "Waiting for boot..."); - vTaskDelay(pdMS_TO_TICKS(1)); - } - } - +esp_err_t AudioDac::Start() { + bool is_booted = WaitForPowerState([](bool booted, PowerState state){ return booted; }); if (!is_booted) { - // TODO: properly handle - ESP_LOGE(TAG, "Timed out waiting for boot!"); - return; - } - - // Now do all the math required to correctly set up the internal clocks. - int p, j, d, r; - int nmac, ndac, ncp, dosr, idac; - if (sample_rate == SAMPLE_RATE_11_025K || - sample_rate == SAMPLE_RATE_22_05K || - sample_rate == SAMPLE_RATE_44_1K) { - //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 (bit_depth == BIT_DEPTH_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 (sample_rate) { - case SAMPLE_RATE_16K: nmac = 6; break; - case SAMPLE_RATE_32K: nmac = 3; break; - default: nmac = 2; break; - }; - - ndac = 16; - ncp = 4; - dosr = 384000 / sample_rate; - idac = 98304000 / nmac / sample_rate; // DSP clock / sample rate - } - - // FS speed mode - int speedMode; - if (sample_rate <= SAMPLE_RATE_48K) { - speedMode = 0; - } else if (sample_rate <= SAMPLE_RATE_96K) { - speedMode = 1; - } else if (sample_rate <= SAMPLE_RATE_192K) { - speedMode = 2; - } else { - speedMode = 3; + ESP_LOGE(TAG, "Timed out waiting for boot"); + return ESP_ERR_TIMEOUT; } - // Set correct I2S config - uint8_t i2s_format = 0; - switch (bit_depth) { - case BIT_DEPTH_16: i2s_format = 0x00; break; - case BIT_DEPTH_24: i2s_format = 0x02; break; - case BIT_DEPTH_32: i2s_format = 0x03; break; - }; + WriteRegister(Register::DE_EMPHASIS, 1 << 4); + WriteVolume(100); - // We've calculated all of our data. Now assemble the big list of registers - // that we need to configure. - I2CTransaction transaction; - transaction - .start() - .write_addr(kPCM5122Address, I2C_MASTER_WRITE) - // All our registers are on the first page. - .write_ack(Register::PAGE_SELECT, 0) - // Disable clock autoset and ignore SCK detection - .write_ack(Register::IGNORE_ERRORS, 0x1A) - // Set PLL clock source to BCK - .write_ack(Register::PLL_CLOCK_SOURCE, 0x10) - // Set DAC clock source to PLL output - .write_ack(Register::DAC_CLOCK_SOURCE, 0x10) - - // Configure PLL - .write_ack(Register::PLL_P, p - 1) - .write_ack(Register::PLL_J, j) - .write_ack(Register::PLL_D_MSB, (d >> 8) & 0x3F) - .write_ack(Register::PLL_D_LSB, d & 0xFF) - .write_ack(Register::PLL_R, r - 1) - - // Clock dividers - .write_ack(Register::DSP_CLOCK_DIV, nmac - 1) - .write_ack(Register::DAC_CLOCK_DIV, ndac - 1) - .write_ack(Register::NCP_CLOCK_DIV, ncp - 1) - .write_ack(Register::OSR_CLOCK_DIV, dosr - 1) - - // IDAC (nb of DSP clock cycles per sample) - .write_ack(Register::IDAC_MSB, (idac >> 8) & 0xFF) - .write_ack(Register::IDAC_LSB, idac & 0xFF) - - .write_ack(Register::FS_SPEED_MODE, speedMode) - .write_ack(Register::I2S_FORMAT, i2s_format) + WaitForPowerState([](bool booted, PowerState state){ + return state == WAIT_FOR_CP || state == RAMP_UP || state == RUN || state == STANDBY; + }); - .stop(); - - ESP_LOGI(TAG, "Configuring DAC"); - // TODO: Handle this gracefully. - ESP_ERROR_CHECK(transaction.Execute()); - - // The DAC takes a moment to reconfigure itself. Give it some time before we - // start asking for its state. - vTaskDelay(pdMS_TO_TICKS(5)); - - // TODO: investigate why it's stuck waiting for CP voltage. - /* - bool is_configured = false; - for (int i=0; i<10; i++) { - uint8_t result = ReadPowerState(); - is_configured = (result & 0b1111) == 0b1001; - if (is_configured) { - break; - } else { - ESP_LOGI(TAG, "Waiting for configure..."); - vTaskDelay(pdMS_TO_TICKS(1)); - } - } + return ESP_OK; +} - if (!is_configured) { - // TODO: properly handle - ESP_LOGE(TAG, "Timed out waiting for configure!"); - return; - } - */ +void AudioDac::WriteVolume(uint8_t volume) { + WriteRegister(Register::DIGITAL_VOLUME_L, volume); + WriteRegister(Register::DIGITAL_VOLUME_R, volume); } -uint8_t AudioDac::ReadPowerState() { +std::pair AudioDac::ReadPowerState() { uint8_t result = 0; I2CTransaction transaction; @@ -201,31 +56,35 @@ uint8_t AudioDac::ReadPowerState() { ESP_ERROR_CHECK(transaction.Execute()); - return result; + bool is_booted = result >> 7; + PowerState detail = (PowerState) (result & 0b1111); + return std::pair(is_booted, detail); } -void AudioDac::WriteVolume(uint8_t volume) { - I2CTransaction transaction; - transaction - .start() - .write_addr(kPCM5122Address, I2C_MASTER_WRITE) - .write_ack(Register::DIGITAL_VOLUME_L, volume) - .write_ack(Register::DIGITAL_VOLUME_R, volume) - .stop(); - - ESP_ERROR_CHECK(transaction.Execute()); -} - -void AudioDac::WritePowerMode(PowerMode mode) { - switch (mode) { - case ON: - case STANDBY: - // TODO: enable power switch. - break; - case OFF: - // TODO: disable power switch. +bool AudioDac::WaitForPowerState(std::function predicate) { + bool has_matched = false; + for (int i=0; i<10; i++) { + std::pair result = ReadPowerState(); + has_matched = predicate(result.first, result.second); + if (has_matched) { break; + } else { + ESP_LOGI(TAG, "Waiting for power state (was %d %x)", result.first, (uint8_t) result.second); + vTaskDelay(pdMS_TO_TICKS(1)); + } } + return has_matched; +} + +void AudioDac::WriteRegister(Register reg, uint8_t val) { + I2CTransaction transaction; + transaction + .start() + .write_addr(kPCM5122Address, I2C_MASTER_WRITE) + .write_ack(reg, val) + .stop(); + // TODO: Retry once? + ESP_ERROR_CHECK(transaction.Execute()); } } // namespace gay_ipod diff --git a/main/dac.h b/main/dac.h index bb06a0e7..bbc8485d 100644 --- a/main/dac.h +++ b/main/dac.h @@ -1,7 +1,9 @@ #pragma once +#include "esp_err.h" #include "gpio-expander.h" #include +#include namespace gay_ipod { @@ -9,41 +11,18 @@ namespace gay_ipod { static const uint8_t kPCM5122Timeout = 100 / portTICK_RATE_MS; /** - * PCM5122PWR - * - * Heavily inspired from the Arduino library here: - * https://github.com/tommag/PCM51xx_Arduino/ + * Interface for a PCM5122PWR DAC, configured over I2C. */ class AudioDac { public: AudioDac(GpioExpander *gpio); ~AudioDac(); - /** Supported sample rates */ - enum SampleRate { - SAMPLE_RATE_8K = 8000, - SAMPLE_RATE_11_025K = 11025, - SAMPLE_RATE_16K = 16000, - SAMPLE_RATE_22_05K = 22050, - SAMPLE_RATE_32K = 32000, - SAMPLE_RATE_44_1K = 44100, - SAMPLE_RATE_48K = 48000, - SAMPLE_RATE_96K = 96000, - SAMPLE_RATE_192K = 192000, - SAMPLE_RATE_384K = 384000 - }; - - enum BitDepth { - BIT_DEPTH_16 = 16, - BIT_DEPTH_24 = 24, - BIT_DEPTH_32 = 32, - }; - /** * Performs initial configuration of the DAC and sets it to begin expecting * I2S audio data. */ - void Start(SampleRate sample_rate, BitDepth bit_depth); + esp_err_t Start(); /** * Sets the volume on a scale from 0 (loudest) to 254 (quietest). A value of @@ -51,47 +30,43 @@ class AudioDac { */ void WriteVolume(uint8_t volume); - enum PowerMode { - ON = 0, - STANDBY = 0x10, - OFF = 0x01, + enum PowerState { + POWERDOWN = 0b0, + WAIT_FOR_CP = 0b1, + CALIBRATION_1 = 0b10, + CALIBRATION_2 = 0b11, + RAMP_UP = 0b100, + RUN = 0b101, + SHORT = 0b110, + RAMP_DOWN = 0b111, + STANDBY = 0b1000, }; - void WritePowerMode(PowerMode state); + /* Returns the current boot-up status and internal state of the DAC */ + std::pair ReadPowerState(); // Not copyable or movable. - // TODO: maybe this could be movable? AudioDac(const AudioDac&) = delete; AudioDac& operator=(const AudioDac&) = delete; private: GpioExpander *gpio_; - PowerMode last_power_state_ = PowerMode::OFF; + + /* + * Pools the power state for up to 10ms, waiting for the given predicate to + * be true. + */ + bool WaitForPowerState(std::function predicate); enum Register { PAGE_SELECT = 0, - IGNORE_ERRORS = 37, - PLL_CLOCK_SOURCE = 13, - DAC_CLOCK_SOURCE = 14, - PLL_P = 20, - PLL_J = 21, - PLL_D_MSB = 22, - PLL_D_LSB = 23, - PLL_R = 24, - DSP_CLOCK_DIV = 27, - DAC_CLOCK_DIV = 14, - NCP_CLOCK_DIV = 29, - OSR_CLOCK_DIV = 30, - IDAC_MSB = 35, - IDAC_LSB = 36, - FS_SPEED_MODE = 34, - I2S_FORMAT = 40, + DE_EMPHASIS = 7, DIGITAL_VOLUME_L = 61, DIGITAL_VOLUME_R = 62, DSP_BOOT_POWER_STATE = 118, }; - uint8_t ReadPowerState(); + void WriteRegister(Register reg, uint8_t val); }; } // namespace gay_ipod diff --git a/main/gay-ipod-fw.cpp b/main/gay-ipod-fw.cpp index 7d91d5b2..dfae9c7b 100644 --- a/main/gay-ipod-fw.cpp +++ b/main/gay-ipod-fw.cpp @@ -7,6 +7,7 @@ #include "driver/adc.h" #include "driver/gpio.h" #include "driver/i2c.h" +#include "driver/i2s.h" #include "driver/sdspi_host.h" #include "driver/spi_common.h" #include "driver/spi_master.h" @@ -16,10 +17,18 @@ #include "gpio-expander.h" #include "hal/adc_types.h" #include "hal/gpio_types.h" +#include "hal/i2s_types.h" #include "hal/spi_types.h" #include "storage.h" -#define I2C_SDA_IO (GPIO_NUM_0) +#include "audio_element.h" +#include "audio_pipeline.h" +#include "audio_common.h" +#include "fatfs_stream.h" +#include "i2s_stream.h" +#include "mp3_decoder.h" + +#define I2C_SDA_IO (GPIO_NUM_2) #define I2C_SCL_IO (GPIO_NUM_4) #define I2C_CLOCK_HZ (400000) @@ -114,31 +123,122 @@ extern "C" void app_main(void) ESP_LOGI(TAG, "Trying to init DAC"); gay_ipod::AudioDac dac(&expander); - dac.Start( - gay_ipod::AudioDac::SAMPLE_RATE_48K, - gay_ipod::AudioDac::BIT_DEPTH_16); + dac.Start(); vTaskDelay(pdMS_TO_TICKS(1000)); - ESP_LOGI(TAG, "Looks okay? Let's list some files!"); - vTaskDelay(pdMS_TO_TICKS(1000)); + ESP_LOGI(TAG, "Looks okay? Let's play some music!"); + + i2s_port_t port = I2S_NUM_0; + + i2s_config_t i2s_config = { + // Weird enum usage in ESP IDF. + .mode = static_cast(I2S_MODE_MASTER | I2S_MODE_TX), + .sample_rate = 44100, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = ESP_INTR_FLAG_LOWMED, + .dma_buf_count = 8, + .dma_buf_len = 64, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, + .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, + }; - DIR *d; - struct dirent *dir; - d = opendir(gay_ipod::kStoragePath); - if (d) { - while ((dir = readdir(d)) != NULL) { - ESP_LOGI(TAG, "file! %s", dir->d_name); - } - closedir(d); - } else { - ESP_LOGI(TAG, "nope!"); - } + //ESP_ERROR_CHECK(i2s_driver_install(port, &i2s_config, 0, NULL)); + + audio_pipeline_handle_t pipeline; + audio_element_handle_t fatfs_stream_reader, i2s_stream_writer, audio_decoder; + + audio_pipeline_cfg_t pipeline_config = audio_pipeline_cfg_t(DEFAULT_AUDIO_PIPELINE_CONFIG()); + pipeline = audio_pipeline_init(&pipeline_config); + assert(pipeline != NULL); + ESP_LOGI(TAG, "Made pipeline okay."); + + fatfs_stream_cfg_t fatfs_stream_config = fatfs_stream_cfg_t(FATFS_STREAM_CFG_DEFAULT()); + fatfs_stream_config.type = AUDIO_STREAM_READER; + fatfs_stream_reader = fatfs_stream_init(&fatfs_stream_config); + assert(fatfs_stream_reader != NULL); + ESP_LOGI(TAG, "Made reader okay."); + + i2s_stream_cfg_t i2s_stream_config = i2s_stream_cfg_t{ + .type = AUDIO_STREAM_WRITER, + .i2s_config = i2s_config, + .i2s_port = port, + .use_alc = false, + .volume = 0, // Does nothing; use the dac + .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE, + .task_stack = I2S_STREAM_TASK_STACK, + .task_core = I2S_STREAM_TASK_CORE, + .task_prio = I2S_STREAM_TASK_PRIO, + .stack_in_ext = false, + .multi_out_num = 0, + .uninstall_drv = true, + .need_expand = false, + .expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT, + }; + // TODO fix hardcoded mclk :( + i2s_stream_writer = i2s_stream_init(&i2s_stream_config); + assert(i2s_stream_writer != NULL); + ESP_LOGI(TAG, "Made i2s stream okay."); + + + ESP_LOGI(TAG, "Init i2s pins"); + i2s_pin_config_t pin_config = { + .mck_io_num = GPIO_NUM_0, + .bck_io_num = GPIO_NUM_26, + .ws_io_num = GPIO_NUM_27, + .data_out_num = GPIO_NUM_5, + .data_in_num = I2S_PIN_NO_CHANGE + }; + + ESP_ERROR_CHECK(i2s_set_pin(port, &pin_config)); + + mp3_decoder_cfg_t decoder_config = mp3_decoder_cfg_t(DEFAULT_MP3_DECODER_CONFIG()); + audio_decoder = mp3_decoder_init(&decoder_config); + assert(audio_decoder != NULL); + ESP_LOGI(TAG, "Made mp3 decoder okay."); + + vTaskDelay(pdMS_TO_TICKS(500)); + ESP_LOGI(TAG, "Putting it all together"); + + audio_pipeline_register(pipeline, fatfs_stream_reader, "file"); + audio_pipeline_register(pipeline, audio_decoder, "dec"); + audio_pipeline_register(pipeline, i2s_stream_writer, "i2s"); + + const char *link_tag[3] = {"file", "dec", "i2s"}; + audio_pipeline_link(pipeline, &link_tag[0], 3); + + ESP_LOGI(TAG, "Trying to play something??"); + audio_element_set_uri(fatfs_stream_reader, "/sdcard/test.mp3"); + audio_pipeline_run(pipeline); vTaskDelay(pdMS_TO_TICKS(1000)); + ESP_LOGI(TAG, "It could be playing? idk"); + + gay_ipod::AudioDac::PowerState state = dac.ReadPowerState().second; + ESP_LOGI(TAG, "dac power state: %x", (uint8_t)state); + + vTaskDelay(pdMS_TO_TICKS(120000)); + ESP_LOGI(TAG, "Time to deinit."); + audio_pipeline_stop(pipeline); + audio_pipeline_wait_for_stop(pipeline); + audio_pipeline_terminate(pipeline); + + audio_pipeline_unregister(pipeline, fatfs_stream_reader); + audio_pipeline_unregister(pipeline, audio_decoder); + audio_pipeline_unregister(pipeline, i2s_stream_writer); + + audio_pipeline_deinit(pipeline); + audio_element_deinit(fatfs_stream_reader); + audio_element_deinit(i2s_stream_writer); + audio_element_deinit(audio_decoder); + storage.Release(); ESP_LOGI(TAG, "Hooray!"); - vTaskDelay(pdMS_TO_TICKS(1000)); } diff --git a/main/gpio-expander.h b/main/gpio-expander.h index 93e2d3e0..f3b5b804 100644 --- a/main/gpio-expander.h +++ b/main/gpio-expander.h @@ -55,7 +55,7 @@ class GpioExpander { // 6 - GPIO // 7 - GPIO // DAC mute output low, everything else is active-low inputs. - static const uint8_t kPortBDefault = 0b11111101; + static const uint8_t kPortBDefault = 0b11111111; /* * Convenience mehod for packing the port a and b bytes into a single 16 bit diff --git a/main/storage.h b/main/storage.h index fc1f8f5d..c93a426b 100644 --- a/main/storage.h +++ b/main/storage.h @@ -22,7 +22,7 @@ namespace sdspi { } } // namespace sdspi -static const char *kStoragePath = "/sd"; +static const char *kStoragePath = "/sdcard"; static const uint8_t kMaxOpenFiles = 8; class SdStorage {