Перевод статьи ESP32 with INMP441 I2S Omnidirectional Microphone Module (Arduino IDE) — Random Nerd Tutorials.

В этом руководстве разберёмся, как работать с всенаправленным I2S-микрофоном INMP441 на ESP32 в Arduino IDE. Подключим модуль к плате и сделаем три примера: измерим интенсивность звука (RMS), выведем звуковую волну в Serial Plotter и соберём проект «хлопни — включится», в котором светодиод (реле или любой другой выход ESP32) управляется хлопками.

Знакомство с INMP441#

INMP441 — всенаправленный MEMS-микрофон (MEMS — микроэлектромеханическая система). Он улавливает звук и выдаёт 24-битные цифровые аудиоданные по интерфейсу I2S — специальному протоколу для передачи звука.

Модуль микрофона INMP441, вид сверху с подписями выводов

Модуль INMP441 и его выводы

Микрофон даёт чистый звук при низком энергопотреблении и хорошем соотношении сигнал/шум. Это заметно лучший выбор, чем аналоговые микрофоны: преобразование звука в отфильтрованный цифровой сигнал происходит прямо в модуле.

Штыревые выводы нужно припаять так, как показано на фото. Если модуль стоит на макетке, маленькое отверстие микрофона должно смотреть на вас.

Основные особенности INMP441:

  • всенаправленность — ловит звук со всех сторон, так что точно позиционировать его не нужно;
  • выход I2S — цифровое аудио сразу по интерфейсу I2S;
  • хорошее качество сигнала и низкий уровень шума;
  • подходит для встраиваемых проектов — маленький размер, низкое потребление и цифровой интерфейс делают его удачным вариантом для IoT, особенно в паре с ESP32, которая умеет I2S.

Интерфейс I2S#

INMP441 общается по I2S (Inter-IC Sound) — синхронному последовательному протоколу для передачи аудиоданных между двумя цифровыми аудиоустройствами.

В ESP32 есть периферийный блок I2S с драйвером. Его можно настроить как вход — для чтения звука, или как выход — для воспроизведения и передачи звука.

Подробности про I2S на ESP32 есть в официальной документации ESP-IDF: Inter-IC Sound (I2S).

Выводы INMP441#

Модуль подключается к ESP32 по I2S и имеет следующие выводы:

  • L/R — выбор левого или правого канала I2S (на GND — левый, на 3V3 — правый). Можно выбрать любой, главное — поправить код соответственно;
  • WS — Word Select (он же LRCLK), подключается к GPIO ESP32;
  • SCK — Serial Clock (он же BCLK), подключается к GPIO ESP32;
  • SD — Serial Data, собственно звук с микрофона, подключается к GPIO ESP32 (вход);
  • GND — к GND ESP32;
  • VDD — питание модуля, 3,3 В от ESP32.

Подключение INMP441 к ESP32#

В этом руководстве используется ESP32-S3 и следующие GPIO. Если у вас плата с другим чипом ESP32, поменяйте выводы под неё.

INMP441ESP32-S3
L/RGND
WSGPIO 40
SCKGPIO 42
SDGPIO 41
GNDGND
VDD3V3
Схема подключения микрофона INMP441 к ESP32-S3 на макетной плате

Подключение INMP441 к ESP32-S3

Жёстко заданных выводов I2S нет — подойдут любые безопасные для использования GPIO ESP32. Полезно почитать: распиновка ESP32-S3 DevKitC и какие выводы ESP32 можно использовать.

Подготовка Arduino IDE#

Перед началом убедитесь, что установлены Arduino IDE и пакет плат ESP32. Если ещё нет — вот инструкция по установке.

1. Интенсивность звука (RMS)#

В этом примере микрофон INMP441 и ESP32 измеряют интенсивность звука, а результат выводится в Serial Plotter в Arduino IDE.

Микрофон непрерывно захватывает звук и отправляет отсчёты в ESP32 по I2S. По этим отсчётам можно посчитать среднеквадратичное значение (RMS, Root Mean Square) — оно показывает силу звука на коротком отрезке времени.

RMS считается так:

  1. возводим каждый отсчёт в квадрат;
  2. находим среднее всех квадратов;
  3. извлекаем из результата квадратный корень.
RMS = √( (x₁² + x₂² + … + xₙ²) / n )

Чем громче звук, тем больше RMS.

Код#

Для получения звука по I2S используем драйвер ESP-IDF driver/i2s.h. Он по умолчанию входит в ядро ESP32 для Arduino, так что сторонние библиотеки не нужны.

Скопируйте код в Arduino IDE:

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Подробности проекта: https://RandomNerdTutorials.com/esp32-inmp441-i2s-microphone-arduino/
  Настоящим любому лицу, получившему копию этого ПО и сопутствующей документации, бесплатно предоставляется разрешение на их использование.
  Приведённое выше уведомление об авторских правах и данное разрешение должны быть включены во все копии или существенные части ПО.
*/
#include <driver/i2s.h>

// Настройка выводов
#define I2S_SCK   42   // BCLK
#define I2S_WS    40   // LRCLK / WS
#define I2S_SD    41   // DOUT с микрофона

#define I2S_PORT  I2S_NUM_0
#define SAMPLE_RATE 16000
#define BUFFER_LEN  512

int32_t sBuffer[BUFFER_LEN];

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

  Serial.println("INMP441 Microphone");

  // Настройка I2S
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,   // L/R = GND
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = BUFFER_LEN,
    .use_apll = false
  };

  // Настройка выводов
  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_SD
  };

  // Запуск I2S
  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin_config);
  i2s_zero_dma_buffer(I2S_PORT);

  Serial.println("I2S started.");
  Serial.println("Reading microphone...");
}

void loop() {
  size_t bytes_read = 0;

  // Читаем данные с микрофона
  i2s_read(I2S_PORT, sBuffer, sizeof(sBuffer), &bytes_read, portMAX_DELAY);

  int samples_read = bytes_read / sizeof(int32_t);
  double sum = 0;

  // Перебираем отсчёты
  for (int i = 0; i < samples_read; i++) {

    // INMP441 передаёт 24-битный звук, выровненный по левому краю 32-битного слова I2S
    int32_t sample = sBuffer[i] >> 8;
    //Serial.println(sample);
    sum += (double)sample * sample;
  }
    // Считаем RMS
    double rms = sqrt(sum / samples_read);

    // Выводим RMS
    //Serial.print("RMS: ");
    Serial.println(rms);
}

Как работает код#

Сначала подключаем драйвер I2S:

#include <driver/i2s.h>

Выводы I2S. Задаём выводы, к которым подключён микрофон. Если у вас другие — поправьте эти строки:

// Настройка выводов
#define I2S_SCK   42    // BCLK
#define I2S_WS    40    // LRCLK / WS
#define I2S_SD    41    // DOUT с микрофона

Порт и частота дискретизации. Выбираем периферийный блок I2S номер 0 и задаём частоту дискретизации — сколько отсчётов захватывается в секунду. 16 000 — типичное значение для голоса и звуковых приложений.

#define I2S_PORT  I2S_NUM_0
#define SAMPLE_RATE 16000

Размер буфера. Задаём, сколько отсчётов будет участвовать в расчёте RMS, и создаём сам буфер sBuffer:

#define BUFFER_LEN  512

int32_t sBuffer[BUFFER_LEN];

Обратите внимание: буфер рассчитан на 32-битные отсчёты. I2S передаёт данные слотами фиксированной длины, и для INMP441 практичнее всего 32-битный слот, хотя разрешение микрофона — 24 бита. Иными словами, 24 бита данных INMP441 лежат внутри 32-битного слова I2S, и нужные биты потом приходится извлекать.

setup(). Инициализируем последовательный порт на скорости 115200:

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

  Serial.println("INMP441 Microphone");

Структуры настройки I2S. Теперь можно настроить периферию I2S. Создаём структуру типа i2s_config_t с именем i2s_config — в ней частота дискретизации, разрядность отсчёта, канал (левый или правый) и прочие параметры.

// Настройка I2S
i2s_config_t i2s_config = {
  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
  .sample_rate = SAMPLE_RATE,
  .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
  .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,   // L/R = GND
  .communication_format = I2S_COMM_FORMAT_STAND_I2S,
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
  .dma_buf_count = 8,
  .dma_buf_len = BUFFER_LEN,
  .use_apll = false
};

Другие возможные значения параметров есть в документации ESP-IDF по I2S.

Затем создаём ещё одну структуру, i2s_pin_config_t, с используемыми выводами I2S:

// Настройка выводов
i2s_pin_config_t pin_config = {
  .bck_io_num = I2S_SCK,
  .ws_io_num = I2S_WS,
  .data_out_num = I2S_PIN_NO_CHANGE,
  .data_in_num = I2S_SD
};

И наконец запускаем I2S, передав функциям созданные структуры:

i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
i2s_set_pin(I2S_PORT, &pin_config);
i2s_zero_dma_buffer(I2S_PORT);

loop(). В цикле читаем данные с микрофона и считаем RMS. Функция i2s_read() читает данные с INMP441 и складывает их в sBuffer. После каждого успешного чтения в буфере лежат 512 отсчётов.

i2s_read(I2S_PORT, sBuffer, sizeof(sBuffer), &bytes_read, portMAX_DELAY);

Затем переводим bytes_read в samples_read, поделив число принятых байт на размер одного отсчёта. Так мы точно знаем, сколько отсчётов в буфере.

int samples_read = bytes_read / sizeof(int32_t);

Следующие строки возводят каждый отсчёт в квадрат и складывают квадраты:

// Перебираем отсчёты
for (int i = 0; i < samples_read; i++) {

  // INMP441 передаёт 24-битный звук, выровненный по левому краю 32-битного слова I2S
  int32_t sample = sBuffer[i] >> 8;
  //Serial.println(sample);
  sum += (double)sample * sample;
}

Потом делим сумму на число отсчётов и извлекаем корень:

// Считаем RMS
double rms = sqrt(sum / samples_read);

Обратите внимание на строку ниже. 24-битные данные микрофона лежат внутри 32-битного значения, и сдвиг >> 8 смещает его на 8 бит вправо, правильно выравнивая звук перед расчётом RMS.

int32_t sample = sBuffer[i] >> 8;

В конце выводим RMS. Печатаем только это число, без подписей, чтобы результат было удобно смотреть в Serial Plotter.

Serial.println(rms);

Проверка#

Загрузите код в ESP32, не забыв выбрать правильную плату и COM-порт. В оригинале используется ESP32-S3.

После загрузки откройте Serial Plotter — его значок находится рядом со значком Serial Monitor. В новом окне появится график RMS во времени. Хлопайте, говорите, издавайте то громкие, то тихие звуки — значение будет меняться.

График RMS с микрофона INMP441 в Serial Plotter

RMS в Serial Plotter: первый пик — хлопок, второй — щелчок пальцами

Посмотрите, какие значения RMS дают звуки разной громкости, и выберите по ним порог. Дальше можно сделать проект, который выполняет какое-то действие, когда RMS превышает этот порог.

2. Отображение звуковой волны#

В этом примере читаем аудиоданные и выводим форму волны в Serial Plotter. Всё просто: получаем данные по I2S и печатаем их в последовательный порт.

Скопируйте код в Arduino IDE:

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Подробности проекта: https://RandomNerdTutorials.com/esp32-inmp441-i2s-microphone-arduino/
  Настоящим любому лицу, получившему копию этого ПО и сопутствующей документации, бесплатно предоставляется разрешение на их использование.
  Приведённое выше уведомление об авторских правах и данное разрешение должны быть включены во все копии или существенные части ПО.
*/
#include <driver/i2s.h>

// Настройка выводов
#define I2S_SCK   42   // BCLK
#define I2S_WS    40   // LRCLK / WS
#define I2S_SD    41   // DOUT с микрофона

#define I2S_PORT  I2S_NUM_0
#define SAMPLE_RATE 16000
#define BUFFER_LEN  512

int32_t sBuffer[BUFFER_LEN];

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

  Serial.println("INMP441 Microphone");

  // Настройка I2S
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,   // L/R = GND
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = BUFFER_LEN,
    .use_apll = false
  };

  // Настройка выводов
  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_SD
  };

  // Запуск I2S
  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin_config);
  i2s_zero_dma_buffer(I2S_PORT);

  Serial.println("I2S started.");
  Serial.println("Reading microphone...");
}

void loop() {
  size_t bytes_read = 0;

  // Читаем данные с микрофона
  i2s_read(I2S_PORT, sBuffer, sizeof(sBuffer), &bytes_read, portMAX_DELAY);

  int samples_read = bytes_read / sizeof(int32_t);

  // Выводим отсчёты
  for (int i = 0; i < samples_read; i++) {

    // INMP441 передаёт 24-битный звук, выровненный по левому краю 32-битного слова I2S
    int32_t sample = sBuffer[i] >> 8;

    Serial.println(sample);
  }
}

