Volume control! Reasonable default volume! Hooray!

custom
jacqueline 2 years ago
parent 72fe82ebc4
commit 3670859d16
  1. 91
      src/audio/i2s_audio_output.cpp
  2. 6
      src/audio/include/i2s_audio_output.hpp
  3. 2
      src/drivers/CMakeLists.txt
  4. 62
      src/drivers/i2s_dac.cpp
  5. 31
      src/drivers/include/wm8523.hpp
  6. 51
      src/drivers/wm8523.cpp

@ -10,6 +10,7 @@
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <memory> #include <memory>
#include <variant> #include <variant>
@ -23,19 +24,32 @@
#include "i2s_dac.hpp" #include "i2s_dac.hpp"
#include "result.hpp" #include "result.hpp"
#include "stream_info.hpp" #include "stream_info.hpp"
#include "wm8523.hpp"
static const char* kTag = "I2SOUT"; static const char* kTag = "I2SOUT";
namespace audio { namespace audio {
// Consumer line level = 0.316 VRMS = -10db = 61
// Professional line level = 1.228 VRMS = +4dB = 111
// Cliipping level = 2.44 VRMS = 133?
// all into 650 ohms
static constexpr uint16_t kMaxVolume = 0x1ff;
static constexpr uint16_t kMinVolume = 0b0;
static constexpr uint16_t kMaxVolumeBeforeClipping = 0x185;
static constexpr uint16_t kLineLevelVolume = 0x13d;
static constexpr uint16_t kDefaultVolume = 0x128;
I2SAudioOutput::I2SAudioOutput(drivers::IGpios* expander, I2SAudioOutput::I2SAudioOutput(drivers::IGpios* expander,
std::weak_ptr<drivers::I2SDac> dac) std::weak_ptr<drivers::I2SDac> dac)
: expander_(expander), : expander_(expander),
dac_(dac.lock()), dac_(dac.lock()),
current_config_(), current_config_(),
left_difference_(0), left_difference_(0),
attenuation_() { current_volume_(kDefaultVolume),
SetVolume(25); // For testing max_volume_(kLineLevelVolume){
SetVolume(GetVolume());
dac_->SetSource(stream()); dac_->SetSource(stream());
} }
@ -53,63 +67,50 @@ auto I2SAudioOutput::SetInUse(bool in_use) -> void {
} }
auto I2SAudioOutput::SetVolumeImbalance(int_fast8_t balance) -> void { auto I2SAudioOutput::SetVolumeImbalance(int_fast8_t balance) -> void {
// TODO. left_difference_ = balance;
SetVolume(GetVolume());
} }
auto I2SAudioOutput::SetVolume(uint_fast8_t percent) -> void { auto I2SAudioOutput::SetVolume(uint_fast8_t percent) -> void {
// TODO. percent = std::min<uint_fast8_t>(percent, 100);
} float new_value = static_cast<float>(max_volume_) / 100 * percent;
current_volume_ = std::max<float>(new_value, kMinVolume);
ESP_LOGI(kTag, "set volume to %u%% = %u", percent, current_volume_);
auto I2SAudioOutput::GetVolume() -> uint_fast8_t { int32_t left_unclamped = current_volume_ + left_difference_;
// TODO. uint16_t left = std::clamp<int32_t>(left_unclamped, kMinVolume, max_volume_);
return 100;
}
auto I2SAudioOutput::GetAdjustedMaxAttenuation() -> int_fast8_t { using drivers::wm8523::Register;
// TODO drivers::wm8523::WriteRegister(Register::kDacGainLeft, left);
return 0; drivers::wm8523::WriteRegister(Register::kDacGainRight, current_volume_ | 0x200);
} }
static uint8_t vol = 0xFF; auto I2SAudioOutput::GetVolume() -> uint_fast8_t {
return
static_cast<uint_fast8_t>(
static_cast<float>(current_volume_) / max_volume_ * 100.0f);
}
auto I2SAudioOutput::AdjustVolumeUp() -> bool { auto I2SAudioOutput::AdjustVolumeUp() -> bool {
vol += 0xF; if (GetVolume() >= 100) {
{ return false;
drivers::I2CTransaction transaction; }
transaction.start() if (GetVolume() >= 95) {
.write_addr(0b0011010, I2C_MASTER_WRITE) SetVolume(100);
.write_ack(6, 0b01, vol) } else {
.stop(); SetVolume(GetVolume() + 5);
transaction.Execute();
}
{
drivers::I2CTransaction transaction;
transaction.start()
.write_addr(0b0011010, I2C_MASTER_WRITE)
.write_ack(7, 0b11, vol)
.stop();
transaction.Execute();
} }
return true; return true;
} }
auto I2SAudioOutput::AdjustVolumeDown() -> bool { auto I2SAudioOutput::AdjustVolumeDown() -> bool {
vol -= 0xF; if (GetVolume() == 0) {
{ return false;
drivers::I2CTransaction transaction; }
transaction.start() if (GetVolume() <= 5) {
.write_addr(0b0011010, I2C_MASTER_WRITE) SetVolume(0);
.write_ack(6, 0b01, vol) } else {
.stop(); SetVolume(GetVolume() - 5);
transaction.Execute();
}
{
drivers::I2CTransaction transaction;
transaction.start()
.write_addr(0b0011010, I2C_MASTER_WRITE)
.write_ack(7, 0b11, vol)
.stop();
transaction.Execute();
} }
return true; return true;
} }

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <sys/_stdint.h>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -41,14 +42,13 @@ class I2SAudioOutput : public IAudioSink {
I2SAudioOutput& operator=(const I2SAudioOutput&) = delete; I2SAudioOutput& operator=(const I2SAudioOutput&) = delete;
private: private:
auto GetAdjustedMaxAttenuation() -> int_fast8_t;
drivers::IGpios* expander_; drivers::IGpios* expander_;
std::shared_ptr<drivers::I2SDac> dac_; std::shared_ptr<drivers::I2SDac> dac_;
std::optional<StreamInfo::Pcm> current_config_; std::optional<StreamInfo::Pcm> current_config_;
int_fast8_t left_difference_; int_fast8_t left_difference_;
uint_fast8_t attenuation_; uint16_t current_volume_;
uint16_t max_volume_;
}; };
} // namespace audio } // namespace audio

