[tas58xx] Add support for tas5825 dac (#19971)

This commit is contained in:
mrtoy-me
2026-10-06 15:22:00 -05:00
committed by GitHub
parent 490d478e54
commit 194273955c
8 changed files with 247 additions and 26 deletions
+9
View File
@@ -1 +1,10 @@
from esphome.config_helpers import filter_source_files_from_defines
CODEOWNERS = ["@mrtoy-me", "@remcom"]
FILTER_SOURCE_FILES = filter_source_files_from_defines(
{
"model_tas5805m.cpp": "USE_TAS58XX_TAS5805M",
"model_tas5825m.cpp": "USE_TAS58XX_TAS5825M",
}
)
+14 -1
View File
@@ -11,6 +11,8 @@ from esphome.const import CONF_ENABLE_PIN, CONF_ID, CONF_MODEL
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
from . import FILTER_SOURCE_FILES # noqa: F401 pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["i2c"]
@@ -23,6 +25,9 @@ CONF_VOLUME_MIN_DB = "volume_min_db"
CONF_VOLUME_MAX_DB = "volume_max_db"
CONF_TAS58XX_ID = "tas58xx_id"
DAC_TAS5805M = "tas5805m"
DAC_TAS5825M = "tas5825m"
tas58xx_ns = cg.esphome_ns.namespace("tas58xx")
TAS58xx = tas58xx_ns.class_("TAS58xx", AudioDac, cg.PollingComponent, i2c.I2CDevice)
@@ -54,13 +59,20 @@ class Model:
MODELS: dict[str, Model] = {
"tas5805m": Model(
DAC_TAS5805M: Model(
model_info=tas58xx_ns.TAS5805M_MODEL,
analog_gain_min_db=-15.5,
volume_min_db=-103.0,
volume_max_db=24.0,
default_address=0x2D,
),
DAC_TAS5825M: Model(
model_info=tas58xx_ns.TAS5825M_MODEL,
analog_gain_min_db=-15.5,
volume_min_db=-103.0,
volume_max_db=24.0,
default_address=0x4C,
),
}
@@ -162,6 +174,7 @@ for _name, _call in (
async def to_code(config: ConfigType) -> None:
model = MODELS[config[CONF_MODEL]]
cg.add_define(f"USE_TAS58XX_{config[CONF_MODEL].upper()}")
var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f"&{model.model_info}"))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+41 -5
View File
@@ -1,15 +1,17 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import DEVICE_CLASS_PROBLEM, ENTITY_CATEGORY_DIAGNOSTIC
from esphome.const import CONF_MODEL, DEVICE_CLASS_PROBLEM, ENTITY_CATEGORY_DIAGNOSTIC
import esphome.final_validate as fv
from esphome.types import ConfigType
from .audio_dac import CONF_TAS58XX_ID, TAS58xx, tas58xx_ns
from . import FILTER_SOURCE_FILES # noqa: F401 pylint: disable=unused-import
from .audio_dac import CONF_TAS58XX_ID, DAC_TAS5825M, TAS58xx, tas58xx_ns
CONF_HAVE_FAULT = "have_fault"
# Each name matches a FaultSensor value in tas58xx.h
FAULT_SENSORS = (
# Faults common to TAS5805M and TAS5825M
COMMON_FAULT_SENSORS = (
"left_channel_dc_fault",
"right_channel_dc_fault",
"left_channel_over_current",
@@ -20,8 +22,22 @@ FAULT_SENSORS = (
"pvdd_over_voltage",
"pvdd_under_voltage",
"over_temp_shutdown",
"over_temp_warning",
"over_temp_warning", # tas582x OTW Level 3 - use tas5805 naming
)
# Additional faults available on TAS5825M
TAS5825M_ONLY_FAULT_SENSORS = (
"load_eeprom_error",
"right_channel_cbc_over_current",
"left_channel_cbc_over_current",
"left_channel_cbc_over_current_warning",
"right_channel_cbc_over_current_warning",
"over_temp_146c_warning", # tas582x OTW Level 4
# "over_temp_122c_warning", # tas582x OTW Level 2 - not currently included
# "over_temp_112c_warning", # tas582x OTW Level 1 - not currently included
)
# Each name matches a FaultSensor value in tas58xx.h
FAULT_SENSORS = COMMON_FAULT_SENSORS + TAS5825M_ONLY_FAULT_SENSORS
FaultSensor = tas58xx_ns.enum("FaultSensor")
@@ -39,6 +55,26 @@ CONFIG_SCHEMA = cv.Schema(
)
def _final_validate(config: ConfigType) -> ConfigType:
fconf = fv.full_config.get()
hub_path = fconf.get_path_for_id(config[CONF_TAS58XX_ID])
hub_conf = fconf.get_config_for_path(hub_path[:-1])
if hub_conf[CONF_MODEL] == DAC_TAS5825M:
return config
unsupported = sorted(frozenset(TAS5825M_ONLY_FAULT_SENSORS).intersection(config))
if unsupported:
raise cv.Invalid(
f"binary_sensor platform tas58xx: '{', '.join(unsupported)}' is only available for 'model: {DAC_TAS5825M}' "
f"- Remove from YAML for 'model: {hub_conf[CONF_MODEL]}'"
)
return config
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_TAS58XX_ID])
binary_sensors = binary_sensor.sub_binary_sensors(config)
+17 -11
View File
@@ -67,17 +67,23 @@ const ModelInfo TAS5805M_MODEL = {
.fault_name = fault_name,
.fault_sensor_bits =
{
3, // FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT
2, // FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT
1, // FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT
0, // FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT
15, // FAULT_SENSOR_OTP_CRC_CHECK
14, // FAULT_SENSOR_BQ_WRITE_FAILED
10, // FAULT_SENSOR_CLOCK_FAULT
9, // FAULT_SENSOR_PVDD_OVER_VOLTAGE
8, // FAULT_SENSOR_PVDD_UNDER_VOLTAGE
16, // FAULT_SENSOR_OVER_TEMP_SHUTDOWN
26, // FAULT_SENSOR_OVER_TEMP_WARNING
3, // FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT
2, // FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT
1, // FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT
0, // FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT
15, // FAULT_SENSOR_OTP_CRC_CHECK
14, // FAULT_SENSOR_BQ_WRITE_FAILED
10, // FAULT_SENSOR_CLOCK_FAULT
9, // FAULT_SENSOR_PVDD_OVER_VOLTAGE
8, // FAULT_SENSOR_PVDD_UNDER_VOLTAGE
16, // FAULT_SENSOR_OVER_TEMP_SHUTDOWN
26, // FAULT_SENSOR_OVER_TEMP_WARNING
NO_BIT, // FAULT_SENSOR_LOAD_EEPROM_ERROR
NO_BIT, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT
NO_BIT, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT
NO_BIT, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT_WARNING
NO_BIT, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT_WARNING
NO_BIT, // FAULT_SENSOR_OVER_TEMP_146C_WARNING
},
};
@@ -0,0 +1,119 @@
#include "tas58xx.h"
#include "esphome/core/hal.h"
namespace esphome::tas58xx {
// Remainder of the startup sequence, run after the reset. Register 0x00 selects the page.
// All writes except starting and last three register writes are exactly the startup sequence from TI PurePath Console.
// Registers 0x46, 0x7D, 0x7E, page 1 register 0x51 and Page 2 registers 0x1D, 0x19 are not documented in the datasheet.
// clang-format off
static const uint8_t STARTUP_SEQUENCE[][2] PROGMEM = {
{0x03, 0x00}, // Deep Sleep
{0x7D, 0x11},
{0x7E, 0xFF},
{0x00, 0x01}, // Page 1
{0x51, 0x05},
{0x00, 0x02}, // Page 2
{0x1D, 0x00},
{0x19, 0x80},
{0x00, 0x00}, // Page 0
{0x46, 0x11},
{0x02, 0x00}, // DEVICE_CTRL_1 - BD MODE, Damp to PBL MODE
{0x53, 0x01}, // ANA_CTRL PWM Phase Control - in phase
{0x54, 0x00}, // AGAIN 0dB
{0x03, 0x02}, // Hi-Z
{0x61, 0x0B}, // GPIO0_SEL - GPIO0 as FAULTZ output
{0x60, 0x01}, // GPIO0_OE - Enable GPIO0 as output
{0x77, 0x07}, // CBC_CONTROL enabling CBC function for warnings and faults
};
// clang-format on
static const LogString *model_name() { return LOG_STR("TAS5825M"); }
// An if chain rather than a switch: a switch table would land in rodata, which is RAM on ESP8266.
static const LogString *fault_name(uint8_t index) {
if (index == 0)
return LOG_STR("Right channel over current");
if (index == 1)
return LOG_STR("Left channel over current");
if (index == 2)
return LOG_STR("Right channel DC fault");
if (index == 3)
return LOG_STR("Left channel DC fault");
if (index == 8)
return LOG_STR("PVDD under voltage");
if (index == 9)
return LOG_STR("PVDD over voltage");
if (index == 10)
return LOG_STR("Clock fault");
if (index == 13)
return LOG_STR("Load EEPROM error");
if (index == 14)
return LOG_STR("BQ write failed");
if (index == 15)
return LOG_STR("OTP CRC check error");
if (index == 16)
return LOG_STR("Over temperature shutdown");
if (index == 17)
return LOG_STR("Left channel CBC over current");
if (index == 18)
return LOG_STR("Right channel CBC over current");
// if (index == 24)
// return LOG_STR("Over temperature warning 112C"); // not currently included
// if (index == 25)
// return LOG_STR("Over temperature warning 122C"); // not currently included
if (index == 26)
return LOG_STR("Over temperature warning");
if (index == 27)
return LOG_STR("Over temperature warning 146C");
if (index == 28)
return LOG_STR("Right channel CBC over current warning");
if (index == 29)
return LOG_STR("Left channel CBC over current warning");
return LOG_STR("Unknown fault");
}
const ModelInfo TAS5825M_MODEL = {
.name = model_name,
.startup_sequence = STARTUP_SEQUENCE,
.startup_sequence_length = sizeof(STARTUP_SEQUENCE) / sizeof(STARTUP_SEQUENCE[0]),
.mixer_book = 0x8C,
.mixer_page = 0x0B,
.mixer_register = 0x14,
// The clock fault is left out of the log and have_fault: it is set whenever the I2S clock stops, which is normal
.fault_error_mask = 0x0007E30F,
.fault_warning_mask =
0x3C000000, // 0x3F000000 if OVER_TEMP_122C_WARNING and OVER_TEMP_112C_WARNING included in future
// DC and over current faults keep the output off until cleared (datasheet 7.5.3.3.1, 7.5.3.3.2). They are not
// cleared automatically: a DC fault re-trips only after 570 ms, so a clear on every poll would pass DC to the
// speaker.
.fault_output_off_mask = 0x0000000F,
.fault_latched_mask = 0x3F07E70F,
.fault_name = fault_name,
.fault_sensor_bits =
{
3, // FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT
2, // FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT
1, // FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT
0, // FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT
15, // FAULT_SENSOR_OTP_CRC_CHECK
14, // FAULT_SENSOR_BQ_WRITE_FAILED
10, // FAULT_SENSOR_CLOCK_FAULT
9, // FAULT_SENSOR_PVDD_OVER_VOLTAGE
8, // FAULT_SENSOR_PVDD_UNDER_VOLTAGE
16, // FAULT_SENSOR_OVER_TEMP_SHUTDOWN
26, // FAULT_SENSOR_OVER_TEMP_WARNING
13, // FAULT_SENSOR_LOAD_EEPROM_ERROR
18, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT
17, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT
29, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT_WARNING
28, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT_WARNING
27, // FAULT_SENSOR_OVER_TEMP_146C_WARNING
// 25, // FAULT_SENSOR_OVER_TEMP_122C_WARNING // not currently included
// 24, // FAULT_SENSOR_OVER_TEMP_112C_WARNING // not currently included
},
};
} // namespace esphome::tas58xx
+4 -3
View File
@@ -196,8 +196,9 @@ bool TAS58xx::read_faults_() {
if (this->have_fault_binary_sensor_ != nullptr)
this->have_fault_binary_sensor_->publish_state((active & model.fault_error_mask) != 0);
for (uint8_t fault = 0; fault < FAULT_SENSOR_COUNT; fault++) {
if (this->fault_binary_sensors_[fault] != nullptr)
this->fault_binary_sensors_[fault]->publish_state(faults & (uint32_t{1} << model.fault_sensor_bits[fault]));
const uint8_t bit = model.fault_sensor_bits[fault];
if (this->fault_binary_sensors_[fault] != nullptr && bit != NO_BIT)
this->fault_binary_sensors_[fault]->publish_state(faults & (uint32_t{1} << bit));
}
#endif
@@ -264,7 +265,7 @@ void TAS58xx::dump_config() {
" Analog Gain: %.1f dB\n"
" DAC Mode: %s\n"
" Mixer Mode: %s\n"
" Volume Range: %.1f dB - %.1f dB",
" Volume Range: %.1f dB to %.1f dB",
this->analog_gain_db_,
this->dac_mode_ == DAC_MODE_PBTL ? LOG_STR_LITERAL("PBTL") : LOG_STR_LITERAL("BTL"),
LOG_STR_ARG(mixer_mode), this->volume_min_db_, this->volume_max_db_);
+28 -4
View File
@@ -27,9 +27,11 @@ enum MixerMode : uint8_t {
MIXER_MODE_RIGHT,
};
/// Fault binary sensors that map to a single fault bit. The Python FAULT_SENSORS list uses the same names.
/// Fault binary sensors that map to a single fault bit. Note that ordering is important.
/// The Python FAULT_SENSORS list in binary_sensor.py uses the same names and ordering.
enum FaultSensor : uint8_t {
FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT = 0,
// Faults common to TAS5805M and TAS5825M
FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT,
FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT,
FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT,
FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT,
@@ -40,9 +42,26 @@ enum FaultSensor : uint8_t {
FAULT_SENSOR_PVDD_UNDER_VOLTAGE,
FAULT_SENSOR_OVER_TEMP_SHUTDOWN,
FAULT_SENSOR_OVER_TEMP_WARNING,
FAULT_SENSOR_COUNT,
// Faults available for TAS5825M Only
FAULT_SENSOR_LOAD_EEPROM_ERROR,
FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT,
FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT,
FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT_WARNING,
FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT_WARNING,
FAULT_SENSOR_OVER_TEMP_146C_WARNING,
// FAULT_SENSOR_OVER_TEMP_122C_WARNING,
// FAULT_SENSOR_OVER_TEMP_112C_WARNING,
FAULT_SENSOR_COUNT, // keep last
};
// compile time check
static_assert(
FAULT_SENSOR_COUNT == 17,
"enum FaultSensor altered: Update FAULT_SENSORS in binary_sensor.py and fault_sensor_bits in each model's cpp");
/// ModelInfo::fault_sensor_bits value for a FaultSensor that the model does not have.
static constexpr uint8_t NO_BIT = 0xFF;
/// Everything that differs between models of the family. One constant instance exists per model, see
/// model_*.cpp, and each TAS58xx instance points to the one for its model.
///
@@ -67,11 +86,16 @@ struct ModelInfo {
/// Faults that stay set after the condition is gone and are cleared after each read
uint32_t fault_latched_mask;
const LogString *(*fault_name)(uint8_t index);
/// Bit index for each FaultSensor
/// Bit index for each FaultSensor, NO_BIT used where the model does not have that fault bit
uint8_t fault_sensor_bits[FAULT_SENSOR_COUNT];
};
#ifdef USE_TAS58XX_TAS5805M
extern const ModelInfo TAS5805M_MODEL;
#endif
#ifdef USE_TAS58XX_TAS5825M
extern const ModelInfo TAS5825M_MODEL;
#endif
class TAS58xx : public audio_dac::AudioDac, public PollingComponent, public i2c::I2CDevice {
public:
+15 -2
View File
@@ -12,10 +12,9 @@ audio_dac:
volume_max_db: 0dB
update_interval: 1s
- platform: tas58xx
model: TAS5805M
model: TAS5825M
id: tas58xx_amp_2
i2c_id: i2c_bus
address: 0x2C
ignore_enable_pin_warning: true
esphome:
@@ -59,3 +58,17 @@ binary_sensor:
name: Second Amp Any Fault
left_channel_dc_fault:
name: Second Amp Left Channel DC Fault
left_channel_cbc_over_current:
name: Left Channel CBC Current Fault
right_channel_cbc_over_current:
name: Right Channel CBC Current Fault
over_temp_shutdown:
name: Over Temperature Shutdown Fault
left_channel_cbc_over_current_warning:
name: Left Channel CBC Current Warning
right_channel_cbc_over_current_warning:
name: Right Channel CBC Current Warning
over_temp_146c_warning:
name: Over Temperature 146C Warning
over_temp_warning:
name: Over Temperature 134C Warning