Запись звука с INMP441 на SD карту с помощью ESP32

     Записываем звук в формате WAV с микрофона I2S INMP441 на SD карту с помощью ESP32-WROOM. Скетч разрабатывался в среде Arduino IDE 2.3.5. Заменил на Arduino IDE 2.3.6, скетч компилируется, загружается и работает.

Скачать Arduino IDE 2.3.6

arduino-ide_2.3.6_Windows_64bit.exe (35 Загрузок)

     Насчет железа. Дополнил проект “ESP32 — проигрывание mp3 файлов с SD через декодер PCM5102A”

https://conntest.ru/program/esp32-proigryvanie-mp3-fajlov-s-sd-cherez-dekoder-pcm5102a

модулем микрофона INMP441 и еще одной кнопкой, которая “начинает запись/останавливает запись”.

     Общий вид средств работы со звуком, на текущий момент.

     Подключение кнопки управления записью: один контакт на GND, другой на P4.

     Таблица подключения модулей к ESP32-WROOM (обозначения контактов ESP32 относятся к переходной плате). Расширенный вариант, PCM5102A к данному скетчу не относится.

КнопкиSDPCM5102AINMP441ESP32
RSTEN
BOOTP0
GNDGND
VCC5V
MISOP19
MOSIP23
SCKP18
CS (SS)P5
VIN5V
GNDGND
LRCK (LCK)P25
DINP22
BCKP26
SCKGND
WSP14
SCKP32
SDP33
L/RGND
VDD3,3V
GNDGND

     Микрофон INMP441 с интерфейсом I2S – моно (контакт L/R подключен к GND). Ввел режим автоусиления, в котором не совсем уверен.

     Алгоритм такой:

1. после сброса определяется наличие карты и ожидается нажатия кнопки;

2. после нажатия начинается чтение с микрофона;

3. после следующего нажатия чтение останавливается и происходит запись на карту. Функция generateFilename выбирает первое свободное имя по шаблону «/record_%03d.wav».

     Процесс работы выводится в Монитор порта Arduino IDE.

     Для начала хватит. Не учитывал возможность длительной записи. Поэкспериментирую с этим вариантом на практике.

Скетч:

#include <driver/i2s.h>
#include <SD.h>
#include <SPI.h>

// INMP441 микрофон
#define I2S_WS     14
#define I2S_SD     33
#define I2S_SCK    32

#define I2S_PORT I2S_NUM_0

#define SD_CS     5      // Пин CS SD-карты (настраиваем по своей схеме)

#define BUFFER_LEN 512
int32_t i2sBuffer[BUFFER_LEN];     // 32-бит (с 24-битами звука)
int16_t wavBuffer[BUFFER_LEN];     // Конвертированный 16-битный WAV

File audioFile;
bool isRecording = false;
int bytesWrittenTotal = 0;
char filename[20];  // Буфер для имени файла

// === WAV-заголовок ===
void writeWavHeader(File file, int sampleRate, int bitsPerSample, int channels, int dataSize) {
  file.seek(0);
  file.write((const uint8_t *)"RIFF", 4);
  uint32_t chunkSize = 36 + dataSize;
  file.write((byte *)&chunkSize, 4);
  file.write((const uint8_t *)"WAVE", 4);

  file.write((const uint8_t *)"fmt ", 4);
  uint32_t subchunk1Size = 16;
  uint16_t audioFormat = 1;
  file.write((byte *)&subchunk1Size, 4);
  file.write((byte *)&audioFormat, 2);
  file.write((byte *)&channels, 2);
  file.write((byte *)&sampleRate, 4);
  uint32_t byteRate = sampleRate * channels * bitsPerSample / 8;
  uint16_t blockAlign = channels * bitsPerSample / 8;
  file.write((byte *)&byteRate, 4);
  file.write((byte *)&blockAlign, 2);
  file.write((byte *)&bitsPerSample, 2);

  file.write((const uint8_t *)"data", 4);
  file.write((byte *)&dataSize, 4);
}

