parent
122306d619
commit
6c793efa0d
@ -1,4 +1,4 @@ |
|||||||
idf_component_register( |
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 "." |
INCLUDE_DIRS "." |
||||||
REQUIRES "esp_adc_cal" "fatfs") |
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