From 3670859d1620ca0fe3492cffb591bf29e5af849c Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 28 Jul 2023 13:01:18 +1000 Subject: [PATCH] Volume control! Reasonable default volume! Hooray! --- src/audio/i2s_audio_output.cpp | 87 +++++++++++++------------- src/audio/include/i2s_audio_output.hpp | 6 +- src/drivers/CMakeLists.txt | 2 +- src/drivers/i2s_dac.cpp | 64 ++++++++----------- src/drivers/include/wm8523.hpp | 31 +++++++++ src/drivers/wm8523.cpp | 51 +++++++++++++++ 6 files changed, 156 insertions(+), 85 deletions(-) create mode 100644 src/drivers/include/wm8523.hpp create mode 100644 src/drivers/wm8523.cpp diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp index 8ce43336..5873d80f 100644 --- a/src/audio/i2s_audio_output.cpp +++ b/src/audio/i2s_audio_output.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -23,19 +24,32 @@ #include "i2s_dac.hpp" #include "result.hpp" #include "stream_info.hpp" +#include "wm8523.hpp" static const char* kTag = "I2SOUT"; 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, std::weak_ptr dac) : expander_(expander), dac_(dac.lock()), current_config_(), left_difference_(0), - attenuation_() { - SetVolume(25); // For testing + current_volume_(kDefaultVolume), + max_volume_(kLineLevelVolume){ + SetVolume(GetVolume()); dac_->SetSource(stream()); } @@ -53,63 +67,50 @@ auto I2SAudioOutput::SetInUse(bool in_use) -> void { } auto I2SAudioOutput::SetVolumeImbalance(int_fast8_t balance) -> void { - // TODO. + left_difference_ = balance; + SetVolume(GetVolume()); } auto I2SAudioOutput::SetVolume(uint_fast8_t percent) -> void { - // TODO. -} + percent = std::min(percent, 100); + float new_value = static_cast(max_volume_) / 100 * percent; + current_volume_ = std::max(new_value, kMinVolume); + ESP_LOGI(kTag, "set volume to %u%% = %u", percent, current_volume_); -auto I2SAudioOutput::GetVolume() -> uint_fast8_t { - // TODO. - return 100; -} + int32_t left_unclamped = current_volume_ + left_difference_; + uint16_t left = std::clamp(left_unclamped, kMinVolume, max_volume_); -auto I2SAudioOutput::GetAdjustedMaxAttenuation() -> int_fast8_t { - // TODO - return 0; + using drivers::wm8523::Register; + drivers::wm8523::WriteRegister(Register::kDacGainLeft, left); + drivers::wm8523::WriteRegister(Register::kDacGainRight, current_volume_ | 0x200); } -static uint8_t vol = 0xFF; +auto I2SAudioOutput::GetVolume() -> uint_fast8_t { + return + static_cast( + static_cast(current_volume_) / max_volume_ * 100.0f); +} auto I2SAudioOutput::AdjustVolumeUp() -> bool { - vol += 0xF; - { - drivers::I2CTransaction transaction; - transaction.start() - .write_addr(0b0011010, I2C_MASTER_WRITE) - .write_ack(6, 0b01, vol) - .stop(); - transaction.Execute(); + if (GetVolume() >= 100) { + return false; } - { - drivers::I2CTransaction transaction; - transaction.start() - .write_addr(0b0011010, I2C_MASTER_WRITE) - .write_ack(7, 0b11, vol) - .stop(); - transaction.Execute(); + if (GetVolume() >= 95) { + SetVolume(100); + } else { + SetVolume(GetVolume() + 5); } return true; } auto I2SAudioOutput::AdjustVolumeDown() -> bool { - vol -= 0xF; - { - drivers::I2CTransaction transaction; - transaction.start() - .write_addr(0b0011010, I2C_MASTER_WRITE) - .write_ack(6, 0b01, vol) - .stop(); - transaction.Execute(); + if (GetVolume() == 0) { + return false; } - { - drivers::I2CTransaction transaction; - transaction.start() - .write_addr(0b0011010, I2C_MASTER_WRITE) - .write_ack(7, 0b11, vol) - .stop(); - transaction.Execute(); + if (GetVolume() <= 5) { + SetVolume(0); + } else { + SetVolume(GetVolume() - 5); } return true; } diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp index d42efc42..43155711 100644 --- a/src/audio/include/i2s_audio_output.hpp +++ b/src/audio/include/i2s_audio_output.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -41,14 +42,13 @@ class I2SAudioOutput : public IAudioSink { I2SAudioOutput& operator=(const I2SAudioOutput&) = delete; private: - auto GetAdjustedMaxAttenuation() -> int_fast8_t; - drivers::IGpios* expander_; std::shared_ptr dac_; std::optional current_config_; int_fast8_t left_difference_; - uint_fast8_t attenuation_; + uint16_t current_volume_; + uint16_t max_volume_; }; } // namespace audio diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index 151a3afc..40cd0c4f 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -4,7 +4,7 @@ idf_component_register( 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" REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp index dd454db3..e4e782c2 100644 --- a/src/drivers/i2s_dac.cpp +++ b/src/drivers/i2s_dac.cpp @@ -28,6 +28,7 @@ #include "hal/i2c_types.h" #include "gpios.hpp" +#include "wm8523.hpp" #include "hal/i2s_types.h" #include "i2c.hpp" #include "soc/clk_tree_defs.h" @@ -36,28 +37,6 @@ namespace drivers { static const char* kTag = "i2s_dac"; 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 { 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); // Reset all registers back to their default values. - write_register(kReset, 0, 1); + wm8523::WriteRegister(wm8523::Register::kReset, 1); vTaskDelay(pdMS_TO_TICKS(10)); // Power up the charge pump. - write_register(kPsCtrl, 0, 0b01); - - // TODO: testing - // write_register(kDacGainLeft, 0b01, 0x50); - // write_register(kDacGainRight, 0b11, 0x50); - write_register(kDacGainLeft, 0b01, 0x0); - write_register(kDacGainRight, 0b11, 0x0); + wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b01); } I2SDac::~I2SDac() { @@ -130,15 +103,23 @@ I2SDac::~I2SDac() { auto I2SDac::Start() -> void { gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, true); + vTaskDelay(pdMS_TO_TICKS(1)); + wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b10); - i2s_channel_enable(i2s_handle_); - write_register(kPsCtrl, 0, 0b11); + uint8_t zeroes[256] {0}; + 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; } auto I2SDac::Stop() -> void { - write_register(kPsCtrl, 0, 0b01); + wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b01); i2s_channel_disable(i2s_handle_); gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, false); @@ -148,8 +129,15 @@ auto I2SDac::Stop() -> void { auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void { - write_register(kPsCtrl, 0, 0b01); - i2s_channel_disable(i2s_handle_); + 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_); + } switch (ch) { 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_)); // 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. - // write_register(kAifCtrl2, 0, bps == BPS_24 ? 0b100 : 0b011); + wm8523::WriteRegister(wm8523::Register::kAifCtrl2, bps == BPS_24 ? 0b100 : 0b011); if (i2s_active_) { i2s_channel_enable(i2s_handle_); - write_register(kPsCtrl, 0, 0b11); + wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b11); } } diff --git a/src/drivers/include/wm8523.hpp b/src/drivers/include/wm8523.hpp new file mode 100644 index 00000000..8b20eda0 --- /dev/null +++ b/src/drivers/include/wm8523.hpp @@ -0,0 +1,31 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ +#pragma once + +#include +#include + +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; +auto WriteRegister(Register reg, uint16_t data) -> bool; +auto WriteRegister(Register reg, uint8_t msb, uint8_t lsb) -> bool; + +} +} diff --git a/src/drivers/wm8523.cpp b/src/drivers/wm8523.cpp new file mode 100644 index 00000000..dbd4a88e --- /dev/null +++ b/src/drivers/wm8523.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ +#include "wm8523.hpp" + +#include + +#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 { + uint8_t msb, lsb; + I2CTransaction transaction; + transaction.start() + .write_addr(kAddress, I2C_MASTER_WRITE) + .write_ack(static_cast(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(reg), msb, lsb) + .stop(); + return transaction.Execute() == ESP_OK; +} + +} +}