Проблема с двумя I2S портами на ESP-32 S3 Super Mini

#include <Adafruit_BMP280.h>

#include <Adafruit_MPU6050.h>

#include <Arduino.h>

#include <Kant_inferencing.h>

#include <SD.h>

#include <SPI.h>

#include <SparkFun_Qwiic_Humidity_AHT20.h>

#include <TinyGPS++.h>

#include <Wire.h>

#include <driver/i2s_std.h>

#include “esp_task_wdt.h”

// =====================================================

// GPS

// =====================================================

#define GPS_RX_PIN 7

// =====================================================

// I2S INMP441

// =====================================================

#define I2S_MIC_PORT I2S_NUM_AUTO

#define I2S_MIC_SCK 4

#define I2S_MIC_WS 5

#define I2S_MIC_SD 6

// =====================================================

// I2S MAX98357A

// =====================================================

#define I2S_SPEAKER_PORT I2S_NUM_AUTO

#define I2S_TTS_BCLK 1

#define I2S_TTS_LRC 2

#define I2S_TTS_DIN 3

// =====================================================

// SD

// =====================================================

#define SD_CS 10

#define SD_SCK 12

#define SD_MISO 13

#define SD_MOSI 11

#define SD_FREQUENCY 4000000

// =====================================================

// РАСПОЗНАВАНИЕ

// =====================================================

#define WAKE_THRESHOLD 0.90f

#define COMMAND_THRESHOLD 0.90f

#define COMMAND_TIMEOUT_MS 7000

#define REQUIRED_CONFIRMATIONS 2

#define MIN_AUDIO_LEVEL 100

#define MIN_CONFIDENCE_GAP 0.20f

// =====================================================

// WAV

// =====================================================

#define WAV_SAMPLE_RATE 16000

#define AUDIO_IO_BUFFER_SIZE 2048

#define AUDIO_WRITE_CHUNK_SIZE 512

#define AUDIO_WRITE_TIMEOUT_MS 200

// =====================================================

// ОБЪЕКТЫ

// =====================================================

Adafruit_BMP280 bmp;

Adafruit_MPU6050 mpu;

AHT20 aht;

HardwareSerial gpsSerial(1);

TinyGPSPlus gps;

// =====================================================

// I2S-КАНАЛЫ

// =====================================================

static i2s_chan_handle_t mic_rx_handle = nullptr;

static i2s_chan_handle_t speaker_tx_handle = nullptr;

// =====================================================

// ДАТЧИКИ

// =====================================================

bool bmp_available = false;

bool aht_available = false;

bool mpu_available = false;

// =====================================================

// EDGE IMPULSE

// =====================================================

static int16_t audio_buffer[EI_CLASSIFIER_SLICE_SIZE];

static int32_t i2s_samples[256];

static ei_impulse_result_t result;

static signal_t my_signal;

static size_t microphone_samples_filled = 0;

// =====================================================

// СОСТОЯНИЕ АССИСТЕНТА

// =====================================================

enum AssistantState { WAIT_KANT, WAIT_COMMAND };

AssistantState assistant_state = WAIT_KANT;

uint32_t command_wait_started = 0;

uint8_t kant_confirmations = 0;

uint8_t weather_confirmations = 0;

uint8_t coordinates_confirmations = 0;

// =====================================================

// WAV-БУФЕРЫ

// =====================================================

static uint8_t wav_input_buffer[AUDIO_IO_BUFFER_SIZE];

static int16_t wav_stereo_buffer[AUDIO_IO_BUFFER_SIZE];

// =====================================================

// WAV ПАРАМЕТРЫ

// =====================================================

static uint8_t wav_channels = 0;

static uint8_t wav_bits_per_sample = 0;

static uint32_t wav_file_sample_rate = 0;

// =====================================================

// ПРЕДОБЪЯВЛЕНИЯ ФУНКЦИЙ

// =====================================================

void reset_microphone_slice();

// =====================================================

// LITTLE-ENDIAN

// =====================================================

uint16_t readLE16(File& file) {

uint8_t data[2];

if (file.read(data, sizeof(data)) != sizeof(data)) {

return 0;

}

return static_cast<uint16_t>(data[0]) | (static_cast<uint16_t>(data[1]) << 8);

}

uint32_t readLE32(File& file) {

uint8_t data[4];

if (file.read(data, sizeof(data)) != sizeof(data)) {

return 0;

}

return static_cast<uint32_t>(data[0]) |

     (static_cast<uint32_t>(data\[1\]) << 8) |

     (static_cast<uint32_t>(data\[2\]) << 16) |

     (static_cast<uint32_t>(data\[3\]) << 24);

}

bool readChunkId(File& file, char* id) {

if (file.read(reinterpret_cast<uint8_t*>(id), 4) != 4) {

return false;

}

id[4] = ‘\0’;

return true;

}

// =====================================================

// I2S МИКРОФОН

// =====================================================

bool setup_i2s_microphone() {

i2s_chan_config_t channel_config =

  I2S_CHANNEL_DEFAULT_CONFIG(I2S_MIC_PORT, I2S_ROLE_MASTER);

channel_config.auto_clear = true;

channel_config.dma_desc_num = 8;

channel_config.dma_frame_num = 128;

esp_err_t err = i2s_new_channel(&channel_config, nullptr, &mic_rx_handle);

if (err != ESP_OK) {

Serial.printf("Ошибка создания I2S RX: %s (%d)\\n", esp_err_to_name(err),

              err);

return false;

}

i2s_std_config_t mic_config = {

  .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EI_CLASSIFIER_FREQUENCY),

  .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT,

                                                  I2S_SLOT_MODE_MONO),

  .gpio_cfg = {.mclk = I2S_GPIO_UNUSED,

               .bclk = static_cast<gpio_num_t>(I2S_MIC_SCK),

               .ws = static_cast<gpio_num_t>(I2S_MIC_WS),

               .dout = I2S_GPIO_UNUSED,

               .din = static_cast<gpio_num_t>(I2S_MIC_SD),

               .invert_flags = {

                   .mclk_inv = false, .bclk_inv = false, .ws_inv = false}}};

mic_config.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;

err = i2s_channel_init_std_mode(mic_rx_handle, &mic_config);

if (err != ESP_OK) {

Serial.printf("Ошибка инициализации I2S RX: %s (%d)\\n",

              esp_err_to_name(err), err);

return false;

}

err = i2s_channel_enable(mic_rx_handle);

if (err != ESP_OK) {

Serial.printf("Ошибка включения I2S RX: %s (%d)\\n", esp_err_to_name(err),

              err);

return false;

}

Serial.println(F(“I2S микрофон инициализирован”));

return true;

}

// =====================================================

// I2S MAX98357A

// =====================================================

bool setup_i2s_speaker() {

i2s_chan_config_t channel_config =

  I2S_CHANNEL_DEFAULT_CONFIG(I2S_SPEAKER_PORT, I2S_ROLE_MASTER);

channel_config.auto_clear = true;

channel_config.dma_desc_num = 6;

channel_config.dma_frame_num = 64;

esp_err_t err = i2s_new_channel(&channel_config, &speaker_tx_handle, nullptr);

if (err != ESP_OK) {

Serial.printf("Ошибка создания I2S TX: %s (%d)\\n", esp_err_to_name(err),

              err);

return false;

}

i2s_std_config_t speaker_config = {

  .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(WAV_SAMPLE_RATE),

  .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,

                                                  I2S_SLOT_MODE_STEREO),

  .gpio_cfg = {.mclk = I2S_GPIO_UNUSED,

               .bclk = static_cast<gpio_num_t>(I2S_TTS_BCLK),

               .ws = static_cast<gpio_num_t>(I2S_TTS_LRC),

               .dout = static_cast<gpio_num_t>(I2S_TTS_DIN),

               .din = I2S_GPIO_UNUSED,

               .invert_flags = {

                   .mclk_inv = false, .bclk_inv = false, .ws_inv = false}}};

err = i2s_channel_init_std_mode(speaker_tx_handle, &speaker_config);

if (err != ESP_OK) {

Serial.printf("Ошибка инициализации I2S TX: %s (%d)\\n",

              esp_err_to_name(err), err);

return false;

}

err = i2s_channel_enable(speaker_tx_handle);

if (err != ESP_OK) {

Serial.printf("Ошибка включения I2S TX: %s (%d)\\n", esp_err_to_name(err),

              err);

return false;

}

Serial.println(F(“I2S MAX98357A запущен”));

return true;

}

// =====================================================

// РАЗБОР WAV

// =====================================================

bool prepare_wav(File& file, uint32_t& data_size) {

char riff[5];

char wave[5];

if (!readChunkId(file, riff)) {

Serial.println(F("Не удалось прочитать RIFF"));

return false;

}

readLE32(file);

if (!readChunkId(file, wave)) {

Serial.println(F("Не удалось прочитать WAVE"));

return false;

}

if (strcmp(riff, “RIFF”) != 0 || strcmp(wave, “WAVE”) != 0) {

Serial.println(F("Файл не является RIFF WAV"));

return false;

}

bool fmt_found = false;

bool data_found = false;

wav_channels = 0;

wav_bits_per_sample = 0;

wav_file_sample_rate = 0;

data_size = 0;

while (file.available()) {

char chunk_id\[5\];



if (!readChunkId(file, chunk_id)) {

  return false;

}



uint32_t chunk_size = readLE32(file);

uint32_t chunk_start = file.position();



if (strcmp(chunk_id, "fmt ") == 0) {

  uint16_t audio_format = readLE16(file);



  wav_channels = readLE16(file);

  wav_file_sample_rate = readLE32(file);



  readLE32(file);

  readLE16(file);



  wav_bits_per_sample = readLE16(file);



  if (audio_format != 1) {

    Serial.println(F("Поддерживается только PCM WAV"));

    return false;

  }



  if (wav_channels != 1 && wav_channels != 2) {

    Serial.println(F("Поддерживается только mono/stereo WAV"));

    return false;

  }



  if (wav_bits_per_sample != 16) {

    Serial.println(F("Поддерживается только 16-bit WAV"));

    return false;

  }



  if (wav_file_sample_rate != WAV_SAMPLE_RATE) {

    Serial.printf("Требуется %d Hz, в файле %lu Hz\\n", WAV_SAMPLE_RATE,

                  static_cast<unsigned long>(wav_file_sample_rate));

    return false;

  }



  fmt_found = true;

} else if (strcmp(chunk_id, "data") == 0) {

  data_size = chunk_size;

  data_found = true;

  break;

}



file.seek(chunk_start + chunk_size);



if (chunk_size & 1) {

  file.seek(file.position() + 1);

}

}

if (!fmt_found || !data_found) {

Serial.println(F("В WAV не найден fmt или data"));

return false;

}

Serial.printf(“WAV: %lu Hz, %u каналов, %u бит, %lu байт данных\n”,

            static_cast<unsigned long>(wav_file_sample_rate), wav_channels,

            wav_bits_per_sample, static_cast<unsigned long>(data_size));

return true;

}

