the music plays!

custom
jacqueline 3 years ago
parent b9ee0eb88f
commit a64fbc1cf5
  1. 2
      main/CMakeLists.txt
  2. 211
      main/dac.cpp
  3. 73
      main/dac.h
  4. 136
      main/gay-ipod-fw.cpp
  5. 2
      main/gpio-expander.h
  6. 2
      main/storage.h

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

@ -1,5 +1,6 @@
#include "dac.h"
#include "esp_err.h"
#include "i2c.h"
#include "esp_log.h"
#include "assert.h"
@ -18,214 +19,72 @@ 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;
ESP_LOGE(TAG, "Timed out waiting for boot");
return ESP_ERR_TIMEOUT;
}
// 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;
};
WriteRegister(Register::DE_EMPHASIS, 1 << 4);
WriteVolume(100);
WaitForPowerState([](bool booted, PowerState state){
return state == WAIT_FOR_CP || state == RAMP_UP || state == RUN || state == STANDBY;
});
ndac = 16;
ncp = 4;
dosr = 384000 / sample_rate;
idac = 98304000 / nmac / sample_rate; // DSP clock / sample rate
return ESP_OK;
}
// 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;
void AudioDac::WriteVolume(uint8_t volume) {
WriteRegister(Register::DIGITAL_VOLUME_L, volume);
WriteRegister(Register::DIGITAL_VOLUME_R, volume);
}
// 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;
};
std::pair<bool, AudioDac::PowerState> AudioDac::ReadPowerState() {
uint8_t result = 0;
// 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)
.write_ack(DSP_BOOT_POWER_STATE)
.start()
.write_addr(kPCM5122Address, I2C_MASTER_READ)
.read(&result, I2C_MASTER_NACK)
.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));
bool is_booted = result >> 7;
PowerState detail = (PowerState) (result & 0b1111);
return std::pair(is_booted, detail);
}
// TODO: investigate why it's stuck waiting for CP voltage.
/*
bool is_configured = false;
bool AudioDac::WaitForPowerState(std::function<bool(bool,AudioDac::PowerState)> predicate) {
bool has_matched = false;
for (int i=0; i<10; i++) {
uint8_t result = ReadPowerState();
is_configured = (result & 0b1111) == 0b1001;
if (is_configured) {
std::pair<bool, PowerState> result = ReadPowerState();
has_matched = predicate(result.first, result.second);
if (has_matched) {
break;
} else {
ESP_LOGI(TAG, "Waiting for configure...");
ESP_LOGI(TAG, "Waiting for power state (was %d %x)", result.first, (uint8_t) result.second);
vTaskDelay(pdMS_TO_TICKS(1));
}
}
if (!is_configured) {
// TODO: properly handle
ESP_LOGE(TAG, "Timed out waiting for configure!");
return;
}
*/
return has_matched;
}
uint8_t AudioDac::ReadPowerState() {
uint8_t result = 0;
void AudioDac::WriteRegister(Register reg, uint8_t val) {
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)
.write_ack(reg, val)
.stop();
// TODO: Retry once?
ESP_ERROR_CHECK(transaction.Execute());
return result;
}
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.
break;
}
}
} // namespace gay_ipod

@ -1,7 +1,9 @@
#pragma once
#include "esp_err.h"
#include "gpio-expander.h"
#include <stdint.h>
#include <functional>
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<bool,PowerState> 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<bool(bool,PowerState)> 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

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

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

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

Loading…
Cancel
Save