@ -4,7 +4,7 @@
idf_component_register( idf_component_register(
SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
"spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" "relative_wheel.cpp" "spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" "relative_wheel.cpp" "wm8523.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks") REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -28,6 +28,7 @@
#include "hal/i2c_types.h" #include "hal/i2c_types.h"
#include "gpios.hpp" #include "gpios.hpp"
#include "wm8523.hpp"
#include "hal/i2s_types.h" #include "hal/i2s_types.h"
#include "i2c.hpp" #include "i2c.hpp"
#include "soc/clk_tree_defs.h" #include "soc/clk_tree_defs.h"
@ -36,28 +37,6 @@ namespace drivers {
static const char* kTag = "i2s_dac"; static const char* kTag = "i2s_dac";
static const i2s_port_t kI2SPort = I2S_NUM_0; static const i2s_port_t kI2SPort = I2S_NUM_0;
static const uint8_t kWm8523Address = 0b0011010;
enum Register {
kReset = 0,
kRevision = 1,
kPsCtrl = 2,
kAifCtrl1 = 3,
kAifCtrl2 = 4,
kDacCtrl = 5,
kDacGainLeft = 6,
kDacGainRight = 7,
kZeroDetect = 8,
};
auto write_register(Register reg, uint8_t msb, uint8_t lsb) -> esp_err_t {
I2CTransaction transaction;
transaction.start()
.write_addr(kWm8523Address, I2C_MASTER_WRITE)
.write_ack(reg, msb, lsb)
.stop();
return transaction.Execute();
}
auto I2SDac::create(IGpios* expander) -> std::optional<I2SDac*> { auto I2SDac::create(IGpios* expander) -> std::optional<I2SDac*> {
i2s_chan_handle_t i2s_handle; i2s_chan_handle_t i2s_handle;
@ -110,17 +89,11 @@ I2SDac::I2SDac(IGpios* gpio, i2s_chan_handle_t i2s_handle)
gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, false); gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, false);
// Reset all registers back to their default values. // Reset all registers back to their default values.
write_register(kReset, 0, 1); wm8523::WriteRegister(wm8523::Register::kReset, 1);
vTaskDelay(pdMS_TO_TICKS(10)); vTaskDelay(pdMS_TO_TICKS(10));
// Power up the charge pump. // Power up the charge pump.
write_register(kPsCtrl, 0, 0b01); wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b01);
// TODO: testing
// write_register(kDacGainLeft, 0b01, 0x50);
// write_register(kDacGainRight, 0b11, 0x50);
write_register(kDacGainLeft, 0b01, 0x0);
write_register(kDacGainRight, 0b11, 0x0);
} }
I2SDac::~I2SDac() { I2SDac::~I2SDac() {
@ -130,15 +103,23 @@ I2SDac::~I2SDac() {
auto I2SDac::Start() -> void { auto I2SDac::Start() -> void {
gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, true); gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, true);
vTaskDelay(pdMS_TO_TICKS(1));
wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b10);
i2s_channel_enable(i2s_handle_); uint8_t zeroes[256] {0};
write_register(kPsCtrl, 0, 0b11); size_t bytes_loaded = 0;
esp_err_t res = ESP_OK;
do {
res = i2s_channel_preload_data(i2s_handle_, zeroes, 256, &bytes_loaded);
} while (bytes_loaded > 0 && res == ESP_OK);
wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b11);
i2s_channel_enable(i2s_handle_);
i2s_active_ = true; i2s_active_ = true;
} }
auto I2SDac::Stop() -> void { auto I2SDac::Stop() -> void {
write_register(kPsCtrl, 0, 0b01); wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b01);
i2s_channel_disable(i2s_handle_); i2s_channel_disable(i2s_handle_);
gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, false); gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, false);
@ -148,8 +129,15 @@ auto I2SDac::Stop() -> void {
auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
-> void { -> void {
write_register(kPsCtrl, 0, 0b01); if (i2s_active_) {
// Ramp down into mute instead of just outright stopping to minimise any
// clicks and pops.
wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b10);
vTaskDelay(pdMS_TO_TICKS(1));
wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b01);
i2s_channel_disable(i2s_handle_); i2s_channel_disable(i2s_handle_);
}
switch (ch) { switch (ch) {
case CHANNELS_MONO: case CHANNELS_MONO:
@ -190,13 +178,13 @@ auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_)); ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_));
// Set the correct word size, and set the input format to I2S-justified. // Set the correct word size, and set the input format to I2S-justified.
write_register(kAifCtrl1, 0, (word_length << 3) | 0b10); wm8523::WriteRegister(wm8523::Register::kAifCtrl1, (word_length << 3) | 0b10);
// Tell the DAC the clock ratio instead of waiting for it to auto detect. // Tell the DAC the clock ratio instead of waiting for it to auto detect.
// write_register(kAifCtrl2, 0, bps == BPS_24 ? 0b100 : 0b011); wm8523::WriteRegister(wm8523::Register::kAifCtrl2, bps == BPS_24 ? 0b100 : 0b011);
if (i2s_active_) { if (i2s_active_) {
i2s_channel_enable(i2s_handle_); i2s_channel_enable(i2s_handle_);
write_register(kPsCtrl, 0, 0b11); wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b11);
} }
} }