// =====================================================

// ЗАПИСЬ В I2S

// =====================================================

bool write_i2s_blocking(const uint8_t* data, size_t data_size) {

if (speaker_tx_handle == nullptr) {

Serial.println(F("I2S TX не инициализирован"));

return false;

}

size_t written = 0;

while (written < data_size) {

size_t remaining = data_size - written;

size_t chunk_size =

    min(remaining, static_cast<size_t>(AUDIO_WRITE_CHUNK_SIZE));



size_t bytes_written = 0;



esp_err_t err = i2s_channel_write(speaker_tx_handle, data + written,

                                  chunk_size, &bytes_written,

                                  pdMS_TO_TICKS(AUDIO_WRITE_TIMEOUT_MS));



if (err != ESP_OK) {

  Serial.printf("Ошибка i2s_channel_write: %s (%d)\\n", esp_err_to_name(err),

                err);

  return false;

}



if (bytes_written == 0) {

  Serial.println(F("I2S записал 0 байт"));

  return false;

}



written += bytes_written;

}

return true;

}

// =====================================================

// СИНХРОННОЕ ВОСПРОИЗВЕДЕНИЕ WAV

// =====================================================

bool play_wav_blocking(const char* path) {

if (!SD.exists(path)) {

Serial.printf("WAV файл не найден: %s\\n", path);

return false;

}

File file = SD.open(path, FILE_READ);

if (!file) {

Serial.printf("Не удалось открыть WAV: %s\\n", path);

return false;

}

// Отключаем микрофон на время воспроизведения

if (mic_rx_handle) {

i2s_channel_disable(mic_rx_handle);

}

uint32_t data_size = 0;

if (!prepare_wav(file, data_size)) {

Serial.println(F("Ошибка заголовка WAV"));

file.close();

if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);

return false;

}

uint32_t bytes_left = data_size;

while (bytes_left > 0) {

size_t bytes_to_read =

    min(static_cast<uint32_t>(sizeof(wav_input_buffer)), bytes_left);



bytes_to_read &= \~1u;



if (bytes_to_read == 0) {

  break;

}



size_t bytes_read = file.read(wav_input_buffer, bytes_to_read);



if (bytes_read == 0) {

  Serial.println(F("Ошибка чтения данных WAV"));

  file.close();

  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);

  return false;

}



bytes_read &= \~1u;

bytes_left -= bytes_read;



const uint8_t\* output_buffer = nullptr;

size_t output_bytes = 0;



if (wav_channels == 1) {

  size_t mono_samples = bytes_read / 2;



  for (size_t i = 0; i < mono_samples; i++) {

    uint16_t low = static_cast<uint16_t>(wav_input_buffer\[i \* 2\]);

    uint16_t high = static_cast<uint16_t>(wav_input_buffer\[i \* 2 + 1\]) << 8;

    int16_t sample = static_cast<int16_t>(low | high);



    wav_stereo_buffer\[i \* 2\] = sample;

    wav_stereo_buffer\[i \* 2 + 1\] = sample;

  }



  output_buffer = reinterpret_cast<const uint8_t\*>(wav_stereo_buffer);

  output_bytes = mono_samples \* 2 \* sizeof(int16_t);

} else if (wav_channels == 2) {

  output_buffer = wav_input_buffer;

  output_bytes = bytes_read;

} else {

  Serial.println(F("Неподдерживаемое число каналов"));

  file.close();

  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);

  return false;

}



if (!write_i2s_blocking(output_buffer, output_bytes)) {

  Serial.println(F("Ошибка вывода WAV в I2S"));

  file.close();

  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);

  return false;

}

}

file.close();

// Снова включаем микрофон

if (mic_rx_handle) {

i2s_channel_enable(mic_rx_handle);

reset_microphone_slice();

}

return true;

}

bool play_number_wav(int number) {

number = abs(number);

String path = “/voice/” + String(number) + “.wav”;

return play_wav_blocking(path.c_str());

}

// =====================================================

// EDGE IMPULSE

// =====================================================

static int get_signal_data(size_t offset, size_t length, float* out_ptr) {

numpy::int16_to_float(&audio_buffer[offset], out_ptr, length);

return 0;

}

void reset_microphone_slice() { microphone_samples_filled = 0; }

bool microphone_slice_ready() {

return microphone_samples_filled >= EI_CLASSIFIER_SLICE_SIZE;

}

bool read_microphone_nonblocking() {

if (mic_rx_handle == nullptr) {

return false;

}

size_t bytes_read = 0;

esp_err_t err =

  i2s_channel_read(mic_rx_handle, i2s_samples, sizeof(i2s_samples),

                   &bytes_read, pdMS_TO_TICKS(10));

if (err != ESP_OK) {

return false;

}

if (bytes_read == 0) {

return false;

}

size_t received_samples = bytes_read / sizeof(int32_t);

for (size_t i = 0; i < received_samples; i++) {

if (microphone_samples_filled >= EI_CLASSIFIER_SLICE_SIZE) {

  break;

}



audio_buffer\[microphone_samples_filled++\] =

    static_cast<int16_t>(i2s_samples\[i\] >> 16);

}

return true;

}

uint32_t get_audio_level() {

int64_t sum = 0;

for (size_t i = 0; i < EI_CLASSIFIER_SLICE_SIZE; i++) {

sum += abs(static_cast<int32_t>(audio_buffer\[i\]));

}

return static_cast<uint32_t>(sum / EI_CLASSIFIER_SLICE_SIZE);

}

// =====================================================

// ПОДТВЕРЖДЕНИЯ

// =====================================================

void reset_command_confirmations() {

weather_confirmations = 0;

coordinates_confirmations = 0;

}

void reset_all_confirmations() {

kant_confirmations = 0;

reset_command_confirmations();

}

// =====================================================

// ПОГОДА

// =====================================================

void execute_weather() {

Serial.println(F(“>>> КОМАНДА: WEATHER”));

if (!bmp_available) {

Serial.println(F("BMP280 недоступен"));

play_wav_blocking("/voice/sensor_error.wav");

return;

}

float temperature_value = bmp.readTemperature();

float pressure_value = bmp.readPressure() / 100.0f;

float humidity_value = NAN;

if (aht_available) {

humidity_value = aht.getHumidity();

}

Serial.printf(“Температура: %.1f C\n”, temperature_value);

Serial.printf(“Давление: %.1f hPa\n”, pressure_value);

Serial.printf(“Влажность: %.1f %%\n”, humidity_value);

if (isnan(temperature_value)) {

play_wav_blocking("/voice/sensor_error.wav");

return;

}

int temperature = static_cast(round(temperature_value));

play_wav_blocking(“/voice/temperature.wav”);

if (temperature < 0) {

play_wav_blocking("/voice/minus.wav");

}

int absolute_temperature = abs(temperature);

if (absolute_temperature < 11) {

play_number_wav(absolute_temperature);

} else {

play_number_wav(absolute_temperature / 10);

play_number_wav(absolute_temperature % 10);

}

play_wav_blocking(“/voice/degrees.wav”);

if (temperature > 0) {

play_wav_blocking("/voice/heat.wav");

} else if (temperature < 0) {

play_wav_blocking("/voice/cold.wav");

} else {

play_wav_blocking("/voice/zero.wav");

}

}

// =====================================================

// GPS

// =====================================================

void execute_coordinates() {

Serial.println(F(“>>> КОМАНДА: COORDINATES”));

if (gps.location.isValid() && gps.satellites.isValid() &&

  gps.satellites.value() >= 3 && gps.hdop.isValid() &&

  gps.hdop.value() < 15) {

Serial.print("Широта: ");

Serial.println(gps.location.lat(), 6);

Serial.print("Долгота: ");

Serial.println(gps.location.lng(), 6);

Serial.print("Высота: ");

Serial.println(gps.altitude.meters());

Serial.print("Спутники: ");

Serial.println(gps.satellites.value());

Serial.print("HDOP: ");

Serial.println(gps.hdop.value());



play_wav_blocking("/voice/coordinates_ready.wav");

} else {

Serial.println(F("Недостаточно спутников"));

play_wav_blocking("/voice/no_satellites.wav");

}

}

// =====================================================

// WAIT_KANT

// =====================================================

void return_to_wait_kant() {

assistant_state = WAIT_KANT;

reset_all_confirmations();

Serial.println(F(“Снова ожидаю: KANT”));

}

// =====================================================

// ОБРАБОТКА РЕЗУЛЬТАТА

// =====================================================

