|
|
|
@ -4,9 +4,9 @@ |
|
|
|
|
|
|
|
|
|
#include "assert.h" |
|
|
|
|
#include "driver/i2c.h" |
|
|
|
|
#include "driver/i2s.h" |
|
|
|
|
#include "driver/i2s_common.h" |
|
|
|
|
#include "driver/i2s_std.h" |
|
|
|
|
#include "driver/i2s_types.h" |
|
|
|
|
#include "driver/i2s_types_legacy.h" |
|
|
|
|
#include "esp_err.h" |
|
|
|
|
#include "esp_log.h" |
|
|
|
|
#include "hal/i2c_types.h" |
|
|
|
@ -28,45 +28,43 @@ static const AudioDac::BitsPerSample kDefaultBps = AudioDac::BPS_16; |
|
|
|
|
|
|
|
|
|
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(I2S_NUM_AUTO, I2S_ROLE_MASTER); |
|
|
|
|
i2s_new_channel(&channel_config, &i2s_handle, NULL); |
|
|
|
|
//
|
|
|
|
|
// First, instantiate the instance so it can do all of its power on
|
|
|
|
|
// configuration.
|
|
|
|
|
std::unique_ptr<AudioDac> dac = std::make_unique<AudioDac>(expander); |
|
|
|
|
std::unique_ptr<AudioDac> dac = std::make_unique<AudioDac>(expander, i2s_handle); |
|
|
|
|
|
|
|
|
|
// Whilst we wait for the initial boot, we can work on installing the I2S
|
|
|
|
|
// driver.
|
|
|
|
|
i2s_config_t i2s_config = { |
|
|
|
|
// static_cast bc esp-adf uses enums incorrectly
|
|
|
|
|
.mode = static_cast<i2s_mode_t>(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, |
|
|
|
|
// TODO(jacqueline): tune dma buffer size. this seems very smol.
|
|
|
|
|
.dma_buf_count = 8, |
|
|
|
|
.dma_buf_len = 64, |
|
|
|
|
.use_apll = false, |
|
|
|
|
.tx_desc_auto_clear = false, |
|
|
|
|
.fixed_mclk = 0, |
|
|
|
|
.mclk_multiple = I2S_MCLK_MULTIPLE_512, // TODO: double check
|
|
|
|
|
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, |
|
|
|
|
i2s_std_config_t i2s_config = { |
|
|
|
|
.clk_cfg = dac->clock_config_, |
|
|
|
|
.slot_cfg = dac->slot_config_, |
|
|
|
|
.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 = false, |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (esp_err_t err = |
|
|
|
|
i2s_driver_install(kI2SPort, &i2s_config, 0, NULL) != ESP_OK) { |
|
|
|
|
ESP_LOGE(kTag, "failed to configure i2s pins %x", err); |
|
|
|
|
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); |
|
|
|
|
return cpp::fail(Error::FAILED_TO_INSTALL_I2S); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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}; |
|
|
|
|
if (esp_err_t err = i2s_set_pin(kI2SPort, &pin_config) != ESP_OK) { |
|
|
|
|
ESP_LOGE(kTag, "failed to configure i2s pins %x", err); |
|
|
|
|
return cpp::fail(Error::FAILED_TO_INSTALL_I2S); |
|
|
|
|
} |
|
|
|
|
// TODO: does starting the channel mean the dac will boot into a more
|
|
|
|
|
// meaningful state?
|
|
|
|
|
i2s_channel_enable(dac->i2s_handle_); |
|
|
|
|
|
|
|
|
|
// Now let's double check that the DAC itself came up whilst we we working.
|
|
|
|
|
bool is_booted = dac->WaitForPowerState( |
|
|
|
@ -92,13 +90,17 @@ auto AudioDac::create(GpioExpander* expander) |
|
|
|
|
return dac; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AudioDac::AudioDac(GpioExpander* gpio) : gpio_(gpio) { |
|
|
|
|
AudioDac::AudioDac(GpioExpander* gpio, i2s_chan_handle_t i2s_handle) : gpio_(gpio), |
|
|
|
|
i2s_handle_(i2s_handle), |
|
|
|
|
clock_config_(I2S_STD_CLK_DEFAULT_CONFIG(48000)), |
|
|
|
|
slot_config_(I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO)) { |
|
|
|
|
gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, true); |
|
|
|
|
gpio_->Write(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AudioDac::~AudioDac() { |
|
|
|
|
i2s_driver_uninstall(kI2SPort); |
|
|
|
|
i2s_channel_disable(i2s_handle_); |
|
|
|
|
i2s_del_channel(i2s_handle_); |
|
|
|
|
gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, false); |
|
|
|
|
gpio_->Write(); |
|
|
|
|
} |
|
|
|
@ -136,7 +138,7 @@ bool AudioDac::WaitForPowerState( |
|
|
|
|
if (has_matched) { |
|
|
|
|
break; |
|
|
|
|
} else { |
|
|
|
|
ESP_LOGI(kTag, "Waiting for power state (was %d %x)", result.first, |
|
|
|
|
ESP_LOGI(kTag, "Waiting for power state (was %d 0x%x)", result.first, |
|
|
|
|
(uint8_t)result.second); |
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(1)); |
|
|
|
|
} |
|
|
|
@ -148,14 +150,23 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> bool { |
|
|
|
|
// 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.
|
|
|
|
|
i2s_set_clk(kI2SPort, rate, bps, I2S_CHANNEL_STEREO); |
|
|
|
|
i2s_channel_disable(i2s_handle_); |
|
|
|
|
|
|
|
|
|
slot_config_.slot_bit_width = (i2s_slot_bit_width_t) bps; |
|
|
|
|
i2s_channel_reconfig_std_slot(i2s_handle_, &slot_config_); |
|
|
|
|
|
|
|
|
|
// TODO: update mclk multiple as well if needed?
|
|
|
|
|
clock_config_.sample_rate_hz = rate; |
|
|
|
|
i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_); |
|
|
|
|
|
|
|
|
|
i2s_channel_enable(i2s_handle_); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto AudioDac::WriteData(const cpp::span<std::byte>& data, TickType_t max_wait) |
|
|
|
|
-> std::size_t { |
|
|
|
|
std::size_t res = 0; |
|
|
|
|
i2s_write(kI2SPort, data.data(), data.size(), &res, max_wait); |
|
|
|
|
i2s_channel_write(i2s_handle_, data.data(), data.size(), &res, max_wait); |
|
|
|
|
return res; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|