[es7210] Add TDM support for future AEC support (#18954)

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
matt123p
2026-10-12 11:22:17 -10:00
committed by GitHub
co-authored by pre-commit-ci-lite[bot]
parent 4e3845ba68
commit 6bf091104f
6 changed files with 168 additions and 16 deletions
+2
View File
@@ -1 +1,3 @@
"""ES7210 audio ADC component."""
DOMAIN = "es7210"
+33 -5
View File
@@ -1,13 +1,18 @@
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components import esp32, i2c
from esphome.components.audio_adc import AudioAdc
from esphome.components.esp32.const import VARIANT_ESP32, VARIANT_ESP32S2
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE
from esphome.core import CORE
from esphome.types import ConfigType
CODEOWNERS = ["@kahrendt"]
DEPENDENCIES = ["i2c"]
CONF_CHANNEL_GAINS = "channel_gains"
CONF_TDM = "tdm"
es7210_ns = cg.esphome_ns.namespace("es7210")
ES7210 = es7210_ns.class_("ES7210", AudioAdc, cg.Component, i2c.I2CDevice)
@@ -21,24 +26,42 @@ ES7210_BITS_PER_SAMPLE_ENUM = {
ES7210_MIC_GAINS = [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 34.5, 36, 37.5]
ES7210_MIC_GAIN_DEFAULT = 24
_validate_bits = cv.float_with_unit("bits", "bit")
CONFIG_SCHEMA = (
def _validate_tdm(value: bool) -> bool:
"""Reject TDM on ESP32 variants without TDM hardware support."""
if value and CORE.is_esp32:
esp32.only_on_variant(
unsupported=[VARIANT_ESP32, VARIANT_ESP32S2],
msg_prefix="ES7210 TDM mode",
)(value)
return value
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ES7210),
cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All(
_validate_bits, cv.enum(ES7210_BITS_PER_SAMPLE_ENUM)
),
cv.Optional(CONF_MIC_GAIN, default="24db"): cv.All(
cv.Optional(CONF_MIC_GAIN): cv.All(
cv.decibel, cv.one_of(*ES7210_MIC_GAINS)
),
cv.Optional(CONF_CHANNEL_GAINS): cv.All(
cv.ensure_list(cv.All(cv.decibel, cv.one_of(*ES7210_MIC_GAINS))),
cv.Length(min=4, max=4),
),
cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1),
cv.Optional(CONF_TDM, default=False): cv.All(cv.boolean, _validate_tdm),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x40))
.extend(i2c.i2c_device_schema(0x40)),
cv.has_at_most_one_key(CONF_MIC_GAIN, CONF_CHANNEL_GAINS),
)
@@ -48,5 +71,10 @@ async def to_code(config: ConfigType) -> None:
await i2c.register_i2c_device(var, config)
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
cg.add(var.set_mic_gain(config[CONF_MIC_GAIN]))
if channel_gains := config.get(CONF_CHANNEL_GAINS):
for channel, gain in enumerate(channel_gains):
cg.add(var.set_channel_gain(channel, gain))
else:
cg.add(var.set_mic_gain(config.get(CONF_MIC_GAIN, ES7210_MIC_GAIN_DEFAULT)))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(var.set_tdm(config[CONF_TDM]))
+23 -8
View File
@@ -27,8 +27,10 @@ void ES7210::dump_config() {
ESP_LOGCONFIG(TAG,
"ES7210 audio ADC:\n"
" Bits Per Sample: %" PRIu8 "\n"
" Sample Rate: %" PRIu32,
this->bits_per_sample_, this->sample_rate_);
" Sample Rate: %" PRIu32 "\n"
" Channel Gains: %.1f dB, %.1f dB, %.1f dB, %.1f dB",
this->bits_per_sample_, this->sample_rate_, this->channel_gains_[0], this->channel_gains_[1],
this->channel_gains_[2], this->channel_gains_[3]);
if (this->is_failed()) {
ESP_LOGE(TAG, " Failed to initialize");
@@ -88,7 +90,17 @@ void ES7210::setup() {
}
bool ES7210::set_mic_gain(float mic_gain) {
this->mic_gain_ = clamp<float>(mic_gain, ES7210_MIC_GAIN_MIN, ES7210_MIC_GAIN_MAX);
this->channel_gains_.fill(clamp<float>(mic_gain, ES7210_MIC_GAIN_MIN, ES7210_MIC_GAIN_MAX));
if (this->setup_complete_) {
return this->configure_mic_gain_();
}
return true;
}
bool ES7210::set_channel_gain(uint8_t channel, float gain) {
if (channel >= this->channel_gains_.size())
return false;
this->channel_gains_[channel] = clamp<float>(gain, ES7210_MIC_GAIN_MIN, ES7210_MIC_GAIN_MAX);
if (this->setup_complete_) {
return this->configure_mic_gain_();
}
@@ -133,7 +145,6 @@ bool ES7210::configure_sample_rate_() {
}
bool ES7210::configure_mic_gain_() {
auto regv = this->es7210_gain_reg_value_(this->mic_gain_);
for (uint8_t i = 0; i < 4; ++i) {
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00));
}
@@ -144,26 +155,30 @@ bool ES7210::configure_mic_gain_() {
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f, regv));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[0])));
// Configure mic 2
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f, regv));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[1])));
// Configure mic 3
// MIC3 uses the ADC3/4 and MIC3/4 clock domains (bits 2 and 4), not the MIC1/2 domains.
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f, regv));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[2])));
// Configure mic 4
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f, regv));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[3])));
return true;
}
+10 -3
View File
@@ -1,5 +1,7 @@
#pragma once
#include <array>
#include "esphome/components/audio_adc/audio_adc.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
@@ -27,10 +29,15 @@ class ES7210 final : public audio_adc::AudioAdc, public Component, public i2c::I
void dump_config() override;
void set_bits_per_sample(ES7210BitsPerSample bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
/// Sets all four channel gains, replacing any configured per-channel gains.
bool set_mic_gain(float mic_gain) override;
/// Sets a zero-based channel gain; returns false for an invalid channel or an I2C failure.
bool set_channel_gain(uint8_t channel, float gain);
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
void set_tdm(bool enable_tdm) { this->enable_tdm_ = enable_tdm; }
float mic_gain() override { return this->mic_gain_; };
/// Returns the MIC1 gain; other channels may have different gains.
float mic_gain() override { return this->channel_gains_[0]; };
protected:
/// @brief Updates an I2C registry address by modifying the current state
@@ -50,8 +57,8 @@ class ES7210 final : public audio_adc::AudioAdc, public Component, public i2c::I
bool configure_sample_rate_();
bool setup_complete_{false};
bool enable_tdm_{false}; // TDM is unsupported in ESPHome as of version 2024.12
float mic_gain_{0};
bool enable_tdm_{false};
std::array<float, 4> channel_gains_{{0.0f, 0.0f, 0.0f, 0.0f}};
ES7210BitsPerSample bits_per_sample_{ES7210_BITS_PER_SAMPLE_16};
uint32_t sample_rate_{0};
};
@@ -0,0 +1,89 @@
"""Tests for ES7210 audio ADC configuration validation."""
import pytest
from esphome.components.es7210.audio_adc import (
CONF_CHANNEL_GAINS,
CONF_TDM,
CONFIG_SCHEMA,
)
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
import esphome.config_validation as cv
from esphome.const import CONF_MIC_GAIN, KEY_VARIANT, PlatformFramework
from tests.component_tests.types import SetCoreConfigCallable
def test_channel_gains_valid_config() -> None:
"""Per-channel gains accept an AEC microphone and reference configuration."""
config = CONFIG_SCHEMA({CONF_CHANNEL_GAINS: ["33db", "33db", "0db", "0db"]})
assert config[CONF_CHANNEL_GAINS] == [33.0, 33.0, 0.0, 0.0]
assert CONF_MIC_GAIN not in config
def test_omitted_gains_remain_absent() -> None:
"""Code generation supplies the default when neither gain option is configured."""
config = CONFIG_SCHEMA({})
assert CONF_MIC_GAIN not in config
assert CONF_CHANNEL_GAINS not in config
@pytest.mark.parametrize("variant", [VARIANT_ESP32, VARIANT_ESP32S2])
def test_tdm_rejected_on_unsupported_variants(
variant: str, set_core_config: SetCoreConfigCallable
) -> None:
"""ESP32 and ESP32-S2 cannot receive TDM audio."""
set_core_config(PlatformFramework.ESP32_IDF, platform_data={KEY_VARIANT: variant})
with pytest.raises(cv.Invalid, match="ES7210 TDM mode is not available") as exc:
CONFIG_SCHEMA({CONF_TDM: True})
assert exc.value.path == [CONF_TDM]
@pytest.mark.parametrize("variant", [VARIANT_ESP32, VARIANT_ESP32S2])
@pytest.mark.parametrize("tdm_config", [{}, {CONF_TDM: False}])
def test_tdm_disabled_on_unsupported_variants(
variant: str,
tdm_config: dict[str, bool],
set_core_config: SetCoreConfigCallable,
) -> None:
"""Unsupported variants still accept standard I2S mode."""
set_core_config(PlatformFramework.ESP32_IDF, platform_data={KEY_VARIANT: variant})
assert CONFIG_SCHEMA(tdm_config)[CONF_TDM] is False
@pytest.mark.parametrize("variant", [VARIANT_ESP32S3, VARIANT_ESP32C3, VARIANT_ESP32P4])
def test_tdm_accepted_on_supported_variants(
variant: str, set_core_config: SetCoreConfigCallable
) -> None:
"""Variants with TDM hardware accept TDM mode."""
set_core_config(PlatformFramework.ESP32_IDF, platform_data={KEY_VARIANT: variant})
assert CONFIG_SCHEMA({CONF_TDM: True})[CONF_TDM] is True
@pytest.mark.parametrize("channel_count", [3, 5])
def test_channel_gains_requires_four_channels(channel_count: int) -> None:
"""Channel gains must provide one value for each ES7210 input."""
with pytest.raises(cv.Invalid):
CONFIG_SCHEMA({CONF_CHANNEL_GAINS: ["0db"] * channel_count})
def test_channel_gains_and_mic_gain_are_mutually_exclusive() -> None:
"""Uniform and per-channel gain configuration cannot be combined."""
with pytest.raises(cv.Invalid, match="Cannot specify more than one"):
CONFIG_SCHEMA(
{
CONF_MIC_GAIN: "24db",
CONF_CHANNEL_GAINS: ["0db"] * 4,
}
)
@@ -0,0 +1,11 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
audio_adc:
- platform: es7210
i2c_id: i2c_bus
id: es7210_adc
bits_per_sample: 16bit
channel_gains: [33db, 33db, 0db, 0db]
sample_rate: 16000
tdm: true