mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[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:
co-authored by
pre-commit-ci-lite[bot]
parent
4e3845ba68
commit
6bf091104f
@@ -1 +1,3 @@
|
||||
"""ES7210 audio ADC component."""
|
||||
|
||||
DOMAIN = "es7210"
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user