Example program for Basic to play audio files from a microSD card.
#include <M5Unified.h>
#include <SPI.h>
#include <SD.h>
#define SD_SPI_CS_PIN 4
#define SD_SPI_SCK_PIN 18
#define SD_SPI_MISO_PIN 19
#define SD_SPI_MOSI_PIN 23
bool playWAVFromSD(const char* filename, uint32_t repeat = 1, int channel = -1, bool stop_current = true);
bool playWAVMemory(File& wavFile, size_t fileSize, uint32_t repeat, int channel, bool stop_current);
bool playWAVSegmented(const char* filename, uint32_t repeat, int channel, bool stop_current);
void printf_log(const char *format, ...);
void println_log(const char *str);
void setup() {
M5.begin();
Serial.begin(115200);
M5.Display.fillRect(0, 0, 320, 240, WHITE);
M5.Display.setTextColor(BLACK);
M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
M5.Display.setCursor(0, 0);
// SD Card Initialization
SPI.begin(SD_SPI_SCK_PIN, SD_SPI_MISO_PIN, SD_SPI_MOSI_PIN, SD_SPI_CS_PIN);
if (!SD.begin(SD_SPI_CS_PIN, SPI, 25000000)) {
println_log("Card failed, or not present");
while (1) delay(1000);
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
printf_log("SD Card Size: %lluMB\n", cardSize);
println_log("Starting audio playback...");
playWAVFromSD("/sample-6s.wav", 1, -1, true);
}
void loop() {
}
bool playWAVFromSD(const char* filename, uint32_t repeat, int channel, bool stop_current) {
if (!SD.exists(filename)) {
println_log("File does not exist!");
return false;
}
File wavFile = SD.open(filename, FILE_READ);
if (!wavFile) {
println_log("Failed to open file!");
return false;
}
size_t fileSize = wavFile.size();
printf_log("File size: %d Byte (%.2f KB)\n", fileSize, fileSize/1024.0);
size_t freeHeap = ESP.getFreeHeap();
printf_log("Free heap: %d bytes\n", freeHeap);
if (fileSize < freeHeap / 2) {
return playWAVMemory(wavFile, fileSize, repeat, channel, stop_current);
}
// File too large, use segmented playback
wavFile.close();
return playWAVSegmented(filename, repeat, channel, stop_current);
}
bool playWAVMemory(File& wavFile, size_t fileSize, uint32_t repeat, int channel, bool stop_current) {
uint8_t* wavData = (uint8_t*)malloc(fileSize);
if (!wavData) {
println_log("Memory allocation failed!");
wavFile.close();
return false;
}
println_log("Loading file to memory...");
size_t bytesRead = wavFile.read(wavData, fileSize);
wavFile.close();
if (bytesRead != fileSize) {
printf_log("Read error: %d/%d bytes\n", bytesRead, fileSize);
free(wavData);
return false;
}
println_log("Starting playback...");
bool result = M5.Speaker.playWav(wavData, fileSize, repeat, channel, stop_current);
if (result) {
while (M5.Speaker.isPlaying()) {
delay(100);
}
println_log("Playback completed!");
}
free(wavData);
return result;
}
// Segment playback (large file) - optimize memory usage
bool playWAVSegmented(const char* filename, uint32_t repeat, int channel, bool stop_current) {
File wavFile = SD.open(filename, FILE_READ);
if (!wavFile) return false;
uint8_t header[44];
if (wavFile.read(header, 44) != 44) {
wavFile.close();
return false;
}
if (strncmp((char*)header, "RIFF", 4) != 0 ||
strncmp((char*)header + 8, "WAVE", 4) != 0) {
println_log("Invalid WAV format");
wavFile.close();
return false;
}
uint32_t totalFileSize = *(uint32_t*)(header + 4) + 8;
uint32_t sampleRate = *(uint32_t*)(header + 24);
uint16_t channels = *(uint16_t*)(header + 22);
uint16_t bitsPerSample = *(uint16_t*)(header + 34);
printf_log("WAV: %dHz, %dch, %dbit\n", sampleRate, channels, bitsPerSample);
size_t dataSize = totalFileSize - 44;
size_t bytesPerSample = (bitsPerSample / 8) * channels;
size_t chunkSizeInSamples = 16384 / bytesPerSample;
size_t chunkSize = chunkSizeInSamples * bytesPerSample;
printf_log("Chunk size: %d bytes (%d samples)\n", chunkSize, chunkSizeInSamples);
uint8_t* chunkBuffer = nullptr;
size_t actualChunkSize = chunkSize;
while (actualChunkSize >= 4096 && !chunkBuffer) { // Minimum 4KB
chunkBuffer = (uint8_t*)malloc(actualChunkSize + 44);
if (!chunkBuffer) {
actualChunkSize /= 2;
chunkSizeInSamples = actualChunkSize / bytesPerSample;
actualChunkSize = chunkSizeInSamples * bytesPerSample;
printf_log("Retrying with smaller chunk: %d bytes\n", actualChunkSize);
}
}
if (!chunkBuffer) {
println_log("Buffer allocation failed even with small chunks!");
wavFile.close();
return false;
}
printf_log("Using chunk size: %d bytes\n", actualChunkSize);
println_log("Starting segmented playback...");
for (uint32_t rep = 0; rep < repeat; rep++) {
size_t totalRead = 0;
int segmentNum = 0;
wavFile.seek(44);
while (totalRead < dataSize) {
size_t bytesToRead = min(actualChunkSize, dataSize - totalRead);
memcpy(chunkBuffer, header, 44);
uint32_t chunkFileSize = bytesToRead + 36;
memcpy(chunkBuffer + 4, &chunkFileSize, 4);
memcpy(chunkBuffer + 40, &bytesToRead, 4);
size_t bytesRead = wavFile.read(chunkBuffer + 44, bytesToRead);
if (bytesRead == 0) break;
totalRead += bytesRead;
segmentNum++;
if (segmentNum % 5 == 1) {
printf_log("Segment %d (%.1f%%)\n",
segmentNum, (float)totalRead / dataSize * 100.0);
}
bool playResult = M5.Speaker.playWav(chunkBuffer, bytesRead + 44, 1, channel, stop_current);
if (!playResult) {
printf_log("Segment %d failed\n", segmentNum);
break;
}
while (M5.Speaker.isPlaying()) {
delay(5);
}
delay(10);
}
if (rep < repeat - 1) {
delay(1000);
}
}
free(chunkBuffer);
wavFile.close();
println_log("Segmented playback completed!");
return true;
}
void printf_log(const char *format, ...) {
char buf[256];
va_list args;
va_start(args, format);
vsnprintf(buf, 256, format, args);
va_end(args);
Serial.print(buf);
M5.Display.printf(buf);
}
void println_log(const char *str) {
Serial.println(str);
M5.Display.println(str);
}