void process_result(ei_impulse_result_t* inference_result) {

float best_value = 0.0f;

float second_best_value = 0.0f;

const char* best_label = “noise”;

for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {

const char\* label = inference_result->classification\[i\].label;

float value = inference_result->classification\[i\].value;



if (value > best_value) {

  second_best_value = best_value;

  best_value = value;

  best_label = label;

} else if (value > second_best_value) {

  second_best_value = value;

}

}

bool confident_result =

  (best_value - second_best_value) >= MIN_CONFIDENCE_GAP;

if (assistant_state == WAIT_KANT) {

if (strcmp(best_label, "kant") == 0 && best_value >= WAKE_THRESHOLD &&

    confident_result) {

  kant_confirmations++;



  Serial.printf("KANT: %.1f%% | gap: %.1f%% | %d/%d\\n", best_value \* 100.0f,

                (best_value - second_best_value) \* 100.0f,

                kant_confirmations, REQUIRED_CONFIRMATIONS);



  if (kant_confirmations >= REQUIRED_CONFIRMATIONS) {

    Serial.println(F(">>> KANT РАСПОЗНАН"));

    Serial.println(F(">>> Скажи WEATHER или COORDINATES"));

    assistant_state = WAIT_COMMAND;

    command_wait_started = millis();

    reset_all_confirmations();

  }

} else {

  kant_confirmations = 0;

}



return;

}

if (assistant_state != WAIT_COMMAND) {

return;

}

if (strcmp(best_label, “weather”) == 0 && best_value >= COMMAND_THRESHOLD &&

  confident_result) {

weather_confirmations++;

coordinates_confirmations = 0;



Serial.printf("WEATHER: %.1f%% | gap: %.1f%% | %d/%d\\n",

              best_value \* 100.0f,

              (best_value - second_best_value) \* 100.0f,

              weather_confirmations, REQUIRED_CONFIRMATIONS);



if (weather_confirmations >= REQUIRED_CONFIRMATIONS) {

  execute_weather();

  return_to_wait_kant();

}



return;

}

if (strcmp(best_label, “coordinates”) == 0 &&

  best_value >= COMMAND_THRESHOLD && confident_result) {

coordinates_confirmations++;

weather_confirmations = 0;



Serial.printf("COORDINATES: %.1f%% | gap: %.1f%% | %d/%d\\n",

              best_value \* 100.0f,

              (best_value - second_best_value) \* 100.0f,

              coordinates_confirmations, REQUIRED_CONFIRMATIONS);



if (coordinates_confirmations >= REQUIRED_CONFIRMATIONS) {

  execute_coordinates();

  return_to_wait_kant();

}



return;

}

reset_command_confirmations();

}

// =====================================================

// SETUP

// =====================================================

void setup() {

Serial.begin(115200);

delay(2000);

// Отключаем Task Watchdog Timer

esp_task_wdt_deinit();

Serial.println();

Serial.println(F(“================================”));

Serial.println(F(“Запуск голосового ассистента”));

Serial.println(F(“================================”));

// ---------------------------------------------------

// I2C

// ---------------------------------------------------

Wire.begin(8, 9);

bmp_available = bmp.begin(0x77);

if (bmp_available) {

Serial.println(F("BMP280 найден"));

} else {

Serial.println(F("BMP280 не найден"));

}

aht_available = aht.begin();

if (aht_available) {

Serial.println(F("AHT20 найден"));

} else {

Serial.println(F("AHT20 не найден"));

}

// ---------------------------------------------------

// SD

// ---------------------------------------------------

SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);

delay(50);

if (!SD.begin(SD_CS, SPI, SD_FREQUENCY)) {

Serial.println(F("SD mount failed"));

while (true) {

  delay(1000);

}

}

Serial.println(F(“SD mounted”));

// Проверка WAV-файлов

if (SD.exists(“/voice/hello.wav”)) {

Serial.println(F("hello.wav найден"));

} else {

Serial.println(F("WARNING: hello.wav не найден!"));

}

delay(100);

// ---------------------------------------------------

// GPS

// ---------------------------------------------------

gpsSerial.begin(9600, SERIAL_8N1, GPS_RX_PIN, -1);

// ---------------------------------------------------

// Микрофон

// ---------------------------------------------------

if (!setup_i2s_microphone()) {

Serial.println(F("Микрофон не запустился"));

while (true) {

  delay(1000);

}

}

Serial.println(F(“ESP32-S3 + INMP441 + Edge Impulse”));

Serial.printf(“Частота модели: %d Hz\n”, EI_CLASSIFIER_FREQUENCY);

Serial.printf(“Сэмплов в slice: %d\n”, EI_CLASSIFIER_SLICE_SIZE);

Serial.printf(

  "Длительность slice: %.3f сек\\n",

  static_cast<float>(EI_CLASSIFIER_SLICE_SIZE) / EI_CLASSIFIER_FREQUENCY);

// ---------------------------------------------------

// MAX98357A

// ---------------------------------------------------

if (!setup_i2s_speaker()) {

Serial.println(F("MAX98357A не запустился"));

while (true) {

  delay(1000);

}

}

Serial.println(F(“MAX98357A готов”));

// ---------------------------------------------------

// Edge Impulse

// ---------------------------------------------------

run_classifier_init();

Serial.println();

Serial.println(F(“Микрофон слушает непрерывно”));

Serial.println(F(“Скажи: KANT”));

// Тестовое воспроизведение

play_wav_blocking(“/voice/hello.wav”);

}

// =====================================================

// LOOP

// =====================================================

void loop() {

// ---------------------------------------------------

// GPS

// ---------------------------------------------------

while (gpsSerial.available()) {

gps.encode(gpsSerial.read());

}

// ---------------------------------------------------

// Тайм-аут команды

// ---------------------------------------------------

if (assistant_state == WAIT_COMMAND &&

  millis() - command_wait_started > COMMAND_TIMEOUT_MS) {

Serial.println(F("Время ожидания команды закончилось"));

return_to_wait_kant();

}

// ---------------------------------------------------

// Микрофон

// ---------------------------------------------------

read_microphone_nonblocking();

if (!microphone_slice_ready()) {

delay(1);

return;

}

uint32_t audio_level = get_audio_level();

my_signal.total_length = EI_CLASSIFIER_SLICE_SIZE;

my_signal.get_data = get_signal_data;

EI_IMPULSE_ERROR error =

  run_classifier_continuous(&my_signal, &result, false);

reset_microphone_slice();

if (error != EI_IMPULSE_OK) {

Serial.printf("Ошибка Edge Impulse: %d\\n", error);

return;

}

if (audio_level < MIN_AUDIO_LEVEL) {

reset_all_confirmations();

return;

}

process_result(&result);

}

ну что, в очередной раз можно сказать только одно - держите нас в курсе.

И не мудрено с такими-то библиотеками :slightly_smiling_face:

@Волшебник
начните с того, что вставьте код правильно. В таком виде его никто смотреть не станет

Выбор esp32c3 не совсем понятен. Синхронизации на одном ядре не добьетесь точно. Микрофон для распознавания голоса? Вам бы для начала посчитать требуемые ресурсы, а то шкурка мала для задачи.

там S3.

Да. Текст не сильно хорошо читаем. Но только - что хочет ТС?

@Волшебник , мы пытаемся понять, но сложно читать текст. Вы бы проявили уважение к присутствующим. Показали бы ИИ вашу наработку, а она бы отформатировала за вас, а потом вы сюда бы скинули.

P.S. А стоит ли вообще рассматривать, если нет вопросов? Или кто то придумает вопрос за ТС?

это точно

А зачем повторять? Я не милиционер. Или опять эго прет впереди паровоза?

Публиковал в спешке, а сайт видимо просто обрезал часть где я описал проблему. ESP32-S3 здесь для распознавания голоса через INMP441. Распознавание сделано через Edge Impulse, и если запускать его отдельно то работает исправно. Но это проект голосового ассистента, и там стоит max98357 для воспроизведения файлов с сд карты. И если его включать в код то почему то ничего не работает. Ни распознавание голоса, ни воспроизведение файлов. Kant_inferencing.h - это библиотека распознавания Edge impulse.

```
#include <Adafruit_BMP280.h>
#include <Adafruit_MPU6050.h>
#include <Arduino.h>
#include <Kant_inferencing.h>
#include <SD.h>
#include <SPI.h>
#include <SparkFun_Qwiic_Humidity_AHT20.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include <driver/i2s_std.h>

#include “esp_task_wdt.h”

// =====================================================
// GPS
// =====================================================

#define GPS_RX_PIN 7

// =====================================================
// I2S INMP441
// =====================================================

#define I2S_MIC_PORT I2S_NUM_AUTO

#define I2S_MIC_SCK 4
#define I2S_MIC_WS 5
#define I2S_MIC_SD 6

// =====================================================
// I2S MAX98357A
// =====================================================

#define I2S_SPEAKER_PORT I2S_NUM_AUTO

#define I2S_TTS_BCLK 1
#define I2S_TTS_LRC 2
#define I2S_TTS_DIN 3

// =====================================================
// SD
// =====================================================

#define SD_CS 10
#define SD_SCK 12
#define SD_MISO 13
#define SD_MOSI 11

#define SD_FREQUENCY 4000000

// =====================================================
// РАСПОЗНАВАНИЕ
// =====================================================

#define WAKE_THRESHOLD 0.90f
#define COMMAND_THRESHOLD 0.90f
#define COMMAND_TIMEOUT_MS 7000
#define REQUIRED_CONFIRMATIONS 2

#define MIN_AUDIO_LEVEL 100
#define MIN_CONFIDENCE_GAP 0.20f

// =====================================================
// WAV
// =====================================================

#define WAV_SAMPLE_RATE 16000

#define AUDIO_IO_BUFFER_SIZE 2048
#define AUDIO_WRITE_CHUNK_SIZE 512
#define AUDIO_WRITE_TIMEOUT_MS 200

// =====================================================
// ОБЪЕКТЫ
// =====================================================

Adafruit_BMP280 bmp;
Adafruit_MPU6050 mpu;
AHT20 aht;

HardwareSerial gpsSerial(1);
TinyGPSPlus gps;

// =====================================================
// I2S-КАНАЛЫ
// =====================================================

static i2s_chan_handle_t mic_rx_handle = nullptr;
static i2s_chan_handle_t speaker_tx_handle = nullptr;

// =====================================================
// ДАТЧИКИ
// =====================================================

bool bmp_available = false;
bool aht_available = false;
bool mpu_available = false;

// =====================================================
// EDGE IMPULSE
// =====================================================

static int16_t audio_buffer[EI_CLASSIFIER_SLICE_SIZE];
static int32_t i2s_samples[256];

static ei_impulse_result_t result;
static signal_t my_signal;

static size_t microphone_samples_filled = 0;

// =====================================================
// СОСТОЯНИЕ АССИСТЕНТА
// =====================================================

enum AssistantState { WAIT_KANT, WAIT_COMMAND };

AssistantState assistant_state = WAIT_KANT;

uint32_t command_wait_started = 0;

uint8_t kant_confirmations = 0;
uint8_t weather_confirmations = 0;
uint8_t coordinates_confirmations = 0;

// =====================================================
// WAV-БУФЕРЫ
// =====================================================

static uint8_t wav_input_buffer[AUDIO_IO_BUFFER_SIZE];
static int16_t wav_stereo_buffer[AUDIO_IO_BUFFER_SIZE];

// =====================================================
// WAV ПАРАМЕТРЫ
// =====================================================

static uint8_t wav_channels = 0;
static uint8_t wav_bits_per_sample = 0;
static uint32_t wav_file_sample_rate = 0;

// =====================================================
// ПРЕДОБЪЯВЛЕНИЯ ФУНКЦИЙ
// =====================================================

void reset_microphone_slice();

// =====================================================
// LITTLE-ENDIAN
// =====================================================

uint16_t readLE16(File& file) {
uint8_t data[2];
if (file.read(data, sizeof(data)) != sizeof(data)) {
return 0;
}
return static_cast<uint16_t>(data[0]) | (static_cast<uint16_t>(data[1]) << 8);
}

uint32_t readLE32(File& file) {
uint8_t data[4];
if (file.read(data, sizeof(data)) != sizeof(data)) {
return 0;
}
return static_cast<uint32_t>(data[0]) |
(static_cast<uint32_t>(data[1]) << 8) |
(static_cast<uint32_t>(data[2]) << 16) |
(static_cast<uint32_t>(data[3]) << 24);
}

bool readChunkId(File& file, char* id) {
if (file.read(reinterpret_cast<uint8_t*>(id), 4) != 4) {
return false;
}
id[4] = ‘\0’;
return true;
}

// =====================================================
// I2S МИКРОФОН
// =====================================================

bool setup_i2s_microphone() {
i2s_chan_config_t channel_config =
I2S_CHANNEL_DEFAULT_CONFIG(I2S_MIC_PORT, I2S_ROLE_MASTER);

channel_config.auto_clear = true;
channel_config.dma_desc_num = 8;
channel_config.dma_frame_num = 128;

esp_err_t err = i2s_new_channel(&channel_config, nullptr, &mic_rx_handle);

if (err != ESP_OK) {
Serial.printf(“Ошибка создания I2S RX: %s (%d)\n”, esp_err_to_name(err), err);
return false;
}

i2s_std_config_t mic_config = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EI_CLASSIFIER_FREQUENCY),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT,
I2S_SLOT_MODE_MONO),
.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,
.bclk = static_cast<gpio_num_t>(I2S_MIC_SCK),
.ws = static_cast<gpio_num_t>(I2S_MIC_WS),
.dout = I2S_GPIO_UNUSED,
.din = static_cast<gpio_num_t>(I2S_MIC_SD),
.invert_flags = {
.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}};

mic_config.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;

err = i2s_channel_init_std_mode(mic_rx_handle, &mic_config);

if (err != ESP_OK) {
Serial.printf(“Ошибка инициализации I2S RX: %s (%d)\n”, esp_err_to_name(err), err);
return false;
}

err = i2s_channel_enable(mic_rx_handle);

if (err != ESP_OK) {
Serial.printf(“Ошибка включения I2S RX: %s (%d)\n”, esp_err_to_name(err), err);
return false;
}

Serial.println(F(“I2S микрофон инициализирован”));
return true;
}

// =====================================================
// I2S MAX98357A
// =====================================================

bool setup_i2s_speaker() {
i2s_chan_config_t channel_config =
I2S_CHANNEL_DEFAULT_CONFIG(I2S_SPEAKER_PORT, I2S_ROLE_MASTER);

channel_config.auto_clear = true;
channel_config.dma_desc_num = 6;
channel_config.dma_frame_num = 64;

esp_err_t err = i2s_new_channel(&channel_config, &speaker_tx_handle, nullptr);

if (err != ESP_OK) {
Serial.printf(“Ошибка создания I2S TX: %s (%d)\n”, esp_err_to_name(err), err);
return false;
}

i2s_std_config_t speaker_config = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(WAV_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO),
.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,
.bclk = static_cast<gpio_num_t>(I2S_TTS_BCLK),
.ws = static_cast<gpio_num_t>(I2S_TTS_LRC),
.dout = static_cast<gpio_num_t>(I2S_TTS_DIN),
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}};

err = i2s_channel_init_std_mode(speaker_tx_handle, &speaker_config);

if (err != ESP_OK) {
Serial.printf(“Ошибка инициализации I2S TX: %s (%d)\n”, esp_err_to_name(err), err);
return false;
}

err = i2s_channel_enable(speaker_tx_handle);

if (err != ESP_OK) {
Serial.printf(“Ошибка включения I2S TX: %s (%d)\n”, esp_err_to_name(err), err);
return false;
}

Serial.println(F(“I2S MAX98357A запущен”));
return true;
}

// =====================================================
// РАЗБОР WAV
// =====================================================

bool prepare_wav(File& file, uint32_t& data_size) {
char riff[5];
char wave[5];

if (!readChunkId(file, riff)) {
Serial.println(F(“Не удалось прочитать RIFF”));
return false;
}

readLE32(file);

if (!readChunkId(file, wave)) {
Serial.println(F(“Не удалось прочитать WAVE”));
return false;
}

if (strcmp(riff, “RIFF”) != 0 || strcmp(wave, “WAVE”) != 0) {
Serial.println(F(“Файл не является RIFF WAV”));
return false;
}

bool fmt_found = false;
bool data_found = false;

wav_channels = 0;
wav_bits_per_sample = 0;
wav_file_sample_rate = 0;
data_size = 0;

while (file.available()) {
char chunk_id[5];

if (!readChunkId(file, chunk_id)) {
  return false;
}

uint32_t chunk_size = readLE32(file);
uint32_t chunk_start = file.position();

if (strcmp(chunk_id, "fmt ") == 0) {
  uint16_t audio_format = readLE16(file);

  wav_channels = readLE16(file);
  wav_file_sample_rate = readLE32(file);

  readLE32(file);
  readLE16(file);

  wav_bits_per_sample = readLE16(file);

  if (audio_format != 1) {
    Serial.println(F("Поддерживается только PCM WAV"));
    return false;
  }

  if (wav_channels != 1 && wav_channels != 2) {
    Serial.println(F("Поддерживается только mono/stereo WAV"));
    return false;
  }

  if (wav_bits_per_sample != 16) {
    Serial.println(F("Поддерживается только 16-bit WAV"));
    return false;
  }

  if (wav_file_sample_rate != WAV_SAMPLE_RATE) {
    Serial.printf("Требуется %d Hz, в файле %lu Hz\n", WAV_SAMPLE_RATE,
                  static_cast<unsigned long>(wav_file_sample_rate));
    return false;
  }

  fmt_found = true;
} else if (strcmp(chunk_id, "data") == 0) {
  data_size = chunk_size;
  data_found = true;
  break;
}

file.seek(chunk_start + chunk_size);

if (chunk_size & 1) {
  file.seek(file.position() + 1);
}

}

if (!fmt_found || !data_found) {
Serial.println(F(“В WAV не найден fmt или data”));
return false;
}

Serial.printf(“WAV: %lu Hz, %u каналов, %u бит, %lu байт данных\n”,
static_cast(wav_file_sample_rate), wav_channels,
wav_bits_per_sample, static_cast(data_size));

return true;
}

// =====================================================
// ЗАПИСЬ В I2S
// =====================================================

bool write_i2s_blocking(const uint8_t* data, size_t data_size) {
if (speaker_tx_handle == nullptr) {
Serial.println(F(“I2S TX не инициализирован”));
return false;
}

size_t written = 0;

while (written < data_size) {
size_t remaining = data_size - written;
size_t chunk_size = min(remaining, static_cast<size_t>(AUDIO_WRITE_CHUNK_SIZE));

size_t bytes_written = 0;

esp_err_t err = i2s_channel_write(speaker_tx_handle, data + written, chunk_size,
                                  &bytes_written,
                                  pdMS_TO_TICKS(AUDIO_WRITE_TIMEOUT_MS));

if (err != ESP_OK) {
  Serial.printf("Ошибка i2s_channel_write: %s (%d)\n", esp_err_to_name(err), err);
  return false;
}

if (bytes_written == 0) {
  Serial.println(F("I2S записал 0 байт"));
  return false;
}

written += bytes_written;

}

return true;
}

// =====================================================
// СИНХРОННОЕ ВОСПРОИЗВЕДЕНИЕ WAV
// =====================================================

bool play_wav_blocking(const char* path) {
if (!SD.exists(path)) {
Serial.printf(“WAV файл не найден: %s\n”, path);
return false;
}

File file = SD.open(path, FILE_READ);

if (!file) {
Serial.printf(“Не удалось открыть WAV: %s\n”, path);
return false;
}

// Отключаем микрофон на время воспроизведения
if (mic_rx_handle) {
i2s_channel_disable(mic_rx_handle);
}

uint32_t data_size = 0;

if (!prepare_wav(file, data_size)) {
Serial.println(F(“Ошибка заголовка WAV”));
file.close();
if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
return false;
}

uint32_t bytes_left = data_size;

while (bytes_left > 0) {
size_t bytes_to_read =
min(static_cast<uint32_t>(sizeof(wav_input_buffer)), bytes_left);

bytes_to_read &= ~1u;

if (bytes_to_read == 0) {
  break;
}

size_t bytes_read = file.read(wav_input_buffer, bytes_to_read);

if (bytes_read == 0) {
  Serial.println(F("Ошибка чтения данных WAV"));
  file.close();
  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
  return false;
}

bytes_read &= ~1u;
bytes_left -= bytes_read;

const uint8_t* output_buffer = nullptr;
size_t output_bytes = 0;

if (wav_channels == 1) {
  size_t mono_samples = bytes_read / 2;

  for (size_t i = 0; i < mono_samples; i++) {
    uint16_t low = static_cast<uint16_t>(wav_input_buffer[i * 2]);
    uint16_t high = static_cast<uint16_t>(wav_input_buffer[i * 2 + 1]) << 8;
    int16_t sample = static_cast<int16_t>(low | high);

    wav_stereo_buffer[i * 2] = sample;
    wav_stereo_buffer[i * 2 + 1] = sample;
  }

  output_buffer = reinterpret_cast<const uint8_t*>(wav_stereo_buffer);
  output_bytes = mono_samples * 2 * sizeof(int16_t);
} else if (wav_channels == 2) {
  output_buffer = wav_input_buffer;
  output_bytes = bytes_read;
} else {
  Serial.println(F("Неподдерживаемое число каналов"));
  file.close();
  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
  return false;
}

if (!write_i2s_blocking(output_buffer, output_bytes)) {
  Serial.println(F("Ошибка вывода WAV в I2S"));
  file.close();
  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
  return false;
}

}

file.close();

// Снова включаем микрофон
if (mic_rx_handle) {
i2s_channel_enable(mic_rx_handle);
reset_microphone_slice();
}

return true;
}

bool play_number_wav(int number) {
number = abs(number);
String path = “/voice/” + String(number) + “.wav”;
return play_wav_blocking(path.c_str());
}

// =====================================================
// EDGE IMPULSE
// =====================================================

static int get_signal_data(size_t offset, size_t length, float* out_ptr) {
numpy::int16_to_float(&audio_buffer[offset], out_ptr, length);
return 0;
}

void reset_microphone_slice() { microphone_samples_filled = 0; }

bool microphone_slice_ready() {
return microphone_samples_filled >= EI_CLASSIFIER_SLICE_SIZE;
}

bool read_microphone_nonblocking() {
if (mic_rx_handle == nullptr) {
return false;
}

size_t bytes_read = 0;

esp_err_t err = i2s_channel_read(mic_rx_handle, i2s_samples, sizeof(i2s_samples),
&bytes_read, pdMS_TO_TICKS(10));

if (err != ESP_OK) {
return false;
}

if (bytes_read == 0) {
return false;
}

size_t received_samples = bytes_read / sizeof(int32_t);

for (size_t i = 0; i < received_samples; i++) {
if (microphone_samples_filled >= EI_CLASSIFIER_SLICE_SIZE) {
break;
}

audio_buffer[microphone_samples_filled++] =
    static_cast<int16_t>(i2s_samples[i] >> 16);

}

return true;
}

uint32_t get_audio_level() {
int64_t sum = 0;
for (size_t i = 0; i < EI_CLASSIFIER_SLICE_SIZE; i++) {
sum += abs(static_cast<int32_t>(audio_buffer[i]));
}
return static_cast<uint32_t>(sum / EI_CLASSIFIER_SLICE_SIZE);
}

// =====================================================
// ПОДТВЕРЖДЕНИЯ
// =====================================================

void reset_command_confirmations() {
weather_confirmations = 0;
coordinates_confirmations = 0;
}

void reset_all_confirmations() {
kant_confirmations = 0;
reset_command_confirmations();
}

// =====================================================
// ПОГОДА
// =====================================================

void execute_weather() {
Serial.println(F(“>>> КОМАНДА: WEATHER”));

if (!bmp_available) {
Serial.println(F(“BMP280 недоступен”));
play_wav_blocking(“/voice/sensor_error.wav”);
return;
}

float temperature_value = bmp.readTemperature();
float pressure_value = bmp.readPressure() / 100.0f;
float humidity_value = NAN;

if (aht_available) {
humidity_value = aht.getHumidity();
}

Serial.printf(“Температура: %.1f C\n”, temperature_value);
Serial.printf(“Давление: %.1f hPa\n”, pressure_value);
Serial.printf(“Влажность: %.1f %%\n”, humidity_value);

if (isnan(temperature_value)) {
play_wav_blocking(“/voice/sensor_error.wav”);
return;
}

int temperature = static_cast(round(temperature_value));

play_wav_blocking(“/voice/temperature.wav”);

if (temperature < 0) {
play_wav_blocking(“/voice/minus.wav”);
}

int absolute_temperature = abs(temperature);

if (absolute_temperature < 11) {
play_number_wav(absolute_temperature);
} else {
play_number_wav(absolute_temperature / 10);
play_number_wav(absolute_temperature % 10);
}

play_wav_blocking(“/voice/degrees.wav”);

if (temperature > 0) {
play_wav_blocking(“/voice/heat.wav”);
} else if (temperature < 0) {
play_wav_blocking(“/voice/cold.wav”);
} else {
play_wav_blocking(“/voice/zero.wav”);
}
}

// =====================================================
// GPS
// =====================================================

void execute_coordinates() {
Serial.println(F(“>>> КОМАНДА: COORDINATES”));

if (gps.location.isValid() && gps.satellites.isValid() &&
gps.satellites.value() >= 3 && gps.hdop.isValid() && gps.hdop.value() < 15) {
Serial.print("Широта: ");
Serial.println(gps.location.lat(), 6);
Serial.print("Долгота: ");
Serial.println(gps.location.lng(), 6);
Serial.print("Высота: ");
Serial.println(gps.altitude.meters());
Serial.print("Спутники: ");
Serial.println(gps.satellites.value());
Serial.print("HDOP: ");
Serial.println(gps.hdop.value());

play_wav_blocking("/voice/coordinates_ready.wav");

} else {
Serial.println(F(“Недостаточно спутников”));
play_wav_blocking(“/voice/no_satellites.wav”);
}
}

// =====================================================
// WAIT_KANT
// =====================================================

void return_to_wait_kant() {
assistant_state = WAIT_KANT;
reset_all_confirmations();
Serial.println(F(“Снова ожидаю: KANT”));
}

// =====================================================
// ОБРАБОТКА РЕЗУЛЬТАТА
// =====================================================

void process_result(ei_impulse_result_t* inference_result) {
float best_value = 0.0f;
float second_best_value = 0.0f;

const char* best_label = “noise”;

for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
const char* label = inference_result->classification[i].label;
float value = inference_result->classification[i].value;

if (value > best_value) {
  second_best_value = best_value;
  best_value = value;
  best_label = label;
} else if (value > second_best_value) {
  second_best_value = value;
}

}

bool confident_result = (best_value - second_best_value) >= MIN_CONFIDENCE_GAP;

if (assistant_state == WAIT_KANT) {
if (strcmp(best_label, “kant”) == 0 && best_value >= WAKE_THRESHOLD &&
confident_result) {
kant_confirmations++;

  Serial.printf("KANT: %.1f%% | gap: %.1f%% | %d/%d\n", best_value * 100.0f,
                (best_value - second_best_value) * 100.0f, kant_confirmations,
                REQUIRED_CONFIRMATIONS);

  if (kant_confirmations >= REQUIRED_CONFIRMATIONS) {
    Serial.println(F(">>> KANT РАСПОЗНАН"));
    Serial.println(F(">>> Скажи WEATHER или COORDINATES"));
    assistant_state = WAIT_COMMAND;
    command_wait_started = millis();
    reset_all_confirmations();
  }
} else {
  kant_confirmations = 0;
}

return;

}

if (assistant_state != WAIT_COMMAND) {
return;
}

if (strcmp(best_label, “weather”) == 0 && best_value >= COMMAND_THRESHOLD &&
confident_result) {
weather_confirmations++;
coordinates_confirmations = 0;

Serial.printf("WEATHER: %.1f%% | gap: %.1f%% | %d/%d\n", best_value * 100.0f,
              (best_value - second_best_value) * 100.0f, weather_confirmations,
              REQUIRED_CONFIRMATIONS);

if (weather_confirmations >= REQUIRED_CONFIRMATIONS) {
  execute_weather();
  return_to_wait_kant();
}

return;

}

if (strcmp(best_label, “coordinates”) == 0 && best_value >= COMMAND_THRESHOLD &&
confident_result) {
coordinates_confirmations++;
weather_confirmations = 0;

Serial.printf("COORDINATES: %.1f%% | gap: %.1f%% | %d/%d\n", best_value * 100.0f,
              (best_value - second_best_value) * 100.0f,
              coordinates_confirmations, REQUIRED_CONFIRMATIONS);

if (coordinates_confirmations >= REQUIRED_CONFIRMATIONS) {
  execute_coordinates();
  return_to_wait_kant();
}

return;

}

reset_command_confirmations();
}

// =====================================================
// SETUP
// =====================================================

void setup() {
Serial.begin(115200);
delay(2000);

// Отключаем Task Watchdog Timer
esp_task_wdt_deinit();

Serial.println();
Serial.println(F(“================================”));
Serial.println(F(“Запуск голосового ассистента”));
Serial.println(F(“================================”));

// ---------------------------------------------------
// I2C
// ---------------------------------------------------

Wire.begin(8, 9);

bmp_available = bmp.begin(0x77);
if (bmp_available) {
Serial.println(F(“BMP280 найден”));
} else {
Serial.println(F(“BMP280 не найден”));
}

aht_available = aht.begin();
if (aht_available) {
Serial.println(F(“AHT20 найден”));
} else {
Serial.println(F(“AHT20 не найден”));
}

// ---------------------------------------------------
// SD
// ---------------------------------------------------

SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
delay(50);

if (!SD.begin(SD_CS, SPI, SD_FREQUENCY)) {
Serial.println(F(“SD mount failed”));
while (true) {
delay(1000);
}
}

Serial.println(F(“SD mounted”));

// Проверка WAV-файлов
if (SD.exists(“/voice/hello.wav”)) {
Serial.println(F(“hello.wav найден”));
} else {
Serial.println(F(“WARNING: hello.wav не найден!”));
}

delay(100);

// ---------------------------------------------------
// GPS
// ---------------------------------------------------

gpsSerial.begin(9600, SERIAL_8N1, GPS_RX_PIN, -1);

// ---------------------------------------------------
// Микрофон
// ---------------------------------------------------

if (!setup_i2s_microphone()) {
Serial.println(F(“Микрофон не запустился”));
while (true) {
delay(1000);
}
}

Serial.println(F(“ESP32-S3 + INMP441 + Edge Impulse”));
Serial.printf(“Частота модели: %d Hz\n”, EI_CLASSIFIER_FREQUENCY);
Serial.printf(“Сэмплов в slice: %d\n”, EI_CLASSIFIER_SLICE_SIZE);
Serial.printf(“Длительность slice: %.3f сек\n”,
static_cast(EI_CLASSIFIER_SLICE_SIZE) / EI_CLASSIFIER_FREQUENCY);

// ---------------------------------------------------
// MAX98357A
// ---------------------------------------------------

if (!setup_i2s_speaker()) {
Serial.println(F(“MAX98357A не запустился”));
while (true) {
delay(1000);
}
}

Serial.println(F(“MAX98357A готов”));

// ---------------------------------------------------
// Edge Impulse
// ---------------------------------------------------

run_classifier_init();

Serial.println();
Serial.println(F(“Микрофон слушает непрерывно”));
Serial.println(F(“Скажи: KANT”));

// Тестовое воспроизведение
play_wav_blocking(“/voice/hello.wav”);
}

// =====================================================
// LOOP
// =====================================================

void loop() {
// ---------------------------------------------------
// GPS
// ---------------------------------------------------

while (gpsSerial.available()) {
gps.encode(gpsSerial.read());
}

// ---------------------------------------------------
// Тайм-аут команды
// ---------------------------------------------------

if (assistant_state == WAIT_COMMAND &&
millis() - command_wait_started > COMMAND_TIMEOUT_MS) {
Serial.println(F(“Время ожидания команды закончилось”));
return_to_wait_kant();
}

// ---------------------------------------------------
// Микрофон
// ---------------------------------------------------

read_microphone_nonblocking();

if (!microphone_slice_ready()) {
delay(1);
return;
}

uint32_t audio_level = get_audio_level();

my_signal.total_length = EI_CLASSIFIER_SLICE_SIZE;
my_signal.get_data = get_signal_data;

EI_IMPULSE_ERROR error = run_classifier_continuous(&my_signal, &result, false);

reset_microphone_slice();

if (error != EI_IMPULSE_OK) {
Serial.printf(“Ошибка Edge Impulse: %d\n”, error);
return;
}

if (audio_level < MIN_AUDIO_LEVEL) {
reset_all_confirmations();
return;
}

process_result(&result);
}
```

опять код криво вставили

Когда открываете новое пустое сообщение на форуме, там в поле ввода - инструкция по вставке кода. Инструкция видна, пока вы не начали редактировать сообщение. Сначала прочитайте, а уже потом вставляйте код.

так я и вставил по инструкции, я вообще без понятия почему он кусками рвется

#include <Adafruit_BMP280.h>
#include <Adafruit_MPU6050.h>
#include <Arduino.h>
#include <Kant_inferencing.h>
#include <SD.h>
#include <SPI.h>
#include <SparkFun_Qwiic_Humidity_AHT20.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include <driver/i2s_std.h>

#include "esp_task_wdt.h"

// =====================================================
// GPS
// =====================================================

#define GPS_RX_PIN 7

// =====================================================
// I2S INMP441
// =====================================================

#define I2S_MIC_PORT I2S_NUM_AUTO

#define I2S_MIC_SCK 4
#define I2S_MIC_WS 5
#define I2S_MIC_SD 6

// =====================================================
// I2S MAX98357A
// =====================================================

#define I2S_SPEAKER_PORT I2S_NUM_AUTO

#define I2S_TTS_BCLK 1
#define I2S_TTS_LRC 2
#define I2S_TTS_DIN 3

// =====================================================
// SD
// =====================================================

#define SD_CS 10
#define SD_SCK 12
#define SD_MISO 13
#define SD_MOSI 11

#define SD_FREQUENCY 4000000

// =====================================================
// РАСПОЗНАВАНИЕ
// =====================================================

#define WAKE_THRESHOLD 0.90f
#define COMMAND_THRESHOLD 0.90f
#define COMMAND_TIMEOUT_MS 7000
#define REQUIRED_CONFIRMATIONS 2

#define MIN_AUDIO_LEVEL 100
#define MIN_CONFIDENCE_GAP 0.20f

// =====================================================
// WAV
// =====================================================

#define WAV_SAMPLE_RATE 16000

#define AUDIO_IO_BUFFER_SIZE 2048
#define AUDIO_WRITE_CHUNK_SIZE 512
#define AUDIO_WRITE_TIMEOUT_MS 200

// =====================================================
// ОБЪЕКТЫ
// =====================================================

Adafruit_BMP280 bmp;
Adafruit_MPU6050 mpu;
AHT20 aht;

HardwareSerial gpsSerial(1);
TinyGPSPlus gps;

// =====================================================
// I2S-КАНАЛЫ
// =====================================================

static i2s_chan_handle_t mic_rx_handle = nullptr;
static i2s_chan_handle_t speaker_tx_handle = nullptr;

// =====================================================
// ДАТЧИКИ
// =====================================================

bool bmp_available = false;
bool aht_available = false;
bool mpu_available = false;

// =====================================================
// EDGE IMPULSE
// =====================================================

static int16_t audio_buffer[EI_CLASSIFIER_SLICE_SIZE];
static int32_t i2s_samples[256];

static ei_impulse_result_t result;
static signal_t my_signal;

static size_t microphone_samples_filled = 0;

// =====================================================
// СОСТОЯНИЕ АССИСТЕНТА
// =====================================================

enum AssistantState { WAIT_KANT, WAIT_COMMAND };

AssistantState assistant_state = WAIT_KANT;

uint32_t command_wait_started = 0;

uint8_t kant_confirmations = 0;
uint8_t weather_confirmations = 0;
uint8_t coordinates_confirmations = 0;

// =====================================================
// WAV-БУФЕРЫ
// =====================================================

static uint8_t wav_input_buffer[AUDIO_IO_BUFFER_SIZE];
static int16_t wav_stereo_buffer[AUDIO_IO_BUFFER_SIZE];

// =====================================================
// WAV ПАРАМЕТРЫ
// =====================================================

static uint8_t wav_channels = 0;
static uint8_t wav_bits_per_sample = 0;
static uint32_t wav_file_sample_rate = 0;

// =====================================================
// ПРЕДОБЪЯВЛЕНИЯ ФУНКЦИЙ
// =====================================================

void reset_microphone_slice();

// =====================================================
// LITTLE-ENDIAN
// =====================================================

uint16_t readLE16(File& file) {
uint8_t data[2];
if (file.read(data, sizeof(data)) != sizeof(data)) {
return 0;
}
return static_cast<uint16_t>(data[0]) | (static_cast<uint16_t>(data[1]) << 8);
}

uint32_t readLE32(File& file) {
uint8_t data[4];
if (file.read(data, sizeof(data)) != sizeof(data)) {
return 0;
}
return static_cast<uint32_t>(data[0]) |
(static_cast<uint32_t>(data[1]) << 8) |
(static_cast<uint32_t>(data[2]) << 16) |
(static_cast<uint32_t>(data[3]) << 24);
}

bool readChunkId(File& file, char* id) {
if (file.read(reinterpret_cast<uint8_t*>(id), 4) != 4) {
return false;
}
id[4] = '\0';
return true;
}

// =====================================================
// I2S МИКРОФОН
// =====================================================

bool setup_i2s_microphone() {
i2s_chan_config_t channel_config =
I2S_CHANNEL_DEFAULT_CONFIG(I2S_MIC_PORT, I2S_ROLE_MASTER);

channel_config.auto_clear = true;
channel_config.dma_desc_num = 8;
channel_config.dma_frame_num = 128;

esp_err_t err = i2s_new_channel(&channel_config, nullptr, &mic_rx_handle);

if (err != ESP_OK) {
Serial.printf("Ошибка создания I2S RX: %s (%d)\n", esp_err_to_name(err), err);
return false;
}

i2s_std_config_t mic_config = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EI_CLASSIFIER_FREQUENCY),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT,
I2S_SLOT_MODE_MONO),
.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,
.bclk = static_cast<gpio_num_t>(I2S_MIC_SCK),
.ws = static_cast<gpio_num_t>(I2S_MIC_WS),
.dout = I2S_GPIO_UNUSED,
.din = static_cast<gpio_num_t>(I2S_MIC_SD),
.invert_flags = {
.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}};

mic_config.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;

err = i2s_channel_init_std_mode(mic_rx_handle, &mic_config);

if (err != ESP_OK) {
Serial.printf("Ошибка инициализации I2S RX: %s (%d)\n", esp_err_to_name(err), err);
return false;
}

err = i2s_channel_enable(mic_rx_handle);

if (err != ESP_OK) {
Serial.printf("Ошибка включения I2S RX: %s (%d)\n", esp_err_to_name(err), err);
return false;
}

Serial.println(F("I2S микрофон инициализирован"));
return true;
}

// =====================================================
// I2S MAX98357A
// =====================================================

bool setup_i2s_speaker() {
i2s_chan_config_t channel_config =
I2S_CHANNEL_DEFAULT_CONFIG(I2S_SPEAKER_PORT, I2S_ROLE_MASTER);

channel_config.auto_clear = true;
channel_config.dma_desc_num = 6;
channel_config.dma_frame_num = 64;

esp_err_t err = i2s_new_channel(&channel_config, &speaker_tx_handle, nullptr);

if (err != ESP_OK) {
Serial.printf("Ошибка создания I2S TX: %s (%d)\n", esp_err_to_name(err), err);
return false;
}

i2s_std_config_t speaker_config = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(WAV_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO),
.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,
.bclk = static_cast<gpio_num_t>(I2S_TTS_BCLK),
.ws = static_cast<gpio_num_t>(I2S_TTS_LRC),
.dout = static_cast<gpio_num_t>(I2S_TTS_DIN),
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}};

err = i2s_channel_init_std_mode(speaker_tx_handle, &speaker_config);

if (err != ESP_OK) {
Serial.printf("Ошибка инициализации I2S TX: %s (%d)\n", esp_err_to_name(err), err);
return false;
}

err = i2s_channel_enable(speaker_tx_handle);

if (err != ESP_OK) {
Serial.printf("Ошибка включения I2S TX: %s (%d)\n", esp_err_to_name(err), err);
return false;
}

Serial.println(F("I2S MAX98357A запущен"));
return true;
}

// =====================================================
// РАЗБОР WAV
// =====================================================

bool prepare_wav(File& file, uint32_t& data_size) {
char riff[5];
char wave[5];

if (!readChunkId(file, riff)) {
Serial.println(F("Не удалось прочитать RIFF"));
return false;
}

readLE32(file);

if (!readChunkId(file, wave)) {
Serial.println(F("Не удалось прочитать WAVE"));
return false;
}

if (strcmp(riff, "RIFF") != 0 || strcmp(wave, "WAVE") != 0) {
Serial.println(F("Файл не является RIFF WAV"));
return false;
}

bool fmt_found = false;
bool data_found = false;

wav_channels = 0;
wav_bits_per_sample = 0;
wav_file_sample_rate = 0;
data_size = 0;

while (file.available()) {
char chunk_id[5];

if (!readChunkId(file, chunk_id)) {
  return false;
}

uint32_t chunk_size = readLE32(file);
uint32_t chunk_start = file.position();

if (strcmp(chunk_id, "fmt ") == 0) {
  uint16_t audio_format = readLE16(file);

  wav_channels = readLE16(file);
  wav_file_sample_rate = readLE32(file);

  readLE32(file);
  readLE16(file);

  wav_bits_per_sample = readLE16(file);

  if (audio_format != 1) {
    Serial.println(F("Поддерживается только PCM WAV"));
    return false;
  }

  if (wav_channels != 1 && wav_channels != 2) {
    Serial.println(F("Поддерживается только mono/stereo WAV"));
    return false;
  }

  if (wav_bits_per_sample != 16) {
    Serial.println(F("Поддерживается только 16-bit WAV"));
    return false;
  }

  if (wav_file_sample_rate != WAV_SAMPLE_RATE) {
    Serial.printf("Требуется %d Hz, в файле %lu Hz\n", WAV_SAMPLE_RATE,
                  static_cast<unsigned long>(wav_file_sample_rate));
    return false;
  }

  fmt_found = true;
} else if (strcmp(chunk_id, "data") == 0) {
  data_size = chunk_size;
  data_found = true;
  break;
}

file.seek(chunk_start + chunk_size);

if (chunk_size & 1) {
  file.seek(file.position() + 1);
}

}

if (!fmt_found || !data_found) {
Serial.println(F("В WAV не найден fmt или data"));
return false;
}

Serial.printf("WAV: %lu Hz, %u каналов, %u бит, %lu байт данных\n",
static_cast(wav_file_sample_rate), wav_channels,
wav_bits_per_sample, static_cast(data_size));

return true;
}

// =====================================================
// ЗАПИСЬ В I2S
// =====================================================

bool write_i2s_blocking(const uint8_t* data, size_t data_size) {
if (speaker_tx_handle == nullptr) {
Serial.println(F("I2S TX не инициализирован"));
return false;
}

size_t written = 0;

while (written < data_size) {
size_t remaining = data_size - written;
size_t chunk_size = min(remaining, static_cast<size_t>(AUDIO_WRITE_CHUNK_SIZE));

size_t bytes_written = 0;

esp_err_t err = i2s_channel_write(speaker_tx_handle, data + written, chunk_size,
                                  &bytes_written,
                                  pdMS_TO_TICKS(AUDIO_WRITE_TIMEOUT_MS));

if (err != ESP_OK) {
  Serial.printf("Ошибка i2s_channel_write: %s (%d)\n", esp_err_to_name(err), err);
  return false;
}

if (bytes_written == 0) {
  Serial.println(F("I2S записал 0 байт"));
  return false;
}

written += bytes_written;

}

return true;
}

// =====================================================
// СИНХРОННОЕ ВОСПРОИЗВЕДЕНИЕ WAV
// =====================================================

bool play_wav_blocking(const char* path) {
if (!SD.exists(path)) {
Serial.printf("WAV файл не найден: %s\n", path);
return false;
}

File file = SD.open(path, FILE_READ);

if (!file) {
Serial.printf("Не удалось открыть WAV: %s\n", path);
return false;
}

// Отключаем микрофон на время воспроизведения
if (mic_rx_handle) {
i2s_channel_disable(mic_rx_handle);
}

uint32_t data_size = 0;

if (!prepare_wav(file, data_size)) {
Serial.println(F("Ошибка заголовка WAV"));
file.close();
if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
return false;
}

uint32_t bytes_left = data_size;

while (bytes_left > 0) {
size_t bytes_to_read =
min(static_cast<uint32_t>(sizeof(wav_input_buffer)), bytes_left);

bytes_to_read &= ~1u;

if (bytes_to_read == 0) {
  break;
}

size_t bytes_read = file.read(wav_input_buffer, bytes_to_read);

if (bytes_read == 0) {
  Serial.println(F("Ошибка чтения данных WAV"));
  file.close();
  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
  return false;
}

bytes_read &= ~1u;
bytes_left -= bytes_read;

const uint8_t* output_buffer = nullptr;
size_t output_bytes = 0;

if (wav_channels == 1) {
  size_t mono_samples = bytes_read / 2;

  for (size_t i = 0; i < mono_samples; i++) {
    uint16_t low = static_cast<uint16_t>(wav_input_buffer[i * 2]);
    uint16_t high = static_cast<uint16_t>(wav_input_buffer[i * 2 + 1]) << 8;
    int16_t sample = static_cast<int16_t>(low | high);

    wav_stereo_buffer[i * 2] = sample;
    wav_stereo_buffer[i * 2 + 1] = sample;
  }

  output_buffer = reinterpret_cast<const uint8_t*>(wav_stereo_buffer);
  output_bytes = mono_samples * 2 * sizeof(int16_t);
} else if (wav_channels == 2) {
  output_buffer = wav_input_buffer;
  output_bytes = bytes_read;
} else {
  Serial.println(F("Неподдерживаемое число каналов"));
  file.close();
  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
  return false;
}

if (!write_i2s_blocking(output_buffer, output_bytes)) {
  Serial.println(F("Ошибка вывода WAV в I2S"));
  file.close();
  if (mic_rx_handle) i2s_channel_enable(mic_rx_handle);
  return false;
}

}

file.close();

// Снова включаем микрофон
if (mic_rx_handle) {
i2s_channel_enable(mic_rx_handle);
reset_microphone_slice();
}

return true;
}

bool play_number_wav(int number) {
number = abs(number);
String path = "/voice/" + String(number) + ".wav";
return play_wav_blocking(path.c_str());
}

// =====================================================
// EDGE IMPULSE
// =====================================================

static int get_signal_data(size_t offset, size_t length, float* out_ptr) {
numpy::int16_to_float(&audio_buffer[offset], out_ptr, length);
return 0;
}

void reset_microphone_slice() { microphone_samples_filled = 0; }

bool microphone_slice_ready() {
return microphone_samples_filled >= EI_CLASSIFIER_SLICE_SIZE;
}

bool read_microphone_nonblocking() {
if (mic_rx_handle == nullptr) {
return false;
}

size_t bytes_read = 0;

esp_err_t err = i2s_channel_read(mic_rx_handle, i2s_samples, sizeof(i2s_samples),
&bytes_read, pdMS_TO_TICKS(10));

if (err != ESP_OK) {
return false;
}

if (bytes_read == 0) {
return false;
}

size_t received_samples = bytes_read / sizeof(int32_t);

for (size_t i = 0; i < received_samples; i++) {
if (microphone_samples_filled >= EI_CLASSIFIER_SLICE_SIZE) {
break;
}

audio_buffer[microphone_samples_filled++] =
    static_cast<int16_t>(i2s_samples[i] >> 16);

}

return true;
}

uint32_t get_audio_level() {
int64_t sum = 0;
for (size_t i = 0; i < EI_CLASSIFIER_SLICE_SIZE; i++) {
sum += abs(static_cast<int32_t>(audio_buffer[i]));
}
return static_cast<uint32_t>(sum / EI_CLASSIFIER_SLICE_SIZE);
}

// =====================================================
// ПОДТВЕРЖДЕНИЯ
// =====================================================

void reset_command_confirmations() {
weather_confirmations = 0;
coordinates_confirmations = 0;
}

void reset_all_confirmations() {
kant_confirmations = 0;
reset_command_confirmations();
}

// =====================================================
// ПОГОДА
// =====================================================

void execute_weather() {
Serial.println(F(">>> КОМАНДА: WEATHER"));

if (!bmp_available) {
Serial.println(F("BMP280 недоступен"));
play_wav_blocking("/voice/sensor_error.wav");
return;
}

float temperature_value = bmp.readTemperature();
float pressure_value = bmp.readPressure() / 100.0f;
float humidity_value = NAN;

if (aht_available) {
humidity_value = aht.getHumidity();
}

Serial.printf("Температура: %.1f C\n", temperature_value);
Serial.printf("Давление: %.1f hPa\n", pressure_value);
Serial.printf("Влажность: %.1f %%\n", humidity_value);

if (isnan(temperature_value)) {
play_wav_blocking("/voice/sensor_error.wav");
return;
}

int temperature = static_cast(round(temperature_value));

play_wav_blocking("/voice/temperature.wav");

if (temperature < 0) {
play_wav_blocking("/voice/minus.wav");
}

int absolute_temperature = abs(temperature);

if (absolute_temperature < 11) {
play_number_wav(absolute_temperature);
} else {
play_number_wav(absolute_temperature / 10);
play_number_wav(absolute_temperature % 10);
}

play_wav_blocking("/voice/degrees.wav");

if (temperature > 0) {
play_wav_blocking("/voice/heat.wav");
} else if (temperature < 0) {
play_wav_blocking("/voice/cold.wav");
} else {
play_wav_blocking("/voice/zero.wav");
}
}

// =====================================================
// GPS
// =====================================================

void execute_coordinates() {
Serial.println(F(">>> КОМАНДА: COORDINATES"));

if (gps.location.isValid() && gps.satellites.isValid() &&
gps.satellites.value() >= 3 && gps.hdop.isValid() && gps.hdop.value() < 15) {
Serial.print("Широта: ");
Serial.println(gps.location.lat(), 6);
Serial.print("Долгота: ");
Serial.println(gps.location.lng(), 6);
Serial.print("Высота: ");
Serial.println(gps.altitude.meters());
Serial.print("Спутники: ");
Serial.println(gps.satellites.value());
Serial.print("HDOP: ");
Serial.println(gps.hdop.value());

play_wav_blocking("/voice/coordinates_ready.wav");

} else {
Serial.println(F("Недостаточно спутников"));
play_wav_blocking("/voice/no_satellites.wav");
}
}

// =====================================================
// WAIT_KANT
// =====================================================

void return_to_wait_kant() {
assistant_state = WAIT_KANT;
reset_all_confirmations();
Serial.println(F("Снова ожидаю: KANT"));
}

// =====================================================
// ОБРАБОТКА РЕЗУЛЬТАТА
// =====================================================

void process_result(ei_impulse_result_t* inference_result) {
float best_value = 0.0f;
float second_best_value = 0.0f;

const char* best_label = "noise";

for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
const char* label = inference_result->classification[i].label;
float value = inference_result->classification[i].value;

if (value > best_value) {
  second_best_value = best_value;
  best_value = value;
  best_label = label;
} else if (value > second_best_value) {
  second_best_value = value;
}

}

bool confident_result = (best_value - second_best_value) >= MIN_CONFIDENCE_GAP;

if (assistant_state == WAIT_KANT) {
if (strcmp(best_label, "kant") == 0 && best_value >= WAKE_THRESHOLD &&
confident_result) {
kant_confirmations++;

  Serial.printf("KANT: %.1f%% | gap: %.1f%% | %d/%d\n", best_value * 100.0f,
                (best_value - second_best_value) * 100.0f, kant_confirmations,
                REQUIRED_CONFIRMATIONS);

  if (kant_confirmations >= REQUIRED_CONFIRMATIONS) {
    Serial.println(F(">>> KANT РАСПОЗНАН"));
    Serial.println(F(">>> Скажи WEATHER или COORDINATES"));
    assistant_state = WAIT_COMMAND;
    command_wait_started = millis();
    reset_all_confirmations();
  }
} else {
  kant_confirmations = 0;
}

return;

}

if (assistant_state != WAIT_COMMAND) {
return;
}

if (strcmp(best_label, "weather") == 0 && best_value >= COMMAND_THRESHOLD &&
confident_result) {
weather_confirmations++;
coordinates_confirmations = 0;

Serial.printf("WEATHER: %.1f%% | gap: %.1f%% | %d/%d\n", best_value * 100.0f,
              (best_value - second_best_value) * 100.0f, weather_confirmations,
              REQUIRED_CONFIRMATIONS);

if (weather_confirmations >= REQUIRED_CONFIRMATIONS) {
  execute_weather();
  return_to_wait_kant();
}

return;

}

if (strcmp(best_label, "coordinates") == 0 && best_value >= COMMAND_THRESHOLD &&
confident_result) {
coordinates_confirmations++;
weather_confirmations = 0;

Serial.printf("COORDINATES: %.1f%% | gap: %.1f%% | %d/%d\n", best_value * 100.0f,
              (best_value - second_best_value) * 100.0f,
              coordinates_confirmations, REQUIRED_CONFIRMATIONS);

if (coordinates_confirmations >= REQUIRED_CONFIRMATIONS) {
  execute_coordinates();
  return_to_wait_kant();
}

return;

}

reset_command_confirmations();
}

// =====================================================
// SETUP
// =====================================================

void setup() {
Serial.begin(115200);
delay(2000);

// Отключаем Task Watchdog Timer
esp_task_wdt_deinit();

Serial.println();
Serial.println(F("================================"));
Serial.println(F("Запуск голосового ассистента"));
Serial.println(F("================================"));

// ---------------------------------------------------
// I2C
// ---------------------------------------------------

Wire.begin(8, 9);

bmp_available = bmp.begin(0x77);
if (bmp_available) {
Serial.println(F("BMP280 найден"));
} else {
Serial.println(F("BMP280 не найден"));
}

aht_available = aht.begin();
if (aht_available) {
Serial.println(F("AHT20 найден"));
} else {
Serial.println(F("AHT20 не найден"));
}

// ---------------------------------------------------
// SD
// ---------------------------------------------------

SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
delay(50);

if (!SD.begin(SD_CS, SPI, SD_FREQUENCY)) {
Serial.println(F("SD mount failed"));
while (true) {
delay(1000);
}
}

Serial.println(F("SD mounted"));

// Проверка WAV-файлов
if (SD.exists("/voice/hello.wav")) {
Serial.println(F("hello.wav найден"));
} else {
Serial.println(F("WARNING: hello.wav не найден!"));
}

delay(100);

// ---------------------------------------------------
// GPS
// ---------------------------------------------------

gpsSerial.begin(9600, SERIAL_8N1, GPS_RX_PIN, -1);

// ---------------------------------------------------
// Микрофон
// ---------------------------------------------------

if (!setup_i2s_microphone()) {
Serial.println(F("Микрофон не запустился"));
while (true) {
delay(1000);
}
}

Serial.println(F("ESP32-S3 + INMP441 + Edge Impulse"));
Serial.printf("Частота модели: %d Hz\n", EI_CLASSIFIER_FREQUENCY);
Serial.printf("Сэмплов в slice: %d\n", EI_CLASSIFIER_SLICE_SIZE);
Serial.printf("Длительность slice: %.3f сек\n",
static_cast(EI_CLASSIFIER_SLICE_SIZE) / EI_CLASSIFIER_FREQUENCY);

// ---------------------------------------------------
// MAX98357A
// ---------------------------------------------------

if (!setup_i2s_speaker()) {
Serial.println(F("MAX98357A не запустился"));
while (true) {
delay(1000);
}
}

Serial.println(F("MAX98357A готов"));

// ---------------------------------------------------
// Edge Impulse
// ---------------------------------------------------

run_classifier_init();

Serial.println();
Serial.println(F("Микрофон слушает непрерывно"));
Serial.println(F("Скажи: KANT"));

// Тестовое воспроизведение
play_wav_blocking("/voice/hello.wav");
}

// =====================================================
// LOOP
// =====================================================

void loop() {
// ---------------------------------------------------
// GPS
// ---------------------------------------------------

while (gpsSerial.available()) {
gps.encode(gpsSerial.read());
}

// ---------------------------------------------------
// Тайм-аут команды
// ---------------------------------------------------

if (assistant_state == WAIT_COMMAND &&
millis() - command_wait_started > COMMAND_TIMEOUT_MS) {
Serial.println(F("Время ожидания команды закончилось"));
return_to_wait_kant();
}

// ---------------------------------------------------
// Микрофон
// ---------------------------------------------------

read_microphone_nonblocking();

if (!microphone_slice_ready()) {
delay(1);
return;
}

uint32_t audio_level = get_audio_level();

my_signal.total_length = EI_CLASSIFIER_SLICE_SIZE;
my_signal.get_data = get_signal_data;

EI_IMPULSE_ERROR error = run_classifier_continuous(&my_signal, &result, false);

reset_microphone_slice();

if (error != EI_IMPULSE_OK) {
Serial.printf("Ошибка Edge Impulse: %d\n", error);
return;
}

if (audio_level < MIN_AUDIO_LEVEL) {
reset_all_confirmations();
return;
}

process_result(&result);
}

вот может получилось

Не полагайтесь на I2S_NUM_AUTO. Укажите явно:

  • Микрофон: I2S_NUM_0 (порт 0)

  • Динамик: I2S_NUM_1 (порт 1)

не помогло

есть еще версия или поставить версию ядра esp32 <3 или наоборот 3.2.0-RC2 если там реально баг исправлен

@Волшебник , вы вообще насколько этот код понимаете?
В этом коде при инициализации I2S портов микрофона и спикера предусмотрен вывод кучи сообщений об ошибках. Вы в момент запуска программы консоль подключаете? Читаете, что там пишет? Он может вам сообщает, в чем проблема, а вы и не знаете…

И на время отладки я бы добавил в каждом таком месте вывод сообщения не только в случае ошибок, но и короткие отчеты когда процесс идет успешно, например:

Да там и писать нечего. Я вообще перестал уже в любой код проверки добавлять, нафиг они не нужны. В консоли всегда пишет, что мол, инициализировалось все и все работает.

вы все врети, вы пишите код через ии, как и я,(я вижу его почерк!))) ) кстате я, волшебник настоящий!))) но творю волшебство только за дорого…

в любом случае кроме установки ядер,(потому что в версии 3+ все может быть поломано, и большинство функций на автомате ) и без скидывания кода отладки, для нас… подсказать кажется нечего… мне по крайней мере))) советы данные ранее может помогут…