今までできなかった、次のような音質のWAVファイル再生が、やっとできました。
チャンネル 2(ステレオ)
サンプルレート 44100Hz
サンプル当たりのビット数 16
再生中のシリアルモニタの表示がこれになります。
コードはこのサイトで見つけました💛。作者に感謝ですね。
サイトの下のほうにある
DEMO
SD WAV Code
からダウンロードできます。使用した構成は
1.Windows 11 Pro
2.Arduino IDE 2.3.6
3.ESP32 Dev Module(Arduino IDE 内でのボード名)
4.SPI接続 microSDカードスロットモジュール
5.PCM5102A使用 32bit/384kHz DAC モジュールhttps://store.shopping.yahoo.co.jp/nfj/h135.html
6.ボードマネージャは「esp32 by Espressif Systems version 3.0.1」にバージョンダウン
7.ツール > Partition Scheme:Huge APP
PCM5102A
のステレオミニジャックに、ダイソー300円スピーカーを繋いで聞けました。ずっとループ再生されます。
ESP32、PCM5102A、microSDスロットモジュールの接続は以下。
ESP32 | PCM5102A | microSDスロット |
---|---|---|
25 | DATA | |
26 | LRCK | |
27 | BICK | |
5V | VCC | |
5 | SC(CS) | |
23 | MOSI | |
19 | MISO | |
18 | SCK(CLK) | |
3V3 | 3V3 | |
GND | GND | GND |
コードです。
//------------------------------------------------------------------------------------------------------------------------
//
// Title: SD Card Wav Player
//
// Description:
// Simple example to demonstrate the fundementals of playing WAV files (digitised sound) from an SD Card via the I2S
// interface of the ESP32. Plays WAV file from SD card. To keep this simple the WAV must be stereo and 16bit samples.
// The Samples Per second can be anything. On the SD Card the wav file must be in root and called wavfile.wav
// Libraries are available to play WAV's on ESP32, this code does not use these so that we can see what is happening.
// This is part 3 in a tutorial series on using I2S on ESP32. See the accompanying web page (which will also include
// a tutorial video).
//
// Boring copyright/usage information:
// (c) XTronical, www.xtronical.com
// Use as you wish for personal or monatary gain, or to rule the world (if that sort of thing spins your bottle)
// However you use it, no warrenty is provided etc. etc. It is not listed as fit for any purpose you perceive
// It may damage your house, steal your lover, drink your beers and more.
//
// http://www.xtronical.com/i2s-ep3
//
//------------------------------------------------------------------------------------------------------------------------
// Includes
#include "SD.h" // SD Card library, usually part of the standard install
#include "driver/i2s.h" // Library of I2S routines, comes with ESP32 standard install
// SD Card
#define SD_CS 5 // SD Card chip select
#define SPI_MOSI 23
#define SPI_MISO 19
#define SPI_SCK 18
// I2S
#define I2S_DOUT 25 // i2S Data out oin
#define I2S_BCLK 27 // Bit clock
#define I2S_LRC 26 // Left/Right clock, also known as Frame clock or word select
#define I2S_NUM 0 // i2s port number
// Wav File reading
#define NUM_BYTES_TO_READ_FROM_FILE 1024 // How many bytes to read from wav file at a time
//------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------
// structures and also variables
// I2S configuration
static const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100, // Note, this will be changed later
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority
.dma_buf_count = 8, // 8 buffers
.dma_buf_len = 64, // 64 bytes per buffer, so 8K of buffer space
.use_apll = 0,
.tx_desc_auto_clear = true,
.fixed_mclk = -1
};
// These are the physical wiring connections to our I2S decoder board/chip from the esp32, there are other connections
// required for the chips mentioned at the top (but not to the ESP32), please visit the page mentioned at the top for
// further information regarding these other connections.
static const i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCLK, // The bit clock connectiom, goes to pin 27 of ESP32
.ws_io_num = I2S_LRC, // Word select, also known as word select or left right clock
.data_out_num = I2S_DOUT, // Data out from the ESP32, connect to DIN on 38357A
.data_in_num = I2S_PIN_NO_CHANGE // we are not interested in I2S data into the ESP32
};
struct WavHeader_Struct {
// RIFF Section
char RIFFSectionID[4]; // Letters "RIFF"
uint32_t Size; // Size of entire file less 8
char RiffFormat[4]; // Letters "WAVE"
// Format Section
char FormatSectionID[4]; // letters "fmt"
uint32_t FormatSize; // Size of format section less 8
uint16_t FormatID; // 1=uncompressed PCM
uint16_t NumChannels; // 1=mono,2=stereo
uint32_t SampleRate; // 44100, 16000, 8000 etc.
uint32_t ByteRate; // =SampleRate * Channels * (BitsPerSample/8)
uint16_t BlockAlign; // =Channels * (BitsPerSample/8)
uint16_t BitsPerSample; // 8,16,24 or 32
// Data Section
char DataSectionID[4]; // The letters "data"
uint32_t DataSize; // Size of the data that follows
} WavHeader;
//------------------------------------------------------------------------------------------------------------------------
// Global Variables/objects
File WavFile; // Object for root of SD card directory
static const i2s_port_t i2s_num = I2S_NUM_0; // i2s port number
//------------------------------------------------------------------------------------------------------------------------
void setup() {
Serial.begin(115200); // Used for info/debug
SDCardInit();
i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
i2s_set_pin(i2s_num, &pin_config);
// get the wav file from the SD card
WavFile = SD.open("/wavefile.wav"); // Open the wav file
if (WavFile == false)
Serial.println("Could not open 'wavfile.wav'");
else {
WavFile.read((byte*)&WavHeader, 44); // Read in the WAV header, which is first 44 bytes of the file.
// We have to typecast to bytes for the "read" function
DumpWAVHeader(&WavHeader); // Dump the header data to serial, optional!
if (ValidWavData(&WavHeader)) // optional if your sure the WAV file will be valid.
i2s_set_sample_rates(i2s_num, WavHeader.SampleRate); //set sample rate
}
}
void loop() {
PlayWav(); // Have to keep calling this to keep the wav file playing
// Your normal code to do your task can go here
}
void PlayWav() {
static bool ReadingFile = true; // True if reading file from SD. false if filling I2S buffer
static byte Samples[NUM_BYTES_TO_READ_FROM_FILE]; // Memory allocated to store the data read in from the wav file
static uint16_t BytesRead; // Num bytes actually read from the wav file which will either be
// NUM_BYTES_TO_READ_FROM_FILE or less than this if we are very
// near the end of the file. i.e. we can't read beyond the file.
if (ReadingFile) // Read next chunk of data in from file if needed
{
BytesRead = ReadFile(Samples); // Read data into our memory buffer, return num bytes read in
ReadingFile = false; // Switch to sending the buffer to the I2S
} else
ReadingFile = FillI2SBuffer(Samples, BytesRead); // We keep calling this routine until it returns true, at which point
// this will swap us back to Reading the next block of data from the file.
// Reading true means it has managed to push all the data to the I2S
// Handler, false means there still more to do and you should call this
// routine again and again until it returns true.
}
uint16_t ReadFile(byte* Samples) {
static uint32_t BytesReadSoFar = 0; // Number of bytes read from file so far
uint16_t BytesToRead; // Number of bytes to read from the file
if (BytesReadSoFar + NUM_BYTES_TO_READ_FROM_FILE > WavHeader.DataSize) // If next read will go past the end then adjust the
BytesToRead = WavHeader.DataSize - BytesReadSoFar; // amount to read to whatever is remaining to read
else
BytesToRead = NUM_BYTES_TO_READ_FROM_FILE; // Default to max to read
WavFile.read(Samples, BytesToRead); // Read in the bytes from the file
BytesReadSoFar += BytesToRead; // Update the total bytes red in so far
if (BytesReadSoFar >= WavHeader.DataSize) // Have we read in all the data?
{
WavFile.seek(44); // Reset to start of wav data
BytesReadSoFar = 0; // Clear to no bytes read in so far
}
return BytesToRead; // return the number of bytes read into buffer
}
bool FillI2SBuffer(byte* Samples, uint16_t BytesInBuffer) {
// Writes bytes to buffer, returns true if all bytes sent else false, keeps track itself of how many left
// to write, so just keep calling this routine until returns true to know they've all been written, then
// you can re-fill the buffer
size_t BytesWritten; // Returned by the I2S write routine,
static uint16_t BufferIdx = 0; // Current pos of buffer to output next
uint8_t* DataPtr; // Point to next data to send to I2S
uint16_t BytesToSend; // Number of bytes to send to I2S
// To make the code eaier to understand I'm using to variables to some calculations, normally I'd write this calcs
// directly into the line of code where they belong, but this make it easier to understand what's happening
DataPtr = Samples + BufferIdx; // Set address to next byte in buffer to send out
BytesToSend = BytesInBuffer - BufferIdx; // This is amount to send (total less what we've already sent)
i2s_write(i2s_num, DataPtr, BytesToSend, &BytesWritten, 1); // Send the bytes, wait 1 RTOS tick to complete
BufferIdx += BytesWritten; // increasue by number of bytes actually written
if (BufferIdx >= BytesInBuffer) {
// sent out all bytes in buffer, reset and return true to indicate this
BufferIdx = 0;
return true;
} else
return false; // Still more data to send to I2S so return false to indicate this
}
void SDCardInit() {
pinMode(SD_CS, OUTPUT);
digitalWrite(SD_CS, HIGH); // SD card chips select, must use GPIO 5 (ESP32 SS)
if (!SD.begin(SD_CS)) {
Serial.println("Error talking to SD card!");
while (true)
; // end program
}
}
bool ValidWavData(WavHeader_Struct* Wav) {
if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0) {
Serial.print("Invalid data - Not RIFF format");
return false;
}
if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0) {
Serial.print("Invalid data - Not Wave file");
return false;
}
if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0) {
Serial.print("Invalid data - No format section found");
return false;
}
if (memcmp(Wav->DataSectionID, "data", 4) != 0) {
Serial.print("Invalid data - data section not found");
return false;
}
if (Wav->FormatID != 1) {
Serial.print("Invalid data - format Id must be 1");
return false;
}
if (Wav->FormatSize != 16) {
Serial.print("Invalid data - format section size must be 16.");
return false;
}
if ((Wav->NumChannels != 1) & (Wav->NumChannels != 2)) {
Serial.print("Invalid data - only mono or stereo permitted.");
return false;
}
if (Wav->SampleRate > 48000) {
Serial.print("Invalid data - Sample rate cannot be greater than 48000");
return false;
}
if ((Wav->BitsPerSample != 8) & (Wav->BitsPerSample != 16)) {
Serial.print("Invalid data - Only 8 or 16 bits per sample permitted.");
return false;
}
return true;
}
void DumpWAVHeader(WavHeader_Struct* Wav) {
if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0) {
Serial.print("Not a RIFF format file - ");
PrintData(Wav->RIFFSectionID, 4);
return;
}
if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0) {
Serial.print("Not a WAVE file - ");
PrintData(Wav->RiffFormat, 4);
return;
}
if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0) {
Serial.print("fmt ID not present - ");
PrintData(Wav->FormatSectionID, 3);
return;
}
if (memcmp(Wav->DataSectionID, "data", 4) != 0) {
Serial.print("data ID not present - ");
PrintData(Wav->DataSectionID, 4);
return;
}
// All looks good, dump the data
Serial.print("Total size :");
Serial.println(Wav->Size);
Serial.print("Format section size :");
Serial.println(Wav->FormatSize);
Serial.print("Wave format :");
Serial.println(Wav->FormatID);
Serial.print("Channels :");
Serial.println(Wav->NumChannels);
Serial.print("Sample Rate :");
Serial.println(Wav->SampleRate);
Serial.print("Byte Rate :");
Serial.println(Wav->ByteRate);
Serial.print("Block Align :");
Serial.println(Wav->BlockAlign);
Serial.print("Bits Per Sample :");
Serial.println(Wav->BitsPerSample);
Serial.print("Data Size :");
Serial.println(Wav->DataSize);
}
void PrintData(const char* Data, uint8_t NumBytes) {
for (uint8_t i = 0; i < NumBytes; i++)
Serial.print(Data[i]);
Serial.println();
}
5,6年ほど前に、音質をおとしたものをNANO
で再生したことはありました。最初に挙げた音質でmicroSD
から再生でき、満足です。最後まで見ていただきありがとうございました。