Записываем звук в формате 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”
модулем микрофона INMP441 и еще одной кнопкой, которая “начинает запись/останавливает запись”.
Общий вид средств работы со звуком, на текущий момент.
Подключение кнопки управления записью: один контакт на GND, другой на P4.
Таблица подключения модулей к ESP32-WROOM (обозначения контактов ESP32 относятся к переходной плате). Расширенный вариант, PCM5102A к данному скетчу не относится.
Кнопки SD PCM5102A INMP441 ESP32 RST EN BOOT P0 GND GND VCC 5V MISO P19 MOSI P23 SCK P18 CS (SS) P5 VIN 5V GND GND LRCK (LCK) P25 DIN P22 BCK P26 SCK GND WS P14 SCK P32 SD P33 L/R GND VDD 3,3V GND GND
Микрофон 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 час). В приведенном коде не изменял.