parent
122306d619
commit
6c793efa0d
@ -1,4 +1,4 @@ |
||||
idf_component_register( |
||||
SRCS "gay-ipod-fw.cpp" "gpio-expander.cpp" "battery.cpp" "storage.cpp" |
||||
SRCS "gay-ipod-fw.cpp" "dac.cpp" "gpio-expander.cpp" "battery.cpp" "storage.cpp" |
||||
INCLUDE_DIRS "." |
||||
REQUIRES "esp_adc_cal" "fatfs") |
||||
|
@ -0,0 +1,220 @@ |
||||
#include "dac.h" |
||||
|
||||
#include "esp_log.h" |
||||
#include "assert.h" |
||||
#include "driver/i2c.h" |
||||
#include "gpio-expander.h" |
||||
#include "hal/i2c_types.h" |
||||
#include <cstdint> |
||||
|
||||
namespace gay_ipod { |
||||
|
||||
static const char* TAG = "AUDIODAC"; |
||||
|
||||
/**
|
||||
* Utility method for writing to a register on the PCM5122. Writes two bytes: |
||||
* first the address for the register that we're writing to, and then the value |
||||
* to write. |
||||
* |
||||
* Note this function assumes that the correct page has already been selected. |
||||
*/ |
||||
static void set_register(i2c_cmd_handle_t handle, uint8_t reg, uint8_t val) { |
||||
i2c_master_write_byte(handle, reg, true); |
||||
i2c_master_write_byte(handle, val, true); |
||||
} |
||||
|
||||
AudioDac::AudioDac(GpioExpander *gpio) { |
||||
this->gpio_ = 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; |
||||
} |
||||
|
||||
// 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; |
||||
} |
||||
|
||||
// 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; |
||||
}; |
||||
|
||||
// We've calculated all of our data. Now assemble the big list of registers
|
||||
// that we need to configure.
|
||||
i2c_cmd_handle_t handle = i2c_cmd_link_create(); |
||||
if (handle == NULL) { |
||||
return; |
||||
} |
||||
|
||||
i2c_master_start(handle); |
||||
i2c_master_write_byte(handle, (kPCM5122Address << 1 | I2C_MASTER_WRITE), true); |
||||
|
||||
// All our registers are on the first page.
|
||||
set_register(handle, Register::PAGE_SELECT, 0); |
||||
|
||||
// Disable clock autoset and ignore SCK detection
|
||||
set_register(handle, Register::IGNORE_ERRORS, 0x1A); |
||||
// Set PLL clock source to BCK
|
||||
set_register(handle, Register::PLL_CLOCK_SOURCE, 0x10); |
||||
// Set DAC clock source to PLL output
|
||||
set_register(handle, Register::DAC_CLOCK_SOURCE, 0x10); |
||||
|
||||
// Configure PLL
|
||||
set_register(handle, Register::PLL_P, p - 1); |
||||
set_register(handle, Register::PLL_J, j); |
||||
set_register(handle, Register::PLL_D_MSB, (d >> 8) & 0x3F); |
||||
set_register(handle, Register::PLL_D_LSB, d & 0xFF); |
||||
set_register(handle, Register::PLL_R, r - 1); |
||||
|
||||
// Clock dividers
|
||||
set_register(handle, Register::DSP_CLOCK_DIV, nmac - 1); |
||||
set_register(handle, Register::DAC_CLOCK_DIV, ndac - 1); |
||||
set_register(handle, Register::NCP_CLOCK_DIV, ncp - 1); |
||||
set_register(handle, Register::OSR_CLOCK_DIV, dosr - 1); |
||||
|
||||
// IDAC (nb of DSP clock cycles per sample)
|
||||
set_register(handle, Register::IDAC_MSB, (idac >> 8) & 0xFF); |
||||
set_register(handle, Register::IDAC_LSB, idac & 0xFF); |
||||
|
||||
set_register(handle, Register::FS_SPEED_MODE, speedMode); |
||||
set_register(handle, Register::I2S_FORMAT, i2s_format); |
||||
|
||||
i2c_master_stop(handle); |
||||
|
||||
// TODO: Handle this gracefully.
|
||||
ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, handle, 50)); |
||||
|
||||
i2c_cmd_link_delete(handle); |
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); |
||||
|
||||
// TODO: Handle this gracefully.
|
||||
assert(ReadPowerState() == 0x05); |
||||
} |
||||
|
||||
uint8_t AudioDac::ReadPowerState() { |
||||
i2c_cmd_handle_t handle = i2c_cmd_link_create(); |
||||
if (handle == NULL) { |
||||
return 0; |
||||
} |
||||
|
||||
uint8_t result = 0; |
||||
|
||||
i2c_master_start(handle); |
||||
i2c_master_write_byte(handle, (kPCM5122Address << 1 | I2C_MASTER_WRITE), true); |
||||
i2c_master_write_byte(handle, DSP_BOOT_POWER_STATE, true); |
||||
i2c_master_start(handle); |
||||
i2c_master_read_byte(handle, &result, I2C_MASTER_NACK); |
||||
i2c_master_stop(handle); |
||||
|
||||
ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, handle, 50)); |
||||
|
||||
i2c_cmd_link_delete(handle); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
void AudioDac::WriteVolume(uint8_t volume) { |
||||
i2c_cmd_handle_t handle = i2c_cmd_link_create(); |
||||
if (handle == NULL) { |
||||
return; |
||||
} |
||||
|
||||
i2c_master_start(handle); |
||||
i2c_master_write_byte(handle, (kPCM5122Address << 1 | I2C_MASTER_WRITE), true); |
||||
|
||||
set_register(handle, Register::DIGITAL_VOLUME_L, volume); |
||||
set_register(handle, Register::DIGITAL_VOLUME_R, volume); |
||||
|
||||
i2c_master_stop(handle); |
||||
|
||||
i2c_master_cmd_begin(I2C_NUM_0, handle, 50); |
||||
|
||||
i2c_cmd_link_delete(handle); |
||||
} |
||||
|
||||
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
|
@ -0,0 +1,96 @@ |
||||
#pragma once |
||||
|
||||
#include "gpio-expander.h" |
||||
#include <stdint.h> |
||||
|
||||
namespace gay_ipod { |
||||
|
||||
static const uint8_t kPCM5122Address = 0x4C; |
||||
|
||||
/**
|
||||
* PCM5122PWR |
||||
* |
||||
* Heavily inspired from the Arduino library here: |
||||
* https://github.com/tommag/PCM51xx_Arduino/
|
||||
*/ |
||||
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); |
||||
|
||||
/**
|
||||
* Sets the volume on a scale from 0 (loudest) to 254 (quietest). A value of |
||||
* 255 engages the soft mute function. |
||||
*/ |
||||
void WriteVolume(uint8_t volume); |
||||
|
||||
enum PowerMode { |
||||
ON = 0, |
||||
STANDBY = 0x10, |
||||
OFF = 0x01, |
||||
}; |
||||
|
||||
void WritePowerMode(PowerMode state); |
||||
|
||||
// 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; |
||||
|
||||
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, |
||||
DIGITAL_VOLUME_L = 61, |
||||
DIGITAL_VOLUME_R = 62, |
||||
DSP_BOOT_POWER_STATE = 118, |
||||
}; |
||||
|
||||
uint8_t ReadPowerState(); |
||||
}; |
||||
|
||||
} // namespace gay_ipod
|
Loading…
Reference in new issue