@ -0,0 +1,31 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include <cstdint>
#include <optional>
namespace drivers {
namespace wm8523 {
enum class Register : uint8_t {
kReset = 0,
kRevision = 1,
kPsCtrl = 2,
kAifCtrl1 = 3,
kAifCtrl2 = 4,
kDacCtrl = 5,
kDacGainLeft = 6,
kDacGainRight = 7,
kZeroDetect = 8,
};
auto ReadRegister(Register reg) -> std::optional<uint16_t>;
auto WriteRegister(Register reg, uint16_t data) -> bool;
auto WriteRegister(Register reg, uint8_t msb, uint8_t lsb) -> bool;
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "wm8523.hpp"
#include <cstdint>
#include "esp_err.h"
#include "hal/i2c_types.h"
#include "i2c.hpp"
namespace drivers {
namespace wm8523 {
static const uint8_t kAddress = 0b0011010;
auto ReadRegister(Register reg) -> std::optional<uint16_t> {
uint8_t msb, lsb;
I2CTransaction transaction;
transaction.start()
.write_addr(kAddress, I2C_MASTER_WRITE)
.write_ack(static_cast<uint8_t>(reg))
.start()
.write_addr(kAddress, I2C_MASTER_READ)
.read(&msb, I2C_MASTER_ACK)
.read(&lsb, I2C_MASTER_LAST_NACK)
.stop();
if (transaction.Execute() != ESP_OK) {
return {};
}
return (msb << 8) & lsb;
}
auto WriteRegister(Register reg, uint16_t data) -> bool {
return WriteRegister(reg, (data >> 8) & 0xFF, data & 0xFF);
}
auto WriteRegister(Register reg, uint8_t msb, uint8_t lsb) -> bool {
I2CTransaction transaction;
transaction.start()
.write_addr(kAddress, I2C_MASTER_WRITE)
.write_ack(static_cast<uint8_t>(reg), msb, lsb)
.stop();
return transaction.Execute() == ESP_OK;
}
}
}
Loading…
Cancel
Save