ESP32 — проигрывание mp3 файлов с SD через декодер PCM5102A
Целью было, как упомянуто в заголовке, последовательно проигрывать аудиофайлы в формате mp3, записанные на SD карту, через плату декодера интерфейса I2S PCM5102A с помощью ESP32-WROOM.
Про железо.
Плата декодера интерфейса I2S PCM5102A
Взято из LabKit :: WiFi интернет-радиоприёмник «Волчонок» для начинающих
На модуле PCM5102A могут быть или могут отсутствовать перемычки. Перемычки нужны!
Перемычки, смонтированные в поставке от продавца, смотрятся убого.
Если перемычки отсутствуют или не нравятся, напаиваем капельки припоя.
Перемычка H1L в положение L.
Перемычка H2L в положение L.
Перемычка H3L в положение H.
Перемычка H4L в положение L.
Модуль адаптер карты Micro SD
Estardyn ESP32 38pin
Еще раз про соединения. Обозначения контактов ESP32 относятся к переходной плате.
Таблица соединений на https://conntest.ru/program/esp32-proigryvanie-mp3-fajlov-s-sd-cherez-dekoder-pcm5102a
Как видно из скетча, нет явных назначений пинов, все назначения пинов используются по умолчанию. Подключение SD и декодера можно определить из следующих картинок и функций, которые в данном скетче не используются, за ненадобностью.
source = new AudioFileSourceSD();
SPI.begin(18, 19, 23, 5);
А, именно, sck – P18, miso – P19, mosi – P23, ss (CS) – P5.
out = new AudioOutputI2S();
out->SetPinout(26, 25, 22);
А, именно, bclkPin (BCK) – P26, wclkPin (LRCK) – P25, doutPin (DIN) – P22, SCK – GND
Дополнительно использовал для удобства загрузки скетча выносные кнопки , побольше чем на самом ESP32. RST замыкает на GND пин EN, BOOT замыкает на GND пин P0.
Добавленная библиотека:
GitHub - earlephilhower/ESP8266Audio: Arduino library to play MOD, WAV, FLAC, MIDI, RTTTL, MP3, and AAC files on I2S DACs or with a software emulated delta-sigma DAC on the ESP8266 and ESP32 and Pico
Пример скетча:
#include «SD.h»
#include «SPI.h»
#include <WiFi.h>
#include «AudioFileSourceSD.h»
#include «AudioGeneratorMP3.h»
#include «AudioOutputI2S.h»
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceSD *source = nullptr;
AudioOutputI2S *out = nullptr;
std::vector<String> fileList;
size_t currentFileIndex = 0;
void listDir(fs::FS &fs, const char *dirname) {
Serial.printf(«Listing directory: %s», dirname);
Serial.println();
File root = fs.open(dirname);
if (!root || !root.isDirectory()) {
Serial.println(«Failed to open directory or not a directory»);
return;
}
File file = root.openNextFile();
while (file) {
if (!file.isDirectory()) {
fileList.push_back(file.path());
Serial.print(» FILE: «);
Serial.print(file.name());
Serial.print(» SIZE: «);
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void playNextFile() {
if (currentFileIndex < fileList.size()) {
Serial.println();
Serial.printf(«Playing: %s», fileList[currentFileIndex].c_str());
Serial.println();
Serial.printf(«Playing: %s», fileList[currentFileIndex].c_str());
Serial.println();
Serial.printf(«Current File Index: %d», currentFileIndex);
Serial.println();
Serial.printf(«Current File Index: %d», currentFileIndex);
Serial.println();
// Создаем новый источник и генератор
source = new AudioFileSourceSD(fileList[currentFileIndex].c_str());
out = new AudioOutputI2S();
out->SetGain(0.2);
mp3 = new AudioGeneratorMP3();
Serial.println(«04»);
delay(100); // 100 миллисекунд пауза
if (!mp3->begin(source, out)) {
Serial.println(«Failed to start MP3 generator. Attempting to clean up…»);
delete source;
delete out;
source = nullptr;
out = nullptr;
currentFileIndex++; // Перейти к следующему файлу даже если текущий не удалось воспроизвести
delay(1000); // 1000 миллисекунд пауза
playNextFile();
return;
}
currentFileIndex++;
} else {
Serial.println(«No more files to play.»);
delay(1000000);
}
}
void setup() {
WiFi.mode(WIFI_OFF);
Serial.begin(115200);
delay(1000);
if (!SD.begin()) {
Serial.println(«Card Mount Failed»);
return;
}
listDir(SD, «/»);
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println(«No SD card attached»);
return;
}
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();
playNextFile(); // Начинаем воспроизведение первого файла
}
void loop() {
if (mp3 && mp3->isRunning()) {
if (!mp3->loop()) {
mp3->stop();
}
} else {
if (mp3) {
mp3->stop();
delete mp3;
mp3 = nullptr;
//delay(100); // 100 миллисекунд пауза
Serial.println(«05»);
}
if (source) {
delete source;
source = nullptr;
Serial.println(«06»);
}
if (out) {
delete out;
out = nullptr;
Serial.println(«07»);
}
delay(1000);
playNextFile(); // Переходим к следующему файлу
}
Не знаю почему, иногда, после перезаписи этого скетча нужно передернуть питание для обнаружения SD. Кажется, дело в плохом контакте CS.
Скачать SD_read_MP3_write_PCM5202A_04.ino:
SD_read_MP3_write_PCM5202A_04.ino (15 Загрузок)
Хотя вывод в монитор порта интересен только для отладки (а, может быть, и не только), но, с другой стороны, выявил проблему, правда, не совсем соотносящуюся с задачей. В Монитор порта Arduino IDE при выводе Serial.printf(«Playing: %s», fileList[currentFileIndex].c_str()), порой, выводятся следующие кракозябры, затирая часть сообщения.
�����������������������������������������������������������������
Решил использовать дублирования этого вывода. В любом случае, второй вывод не затирался, так и оставил. Оставляю все отладочные Serial.println(«xx»). Так как позже добавил для отладки Serial.printf(«Current File Index: %d», currentFileIndex), то получал кракозябры и после них, поэтому добавил, как и для Serial.printf(«Playing: %s», fileList[currentFileIndex].c_str()) повтор.
Кстати, не помогли следующие ухищрения:
— delay(1000);
— if (Serial.available()) {
// Игнорируем входящие данные
while (Serial.available()) {
Serial.read();
}
— fileList.clear();
fileList.resize(0); // Очищает все элементы и освобождает память
Проверял на своем альбоме OWT (One Way Ticket), 103 песни. Проигрывались первые 18 или 80 файлов, для оставшихся, после Serial.printf(«Playing: %s», fileList[currentFileIndex].c_str()) и Serial.printf(«Current File Index: %d», currentFileIndex)
выводилось “Failed to start MP3 generator. Attempting to clean up…”, и так до “No more files to play.”. 80 файлов проигрывалось гораздо реже (один раз).