Записываем звук в формате 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 (11 Загрузок)
Насчет железа. Дополнил проект “ESP32 — проигрывание mp3 файлов с SD через декодер PCM5102A”
https://conntest.ru/program/esp32-proigryvanie-mp3-fajlov-s-sd-cherez-dekoder-pcm5102a
модулем микрофона INMP441 и еще одной кнопкой, которая “начинает запись/останавливает запись”.
Общий вид средств работы со звуком, на текущий момент.
Подключение кнопки управления записью: один контакт на GND, другой на P4.
Таблица подключения модулей к ESP32-WROOM (обозначения контактов ESP32 относятся к переходной плате). Расширенный вариант, PCM5102A к данному скетчу не относится.
Таблицу соединений можно легко посмотреть здесь:
https://conntest.ru/program/peredacha-zvuka-s-inmp441-na-dekoder-pcm5102a-s-pomoshhyu-esp32
Микрофон INMP441 с интерфейсом I2S – моно (контакт L/ R подключен к GND). Ввел режим автоусиления, в котором не совсем уверен.
Алгоритм такой:
-
после сброса определяется наличие карты и ожидается нажатия кнопки;
-
после нажатия начинается чтение с микрофона;
-
после следующего нажатия чтение останавливается и происходит запись на карту. Функция generateFilename выбирает первое свободное имя по шаблону «/record_%03d.wav».
Процесс работы выводится в Монитор порта Arduino IDE.
Для начала хватит. Не учитывал возможность длительной записи. Поэкспериментирую с этим вариантом на практике.
Скетч:
#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);
}```