mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +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:
		| @@ -8,7 +8,15 @@ from esphome.components.esp32.const import ( | |||||||
|     VARIANT_ESP32S3, |     VARIANT_ESP32S3, | ||||||
| ) | ) | ||||||
| import esphome.config_validation as cv | 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 | from esphome.cpp_generator import MockObjClass | ||||||
| import esphome.final_validate as fv | import esphome.final_validate as fv | ||||||
|  |  | ||||||
| @@ -35,6 +43,9 @@ CONF_MONO = "mono" | |||||||
| CONF_LEFT = "left" | CONF_LEFT = "left" | ||||||
| CONF_RIGHT = "right" | CONF_RIGHT = "right" | ||||||
| CONF_STEREO = "stereo" | CONF_STEREO = "stereo" | ||||||
|  | CONF_BOTH = "both" | ||||||
|  |  | ||||||
|  | CONF_USE_LEGACY = "use_legacy" | ||||||
|  |  | ||||||
| i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") | i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") | ||||||
| I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) | I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) | ||||||
| @@ -50,6 +61,12 @@ I2S_MODE_OPTIONS = { | |||||||
|     CONF_SECONDARY: i2s_mode_t.I2S_MODE_SLAVE,  # NOLINT |     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 | # https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h | ||||||
| I2S_PORTS = { | I2S_PORTS = { | ||||||
|     VARIANT_ESP32: 2, |     VARIANT_ESP32: 2, | ||||||
| @@ -60,10 +77,23 @@ I2S_PORTS = { | |||||||
|  |  | ||||||
| i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") | i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") | ||||||
| I2S_CHANNELS = { | I2S_CHANNELS = { | ||||||
|     CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_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, |     CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,  # mono data | ||||||
|     CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, |     CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,  # mono data | ||||||
|     CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT, |     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") | 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, |     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") | _validate_bits = cv.float_with_unit("bits", "bit") | ||||||
|  |  | ||||||
|  | _use_legacy_driver = None | ||||||
|  |  | ||||||
|  |  | ||||||
| def i2s_audio_component_schema( | def i2s_audio_component_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
| @@ -97,20 +138,22 @@ def i2s_audio_component_schema( | |||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(class_), |             cv.GenerateID(): cv.declare_id(class_), | ||||||
|             cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), |             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( |             cv.Optional(CONF_SAMPLE_RATE, default=default_sample_rate): cv.int_range( | ||||||
|                 min=1 |                 min=1 | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_BITS_PER_SAMPLE, default=default_bits_per_sample): cv.All( |             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( |             cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.one_of( | ||||||
|                 I2S_MODE_OPTIONS, lower=True |                 *I2S_MODE_OPTIONS, lower=True | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_USE_APLL, default=False): cv.boolean, |             cv.Optional(CONF_USE_APLL, default=False): cv.boolean, | ||||||
|             cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All( |             cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All( | ||||||
|                 cv.Any(cv.float_with_unit("bits", "bit"), "default"), |                 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): | async def register_i2s_audio_component(var, config): | ||||||
|     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) |     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) | ||||||
|  |     if use_legacy(): | ||||||
|     cg.add(var.set_i2s_mode(config[CONF_I2S_MODE])) |         cg.add(var.set_i2s_mode(I2S_MODE_OPTIONS[config[CONF_I2S_MODE]])) | ||||||
|     cg.add(var.set_channel(config[CONF_CHANNEL])) |         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_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])) |     cg.add(var.set_use_apll(config[CONF_USE_APLL])) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( | def validate_use_legacy(value): | ||||||
|     { |     global _use_legacy_driver  # noqa: PLW0603 | ||||||
|         cv.GenerateID(): cv.declare_id(I2SAudioComponent), |     if CONF_USE_LEGACY in value: | ||||||
|         cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, |         if (_use_legacy_driver is not None) and ( | ||||||
|         cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, |             _use_legacy_driver != value[CONF_USE_LEGACY] | ||||||
|         cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number, |         ): | ||||||
|     } |             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 | FINAL_VALIDATE_SCHEMA = _final_validate | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     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])) |     cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) | ||||||
|     if CONF_I2S_BCLK_PIN in config: |     if CONF_I2S_BCLK_PIN in config: | ||||||
|   | |||||||
| @@ -2,9 +2,14 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include <driver/i2s.h> |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/helpers.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 esphome { | ||||||
| namespace i2s_audio { | namespace i2s_audio { | ||||||
| @@ -13,19 +18,33 @@ class I2SAudioComponent; | |||||||
|  |  | ||||||
| class I2SAudioBase : public Parented<I2SAudioComponent> { | class I2SAudioBase : public Parented<I2SAudioComponent> { | ||||||
|  public: |  public: | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; } |   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_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_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; } |   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; } |   void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   i2s_mode_t i2s_mode_{}; |   i2s_mode_t i2s_mode_{}; | ||||||
|   i2s_channel_fmt_t channel_; |   i2s_channel_fmt_t channel_; | ||||||
|   uint32_t sample_rate_; |  | ||||||
|   i2s_bits_per_sample_t bits_per_sample_; |   i2s_bits_per_sample_t bits_per_sample_; | ||||||
|   i2s_bits_per_chan_t bits_per_channel_; |   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_; |   bool use_apll_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -37,6 +56,7 @@ class I2SAudioComponent : public Component { | |||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|  |  | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   i2s_pin_config_t get_pin_config() const { |   i2s_pin_config_t get_pin_config() const { | ||||||
|     return { |     return { | ||||||
|         .mck_io_num = this->mclk_pin_, |         .mck_io_num = this->mclk_pin_, | ||||||
| @@ -46,6 +66,20 @@ class I2SAudioComponent : public Component { | |||||||
|         .data_in_num = I2S_PIN_NO_CHANGE, |         .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_mclk_pin(int pin) { this->mclk_pin_ = pin; } | ||||||
|   void set_bclk_pin(int pin) { this->bclk_pin_ = pin; } |   void set_bclk_pin(int pin) { this->bclk_pin_ = pin; } | ||||||
| @@ -62,9 +96,13 @@ class I2SAudioComponent : public Component { | |||||||
|  |  | ||||||
|   I2SAudioIn *audio_in_{nullptr}; |   I2SAudioIn *audio_in_{nullptr}; | ||||||
|   I2SAudioOut *audio_out_{nullptr}; |   I2SAudioOut *audio_out_{nullptr}; | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   int mclk_pin_{I2S_PIN_NO_CHANGE}; |   int mclk_pin_{I2S_PIN_NO_CHANGE}; | ||||||
|   int bclk_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_; |   int lrclk_pin_; | ||||||
|   i2s_port_t port_{}; |   i2s_port_t port_{}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ from .. import ( | |||||||
|     I2SAudioComponent, |     I2SAudioComponent, | ||||||
|     I2SAudioOut, |     I2SAudioOut, | ||||||
|     i2s_audio_ns, |     i2s_audio_ns, | ||||||
|  |     use_legacy, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | 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): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     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 esphome.const import CONF_ID, CONF_NUMBER | ||||||
|  |  | ||||||
| from .. import ( | from .. import ( | ||||||
|  |     CONF_CHANNEL, | ||||||
|     CONF_I2S_DIN_PIN, |     CONF_I2S_DIN_PIN, | ||||||
|  |     CONF_MONO, | ||||||
|     CONF_RIGHT, |     CONF_RIGHT, | ||||||
|     I2SAudioIn, |     I2SAudioIn, | ||||||
|     i2s_audio_component_schema, |     i2s_audio_component_schema, | ||||||
|     i2s_audio_ns, |     i2s_audio_ns, | ||||||
|     register_i2s_audio_component, |     register_i2s_audio_component, | ||||||
|  |     use_legacy, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| @@ -43,6 +46,12 @@ def validate_esp32_variant(config): | |||||||
|     raise NotImplementedError |     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( | BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( | ||||||
|     i2s_audio_component_schema( |     i2s_audio_component_schema( | ||||||
|         I2SAudioMicrophone, |         I2SAudioMicrophone, | ||||||
| @@ -71,9 +80,19 @@ CONFIG_SCHEMA = cv.All( | |||||||
|         key=CONF_ADC_TYPE, |         key=CONF_ADC_TYPE, | ||||||
|     ), |     ), | ||||||
|     validate_esp32_variant, |     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): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|   | |||||||
| @@ -2,7 +2,12 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
| #include <driver/i2s.h> | #include <driver/i2s.h> | ||||||
|  | #else | ||||||
|  | #include <driver/i2s_std.h> | ||||||
|  | #include <driver/i2s_pdm.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| @@ -16,6 +21,7 @@ static const char *const TAG = "i2s_audio.microphone"; | |||||||
|  |  | ||||||
| void I2SAudioMicrophone::setup() { | void I2SAudioMicrophone::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); |   ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
| #if SOC_I2S_SUPPORTS_ADC | #if SOC_I2S_SUPPORTS_ADC | ||||||
|   if (this->adc_) { |   if (this->adc_) { | ||||||
|     if (this->parent_->get_port() != I2S_NUM_0) { |     if (this->parent_->get_port() != I2S_NUM_0) { | ||||||
| @@ -24,6 +30,7 @@ void I2SAudioMicrophone::setup() { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } else |   } else | ||||||
|  | #endif | ||||||
| #endif | #endif | ||||||
|   { |   { | ||||||
|     if (this->pdm_) { |     if (this->pdm_) { | ||||||
| @@ -47,6 +54,9 @@ void I2SAudioMicrophone::start_() { | |||||||
|   if (!this->parent_->try_lock()) { |   if (!this->parent_->try_lock()) { | ||||||
|     return;  // Waiting for another i2s to return lock |     return;  // Waiting for another i2s to return lock | ||||||
|   } |   } | ||||||
|  |   esp_err_t err; | ||||||
|  |  | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   i2s_driver_config_t config = { |   i2s_driver_config_t config = { | ||||||
|       .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX), |       .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX), | ||||||
|       .sample_rate = this->sample_rate_, |       .sample_rate = this->sample_rate_, | ||||||
| @@ -63,8 +73,6 @@ void I2SAudioMicrophone::start_() { | |||||||
|       .bits_per_chan = this->bits_per_channel_, |       .bits_per_chan = this->bits_per_channel_, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   esp_err_t err; |  | ||||||
|  |  | ||||||
| #if SOC_I2S_SUPPORTS_ADC | #if SOC_I2S_SUPPORTS_ADC | ||||||
|   if (this->adc_) { |   if (this->adc_) { | ||||||
|     config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN); |     config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN); | ||||||
| @@ -111,6 +119,109 @@ void I2SAudioMicrophone::start_() { | |||||||
|       return; |       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->state_ = microphone::STATE_RUNNING; | ||||||
|   this->high_freq_.start(); |   this->high_freq_.start(); | ||||||
|   this->status_clear_error(); |   this->status_clear_error(); | ||||||
| @@ -128,6 +239,7 @@ void I2SAudioMicrophone::stop() { | |||||||
|  |  | ||||||
| void I2SAudioMicrophone::stop_() { | void I2SAudioMicrophone::stop_() { | ||||||
|   esp_err_t err; |   esp_err_t err; | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
| #if SOC_I2S_SUPPORTS_ADC | #if SOC_I2S_SUPPORTS_ADC | ||||||
|   if (this->adc_) { |   if (this->adc_) { | ||||||
|     err = i2s_adc_disable(this->parent_->get_port()); |     err = i2s_adc_disable(this->parent_->get_port()); | ||||||
| @@ -150,6 +262,22 @@ void I2SAudioMicrophone::stop_() { | |||||||
|     this->status_set_error(); |     this->status_set_error(); | ||||||
|     return; |     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->parent_->unlock(); | ||||||
|   this->state_ = microphone::STATE_STOPPED; |   this->state_ = microphone::STATE_STOPPED; | ||||||
|   this->high_freq_.stop(); |   this->high_freq_.stop(); | ||||||
| @@ -158,7 +286,11 @@ void I2SAudioMicrophone::stop_() { | |||||||
|  |  | ||||||
| size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { | size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { | ||||||
|   size_t bytes_read = 0; |   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)); |   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) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); |     ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
| @@ -171,6 +303,7 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { | |||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
|   // ESP-IDF I2S implementation right-extends 8-bit data to 16 bits, |   // ESP-IDF I2S implementation right-extends 8-bit data to 16 bits, | ||||||
|   // and 24-bit data to 32 bits. |   // and 24-bit data to 32 bits. | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   switch (this->bits_per_sample_) { |   switch (this->bits_per_sample_) { | ||||||
|     case I2S_BITS_PER_SAMPLE_8BIT: |     case I2S_BITS_PER_SAMPLE_8BIT: | ||||||
|     case I2S_BITS_PER_SAMPLE_16BIT: |     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_); |       ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); | ||||||
|       return 0; |       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_() { | void I2SAudioMicrophone::read_() { | ||||||
|   | |||||||
| @@ -17,17 +17,23 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | |||||||
|   void stop() override; |   void stop() override; | ||||||
|  |  | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   void set_din_pin(int8_t pin) { this->din_pin_ = pin; } |   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; } |   void set_pdm(bool pdm) { this->pdm_ = pdm; } | ||||||
|  |  | ||||||
|   size_t read(int16_t *buf, size_t len) override; |   size_t read(int16_t *buf, size_t len) override; | ||||||
|  |  | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
| #if SOC_I2S_SUPPORTS_ADC | #if SOC_I2S_SUPPORTS_ADC | ||||||
|   void set_adc_channel(adc1_channel_t channel) { |   void set_adc_channel(adc1_channel_t channel) { | ||||||
|     this->adc_channel_ = channel; |     this->adc_channel_ = channel; | ||||||
|     this->adc_ = true; |     this->adc_ = true; | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| @@ -35,10 +41,15 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | |||||||
|   void stop_(); |   void stop_(); | ||||||
|   void read_(); |   void read_(); | ||||||
|  |  | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|   int8_t din_pin_{I2S_PIN_NO_CHANGE}; |   int8_t din_pin_{I2S_PIN_NO_CHANGE}; | ||||||
| #if SOC_I2S_SUPPORTS_ADC | #if SOC_I2S_SUPPORTS_ADC | ||||||
|   adc1_channel_t adc_channel_{ADC1_CHANNEL_MAX}; |   adc1_channel_t adc_channel_{ADC1_CHANNEL_MAX}; | ||||||
|   bool adc_{false}; |   bool adc_{false}; | ||||||
|  | #endif | ||||||
|  | #else | ||||||
|  |   gpio_num_t din_pin_{I2S_GPIO_UNUSED}; | ||||||
|  |   i2s_chan_handle_t rx_handle_; | ||||||
| #endif | #endif | ||||||
|   bool pdm_{false}; |   bool pdm_{false}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ from .. import ( | |||||||
|     i2s_audio_component_schema, |     i2s_audio_component_schema, | ||||||
|     i2s_audio_ns, |     i2s_audio_ns, | ||||||
|     register_i2s_audio_component, |     register_i2s_audio_component, | ||||||
|  |     use_legacy, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| AUTO_LOAD = ["audio"] | AUTO_LOAD = ["audio"] | ||||||
| @@ -60,7 +61,7 @@ I2C_COMM_FMT_OPTIONS = { | |||||||
|     "pcm_long": i2s_comm_format_t.I2S_COMM_FORMAT_PCM_LONG, |     "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): | def _set_num_channels_from_config(config): | ||||||
| @@ -101,7 +102,7 @@ def _validate_esp32_variant(config): | |||||||
|     if config[CONF_DAC_TYPE] != "internal": |     if config[CONF_DAC_TYPE] != "internal": | ||||||
|         return config |         return config | ||||||
|     variant = esp32.get_esp32_variant() |     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") |         raise cv.Invalid(f"{variant} does not have an internal DAC") | ||||||
|     return config |     return config | ||||||
|  |  | ||||||
| @@ -143,8 +144,8 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                     cv.Required( |                     cv.Required( | ||||||
|                         CONF_I2S_DOUT_PIN |                         CONF_I2S_DOUT_PIN | ||||||
|                     ): pins.internal_gpio_output_pin_number, |                     ): pins.internal_gpio_output_pin_number, | ||||||
|                     cv.Optional(CONF_I2S_COMM_FMT, default="stand_i2s"): cv.enum( |                     cv.Optional(CONF_I2S_COMM_FMT, default="stand_i2s"): cv.one_of( | ||||||
|                         I2C_COMM_FMT_OPTIONS, lower=True |                         *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): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     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])) |         cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL])) | ||||||
|     else: |     else: | ||||||
|         cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) |         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: |     if config[CONF_TIMEOUT] != CONF_NEVER: | ||||||
|         cg.add(var.set_timeout(config[CONF_TIMEOUT])) |         cg.add(var.set_timeout(config[CONF_TIMEOUT])) | ||||||
|     cg.add(var.set_buffer_duration(config[CONF_BUFFER_DURATION])) |     cg.add(var.set_buffer_duration(config[CONF_BUFFER_DURATION])) | ||||||
|   | |||||||
| @@ -2,7 +2,11 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
| #include <driver/i2s.h> | #include <driver/i2s.h> | ||||||
|  | #else | ||||||
|  | #include <driver/i2s_std.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #include "esphome/components/audio/audio.h" | #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. |         // Audio stream info changed, stop the speaker task so it will restart with the proper settings. | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|       i2s_event_t i2s_event; |       i2s_event_t i2s_event; | ||||||
|       while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { |       while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { | ||||||
|         if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { |         if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { | ||||||
|           tx_dma_underflow = true; |           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_) { |       if (this_speaker->pause_state_) { | ||||||
|         // Pause state is accessed atomically, so thread safe |         // 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_); |                              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 |         // Write the audio data to a single DMA buffer at a time to reduce latency for the audio duration played | ||||||
|         // callback. |         // callback. | ||||||
|         const uint32_t batches = (bytes_read + single_dma_buffer_input_size - 1) / single_dma_buffer_input_size; |         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_written = 0; | ||||||
|           size_t bytes_to_write = std::min(single_dma_buffer_input_size, bytes_read); |           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_) { |           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, |             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)); |                       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, |                              audio_stream_info.get_bits_per_sample(), this_speaker->bits_per_sample_, &bytes_written, | ||||||
|                              pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS * 5)); |                              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(); |           uint32_t write_timestamp = micros(); | ||||||
|  |  | ||||||
| @@ -369,8 +398,12 @@ void I2SAudioSpeaker::speaker_task(void *params) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); |     xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|     i2s_driver_uninstall(this_speaker->parent_->get_port()); |     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(); |     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) { | 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 |   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 |     // Can't reconfigure I2S bus, so the sample rate must match the configured value | ||||||
|     return ESP_ERR_NOT_SUPPORTED; |     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_) { |   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 |     // Currently can't handle the case when the incoming audio has more bits per sample than the configured value | ||||||
|     return ESP_ERR_NOT_SUPPORTED; |     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; |     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_; |   i2s_channel_fmt_t channel = this->channel_; | ||||||
|  |  | ||||||
|   if (audio_stream_info.get_channels() == 1) { |   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; |     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 = { |   i2s_driver_config_t config = { | ||||||
|     .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), |     .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), | ||||||
|     .sample_rate = audio_stream_info.get_sample_rate(), |     .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_, |     .communication_format = this->i2s_comm_fmt_, | ||||||
|     .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, |     .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||||||
|     .dma_buf_count = DMA_BUFFERS_COUNT, |     .dma_buf_count = DMA_BUFFERS_COUNT, | ||||||
|     .dma_buf_len = dma_buffer_length, |     .dma_buf_len = (int) dma_buffer_length, | ||||||
|     .use_apll = this->use_apll_, |     .use_apll = this->use_apll_, | ||||||
|     .tx_desc_auto_clear = true, |     .tx_desc_auto_clear = true, | ||||||
|     .fixed_mclk = I2S_PIN_NO_CHANGE, |     .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()); |     i2s_driver_uninstall(this->parent_->get_port()); | ||||||
|     this->parent_->unlock(); |     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; |   return err; | ||||||
| } | } | ||||||
| @@ -564,6 +690,15 @@ void I2SAudioSpeaker::delete_task_(size_t buffer_size) { | |||||||
|   vTaskDelete(nullptr); |   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 i2s_audio | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,8 +4,6 @@ | |||||||
|  |  | ||||||
| #include "../i2s_audio.h" | #include "../i2s_audio.h" | ||||||
|  |  | ||||||
| #include <driver/i2s.h> |  | ||||||
|  |  | ||||||
| #include <freertos/event_groups.h> | #include <freertos/event_groups.h> | ||||||
| #include <freertos/queue.h> | #include <freertos/queue.h> | ||||||
| #include <freertos/FreeRTOS.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_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_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 | #if SOC_I2S_SUPPORTS_DAC | ||||||
|   void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } |   void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } | ||||||
| #endif | #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; } |   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 start() override; | ||||||
|   void stop() 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 |   /// @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); |   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 |   /// @brief Allocates the data buffer and ring buffer | ||||||
|   /// @param data_buffer_size Number of bytes to allocate for the data 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. |   /// @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_; |   uint32_t buffer_duration_ms_; | ||||||
|  |  | ||||||
|   optional<uint32_t> timeout_; |   optional<uint32_t> timeout_; | ||||||
|   uint8_t dout_pin_; |  | ||||||
|  |  | ||||||
|   bool task_created_{false}; |   bool task_created_{false}; | ||||||
|   bool pause_state_{false}; |   bool pause_state_{false}; | ||||||
| @@ -130,10 +136,17 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp | |||||||
|  |  | ||||||
|   size_t bytes_written_{0}; |   size_t bytes_written_{0}; | ||||||
|  |  | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
| #if SOC_I2S_SUPPORTS_DAC | #if SOC_I2S_SUPPORTS_DAC | ||||||
|   i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; |   i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; | ||||||
| #endif | #endif | ||||||
|  |   uint8_t dout_pin_; | ||||||
|   i2s_comm_format_t i2s_comm_fmt_; |   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}; |   uint32_t accumulated_frames_written_{0}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -115,6 +115,7 @@ | |||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
| #define USE_PROMETHEUS | #define USE_PROMETHEUS | ||||||
| #define USE_WIFI_WPA2_EAP | #define USE_WIFI_WPA2_EAP | ||||||
|  | #define USE_I2S_LEGACY | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| // IDF-specific feature flags | // IDF-specific feature flags | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ microphone: | |||||||
|     i2s_din_pin: GPIO17 |     i2s_din_pin: GPIO17 | ||||||
|     adc_type: external |     adc_type: external | ||||||
|     pdm: true |     pdm: true | ||||||
|  |     bits_per_sample: 16bit | ||||||
|  |  | ||||||
| micro_wake_word: | micro_wake_word: | ||||||
|   on_wake_word_detected: |   on_wake_word_detected: | ||||||
|   | |||||||
| @@ -4,9 +4,18 @@ substitutions: | |||||||
|   i2s_mclk_pin: GPIO17 |   i2s_mclk_pin: GPIO17 | ||||||
|   i2s_din_pin: GPIO33 |   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: | microphone: | ||||||
|  |   - platform: i2s_audio | ||||||
|  |     id: mic_id_external | ||||||
|  |     i2s_din_pin: ${i2s_din_pin} | ||||||
|  |     adc_type: external | ||||||
|  |     pdm: false | ||||||
|   - platform: i2s_audio |   - platform: i2s_audio | ||||||
|     id: mic_id_adc |     id: mic_id_adc | ||||||
|     adc_pin: 32 |     adc_pin: 32 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user