ESP32 — проигрывание mp3 файлов с SD через декодер PCM5102A

     Целью было, как упомянуто в заголовке, последовательно проигрывать аудиофайлы в формате mp3, записанные на SD карту, через плату декодера интерфейса I2S PCM5102A с помощью ESP32-WROOM.

     Про железо.

     Плата декодера интерфейса I2S PCM5102A

     Взято из https://labkit.ru/html/radio_shm?id=507

На модуле PCM5102A могут быть или могут отсутствовать перемычки. Перемычки нужны!
Перемычки, смонтированные в поставке от продавца, смотрятся убого.
Если перемычки отсутствуют или не нравятся, напаиваем капельки припоя.
Перемычка H1L в положение L.
Перемычка H2L в положение L.
Перемычка H3L в положение H.
Перемычка H4L в положение L.

    Модуль адаптер карты Micro SD

    Estardyn ESP32 38pin

     Еще раз про соединения. Обозначения контактов ESP32 относятся к переходной плате.

КнопкиSDPCM5102AESP32
RSTEN
BOOTP0
GNDGND
VCC5V
MISOP19
MOSIP23
SCKP18
CS (SS)P5
VIN5V
GNDGND
LRCK (LCK)P25
DINP22
BCKP26
SCKGND

    Как видно из скетча, нет явных назначений пинов, все назначения пинов используются по умолчанию. Подключение 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.

Добавленная библиотека: 

https://github.com/earlephilhower/ESP8266Audio.git

     Пример скетча:

#include «FS.h»
#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); // 100 миллисекунд пауза
      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 (13 Загрузок)

     Хотя вывод в монитор порта интересен только для отладки (а, может быть, и не только), но, с другой стороны, выявил проблему, правда, не совсем соотносящуюся с задачей. В Монитор порта 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 файлов проигрывалось гораздо реже (один раз).

     Скачать альбом OWT:

OWT_favourites_1.rar - папка с песнями (102 Загрузки)

    Проверил вывод не через встроенный в ESP32 COM порт, а через электронный пульт-имитатор, в котором мост USB-UART обеспечивается микросхемой FT232RL. Никаких кракозябр. Оставил первые 18 файлов, а остальные переместил в папку END на этой же SD и получил корректный результат.

Электронный пульт-имитатор Управление матричной клавиатурой и светодиодами

     При подключении через пульт, заново запаял делитель для RX и, не проверив, подключил. Получились те же 5В, только через резистор 2,4 кОм. Вывод в монитор удался, а вот запись скетча — увы. Сильно обрадовало, что все работает по встроенному COM порту. Третий раз проигрываю 18 файлов — кракозябр пока не видно, завершение по плану. Питание и связь по встроенному COM порту. Может быть этому способствовало вырубание RX на внешнем контакте, или, его нужно было просто подтянуть к 3,3 В через резистор. Не понятно, как будто ESP32 думает, и, довольно глупо. Не будем уподобляться Нине Леонидовне, которая говорила: — Я все правильно делала, а IBM ошибалась.

    Все это получилось с SD 4 GB, а с 32 GB – сбой, не видит карту. Причину пока не установил. Кажется, установил. Видимо, тот же CS, но тогда не додумался передернуть питание. Сейчас гоняет 103 файла на 32 GB, утром посмотрим. Утра ждать не пришлось, проиграл 16 файлов и, как обычно. Можно, конечно, сделать JOIN, но, лучше подумать. Ведь, почти все работает, правда, библиотеки чужие. И не от Microsoft, Borland или IAR. Хотя, и на том спасибо. Запустил снова, отыграл до конца, без кракозябр. Добавил слитый файл ( JOIN всех 103-х), переименовал на SD с 0000_ в начале, но почему-то в перечислении он оказался последним. Полезли кракозябры, кстати, они вылезают и на Serial.println(«xx»), не важно. Время проигрывания немного больше 6 часов. Еще 6 часов, если дойдет до слитого, но его проигрывать, вроде бы, не имеет смысла. На сей раз не прошло, на 26-м сломался. ХЗЧ. Нет, граждане, имею заметить, что уже несколько часов играет слитый. Добавлю о конце. Проиграл безупречно, все. Если есть вопросы, то не ко мне, а к ESP32, видимо, он сам решает.

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

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