// === I2S ===
void i2s_install() {
  const i2s_config_t i2s_config = {
    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S_MSB,
    .intr_alloc_flags = 0,
    .dma_buf_count = 8,
    .dma_buf_len = BUFFER_LEN,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };
  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
}

void i2s_setpin() {
  const i2s_pin_config_t pin_config = {
    .mck_io_num = -1,
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = -1,
    .data_in_num = I2S_SD,
  };
  i2s_set_pin(I2S_PORT, &pin_config);
}

// Генерация уникального имени файла
void generateFilename() {
  for (int i = 0; i < 1000; i++) {
    sprintf(filename, "/record_%03d.wav", i);
    if (!SD.exists(filename)) {
      Serial.print("Создан файл для записи: ");
      Serial.println(filename);
      return;
    }
  }
  Serial.println("Не удалось найти свободное имя файла!");
  while (true); // Стоп, если всё занято
}

float gain = 2.0;                      // Начальное усиление
const float targetLevel = 12000.0;     // Целевая амплитуда
const float maxGain = 10.0;
const float minGain = 0.1;
const float adaptRate = 0.005;         // Скорость адаптации

unsigned long lastButtonPress = 0;
bool buttonState = HIGH;
bool lastButtonState = HIGH;

void handleButton() {
  bool currentState = digitalRead(4);
  if (currentState != lastButtonState) {
    lastButtonState = currentState;
    if (currentState == LOW && millis() - lastButtonPress > 300) {
      lastButtonPress = millis();
      
      if (!isRecording) {
        // Начинаем запись
        generateFilename();
        audioFile = SD.open(filename, FILE_WRITE);
        if (audioFile) {
          writeWavHeader(audioFile, 16000, 16, 1, 0);
          isRecording = true;
          bytesWrittenTotal = 0;
          Serial.println("Запись начата");
        } else {
          Serial.println("Ошибка создания файла");
        }
      } else {
        // Останавливаем запись
        isRecording = false;
        writeWavHeader(audioFile, 16000, 16, 1, bytesWrittenTotal);
        audioFile.flush();
        audioFile.close();
        Serial.println("Запись остановлена. Файл сохранен.");
      }
    }
  }
}

// === SETUP ===
void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Инициализация...");

  // Инициализация I2S
  i2s_install();
  i2s_setpin();
  i2s_start(I2S_PORT);

  // Инициализация SD-карты
  if (!SD.begin(SD_CS)) {
    Serial.println("SD-карта не найдена");
    while (true);
  }
  Serial.println("SD-карта подключена.");

  // Настройка кнопки
  pinMode(4, INPUT_PULLUP);
  
  Serial.println("Готов к работе. Нажмите кнопку для начала записи.");
}

// === LOOP ===
void loop() {
  handleButton();
  
  if (!isRecording) return;

  size_t bytesRead = 0;
  i2s_read(I2S_PORT, (void *)i2sBuffer, sizeof(i2sBuffer), &bytesRead, portMAX_DELAY);
  int samples = bytesRead / sizeof(int32_t);

  float peak = 0;

  for (int i = 0; i < samples; i++) {
    int32_t sample24 = i2sBuffer[i] >> 9;
    float amplified = sample24 * gain;

    if (amplified > 32767.0f) amplified = 32767.0f;
    if (amplified < -32768.0f) amplified = -32768.0f;

    wavBuffer[i] = (int16_t)amplified;

    if (fabs(sample24) > peak) peak = fabs(sample24);
  }

  // Автоусиление
  if (peak > 0) {
    float targetGain = targetLevel / peak;
    gain += (targetGain - gain) * adaptRate;

    if (gain > maxGain) gain = maxGain;
    if (gain < minGain) gain = minGain;
  }

  audioFile.write((byte *)wavBuffer, samples * sizeof(int16_t));
  bytesWrittenTotal += samples * sizeof(int16_t);
}

Скачать INMP441_ESP32_SD.INO