Как работает код#

Код работает так же, как предыдущий, только не считает RMS. Всё остальное без изменений.

Проходим по всем отсчётам в буфере и печатаем их. Каждый отсчёт — это амплитуда звукового сигнала в конкретный момент времени.

// Выводим отсчёты
for (int i = 0; i < samples_read; i++) {

  // INMP441 передаёт 24-битный звук, выровненный по левому краю 32-битного слова I2S
  int32_t sample = sBuffer[i] >> 8;

  Serial.println(sample);
}

Проверка#

Загрузите код в ESP32 и откройте Serial Plotter. Теперь видна амплитуда звуковой волны в каждый момент. Попробуйте тянуть разные гласные — «аааа», «ээээ», «ииии», «оооо», «уууу» — и посмотрите, как меняется волна.

Формы звуковой волны для разных гласных, записанные с INMP441

Разные гласные дают разные сочетания частот, а значит, и разную форму волны

Serial Plotter — не лучший инструмент, чтобы разглядывать форму волны. Можно написать программу на Python с Matplotlib: там удобно менять масштаб и сохранять снимок графика.

Дополнительно: волна в Matplotlib#

  1. Установите Python (при установке отметьте пункт Add Python to PATH).
  2. Откройте терминал или командную строку и установите нужные библиотеки:
pip install pyserial matplotlib
  1. Убедитесь, что на ESP32 работает скетч из этого раздела. Посмотрите в Arduino IDE номер последовательного порта (например, COM5) и закройте Arduino IDE — иначе порт будет занят.
  2. Создайте новый файл Python — подойдёт даже «Блокнот», главное сохранить с расширением .py.
  3. Скопируйте в него код ниже (файл в репозитории авторов), укажите свой COM-порт и сохраните, например, на рабочий стол под именем INMP441-microphone.py.
# Подробности проекта: https://RandomNerdTutorials.com/esp32-inmp441-i2s-microphone-arduino/
import serial
import matplotlib.pyplot as plt

PORT = "COM6"
BAUD_RATE = 115200
NUM_SAMPLES = 512

ser = serial.Serial(PORT, BAUD_RATE, timeout=1)

print("Reading samples from ESP32...")
print("Close the plot window to stop.")

plt.ion()
fig, ax = plt.subplots()

while True:
    samples = []

    while len(samples) < NUM_SAMPLES:
        line = ser.readline().decode("utf-8", errors="ignore").strip()

        if line:
            try:
                samples.append(int(line))
            except ValueError:
                pass

    ax.clear()
    ax.plot(samples)

    ax.set_title("INMP441 Audio Waveform")
    ax.set_xlabel("Sample")
    ax.set_ylabel("Amplitude")
    ax.grid(True)

    plt.pause(0.001)

ser.close()
  1. В терминале перейдите в папку с файлом и запустите программу:
cd Desktop
python INMP441-microphone.py

Откроется окно Matplotlib с графиком звуковой волны.

Звуковая волна с INMP441 в окне Matplotlib

Та же волна в Matplotlib

3. Управление выходом ESP32 хлопками#

С тем, что мы уже знаем, несложно собрать простой проект, в котором выходы ESP32 управляются хлопками:

  • управляем одним GPIO ESP32 — для наглядности светодиодом. Вместо него можно поставить реле и управлять, например, лампой или жалюзи;
  • два хлопка инвертируют текущее состояние светодиода.
ESP32 с микрофоном INMP441 и светодиодом, управляемым хлопками

Светодиод включается и выключается двойным хлопком

При хлопке амплитуда звуковой волны резко растёт, и появляется пик, который хорошо выделяется на фоне окружающего шума. Если за короткое время мы видим два пика выше заданного порога, считаем, что был двойной хлопок, и инвертируем состояние светодиода.

График амплитуды с двумя пиками от двойного хлопка

Двойной хлопок — два пика выше порога

Важно: на самом деле проект не распознаёт именно хлопок. Он ловит два последовательных пика звуковой волны выше порога в пределах заданного интервала. Так что сработает и щелчок пальцами, и стук и т. п.

Перед началом подключите к ESP32 светодиод. В оригинале он висит на GPIO 38 (плата ESP32-S3 DevKitC). Для другой платы выберите другой GPIO.

Что понадобится:

  • плата ESP32-S3;
  • модуль микрофона INMP441;
  • резистор 220 Ом (или близкого номинала);
  • светодиод;
  • макетная плата;
  • соединительные провода.
Схема подключения INMP441 и светодиода к ESP32-S3

Схема с микрофоном и светодиодом

Собрав схему, скопируйте код:

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Подробности проекта: https://RandomNerdTutorials.com/esp32-inmp441-i2s-microphone-arduino/
  Настоящим любому лицу, получившему копию этого ПО и сопутствующей документации, бесплатно предоставляется разрешение на их использование.
  Приведённое выше уведомление об авторских правах и данное разрешение должны быть включены во все копии или существенные части ПО.
*/
#include <driver/i2s.h>

// Настройка выводов
#define I2S_SCK   42   // BCLK
#define I2S_WS    40   // LRCLK / WS
#define I2S_SD    41   // DOUT с микрофона

#define I2S_PORT  I2S_NUM_0
#define SAMPLE_RATE 16000
#define BUFFER_LEN  512

int32_t sBuffer[BUFFER_LEN];

// Светодиод
#define LED_PIN 38

// Детекция хлопка
#define CLAP_THRESHOLD 1000000

// Максимальное время между первым и вторым хлопком
const unsigned long CLAP_GAP = 700;

// Не даём одному и тому же хлопку засчитаться несколько раз
const unsigned long CLAP_COOLDOWN = 250;

// Переменные состояния
bool ledState = false;

int clapCount = 0;

unsigned long firstClapTime = 0;
unsigned long lastClapTime = 0;

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

  Serial.println("INMP441 Microphone");

  // Настройка светодиода
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // Настройка I2S
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,   // L/R = GND
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = BUFFER_LEN,
    .use_apll = false
  };

  // Настройка выводов
  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_SD
  };

  // Запуск I2S
  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin_config);
  i2s_zero_dma_buffer(I2S_PORT);

  Serial.println("I2S started.");
  Serial.println("Double clap to control LED");
}

void loop() {
  size_t bytes_read = 0;

  // Читаем данные с микрофона
  i2s_read(I2S_PORT, sBuffer, sizeof(sBuffer), &bytes_read, portMAX_DELAY);

  int samples_read = bytes_read / sizeof(int32_t);

  // Ищем самый громкий отсчёт
  int32_t peak = 0;

  // Перебираем отсчёты
  for (int i = 0; i < samples_read; i++) {
    int32_t sample = sBuffer[i] >> 8;
    int32_t magnitude = abs(sample);

    if (magnitude > peak) {
      peak = magnitude;
    }
  }

  // Выводим пик
  Serial.println(peak);

  // Детекция хлопка
  unsigned long now = millis();

  if (peak > CLAP_THRESHOLD && now - lastClapTime > CLAP_COOLDOWN) {
    lastClapTime = now;

    // Первый хлопок
    if (clapCount == 0) {
      clapCount = 1;
      firstClapTime = now;
      Serial.println("First clap detected");
    }

    // Второй хлопок
    else if (now - firstClapTime <= CLAP_GAP) {
      clapCount = 0;

      // Двойной хлопок — инвертируем состояние светодиода
      ledState = !ledState;
      digitalWrite(LED_PIN, ledState);
      if (ledState) {
        Serial.println("DOUBLE CLAP - LED ON");
      }
      else {
        Serial.println("DOUBLE CLAP - LED OFF");
      }
    }

    // Прошло слишком много времени
    else {

      // Считаем этот хлопок началом новой пары
      clapCount = 1;
      firstClapTime = now;
      Serial.println("New first clap");
    }
  }

  // Сброс, если второй хлопок не пришёл вовремя
  if (clapCount == 1 && now - firstClapTime > CLAP_GAP) {
    clapCount = 0;
  }
}

Как работает код#

Инициализация I2S и получение звука устроены так же, как в предыдущих примерах.

Управляем GPIO 38 — при необходимости поменяйте номер:

// Светодиод
#define LED_PIN 38

Нужно задать порог хлопка — амплитуду, выше которой считаем, что был хлопок. Он должен заметно превышать амплитуду фонового шума; возможно, значение придётся подобрать под ваши условия.

// Детекция хлопка
#define CLAP_THRESHOLD 1000000

Двойным хлопком считаем два пика выше порога, между которыми прошло не больше 700 мс:

const unsigned long CLAP_GAP = 700;

Пики, пришедшие в течение 250 мс после предыдущего, игнорируем — так один и тот же хлопок не засчитается несколько раз:

const unsigned long CLAP_COOLDOWN = 250;

Запоминаем время первого и последнего хлопка — по ним определяем двойной хлопок. Время хранится в переменных firstClapTime и lastClapTime, а номер хлопка (первый или второй) — в clapCount.

int clapCount = 0;

unsigned long firstClapTime = 0;
unsigned long lastClapTime = 0;

В loop() непрерывно получаем аудиоданные в буфер sBuffer:

// Читаем данные с микрофона
i2s_read(I2S_PORT, sBuffer, sizeof(sBuffer), &bytes_read, portMAX_DELAY);

int samples_read = bytes_read / sizeof(int32_t);

В каждом полученном буфере проверяем модуль (амплитуду) каждого отсчёта и находим пик:

// Ищем самый громкий отсчёт
int32_t peak = 0;

// Перебираем отсчёты
for (int i = 0; i < samples_read; i++) {
  int32_t sample = sBuffer[i] >> 8;
  int32_t magnitude = abs(sample);

  if (magnitude > peak) {
    peak = magnitude;
  }
}

Запоминаем текущее время и проверяем условия хлопка: пик должен быть выше порога, а с предыдущего пика должно пройти больше времени, чем CLAP_COOLDOWN.

// Детекция хлопка
unsigned long now = millis();

if (peak > CLAP_THRESHOLD && now - lastClapTime > CLAP_COOLDOWN) {

Если условия выполнены, а первого хлопка ещё не было, считаем этот хлопок первым и запоминаем его время в firstClapTime:

// Первый хлопок
if (clapCount == 0) {
  clapCount = 1;
  firstClapTime = now;
  Serial.println("First clap detected");
}

Если же пришёл второй хлопок и интервал между ним и первым меньше CLAP_GAP, инвертируем состояние светодиода и сбрасываем clapCount в 0, чтобы искать следующий двойной хлопок. Если интервал оказался больше, этот хлопок считается первым в новой паре.

// Второй хлопок
else if (now - firstClapTime <= CLAP_GAP) {
  clapCount = 0;

  // Двойной хлопок — инвертируем состояние светодиода
  ledState = !ledState;
  digitalWrite(LED_PIN, ledState);
  if (ledState) {
    Serial.println("DOUBLE CLAP - LED ON");
  }
  else {
    Serial.println("DOUBLE CLAP - LED OFF");
  }
}

// Прошло слишком много времени
else {

  // Считаем этот хлопок началом новой пары
  clapCount = 1;
  firstClapTime = now;
  Serial.println("New first clap");
}

Если второй хлопок так и не пришёл вовремя, сбрасываем clapCount в 0 и начинаем поиск заново:

// Сброс, если второй хлопок не пришёл вовремя
if (clapCount == 1 && now - firstClapTime > CLAP_GAP) {
  clapCount = 0;
}

Проверка#

Загрузите код в ESP32, проверив плату и COM-порт. Хлопните дважды — светодиод загорится. Ещё два хлопка — погаснет. Короткое видео с демонстрацией есть в оригинальной статье.

Если светодиод постоянно мигает, увеличьте порог CLAP_THRESHOLD:

#define CLAP_THRESHOLD 1000000

Если светодиод не реагирует на хлопки — уменьшите его.

Чтобы подобрать подходящее значение, смотрите на амплитуду, которую печатает строка:

Serial.println(peak);

Запишите значения фонового шума (когда вы не хлопаете) и значения при хлопке. Порог должен быть между ними — чуть ниже пика, который даёт хлопок.

Итоги#

Мы подключили микрофонный модуль INMP441 к ESP32 по I2S, настроили шину и научились получать по ней данные.

INMP441 непрерывно отдаёт аудиоданные, которые мы складываем в буфер. Дальше этот буфер можно использовать для отображения звуковой волны или для расчётов вроде RMS.

В звуковых проектах пригодится и усилитель вроде MAX98357 для вывода звука — он тоже работает по I2S. Авторы обещают вскоре выпустить руководство и по нему.