Start on configuring the DAC over i2c

custom
jacqueline 3 years ago
parent 122306d619
commit 6c793efa0d
  1. 2
      main/CMakeLists.txt
  2. 5
      main/battery.h
  3. 220
      main/dac.cpp
  4. 96
      main/dac.h
  5. 22
      main/gay-ipod-fw.cpp
  6. 6
      main/gpio-expander.cpp
  7. 11
      main/gpio-expander.h
  8. 5
      main/storage.h

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

@ -1,5 +1,4 @@
#ifndef BATTERY_H
#define BATTERY_H
#pragma once
#include <stdint.h>
@ -15,5 +14,3 @@ esp_err_t init_adc(void);
uint32_t read_battery_voltage(void);
} // namespace gay_ipod
#endif

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

@ -2,6 +2,7 @@
#include <dirent.h>
#include "battery.h"
#include "dac.h"
#include "driver/adc.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
@ -101,17 +102,28 @@ extern "C" void app_main(void)
ESP_LOGI(TAG, "Init ADC");
ESP_ERROR_CHECK(gay_ipod::init_adc());
ESP_LOGI(TAG, "Everything looks good! Waiting a mo for debugger.");
vTaskDelay(pdMS_TO_TICKS(2500));
ESP_LOGI(TAG, "Trying to init SD card");
ESP_LOGI(TAG, "Init SD card");
gay_ipod::SdStorage storage(&expander);
gay_ipod::SdStorage::Error err = storage.Acquire();
if (err != gay_ipod::SdStorage::Error::OK) {
ESP_LOGE(TAG, "Failed to acquire storage!");
return;
}
ESP_LOGI(TAG, "Everything looks good! Waiting a mo for debugger.");
vTaskDelay(pdMS_TO_TICKS(1500));
/*
* TODO: not working :(
ESP_LOGI(TAG, "Trying to init DAC");
gay_ipod::AudioDac dac(&expander);
dac.Start(
gay_ipod::AudioDac::SAMPLE_RATE_44_1K,
gay_ipod::AudioDac::BIT_DEPTH_16);
vTaskDelay(pdMS_TO_TICKS(1000));
*/
ESP_LOGI(TAG, "Looks okay? Let's list some files!");
vTaskDelay(pdMS_TO_TICKS(1000));

@ -48,16 +48,16 @@ esp_err_t GpioExpander::Read() {
return ret;
}
bool GpioExpander::charge_power_ok(void) {
bool GpioExpander::charge_power_ok(void) const {
// Active-low.
return (input_a_ & (1 << 4)) == 0;
}
bool GpioExpander::headphone_detect(void) {
bool GpioExpander::headphone_detect(void) const {
return (input_b_ & (1 << 0));
}
uint8_t GpioExpander::key_states(void) {
uint8_t GpioExpander::key_states(void) const {
return input_b_ & 0b00111111;
}

@ -1,5 +1,4 @@
#ifndef GPIO_EXPANDER_H
#define GPIO_EXPANDER_H
#pragma once
#include <stdint.h>
@ -23,9 +22,9 @@ class GpioExpander {
esp_err_t Write(void);
esp_err_t Read(void);
bool charge_power_ok(void);
bool headphone_detect(void);
uint8_t key_states(void);
bool charge_power_ok(void) const;
bool headphone_detect(void) const;
uint8_t key_states(void) const;
enum SdMuxController {
ESP,
@ -71,5 +70,3 @@ class GpioExpander {
};
} // namespace gay_ipod
#endif

@ -1,5 +1,4 @@
#ifndef STORAGE_H
#define STORAGE_H
#pragma once
#include "driver/sdmmc_types.h"
#include "driver/sdspi_host.h"
@ -58,5 +57,3 @@ class SdStorage {
};
} // namespace gay_ipod
#endif

Loading…
Cancel
Save