INMP441_ESP32_SD.ino (18 Загрузок)

Изменил скетч:

1. удалил автоусиление;
2. добавил ФВЧ (пока не понял, улучшило это качества звука или нет);
3. ввел периодический сброс на SD (каждые 5 секунд, можно изменить) с  освобождением памяти ESP32;
4. добавил светодиод (подключен плюсом к 3,3 V через резистор 180 Ом,  минусом к P2) для индикации процесса записи, запись началась – горит, запись остановлена или ошибка – не горит.
5. добавил вывод параметров SD карты при запуске.

Новый скетч:

#include <driver/i2s.h>
#include <SD.h>
#include <SPI.h>
#include <algorithm> 
using std::max;

// INMP441 микрофон
#define I2S_WS     14
#define I2S_SD     33
#define I2S_SCK    32
#define I2S_PORT I2S_NUM_0

#define SD_CS     5      // Пин CS SD-карты
#define BUFFER_LEN 1024
#define FLUSH_INTERVAL 5000
#define LED_PIN   2      // Пин светодиода

int32_t i2sBuffer[BUFFER_LEN];     // 32-бит (с 24-битами звука)
int16_t wavBuffer[BUFFER_LEN];     // Конвертированный 16-битный WAV

File audioFile;
bool isRecording = false;
int bytesWrittenTotal = 0;
char filename[20];  // Буфер для имени файла
unsigned long lastFlushTime = 0;
bool sdError = false;

// Параметры фильтра высоких частот
float alpha = 0.98;  // Коэффициент фильтра
float hpFilterState = 0.0;  // Состояние фильтра
float hpFilterPrevInput = 0.0;  // Предыдущее входное значение

// Функция фильтра высоких частот
float highPassFilter(float input) {
    float output = alpha * (hpFilterState + input - hpFilterPrevInput);
    hpFilterState = output;
    hpFilterPrevInput = input;
    return output;
}

// === WAV-заголовок ===
void writeWavHeader(File file, int sampleRate, int bitsPerSample, int channels, int dataSize) {
  file.seek(0);
  file.write((const uint8_t *)"RIFF", 4);
  uint32_t chunkSize = 36 + dataSize;
  file.write((byte *)&chunkSize, 4);
  file.write((const uint8_t *)"WAVE", 4);

  file.write((const uint8_t *)"fmt ", 4);
  uint32_t subchunk1Size = 16;
  uint16_t audioFormat = 1;
  file.write((byte *)&subchunk1Size, 4);
  file.write((byte *)&audioFormat, 2);
  file.write((byte *)&channels, 2);
  file.write((byte *)&sampleRate, 4);
  uint32_t byteRate = sampleRate * channels * bitsPerSample / 8;
  uint16_t blockAlign = channels * bitsPerSample / 8;
  file.write((byte *)&byteRate, 4);
  file.write((byte *)&blockAlign, 2);
  file.write((byte *)&bitsPerSample, 2);

  file.write((const uint8_t *)"data", 4);
  file.write((byte *)&dataSize, 4);
}

// === I2S ===
void i2s_install() {
  const i2s_config_t i2s_config = {
    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S_MSB,
    .intr_alloc_flags = 0,
    .dma_buf_count = 8,
    .dma_buf_len = BUFFER_LEN,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };
  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
}

void i2s_setpin() {
  const i2s_pin_config_t pin_config = {
    .mck_io_num = -1,
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = -1,
    .data_in_num = I2S_SD,
  };
  i2s_set_pin(I2S_PORT, &pin_config);
}

void generateFilename() {
  for (int i = 0; i < 1000; i++) {
    sprintf(filename, "/record_%03d.wav", i);
    if (!SD.exists(filename)) {
      Serial.print("Создан файл для записи: ");
      Serial.println(filename);
      return;
    }
  }
  Serial.println("Не удалось найти свободное имя файла!");
  while (true); // Стоп, если всё занято
}

// Удалены переменные автоусиления
unsigned long lastButtonPress = 0;
bool buttonState = HIGH;
bool lastButtonState = HIGH;

