mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 06:06:33 +00:00
[i2s_audio] Move microphone reads into a task (#8651)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
07ba9fdf8f
commit
20062576a3
@ -39,6 +39,7 @@ CONF_SECONDARY = "secondary"
|
||||
|
||||
CONF_USE_APLL = "use_apll"
|
||||
CONF_BITS_PER_CHANNEL = "bits_per_channel"
|
||||
CONF_MCLK_MULTIPLE = "mclk_multiple"
|
||||
CONF_MONO = "mono"
|
||||
CONF_LEFT = "left"
|
||||
CONF_RIGHT = "right"
|
||||
@ -122,8 +123,25 @@ I2S_SLOT_BIT_WIDTH = {
|
||||
32: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_32BIT,
|
||||
}
|
||||
|
||||
i2s_mclk_multiple_t = cg.global_ns.enum("i2s_mclk_multiple_t")
|
||||
I2S_MCLK_MULTIPLE = {
|
||||
128: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_128,
|
||||
256: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_256,
|
||||
384: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_384,
|
||||
512: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_512,
|
||||
}
|
||||
|
||||
_validate_bits = cv.float_with_unit("bits", "bit")
|
||||
|
||||
|
||||
def validate_mclk_divisible_by_3(config):
|
||||
if config[CONF_BITS_PER_SAMPLE] == 24 and config[CONF_MCLK_MULTIPLE] % 3 != 0:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_MCLK_MULTIPLE} must be divisible by 3 when bits per sample is 24"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
_use_legacy_driver = None
|
||||
|
||||
|
||||
@ -155,6 +173,7 @@ def i2s_audio_component_schema(
|
||||
cv.Any(cv.float_with_unit("bits", "bit"), "default"),
|
||||
cv.one_of(*I2S_BITS_PER_CHANNEL),
|
||||
),
|
||||
cv.Optional(CONF_MCLK_MULTIPLE, default=256): cv.one_of(*I2S_MCLK_MULTIPLE),
|
||||
}
|
||||
)
|
||||
|
||||
@ -182,11 +201,10 @@ async def register_i2s_audio_component(var, config):
|
||||
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_slot_bit_width(I2S_SLOT_BIT_WIDTH[config[CONF_BITS_PER_SAMPLE]]))
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
||||
cg.add(var.set_mclk_multiple(I2S_MCLK_MULTIPLE[config[CONF_MCLK_MULTIPLE]]))
|
||||
|
||||
|
||||
def validate_use_legacy(value):
|
||||
|
@ -31,6 +31,7 @@ class I2SAudioBase : public Parented<I2SAudioComponent> {
|
||||
#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_mclk_multiple(i2s_mclk_multiple_t mclk_multiple) { this->mclk_multiple_ = mclk_multiple; }
|
||||
|
||||
protected:
|
||||
#ifdef USE_I2S_LEGACY
|
||||
@ -46,6 +47,7 @@ class I2SAudioBase : public Parented<I2SAudioComponent> {
|
||||
#endif
|
||||
uint32_t sample_rate_;
|
||||
bool use_apll_;
|
||||
i2s_mclk_multiple_t mclk_multiple_;
|
||||
};
|
||||
|
||||
class I2SAudioIn : public I2SAudioBase {};
|
||||
|
@ -22,6 +22,7 @@ from .. import (
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
use_legacy,
|
||||
validate_mclk_divisible_by_3,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@ -112,6 +113,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
_validate_channel,
|
||||
_set_num_channels_from_config,
|
||||
_set_stream_limits,
|
||||
validate_mclk_divisible_by_3,
|
||||
)
|
||||
|
||||
|
||||
|
@ -15,10 +15,25 @@
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
|
||||
static const size_t BUFFER_SIZE = 512;
|
||||
static const UBaseType_t MAX_LISTENERS = 16;
|
||||
|
||||
static const uint32_t READ_DURATION_MS = 16;
|
||||
|
||||
static const size_t TASK_STACK_SIZE = 4096;
|
||||
static const ssize_t TASK_PRIORITY = 23;
|
||||
|
||||
static const char *const TAG = "i2s_audio.microphone";
|
||||
|
||||
enum MicrophoneEventGroupBits : uint32_t {
|
||||
COMMAND_STOP = (1 << 0), // stops the microphone task
|
||||
TASK_STARTING = (1 << 10),
|
||||
TASK_RUNNING = (1 << 11),
|
||||
TASK_STOPPING = (1 << 12),
|
||||
TASK_STOPPED = (1 << 13),
|
||||
|
||||
ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
|
||||
};
|
||||
|
||||
void I2SAudioMicrophone::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
|
||||
#ifdef USE_I2S_LEGACY
|
||||
@ -41,18 +56,32 @@ void I2SAudioMicrophone::setup() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS);
|
||||
if (this->active_listeners_semaphore_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create semaphore");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->event_group_ = xEventGroupCreate();
|
||||
if (this->event_group_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create event group");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::start() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
if (this->state_ == microphone::STATE_RUNNING)
|
||||
return; // Already running
|
||||
this->state_ = microphone::STATE_STARTING;
|
||||
|
||||
xSemaphoreTake(this->active_listeners_semaphore_, 0);
|
||||
}
|
||||
void I2SAudioMicrophone::start_() {
|
||||
|
||||
bool I2SAudioMicrophone::start_driver_() {
|
||||
if (!this->parent_->try_lock()) {
|
||||
return; // Waiting for another i2s to return lock
|
||||
return false; // Waiting for another i2s to return lock
|
||||
}
|
||||
esp_err_t err;
|
||||
|
||||
@ -94,11 +123,11 @@ void I2SAudioMicrophone::start_() {
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 4,
|
||||
.dma_buf_len = 256,
|
||||
.dma_buf_len = 240, // Must be divisible by 3 to support 24 bits per sample on old driver and newer variants
|
||||
.use_apll = this->use_apll_,
|
||||
.tx_desc_auto_clear = false,
|
||||
.fixed_mclk = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.mclk_multiple = this->mclk_multiple_,
|
||||
.bits_per_chan = this->bits_per_channel_,
|
||||
};
|
||||
|
||||
@ -109,20 +138,20 @@ void I2SAudioMicrophone::start_() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
err = i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error setting ADC mode: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
err = i2s_adc_enable(this->parent_->get_port());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error enabling ADC: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else
|
||||
@ -135,7 +164,7 @@ void I2SAudioMicrophone::start_() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||
@ -145,7 +174,7 @@ void I2SAudioMicrophone::start_() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error setting I2S pin: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
@ -161,7 +190,7 @@ void I2SAudioMicrophone::start_() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
i2s_clock_src_t clk_src = I2S_CLK_SRC_DEFAULT;
|
||||
@ -178,7 +207,7 @@ void I2SAudioMicrophone::start_() {
|
||||
i2s_pdm_rx_clk_config_t clk_cfg = {
|
||||
.sample_rate_hz = this->sample_rate_,
|
||||
.clk_src = clk_src,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.mclk_multiple = this->mclk_multiple_,
|
||||
.dn_sample_mode = I2S_PDM_DSR_8S,
|
||||
};
|
||||
|
||||
@ -216,7 +245,7 @@ void I2SAudioMicrophone::start_() {
|
||||
i2s_std_clk_config_t clk_cfg = {
|
||||
.sample_rate_hz = this->sample_rate_,
|
||||
.clk_src = clk_src,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.mclk_multiple = this->mclk_multiple_,
|
||||
};
|
||||
i2s_std_slot_config_t std_slot_cfg =
|
||||
I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) this->slot_bit_width_, this->slot_mode_);
|
||||
@ -236,7 +265,7 @@ void I2SAudioMicrophone::start_() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Before reading data, start the RX channel first */
|
||||
@ -244,28 +273,25 @@ void I2SAudioMicrophone::start_() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err));
|
||||
this->status_set_error();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
|
||||
|
||||
this->state_ = microphone::STATE_RUNNING;
|
||||
this->high_freq_.start();
|
||||
this->status_clear_error();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::stop() {
|
||||
if (this->state_ == microphone::STATE_STOPPED || this->is_failed())
|
||||
return;
|
||||
if (this->state_ == microphone::STATE_STARTING) {
|
||||
this->state_ = microphone::STATE_STOPPED;
|
||||
return;
|
||||
}
|
||||
this->state_ = microphone::STATE_STOPPING;
|
||||
|
||||
xSemaphoreGive(this->active_listeners_semaphore_);
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::stop_() {
|
||||
void I2SAudioMicrophone::stop_driver_() {
|
||||
esp_err_t err;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
@ -307,11 +333,51 @@ void I2SAudioMicrophone::stop_() {
|
||||
}
|
||||
#endif
|
||||
this->parent_->unlock();
|
||||
this->state_ = microphone::STATE_STOPPED;
|
||||
this->high_freq_.stop();
|
||||
this->status_clear_error();
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::mic_task(void *params) {
|
||||
I2SAudioMicrophone *this_microphone = (I2SAudioMicrophone *) params;
|
||||
|
||||
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
|
||||
|
||||
uint8_t start_counter = 0;
|
||||
bool started = this_microphone->start_driver_();
|
||||
while (!started && start_counter < 10) {
|
||||
// Attempt to load the driver again in 100 ms. Doesn't slow down main loop since its in a task.
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
++start_counter;
|
||||
started = this_microphone->start_driver_();
|
||||
}
|
||||
|
||||
if (started) {
|
||||
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
|
||||
const size_t bytes_to_read = this_microphone->audio_stream_info_.ms_to_bytes(READ_DURATION_MS);
|
||||
std::vector<uint8_t> samples;
|
||||
samples.reserve(bytes_to_read);
|
||||
|
||||
while (!(xEventGroupGetBits(this_microphone->event_group_) & COMMAND_STOP)) {
|
||||
if (this_microphone->data_callbacks_.size() > 0) {
|
||||
samples.resize(bytes_to_read);
|
||||
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
|
||||
samples.resize(bytes_read);
|
||||
this_microphone->data_callbacks_.call(samples);
|
||||
} else {
|
||||
delay(READ_DURATION_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
|
||||
this_microphone->stop_driver_();
|
||||
|
||||
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
|
||||
while (true) {
|
||||
// Continuously delay until the loop method delete the task
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
||||
size_t bytes_read = 0;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
@ -345,29 +411,60 @@ size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_w
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::read_() {
|
||||
std::vector<uint8_t> samples;
|
||||
const size_t bytes_to_read = this->audio_stream_info_.ms_to_bytes(32);
|
||||
samples.resize(bytes_to_read);
|
||||
size_t bytes_read = this->read_(samples.data(), bytes_to_read, 0);
|
||||
samples.resize(bytes_read);
|
||||
this->data_callbacks_.call(samples);
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::loop() {
|
||||
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
|
||||
|
||||
if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) {
|
||||
ESP_LOGD(TAG, "Task has started, attempting to setup I2S audio driver");
|
||||
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
|
||||
}
|
||||
|
||||
if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) {
|
||||
ESP_LOGD(TAG, "Task is running and reading data");
|
||||
|
||||
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
|
||||
this->state_ = microphone::STATE_RUNNING;
|
||||
}
|
||||
|
||||
if (event_group_bits & MicrophoneEventGroupBits::TASK_STOPPING) {
|
||||
ESP_LOGD(TAG, "Task is stopping, attempting to unload the I2S audio driver");
|
||||
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
|
||||
}
|
||||
|
||||
if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) {
|
||||
ESP_LOGD(TAG, "Task is finished, freeing resources");
|
||||
vTaskDelete(this->task_handle_);
|
||||
this->task_handle_ = nullptr;
|
||||
xEventGroupClearBits(this->event_group_, ALL_BITS);
|
||||
this->state_ = microphone::STATE_STOPPED;
|
||||
}
|
||||
|
||||
if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) < MAX_LISTENERS) &&
|
||||
(this->state_ == microphone::STATE_STOPPED)) {
|
||||
this->state_ = microphone::STATE_STARTING;
|
||||
}
|
||||
if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) == MAX_LISTENERS) &&
|
||||
(this->state_ == microphone::STATE_RUNNING)) {
|
||||
this->state_ = microphone::STATE_STOPPING;
|
||||
}
|
||||
|
||||
switch (this->state_) {
|
||||
case microphone::STATE_STOPPED:
|
||||
break;
|
||||
case microphone::STATE_STARTING:
|
||||
this->start_();
|
||||
break;
|
||||
case microphone::STATE_RUNNING:
|
||||
if (this->data_callbacks_.size() > 0) {
|
||||
this->read_();
|
||||
if ((this->task_handle_ == nullptr) && !this->status_has_error()) {
|
||||
xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY,
|
||||
&this->task_handle_);
|
||||
|
||||
if (this->task_handle_ == nullptr) {
|
||||
this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case microphone::STATE_RUNNING:
|
||||
break;
|
||||
case microphone::STATE_STOPPING:
|
||||
this->stop_();
|
||||
xEventGroupSetBits(this->event_group_, MicrophoneEventGroupBits::COMMAND_STOP);
|
||||
break;
|
||||
case microphone::STATE_STOPPED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,9 @@
|
||||
#include "esphome/components/microphone/microphone.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
|
||||
@ -35,11 +38,18 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void start_();
|
||||
void stop_();
|
||||
void read_();
|
||||
bool start_driver_();
|
||||
void stop_driver_();
|
||||
|
||||
size_t read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait);
|
||||
|
||||
static void mic_task(void *params);
|
||||
|
||||
SemaphoreHandle_t active_listeners_semaphore_{nullptr};
|
||||
EventGroupHandle_t event_group_{nullptr};
|
||||
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
int8_t din_pin_{I2S_PIN_NO_CHANGE};
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
@ -51,8 +61,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
i2s_chan_handle_t rx_handle_;
|
||||
#endif
|
||||
bool pdm_{false};
|
||||
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
@ -27,6 +27,7 @@ from .. import (
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
use_legacy,
|
||||
validate_mclk_divisible_by_3,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["audio"]
|
||||
@ -155,6 +156,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
_validate_esp32_variant,
|
||||
_set_num_channels_from_config,
|
||||
_set_stream_limits,
|
||||
validate_mclk_divisible_by_3,
|
||||
)
|
||||
|
||||
|
||||
|
@ -545,7 +545,7 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
.use_apll = this->use_apll_,
|
||||
.tx_desc_auto_clear = true,
|
||||
.fixed_mclk = I2S_PIN_NO_CHANGE,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.mclk_multiple = this->mclk_multiple_,
|
||||
.bits_per_chan = this->bits_per_channel_,
|
||||
#if SOC_I2S_SUPPORTS_TDM
|
||||
.chan_mask = (i2s_channel_t) (I2S_TDM_ACTIVE_CH0 | I2S_TDM_ACTIVE_CH1),
|
||||
@ -614,7 +614,7 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
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,
|
||||
.mclk_multiple = this->mclk_multiple_,
|
||||
};
|
||||
|
||||
i2s_slot_mode_t slot_mode = this->slot_mode_;
|
||||
|
@ -9,3 +9,4 @@ microphone:
|
||||
i2s_din_pin: ${i2s_din_pin}
|
||||
adc_type: external
|
||||
pdm: false
|
||||
mclk_multiple: 384
|
||||
|
Loading…
x
Reference in New Issue
Block a user