mirror of
https://github.com/esphome/esphome.git
synced 2025-07-24 04:06:35 +00:00
[i2s_audio, i2s_audio_microphone, i2s_audio_speaker] Add basic support for new esp-idf 5.x.x i2s driver. (#8181)
This commit is contained in:
parent
f29ccb9e75
commit
6792ff6d58
@ -8,7 +8,15 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
|
||||
from esphome.const import (
|
||||
CONF_BITS_PER_SAMPLE,
|
||||
CONF_CHANNEL,
|
||||
CONF_ID,
|
||||
CONF_SAMPLE_RATE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
import esphome.final_validate as fv
|
||||
|
||||
@ -35,6 +43,9 @@ CONF_MONO = "mono"
|
||||
CONF_LEFT = "left"
|
||||
CONF_RIGHT = "right"
|
||||
CONF_STEREO = "stereo"
|
||||
CONF_BOTH = "both"
|
||||
|
||||
CONF_USE_LEGACY = "use_legacy"
|
||||
|
||||
i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
|
||||
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
|
||||
@ -50,6 +61,12 @@ I2S_MODE_OPTIONS = {
|
||||
CONF_SECONDARY: i2s_mode_t.I2S_MODE_SLAVE, # NOLINT
|
||||
}
|
||||
|
||||
i2s_role_t = cg.global_ns.enum("i2s_role_t")
|
||||
I2S_ROLE_OPTIONS = {
|
||||
CONF_PRIMARY: i2s_role_t.I2S_ROLE_MASTER, # NOLINT
|
||||
CONF_SECONDARY: i2s_role_t.I2S_ROLE_SLAVE, # NOLINT
|
||||
}
|
||||
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h
|
||||
I2S_PORTS = {
|
||||
VARIANT_ESP32: 2,
|
||||
@ -60,10 +77,23 @@ I2S_PORTS = {
|
||||
|
||||
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
|
||||
I2S_CHANNELS = {
|
||||
CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT,
|
||||
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
|
||||
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT, # left data to both channels
|
||||
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, # mono data
|
||||
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, # mono data
|
||||
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT, # stereo data to both channels
|
||||
}
|
||||
|
||||
i2s_slot_mode_t = cg.global_ns.enum("i2s_slot_mode_t")
|
||||
I2S_SLOT_MODE = {
|
||||
CONF_MONO: i2s_slot_mode_t.I2S_SLOT_MODE_MONO,
|
||||
CONF_STEREO: i2s_slot_mode_t.I2S_SLOT_MODE_STEREO,
|
||||
}
|
||||
|
||||
i2s_std_slot_mask_t = cg.global_ns.enum("i2s_std_slot_mask_t")
|
||||
I2S_STD_SLOT_MASK = {
|
||||
CONF_LEFT: i2s_std_slot_mask_t.I2S_STD_SLOT_LEFT,
|
||||
CONF_RIGHT: i2s_std_slot_mask_t.I2S_STD_SLOT_RIGHT,
|
||||
CONF_BOTH: i2s_std_slot_mask_t.I2S_STD_SLOT_BOTH,
|
||||
}
|
||||
|
||||
i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t")
|
||||
@ -83,8 +113,19 @@ I2S_BITS_PER_CHANNEL = {
|
||||
32: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_32BIT,
|
||||
}
|
||||
|
||||
i2s_slot_bit_width_t = cg.global_ns.enum("i2s_slot_bit_width_t")
|
||||
I2S_SLOT_BIT_WIDTH = {
|
||||
"default": i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
8: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_8BIT,
|
||||
16: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_16BIT,
|
||||
24: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_24BIT,
|
||||
32: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_32BIT,
|
||||
}
|
||||
|
||||
_validate_bits = cv.float_with_unit("bits", "bit")
|
||||
|
||||
_use_legacy_driver = None
|
||||
|
||||
|
||||
def i2s_audio_component_schema(
|
||||
class_: MockObjClass,
|
||||
@ -97,20 +138,22 @@ def i2s_audio_component_schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Optional(CONF_CHANNEL, default=default_channel): cv.enum(I2S_CHANNELS),
|
||||
cv.Optional(CONF_CHANNEL, default=default_channel): cv.one_of(
|
||||
*I2S_CHANNELS
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default=default_sample_rate): cv.int_range(
|
||||
min=1
|
||||
),
|
||||
cv.Optional(CONF_BITS_PER_SAMPLE, default=default_bits_per_sample): cv.All(
|
||||
_validate_bits, cv.enum(I2S_BITS_PER_SAMPLE)
|
||||
_validate_bits, cv.one_of(*I2S_BITS_PER_SAMPLE)
|
||||
),
|
||||
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
|
||||
I2S_MODE_OPTIONS, lower=True
|
||||
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.one_of(
|
||||
*I2S_MODE_OPTIONS, lower=True
|
||||
),
|
||||
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All(
|
||||
cv.Any(cv.float_with_unit("bits", "bit"), "default"),
|
||||
cv.enum(I2S_BITS_PER_CHANNEL),
|
||||
cv.one_of(*I2S_BITS_PER_CHANNEL),
|
||||
),
|
||||
}
|
||||
)
|
||||
@ -118,22 +161,60 @@ def i2s_audio_component_schema(
|
||||
|
||||
async def register_i2s_audio_component(var, config):
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
|
||||
cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
if use_legacy():
|
||||
cg.add(var.set_i2s_mode(I2S_MODE_OPTIONS[config[CONF_I2S_MODE]]))
|
||||
cg.add(var.set_channel(I2S_CHANNELS[config[CONF_CHANNEL]]))
|
||||
cg.add(
|
||||
var.set_bits_per_sample(I2S_BITS_PER_SAMPLE[config[CONF_BITS_PER_SAMPLE]])
|
||||
)
|
||||
cg.add(
|
||||
var.set_bits_per_channel(
|
||||
I2S_BITS_PER_CHANNEL[config[CONF_BITS_PER_CHANNEL]]
|
||||
)
|
||||
)
|
||||
else:
|
||||
cg.add(var.set_i2s_role(I2S_ROLE_OPTIONS[config[CONF_I2S_MODE]]))
|
||||
slot_mode = config[CONF_CHANNEL]
|
||||
if slot_mode != CONF_STEREO:
|
||||
slot_mode = CONF_MONO
|
||||
slot_mask = config[CONF_CHANNEL]
|
||||
if slot_mask not in [CONF_LEFT, CONF_RIGHT]:
|
||||
slot_mask = CONF_BOTH
|
||||
cg.add(var.set_slot_mode(I2S_SLOT_MODE[slot_mode]))
|
||||
cg.add(var.set_std_slot_mask(I2S_STD_SLOT_MASK[slot_mask]))
|
||||
cg.add(
|
||||
var.set_slot_bit_width(I2S_SLOT_BIT_WIDTH[config[CONF_BITS_PER_CHANNEL]])
|
||||
)
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
||||
cg.add(var.set_bits_per_channel(config[CONF_BITS_PER_CHANNEL]))
|
||||
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
|
||||
cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
}
|
||||
def validate_use_legacy(value):
|
||||
global _use_legacy_driver # noqa: PLW0603
|
||||
if CONF_USE_LEGACY in value:
|
||||
if (_use_legacy_driver is not None) and (
|
||||
_use_legacy_driver != value[CONF_USE_LEGACY]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"All i2s_audio components must set {CONF_USE_LEGACY} to the same value."
|
||||
)
|
||||
if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino):
|
||||
raise cv.Invalid("Arduino supports only the legacy i2s driver.")
|
||||
_use_legacy_driver = value[CONF_USE_LEGACY]
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
|
||||
cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_USE_LEGACY): cv.boolean,
|
||||
},
|
||||
),
|
||||
validate_use_legacy,
|
||||
)
|
||||
|
||||
|
||||
@ -148,12 +229,22 @@ def _final_validate(_):
|
||||
)
|
||||
|
||||
|
||||
def use_legacy():
|
||||
framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0):
|
||||
if not _use_legacy_driver:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if use_legacy():
|
||||
cg.add_define("USE_I2S_LEGACY")
|
||||
|
||||
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
|
||||
if CONF_I2S_BCLK_PIN in config:
|
||||
|
@ -2,9 +2,14 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <driver/i2s.h>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#include <driver/i2s.h>
|
||||
#else
|
||||
#include <driver/i2s_std.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
@ -13,19 +18,33 @@ class I2SAudioComponent;
|
||||
|
||||
class I2SAudioBase : public Parented<I2SAudioComponent> {
|
||||
public:
|
||||
#ifdef USE_I2S_LEGACY
|
||||
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
|
||||
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
||||
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
||||
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
||||
void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; }
|
||||
#else
|
||||
void set_i2s_role(i2s_role_t role) { this->i2s_role_ = role; }
|
||||
void set_slot_mode(i2s_slot_mode_t slot_mode) { this->slot_mode_ = slot_mode; }
|
||||
void set_std_slot_mask(i2s_std_slot_mask_t std_slot_mask) { this->std_slot_mask_ = std_slot_mask; }
|
||||
void set_slot_bit_width(i2s_slot_bit_width_t slot_bit_width) { this->slot_bit_width_ = slot_bit_width; }
|
||||
#endif
|
||||
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
||||
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
|
||||
|
||||
protected:
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_mode_t i2s_mode_{};
|
||||
i2s_channel_fmt_t channel_;
|
||||
uint32_t sample_rate_;
|
||||
i2s_bits_per_sample_t bits_per_sample_;
|
||||
i2s_bits_per_chan_t bits_per_channel_;
|
||||
#else
|
||||
i2s_role_t i2s_role_{};
|
||||
i2s_slot_mode_t slot_mode_;
|
||||
i2s_std_slot_mask_t std_slot_mask_;
|
||||
i2s_slot_bit_width_t slot_bit_width_;
|
||||
#endif
|
||||
uint32_t sample_rate_;
|
||||
bool use_apll_;
|
||||
};
|
||||
|
||||
@ -37,6 +56,7 @@ class I2SAudioComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_pin_config_t get_pin_config() const {
|
||||
return {
|
||||
.mck_io_num = this->mclk_pin_,
|
||||
@ -46,6 +66,20 @@ class I2SAudioComponent : public Component {
|
||||
.data_in_num = I2S_PIN_NO_CHANGE,
|
||||
};
|
||||
}
|
||||
#else
|
||||
i2s_std_gpio_config_t get_pin_config() const {
|
||||
return {.mclk = (gpio_num_t) this->mclk_pin_,
|
||||
.bclk = (gpio_num_t) this->bclk_pin_,
|
||||
.ws = (gpio_num_t) this->lrclk_pin_,
|
||||
.dout = I2S_GPIO_UNUSED, // add local ports
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
}};
|
||||
}
|
||||
#endif
|
||||
|
||||
void set_mclk_pin(int pin) { this->mclk_pin_ = pin; }
|
||||
void set_bclk_pin(int pin) { this->bclk_pin_ = pin; }
|
||||
@ -62,9 +96,13 @@ class I2SAudioComponent : public Component {
|
||||
|
||||
I2SAudioIn *audio_in_{nullptr};
|
||||
I2SAudioOut *audio_out_{nullptr};
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
int mclk_pin_{I2S_PIN_NO_CHANGE};
|
||||
int bclk_pin_{I2S_PIN_NO_CHANGE};
|
||||
#else
|
||||
int mclk_pin_{I2S_GPIO_UNUSED};
|
||||
int bclk_pin_{I2S_GPIO_UNUSED};
|
||||
#endif
|
||||
int lrclk_pin_;
|
||||
i2s_port_t port_{};
|
||||
};
|
||||
|
@ -14,6 +14,7 @@ from .. import (
|
||||
I2SAudioComponent,
|
||||
I2SAudioOut,
|
||||
i2s_audio_ns,
|
||||
use_legacy,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@ -87,6 +88,14 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(_):
|
||||
if not use_legacy():
|
||||
raise cv.Invalid("I2S media player is only compatible with legacy i2s driver.")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
@ -6,12 +6,15 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NUMBER
|
||||
|
||||
from .. import (
|
||||
CONF_CHANNEL,
|
||||
CONF_I2S_DIN_PIN,
|
||||
CONF_MONO,
|
||||
CONF_RIGHT,
|
||||
I2SAudioIn,
|
||||
i2s_audio_component_schema,
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
use_legacy,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@ -43,6 +46,12 @@ def validate_esp32_variant(config):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_channel(config):
|
||||
if config[CONF_CHANNEL] == CONF_MONO:
|
||||
raise cv.Invalid(f"I2S microphone does not support {CONF_MONO}.")
|
||||
return config
|
||||
|
||||
|
||||
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
|
||||
i2s_audio_component_schema(
|
||||
I2SAudioMicrophone,
|
||||
@ -71,9 +80,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
key=CONF_ADC_TYPE,
|
||||
),
|
||||
validate_esp32_variant,
|
||||
validate_channel,
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
if not use_legacy():
|
||||
if config[CONF_ADC_TYPE] == "internal":
|
||||
raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver.")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
@ -2,7 +2,12 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#include <driver/i2s.h>
|
||||
#else
|
||||
#include <driver/i2s_std.h>
|
||||
#include <driver/i2s_pdm.h>
|
||||
#endif
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@ -16,6 +21,7 @@ static const char *const TAG = "i2s_audio.microphone";
|
||||
|
||||
void I2SAudioMicrophone::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
if (this->parent_->get_port() != I2S_NUM_0) {
|
||||
@ -24,6 +30,7 @@ void I2SAudioMicrophone::setup() {
|
||||
return;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
if (this->pdm_) {
|
||||
@ -47,6 +54,9 @@ void I2SAudioMicrophone::start_() {
|
||||
if (!this->parent_->try_lock()) {
|
||||
return; // Waiting for another i2s to return lock
|
||||
}
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_driver_config_t config = {
|
||||
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
|
||||
.sample_rate = this->sample_rate_,
|
||||
@ -63,8 +73,6 @@ void I2SAudioMicrophone::start_() {
|
||||
.bits_per_chan = this->bits_per_channel_,
|
||||
};
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
|
||||
@ -111,6 +119,109 @@ void I2SAudioMicrophone::start_() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#else
|
||||
i2s_chan_config_t chan_cfg = {
|
||||
.id = this->parent_->get_port(),
|
||||
.role = this->i2s_role_,
|
||||
.dma_desc_num = 4,
|
||||
.dma_frame_num = 256,
|
||||
.auto_clear = false,
|
||||
};
|
||||
/* Allocate a new RX channel and get the handle of this channel */
|
||||
err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
i2s_clock_src_t clk_src = I2S_CLK_SRC_DEFAULT;
|
||||
#ifdef I2S_CLK_SRC_APLL
|
||||
if (this->use_apll_) {
|
||||
clk_src = I2S_CLK_SRC_APLL;
|
||||
}
|
||||
#endif
|
||||
i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
if (this->pdm_) {
|
||||
i2s_pdm_rx_clk_config_t clk_cfg = {
|
||||
.sample_rate_hz = this->sample_rate_,
|
||||
.clk_src = clk_src,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.dn_sample_mode = I2S_PDM_DSR_8S,
|
||||
};
|
||||
|
||||
i2s_pdm_rx_slot_config_t slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, this->slot_mode_);
|
||||
switch (this->std_slot_mask_) {
|
||||
case I2S_STD_SLOT_LEFT:
|
||||
slot_cfg.slot_mask = I2S_PDM_SLOT_LEFT;
|
||||
break;
|
||||
case I2S_STD_SLOT_RIGHT:
|
||||
slot_cfg.slot_mask = I2S_PDM_SLOT_RIGHT;
|
||||
break;
|
||||
case I2S_STD_SLOT_BOTH:
|
||||
slot_cfg.slot_mask = I2S_PDM_SLOT_BOTH;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Init the channel into PDM RX mode */
|
||||
i2s_pdm_rx_config_t pdm_rx_cfg = {
|
||||
.clk_cfg = clk_cfg,
|
||||
.slot_cfg = slot_cfg,
|
||||
.gpio_cfg =
|
||||
{
|
||||
.clk = pin_config.ws,
|
||||
.din = this->din_pin_,
|
||||
.invert_flags =
|
||||
{
|
||||
.clk_inv = pin_config.invert_flags.ws_inv,
|
||||
},
|
||||
},
|
||||
};
|
||||
err = i2s_channel_init_pdm_rx_mode(this->rx_handle_, &pdm_rx_cfg);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
i2s_std_clk_config_t clk_cfg = {
|
||||
.sample_rate_hz = this->sample_rate_,
|
||||
.clk_src = clk_src,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
};
|
||||
i2s_data_bit_width_t data_bit_width;
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_8BIT) {
|
||||
data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
|
||||
} else {
|
||||
data_bit_width = I2S_DATA_BIT_WIDTH_8BIT;
|
||||
}
|
||||
i2s_std_slot_config_t std_slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(data_bit_width, this->slot_mode_);
|
||||
std_slot_cfg.slot_bit_width = this->slot_bit_width_;
|
||||
std_slot_cfg.slot_mask = this->std_slot_mask_;
|
||||
|
||||
pin_config.din = this->din_pin_;
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = clk_cfg,
|
||||
.slot_cfg = std_slot_cfg,
|
||||
.gpio_cfg = pin_config,
|
||||
};
|
||||
/* Initialize the channel */
|
||||
err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Before reading data, start the RX channel first */
|
||||
i2s_channel_enable(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->state_ = microphone::STATE_RUNNING;
|
||||
this->high_freq_.start();
|
||||
this->status_clear_error();
|
||||
@ -128,6 +239,7 @@ void I2SAudioMicrophone::stop() {
|
||||
|
||||
void I2SAudioMicrophone::stop_() {
|
||||
esp_err_t err;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
err = i2s_adc_disable(this->parent_->get_port());
|
||||
@ -150,6 +262,22 @@ void I2SAudioMicrophone::stop_() {
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
/* Have to stop the channel before deleting it */
|
||||
err = i2s_channel_disable(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
/* If the handle is not needed any more, delete it to release the channel resources */
|
||||
err = i2s_del_channel(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error deleting I2S channel: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
this->parent_->unlock();
|
||||
this->state_ = microphone::STATE_STOPPED;
|
||||
this->high_freq_.stop();
|
||||
@ -158,7 +286,11 @@ void I2SAudioMicrophone::stop_() {
|
||||
|
||||
size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
||||
size_t bytes_read = 0;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
esp_err_t err = i2s_read(this->parent_->get_port(), buf, len, &bytes_read, (100 / portTICK_PERIOD_MS));
|
||||
#else
|
||||
esp_err_t err = i2s_channel_read(this->rx_handle_, buf, len, &bytes_read, (100 / portTICK_PERIOD_MS));
|
||||
#endif
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err));
|
||||
this->status_set_warning();
|
||||
@ -171,6 +303,7 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
||||
this->status_clear_warning();
|
||||
// ESP-IDF I2S implementation right-extends 8-bit data to 16 bits,
|
||||
// and 24-bit data to 32 bits.
|
||||
#ifdef USE_I2S_LEGACY
|
||||
switch (this->bits_per_sample_) {
|
||||
case I2S_BITS_PER_SAMPLE_8BIT:
|
||||
case I2S_BITS_PER_SAMPLE_16BIT:
|
||||
@ -188,6 +321,30 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
||||
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#ifndef USE_ESP32_VARIANT_ESP32
|
||||
// For newer ESP32 variants 8 bit data needs to be extended to 16 bit.
|
||||
if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_8BIT) {
|
||||
size_t samples_read = bytes_read / sizeof(int8_t);
|
||||
for (size_t i = samples_read - 1; i >= 0; i--) {
|
||||
int16_t temp = static_cast<int16_t>(reinterpret_cast<int8_t *>(buf)[i]) << 8;
|
||||
buf[i] = temp;
|
||||
}
|
||||
return samples_read * sizeof(int16_t);
|
||||
}
|
||||
#else
|
||||
// For ESP32 8/16 bit standard mono mode samples need to be switched.
|
||||
if (this->slot_mode_ == I2S_SLOT_MODE_MONO && this->slot_bit_width_ <= 16 && !this->pdm_) {
|
||||
size_t samples_read = bytes_read / sizeof(int16_t);
|
||||
for (int i = 0; i < samples_read; i += 2) {
|
||||
int16_t tmp = buf[i];
|
||||
buf[i] = buf[i + 1];
|
||||
buf[i + 1] = tmp;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return bytes_read;
|
||||
#endif
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::read_() {
|
||||
|
@ -17,17 +17,23 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
void stop() override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
|
||||
#else
|
||||
void set_din_pin(int8_t pin) { this->din_pin_ = (gpio_num_t) pin; }
|
||||
#endif
|
||||
|
||||
void set_pdm(bool pdm) { this->pdm_ = pdm; }
|
||||
|
||||
size_t read(int16_t *buf, size_t len) override;
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
void set_adc_channel(adc1_channel_t channel) {
|
||||
this->adc_channel_ = channel;
|
||||
this->adc_ = true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
protected:
|
||||
@ -35,10 +41,15 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
void stop_();
|
||||
void read_();
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
int8_t din_pin_{I2S_PIN_NO_CHANGE};
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
adc1_channel_t adc_channel_{ADC1_CHANNEL_MAX};
|
||||
bool adc_{false};
|
||||
#endif
|
||||
#else
|
||||
gpio_num_t din_pin_{I2S_GPIO_UNUSED};
|
||||
i2s_chan_handle_t rx_handle_;
|
||||
#endif
|
||||
bool pdm_{false};
|
||||
|
||||
|
@ -26,6 +26,7 @@ from .. import (
|
||||
i2s_audio_component_schema,
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
use_legacy,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["audio"]
|
||||
@ -60,7 +61,7 @@ I2C_COMM_FMT_OPTIONS = {
|
||||
"pcm_long": i2s_comm_format_t.I2S_COMM_FORMAT_PCM_LONG,
|
||||
}
|
||||
|
||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
||||
INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32]
|
||||
|
||||
|
||||
def _set_num_channels_from_config(config):
|
||||
@ -101,7 +102,7 @@ def _validate_esp32_variant(config):
|
||||
if config[CONF_DAC_TYPE] != "internal":
|
||||
return config
|
||||
variant = esp32.get_esp32_variant()
|
||||
if variant in NO_INTERNAL_DAC_VARIANTS:
|
||||
if variant not in INTERNAL_DAC_VARIANTS:
|
||||
raise cv.Invalid(f"{variant} does not have an internal DAC")
|
||||
return config
|
||||
|
||||
@ -143,8 +144,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(
|
||||
CONF_I2S_DOUT_PIN
|
||||
): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_COMM_FMT, default="stand_i2s"): cv.enum(
|
||||
I2C_COMM_FMT_OPTIONS, lower=True
|
||||
cv.Optional(CONF_I2S_COMM_FMT, default="stand_i2s"): cv.one_of(
|
||||
*I2C_COMM_FMT_OPTIONS, lower=True
|
||||
),
|
||||
}
|
||||
),
|
||||
@ -157,6 +158,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
if not use_legacy():
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
raise cv.Invalid("Internal DAC is only compatible with legacy i2s driver.")
|
||||
if config[CONF_I2S_COMM_FMT] == "stand_max":
|
||||
raise cv.Invalid(
|
||||
"I2S standard max format only implemented with legacy i2s driver."
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
@ -167,7 +181,17 @@ async def to_code(config):
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
||||
else:
|
||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||
cg.add(var.set_i2s_comm_fmt(config[CONF_I2S_COMM_FMT]))
|
||||
if use_legacy():
|
||||
cg.add(
|
||||
var.set_i2s_comm_fmt(I2C_COMM_FMT_OPTIONS[config[CONF_I2S_COMM_FMT]])
|
||||
)
|
||||
else:
|
||||
fmt = "std" # equals stand_i2s, stand_pcm_long, i2s_msb, pcm_long
|
||||
if config[CONF_I2S_COMM_FMT] in ["stand_msb", "i2s_lsb"]:
|
||||
fmt = "msb"
|
||||
elif config[CONF_I2S_COMM_FMT] in ["stand_pcm_short", "pcm_short", "pcm"]:
|
||||
fmt = "pcm"
|
||||
cg.add(var.set_i2s_comm_fmt(fmt))
|
||||
if config[CONF_TIMEOUT] != CONF_NEVER:
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_buffer_duration(config[CONF_BUFFER_DURATION]))
|
||||
|
@ -2,7 +2,11 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#include <driver/i2s.h>
|
||||
#else
|
||||
#include <driver/i2s_std.h>
|
||||
#endif
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
|
||||
@ -294,13 +298,21 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
// Audio stream info changed, stop the speaker task so it will restart with the proper settings.
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_event_t i2s_event;
|
||||
while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
|
||||
if (i2s_event.type == I2S_EVENT_TX_Q_OVF) {
|
||||
tx_dma_underflow = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
bool overflow;
|
||||
while (xQueueReceive(this_speaker->i2s_event_queue_, &overflow, 0)) {
|
||||
if (overflow) {
|
||||
tx_dma_underflow = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this_speaker->pause_state_) {
|
||||
// Pause state is accessed atomically, so thread safe
|
||||
@ -319,6 +331,18 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
bytes_read / sizeof(int16_t), this_speaker->q15_volume_factor_);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// For ESP32 8/16 bit mono mode samples need to be switched.
|
||||
if (audio_stream_info.get_channels() == 1 && audio_stream_info.get_bits_per_sample() <= 16) {
|
||||
size_t len = bytes_read / sizeof(int16_t);
|
||||
int16_t *tmp_buf = (int16_t *) this_speaker->data_buffer_;
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
int16_t tmp = tmp_buf[i];
|
||||
tmp_buf[i] = tmp_buf[i + 1];
|
||||
tmp_buf[i + 1] = tmp;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Write the audio data to a single DMA buffer at a time to reduce latency for the audio duration played
|
||||
// callback.
|
||||
const uint32_t batches = (bytes_read + single_dma_buffer_input_size - 1) / single_dma_buffer_input_size;
|
||||
@ -327,6 +351,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
size_t bytes_written = 0;
|
||||
size_t bytes_to_write = std::min(single_dma_buffer_input_size, bytes_read);
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
if (audio_stream_info.get_bits_per_sample() == (uint8_t) this_speaker->bits_per_sample_) {
|
||||
i2s_write(this_speaker->parent_->get_port(), this_speaker->data_buffer_ + i * single_dma_buffer_input_size,
|
||||
bytes_to_write, &bytes_written, pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS * 5));
|
||||
@ -336,6 +361,10 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
audio_stream_info.get_bits_per_sample(), this_speaker->bits_per_sample_, &bytes_written,
|
||||
pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS * 5));
|
||||
}
|
||||
#else
|
||||
i2s_channel_write(this_speaker->tx_handle_, this_speaker->data_buffer_ + i * single_dma_buffer_input_size,
|
||||
bytes_to_write, &bytes_written, pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS * 5));
|
||||
#endif
|
||||
|
||||
uint32_t write_timestamp = micros();
|
||||
|
||||
@ -369,8 +398,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
}
|
||||
|
||||
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_driver_uninstall(this_speaker->parent_->get_port());
|
||||
#else
|
||||
i2s_channel_disable(this_speaker->tx_handle_);
|
||||
i2s_del_channel(this_speaker->tx_handle_);
|
||||
#endif
|
||||
|
||||
this_speaker->parent_->unlock();
|
||||
}
|
||||
@ -462,12 +495,21 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin
|
||||
}
|
||||
|
||||
esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) {
|
||||
#ifdef USE_I2S_LEGACY
|
||||
if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.get_sample_rate())) { // NOLINT
|
||||
#else
|
||||
if ((this->i2s_role_ & I2S_ROLE_SLAVE) && (this->sample_rate_ != audio_stream_info.get_sample_rate())) { // NOLINT
|
||||
#endif
|
||||
// Can't reconfigure I2S bus, so the sample rate must match the configured value
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
if ((i2s_bits_per_sample_t) audio_stream_info.get_bits_per_sample() > this->bits_per_sample_) {
|
||||
#else
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO &&
|
||||
(i2s_slot_bit_width_t) audio_stream_info.get_bits_per_sample() > this->slot_bit_width_) {
|
||||
#endif
|
||||
// Currently can't handle the case when the incoming audio has more bits per sample than the configured value
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
@ -476,6 +518,9 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint32_t dma_buffer_length = audio_stream_info.ms_to_frames(DMA_BUFFER_DURATION_MS);
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_channel_fmt_t channel = this->channel_;
|
||||
|
||||
if (audio_stream_info.get_channels() == 1) {
|
||||
@ -488,8 +533,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
channel = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
}
|
||||
|
||||
int dma_buffer_length = audio_stream_info.ms_to_frames(DMA_BUFFER_DURATION_MS);
|
||||
|
||||
i2s_driver_config_t config = {
|
||||
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX),
|
||||
.sample_rate = audio_stream_info.get_sample_rate(),
|
||||
@ -498,7 +541,7 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
.communication_format = this->i2s_comm_fmt_,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = DMA_BUFFERS_COUNT,
|
||||
.dma_buf_len = dma_buffer_length,
|
||||
.dma_buf_len = (int) dma_buffer_length,
|
||||
.use_apll = this->use_apll_,
|
||||
.tx_desc_auto_clear = true,
|
||||
.fixed_mclk = I2S_PIN_NO_CHANGE,
|
||||
@ -545,6 +588,89 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
i2s_driver_uninstall(this->parent_->get_port());
|
||||
this->parent_->unlock();
|
||||
}
|
||||
#else
|
||||
i2s_chan_config_t chan_cfg = {
|
||||
.id = this->parent_->get_port(),
|
||||
.role = this->i2s_role_,
|
||||
.dma_desc_num = DMA_BUFFERS_COUNT,
|
||||
.dma_frame_num = dma_buffer_length,
|
||||
.auto_clear = true,
|
||||
};
|
||||
/* Allocate a new TX channel and get the handle of this channel */
|
||||
esp_err_t err = i2s_new_channel(&chan_cfg, &this->tx_handle_, NULL);
|
||||
if (err != ESP_OK) {
|
||||
this->parent_->unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
i2s_clock_src_t clk_src = I2S_CLK_SRC_DEFAULT;
|
||||
#ifdef I2S_CLK_SRC_APLL
|
||||
if (this->use_apll_) {
|
||||
clk_src = I2S_CLK_SRC_APLL;
|
||||
}
|
||||
#endif
|
||||
i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
|
||||
|
||||
i2s_std_clk_config_t clk_cfg = {
|
||||
.sample_rate_hz = audio_stream_info.get_sample_rate(),
|
||||
.clk_src = clk_src,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
};
|
||||
|
||||
i2s_slot_mode_t slot_mode = this->slot_mode_;
|
||||
i2s_std_slot_mask_t slot_mask = this->std_slot_mask_;
|
||||
if (audio_stream_info.get_channels() == 1) {
|
||||
slot_mode = I2S_SLOT_MODE_MONO;
|
||||
} else if (audio_stream_info.get_channels() == 2) {
|
||||
slot_mode = I2S_SLOT_MODE_STEREO;
|
||||
slot_mask = I2S_STD_SLOT_BOTH;
|
||||
}
|
||||
|
||||
i2s_std_slot_config_t std_slot_cfg;
|
||||
if (this->i2s_comm_fmt_ == "std") {
|
||||
std_slot_cfg =
|
||||
I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) audio_stream_info.get_bits_per_sample(), slot_mode);
|
||||
} else if (this->i2s_comm_fmt_ == "pcm") {
|
||||
std_slot_cfg =
|
||||
I2S_STD_PCM_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) audio_stream_info.get_bits_per_sample(), slot_mode);
|
||||
} else {
|
||||
std_slot_cfg =
|
||||
I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) audio_stream_info.get_bits_per_sample(), slot_mode);
|
||||
}
|
||||
std_slot_cfg.slot_bit_width = this->slot_bit_width_;
|
||||
std_slot_cfg.slot_mask = slot_mask;
|
||||
|
||||
pin_config.dout = this->dout_pin_;
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = clk_cfg,
|
||||
.slot_cfg = std_slot_cfg,
|
||||
.gpio_cfg = pin_config,
|
||||
};
|
||||
/* Initialize the channel */
|
||||
err = i2s_channel_init_std_mode(this->tx_handle_, &std_cfg);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
i2s_del_channel(this->tx_handle_);
|
||||
this->parent_->unlock();
|
||||
return err;
|
||||
}
|
||||
if (this->i2s_event_queue_ == nullptr) {
|
||||
this->i2s_event_queue_ = xQueueCreate(1, sizeof(bool));
|
||||
}
|
||||
const i2s_event_callbacks_t callbacks = {
|
||||
.on_send_q_ovf = i2s_overflow_cb,
|
||||
};
|
||||
|
||||
i2s_channel_register_event_callback(this->tx_handle_, &callbacks, this);
|
||||
|
||||
/* Before reading data, start the TX channel first */
|
||||
i2s_channel_enable(this->tx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
i2s_del_channel(this->tx_handle_);
|
||||
this->parent_->unlock();
|
||||
}
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
@ -564,6 +690,15 @@ void I2SAudioSpeaker::delete_task_(size_t buffer_size) {
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
#ifndef USE_I2S_LEGACY
|
||||
bool IRAM_ATTR I2SAudioSpeaker::i2s_overflow_cb(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) {
|
||||
I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) user_ctx;
|
||||
bool overflow = true;
|
||||
xQueueOverwrite(this_speaker->i2s_event_queue_, &overflow);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace i2s_audio
|
||||
} // namespace esphome
|
||||
|
||||
|
@ -4,8 +4,6 @@
|
||||
|
||||
#include "../i2s_audio.h"
|
||||
|
||||
#include <driver/i2s.h>
|
||||
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
@ -30,11 +28,16 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||
|
||||
void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }
|
||||
void set_timeout(uint32_t ms) { this->timeout_ = ms; }
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
||||
#endif
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
||||
void set_i2s_comm_fmt(i2s_comm_format_t mode) { this->i2s_comm_fmt_ = mode; }
|
||||
#else
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = (gpio_num_t) pin; }
|
||||
void set_i2s_comm_fmt(std::string mode) { this->i2s_comm_fmt_ = std::move(mode); }
|
||||
#endif
|
||||
|
||||
void start() override;
|
||||
void stop() override;
|
||||
@ -86,6 +89,10 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||
/// @return True if an ERR_ESP bit is set and false if err == ESP_OK
|
||||
bool send_esp_err_to_event_group_(esp_err_t err);
|
||||
|
||||
#ifndef USE_I2S_LEGACY
|
||||
static bool i2s_overflow_cb(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx);
|
||||
#endif
|
||||
|
||||
/// @brief Allocates the data buffer and ring buffer
|
||||
/// @param data_buffer_size Number of bytes to allocate for the data buffer.
|
||||
/// @param ring_buffer_size Number of bytes to allocate for the ring buffer.
|
||||
@ -121,7 +128,6 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||
uint32_t buffer_duration_ms_;
|
||||
|
||||
optional<uint32_t> timeout_;
|
||||
uint8_t dout_pin_;
|
||||
|
||||
bool task_created_{false};
|
||||
bool pause_state_{false};
|
||||
@ -130,10 +136,17 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||
|
||||
size_t bytes_written_{0};
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||
#endif
|
||||
uint8_t dout_pin_;
|
||||
i2s_comm_format_t i2s_comm_fmt_;
|
||||
#else
|
||||
gpio_num_t dout_pin_;
|
||||
std::string i2s_comm_fmt_;
|
||||
i2s_chan_handle_t tx_handle_;
|
||||
#endif
|
||||
|
||||
uint32_t accumulated_frames_written_{0};
|
||||
};
|
||||
|
@ -115,6 +115,7 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#define USE_PROMETHEUS
|
||||
#define USE_WIFI_WPA2_EAP
|
||||
#define USE_I2S_LEGACY
|
||||
#endif
|
||||
|
||||
// IDF-specific feature flags
|
||||
|
@ -8,6 +8,7 @@ microphone:
|
||||
i2s_din_pin: GPIO17
|
||||
adc_type: external
|
||||
pdm: true
|
||||
bits_per_sample: 16bit
|
||||
|
||||
micro_wake_word:
|
||||
on_wake_word_detected:
|
||||
|
@ -4,9 +4,18 @@ substitutions:
|
||||
i2s_mclk_pin: GPIO17
|
||||
i2s_din_pin: GPIO33
|
||||
|
||||
<<: !include common.yaml
|
||||
i2s_audio:
|
||||
i2s_bclk_pin: ${i2s_bclk_pin}
|
||||
i2s_lrclk_pin: ${i2s_lrclk_pin}
|
||||
i2s_mclk_pin: ${i2s_mclk_pin}
|
||||
use_legacy: true
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: mic_id_external
|
||||
i2s_din_pin: ${i2s_din_pin}
|
||||
adc_type: external
|
||||
pdm: false
|
||||
- platform: i2s_audio
|
||||
id: mic_id_adc
|
||||
adc_pin: 32
|
||||
|
Loading…
x
Reference in New Issue
Block a user