Volume control! Reasonable default volume! Hooray!
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
@@ -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<drivers::I2SDac> 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<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_);
|
||||
|
||||
int32_t left_unclamped = current_volume_ + left_difference_;
|
||||
uint16_t left = std::clamp<int32_t>(left_unclamped, kMinVolume, max_volume_);
|
||||
|
||||
using drivers::wm8523::Register;
|
||||
drivers::wm8523::WriteRegister(Register::kDacGainLeft, left);
|
||||
drivers::wm8523::WriteRegister(Register::kDacGainRight, current_volume_ | 0x200);
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::GetVolume() -> uint_fast8_t {
|
||||
// TODO.
|
||||
return 100;
|
||||
return
|
||||
static_cast<uint_fast8_t>(
|
||||
static_cast<float>(current_volume_) / max_volume_ * 100.0f);
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::GetAdjustedMaxAttenuation() -> int_fast8_t {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t vol = 0xFF;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/_stdint.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -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<drivers::I2SDac> dac_;
|
||||
|
||||
std::optional<StreamInfo::Pcm> current_config_;
|
||||
int_fast8_t left_difference_;
|
||||
uint_fast8_t attenuation_;
|
||||
uint16_t current_volume_;
|
||||
uint16_t max_volume_;
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -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})
|
||||
|
||||
+26
-38
@@ -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<I2SDac*> {
|
||||
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);
|
||||
|
||||
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_);
|
||||
write_register(kPsCtrl, 0, 0b11);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user