void handleButton() {
  bool currentState = digitalRead(4);
  if (currentState != lastButtonState) {
    lastButtonState = currentState;
    if (currentState == LOW && millis() - lastButtonPress > 300) {
      lastButtonPress = millis();
      
      if (!isRecording) {
        // Начинаем запись
        generateFilename();
        audioFile = SD.open(filename, FILE_WRITE);
        if (audioFile) {
          writeWavHeader(audioFile, 16000, 16, 1, 0);
          isRecording = true;
          bytesWrittenTotal = 0;
          hpFilterState = 0.0;  // Сброс состояния фильтра
          digitalWrite(LED_PIN, LOW); // Включаем светодиод
          Serial.println("Запись начата");
        } else {
          Serial.println("Ошибка создания файла");
        }
      } else {
        // Останавливаем запись
        isRecording = false;
        writeWavHeader(audioFile, 16000, 16, 1, bytesWrittenTotal);
        audioFile.flush();
        audioFile.close();
        digitalWrite(LED_PIN, HIGH); // Выключаем светодиод
        Serial.println("Запись остановлена. Файл сохранен.");
      }
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Инициализация...");

  // Инициализация светодиода
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  // Инициализация I2S
  i2s_install();
  i2s_setpin();
  i2s_start(I2S_PORT);

  // Инициализация SD-карты
  if (!SD.begin(SD_CS)) {
    Serial.println("SD-карта не найдена");
    while (true);
  }
  Serial.println("SD-карта подключена.");

  // Настройка кнопки
  pinMode(4, INPUT_PULLUP);

  uint8_t cardType = SD.cardType();
  Serial.println();
  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }
  
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB", cardSize);

  Serial.println();
  Serial.println();
  Serial.printf("Total space: %lluMB", SD.totalBytes() / (1024 * 1024));
  Serial.println();
  Serial.printf("Used space: %lluMB", SD.usedBytes() / (1024 * 1024));
  Serial.println();
  Serial.println();
  
  Serial.println("Готов к работе. Нажмите кнопку для начала записи.");
}

void loop() {
  handleButton();
  
  if (!isRecording || sdError) return;

  // Чтение данных
  size_t bytesRead = 0;
  esp_err_t readResult = i2s_read(I2S_PORT, (void *)i2sBuffer, sizeof(i2sBuffer), &bytesRead, portMAX_DELAY);
  
  if (readResult != ESP_OK) {
    Serial.println("Ошибка чтения I2S");
    return;
  }

  int samples = bytesRead / sizeof(int32_t);

  // Конвертация и обработка
  for (int i = 0; i < samples; i++) {
    int32_t sample24 = i2sBuffer[i] >> 9;  // 24-бит в 32-бит
    
    // Применение фильтра высоких частот
    float filteredSample = highPassFilter(sample24);
    
    // Фиксированное усиление (ранее было автоусиление)
    float amplified = constrain(filteredSample * 2.0, -32768.0f, 32767.0f);
    wavBuffer[i] = (int16_t)amplified;
  }

  // Запись в файл
  size_t bytesWritten = audioFile.write((byte *)wavBuffer, samples * sizeof(int16_t));
  if (bytesWritten != samples * sizeof(int16_t)) {
    Serial.println("Ошибка записи на SD!");
    sdError = true;
    digitalWrite(LED_PIN, HIGH); // Выключаем светодиод при ошибке
    return;
  }
  
  bytesWrittenTotal += bytesWritten;

  // Периодический сброс буферов
  if (millis() - lastFlushTime > FLUSH_INTERVAL) {
    audioFile.flush();
    lastFlushTime = millis();
    Serial.println("Данные сброшены на SD");
  }
}

Скачать INMP441_ESP32_SD_WAV.INO

INMP441_ESP32_SD_WAV.INO (10 Загрузок)

     Изменил #define FLUSH_INTERVAL 3600000 (1 час). В приведенном коде не изменял.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *