● 実験テーマ171
「ESP32S3+SDカード+液晶+MP3デコーダIC_MP3_Player製作記録_その3」
(SDカードに収録されているMP3ファイルを最大16個まで読出し液晶にリスト表示し、操作SWにより順送りなどの再生が可能なスケッチの作成~動作確認)
■ 2026.5.9
<今回の動作仕様>
① SDに収録されている、MP3ファイルを、最大16個まで読出し、そのファイルリストを、カラーQVGA液晶に表示する。
16個に満たない場合は、その数のファイルリストを表示する。今回は、スクロール機能は無しとする。
② 通常、操作SWを押さなければ、最初の曲から順次最大16曲目までの再生を繰返すループ再生を行う。
再生中の曲の先頭に、マーカー('●')を表示する。これを移動しながら再生を繰返す。
③ 再生中に、SKIPボタンを押すと、再生を中断し、次の曲の再生に移る。
④ 再生中に、TOPボタンを押すと、再生を中断し、最初の曲の再生に戻る。
⑤ 再生中に、VOL+ボタンを押すと、音量をアップする。
⑥ 再生中に、VOL-ボタンを押すと、音量をダウンする。
■ 2026.5.28
・今回の懸念ポイントは、以下の3点であったが、Copilotのアドバイスを受けながら何とかスケッチを書上げた。
① ArduinoでのSW(操作スイッチ)の状態変化割込みの記述
② filename = file.name();で得たmp3ファイル名をフルパスで2次元配列:fileName[fileCount]に格納するための記述
③ 再生中に音量SWが押された場合、押した瞬間のノイズをいかに抑えるか。
また音の途切れがないこと。
■ 2026.6.5
<最初の動作確認>
① 初期メッセージ:"Touch MP3 Player Test"はok
② その後のテストサイン音もOK
③ SDカードの初期化時にCSピンを指定しマウントもOK(以下の液晶表示出てる)
"Card Mounted Successfully"

④ ③の後に"File not
found"が表示される。
→ 調べたら、そもそもmp3ファイルリストの検索と液晶への表示が駄目。まずはそこで止めてみてそのリスト表示詳細を確認してみた。
→ 結果:mp3ファイルリスト全く表示してない。
SDカードからmp3ファイルを読込むと何故か"SYSTEM VOLUME"という文字列が液晶に表示されてしまう。
またファイル名の先頭のマーカーが'●'でなく'★'なってる。
■ 2026.6.12
・"●"が"★"になる問題解決。
単純ミスといえば単純ミス。
一文字描画なので、LCD_DrawChar()を使う必要があった。
自分のPIC32MXの例でもこれを使ってた。よく見れば気が付いたが・・・
void LCD_DrawChar(char colum, char line, unsigned char
letter, unsigned int color1, unsigned int color2);
これは引数のletter(文字コード)がunsigned charになっている。
今までvoid LCD_DrawString(char colum, char line, char *s,
unsigned int color1, unsigned int color2);の方を使ってた。
<修正前>
LCD_DrawString(0, 0, 0xE4, GREEN, BLACK); //
'●'を選択初期位置(先頭)に表示)
<修正後>
LCD_DrawChar(0, 0, 0xE4, GREEN, BLACK); //
'●'を選択初期位置(先頭)に表示)
→ これでokになる。
・SDカードからMP3ファイルを読込むと何故か"SYSTEM
VOLUME"という文字列が液晶に表示されてしまう問題も解決。
これは以下の修正後のスケッチの通り、MP3ファイルのみ読込み処理対象に修正でOKになる。
尚、Copilotに聞いたら「SDカード内にWindowsが自動生成した隠しフォルダ「System Volume Information」が残っており、
ファイル読み込みのループ処理(openNextFile()など)でその名前を拾ってしまっていることが原因」とのことでした。
<修正後:MP3ファイルのみ処理対象>
/**********************************************************
* SDカードのルートディレクトリにあるファイルを読込み
* 液晶の1画面のファイル名リストを表示する。
***********************************************************/
void listRootFiles()
{
/// ルートディレクトリを開く
root = SD.open("/");
if (!root) {
Serial.println("ルートディレクトリが開けません"); // シリアルモニタに出力
return;
}
if (!root.isDirectory()) {
Serial.println("ディレクトリではありません"); // シリアルモニタに出力
return;
}
/// ループで次々とファイルを取得
File file = root.openNextFile();
while(file) {
/// ファイル名取得
const char* filename = file.name();
String nameStr = String(filename);
//// 拡張子が ".mp3" 或いは ".MP3"かどうかをチェックし対象ファイルのみ以下を実行
if (nameStr.endsWith(".mp3") || nameStr.endsWith(".MP3")) {
/// 2次元配列へコピー・念のため終端文字を保証
strncpy(fileNames[fileCount], filename,
MAX_NAME_LEN-1);
fileNames[fileCount][MAX_NAME_LEN - 1] = '\0';
/// ファイル名の文字フォントデータを液晶表示関数に渡しファイル名を表示
digitalWrite(SD_CS,HIGH);
digitalWrite(D_CS, LOW);
LCD_DrawString(2, fileCount,
fileNames[fileCount], WHITE, BLACK);
digitalWrite(D_CS, HIGH);
digitalWrite(SD_CS, LOW);
/// 次のファイルへ
fileCount++;
if (fileCount < MAX_FILES) file =
root.openNextFile();
else break; // 17ファイル分終了したらループブレークしクローズ
}
else break;
} // end while
root.close();
}
→ これでマーカーが正しく表示され、リストに"System Volume"が表示されなくなった。
これで先に進めてみる。
■ 2026.6.13
・SD内のファイルの読込み時、液晶の最終行に読込結果の良否を表示させているのだが、"File not found"になってしまう。
これは、SD_CS(SDカードのチップセレクト)とD_CS(液晶のCS)のトグル切替(共通のSPI使用)の見直し等で不適当な部分
を修正したら"File Detected"表示が出たがまだ再生はしない。
■ 2026.6.14
・MP3ファイル再生ループの構造は、do{ }while();の構文を使っている。
while()の条件として、現在は(!flagSkip || !flagTop);としているが、
これは(!flagSkip && !flagTop); // 再生途中にSW割込みが無く1曲分の再生が終わった時と音量SW以外のSW割込みが入った場合はループを抜ける
のミスなので修正したが、まだ駄目、再生しない。
あとSW割込みを続けてかけていくと暴走する。マーカーが勝手に動く。
まずは割込み無しで再生できるかに絞ってチェックを進めることにした。
・ファイルサイズそのものの読み込みが上手く行ってないみたい。
シリアルモニターに表示させると、最初の読み込みMP3ファイルのサイズが0byteになってる。
■ 2026.6.15
<今日の結果>
① オープンしたMP3ファイル名(配列内に格納)をシリアルモニタで確認したが、ちゃんと意図通りの
文字列になってた。
② しかしそのサイズをシリアルモニタに出力してみたら、0byteになってる?
ちなみに、CHK_INFO.mp3のサイズは、423.936byte
Sine Test Started...
Sine Test Stopped.
Detect mp3 file name: CHK_INFO.mp3
Total Size: 0
■ 2026.6.17
・この原因だが、基本的な部分だった。
MP3ファイルは全てSDカードのルートディレクトリに収録することにしているので、ファイル名の頭に"/"を追加して
読み出す必要があった。
■ 2026.6.18
・以下の修正の通り、文字列結合で、"/CHK_INFO.mp3"としてルートオープンしてみる。
<修正後:main>
while(1) {
/// ファイルNo.表示
itostring(2, fileCount+1, str_N+1);
LCD_DrawString(21, 15, str_N, WHITE, BLACK); // 'Nxx'表示
/****** 選択されたファイルの再生処理 ***********/
///// ルートにある選択されたファイルをフルパスで開く
digitalWrite(D_CS, HIGH);
digitalWrite(SD_CS, LOW);
/// 結合後のフルパスを格納する変数を用意(サイズはMAX_NAME_LEN + 1文字:"/"分)
char fullPath[MAX_NAME_LEN + 1];
/// 先頭に "/" を付けた文字列を fullPath に作成
snprintf(fullPath, sizeof(fullPath), "/%s", fileNames[fileCount]);
/// 結合したパスと FILE_READ を指定してファイルを開く
File musicFile = SD.open(fullPath, FILE_READ);
/// 音楽ファイルが見つからない場合
if (!musicFile) {
digitalWrite(SD_CS, HIGH);
digitalWrite(D_CS, LOW);
LCD_DrawString(0, 16, "File not found ", CYAN, BLACK);
}
/// 音楽ファイル検知の場合
sendEndFill(); // 再生を開始する前に2024個の0をSDIに転送+ソフトリセットしてデコーダを「待ち」の状態にする
digitalWrite(SD_CS, HIGH);
digitalWrite(D_CS, LOW);
LCD_DrawString(0, 16, "File Detected ", CYAN, BLACK);
digitalWrite(D_CS, HIGH);
digitalWrite(SD_CS, LOW);
/// 確認用:結合されたフルパスをモニタに出力
Serial.print("Opening path: ");
Serial.println(fullPath);
/// 確認用:ファイル全体のサイズを取得しモニタに出力
unsigned long fileSize = musicFile.size(); // ファイル全体のサイズを取得
Serial.print("Total Size: ");
Serial.println(fileSize);
while(1); // とりあえずここまで確認
・
・
}
<結果>
・上手く行った
PCでSDカードのプロパティで確認したサイズ項目の値:423188と一致した。
Opening path: /CHK_INFO.mp3
Total Size: 423188
---------------------------------------------------------------------------------------------------
・再生も出来てるが、SW割込み無しのループ再生の最後が1曲手前になってたり、
もどる場所が最初になってない?これは後で見る。たぶんイージーミス。
また●の動きも一部変。
・SW割込みは未だ変。まともに動いてない。
音量SWはまるっきり効いてない。
SKIP/TOP SWもNG
割込み関係は再生中の音量SWから調査予定。
----------------------------------------------------------------------------------------------------
■ 2026.6.19
<修正前>
void MakerDisp()
{
if (fileCount < MAX_FILES-1) {
LCD_DrawChar(0, fileCount-1, 0x20, BLACK, BLACK); //
その前に表示されている、選択キャラクタ'●'をクリアする
LCD_DrawChar(0, fileCount, 0xE4, GREEN, BLACK); //
'●'を選択初期位置(先頭)に表示
}
else{ // 最終行を超えた場合、先頭に戻す
fileCount = 0;
LCD_DrawChar(0, 15, 0x20, BLACK, BLACK); //
その前に表示されている、選択キャラクタ'●'をクリアする
LCD_DrawChar(0, fileCount, 0xE4, GREEN, BLACK); //
'●'を選択初期位置(先頭)に表示
}
}
<修正後>
void MakerDisp()
{
if (fileCount < MAX_FILES) {
LCD_DrawChar(0, (fileCount-1, 0x20, BLACK, BLACK); //
その前に表示されている、選択キャラクタ'●'をクリアする
LCD_DrawChar(0, fileCount, 0xE4, GREEN, BLACK); //
'●'を選択初期位置(先頭)に表示
}
else{ // 最終行を超えた場合、先頭に戻す
fileCount = 0;
LCD_DrawChar(0, 15, 0x20, BLACK, BLACK); //
その前に表示されている、選択キャラクタ'●'をクリアする
LCD_DrawChar(0, fileCount, 0xE4, GREEN, BLACK); //
'●'を選択初期位置(先頭)に表示
}
→ これで何もSW押されてない場合のループ再生はOK
しかし最終mp3ファイル再生後、トップに●が移動するが、元の位置(最終行)の●が削除されない。
→ <修正:以下のif文コメントアウトでokになる-> fileCountの初期化がMakerDisp()の中でもダブルで処理されていた>
if (!flagSkip &&
!flagTop) { // 再生中にボタンが押されなかった場合の処理(音量ボタン以外)
fileCount++;
// if
(fileCount >= MAX_FILES ) fileCount = 0;
MakerDisp();
// 選択マーカー移動表示
}
・これでSW割込み無しのマーカー移動を含めたループ再生はOKになる。
■ 2026.6.20
<修正点①:TOP SWの動き修正:最終行でTOP SWを押した場合、最終行のマーカーが削除されないのを修正>
・修正前
if (!flagSkip && !flagTop) { // 再生中にボタンが押されなかった場合の処理(音量ボタン以外)
fileCount++;
MakerDisp(); // 選択マーカー移動表示
}
else { // 再生中にボタンが押された場合の処理(音量ボタン以外)
if (flagSkip) {
flagSkip = 0;
fileCount++; // 次の再生ファイルNo取得
MakerDisp(); // 選択マーカー移動表示
}
else {
if (flagTop) {
flagTop = 0;
fileCount = 0; // 先頭の再生ファイルNoに戻す
MakerDisp(); // 選択マーカー移動表示
}
}
}
・修正後
if (!flagSkip && !flagTop) { // 再生中にボタンが押されなかった場合の処理(音量ボタン以外)
fileCount++;
MakerDisp(); // 選択マーカー移動表示
}
else { // 再生中にボタンが押された場合の処理(音量ボタン以外)
if (flagSkip) {
flagSkip = 0;
fileCount++; // 次の再生ファイルNo取得
MakerDisp(); // 選択マーカー移動表示
}
else {
if (flagTop) {
flagTop = 0;
LCD_DrawChar(0, fileCount, 0x20, BLACK, BLACK);
// 現在位置の選択キャラクタ'●'をクリアする
fileCount = 0;
LCD_DrawChar(0, fileCount, 0xE4, GREEN, BLACK);
// '●'を選択初期位置(先頭)に表示
}
}
}
<結果>
・これで、TOP SWの動き修正:最終行でTOP SWを押した場合、最終行のマーカーが削除されないのを修正OKになる。
残る問題は、音量SW
■ 2026.6.22
・音量SWをループ内で直接読込む方法だと必ずチャタリング防止の遅延が出るので、たぶん再生音に影響する。
なので、音量SWも割込み処理が必要と考える。
割込み内では、音量SWの読込み判断+Volume値を変数にセット+ChgFlg = 1のみ実行し、
メインループ内で、VS1011へMP3データを流し込む前に
ChgFlgをチェックしセットされていたら、VS1011へVolume値を送信+音量表示更新を実施することにしてみる。(ここは修正なし)
<修正後①>
・スケッチ冒頭の以下コメントアウト(削除)
/// 割込み用フラグ(メインループと共有するため volatile)
volatile bool flagSkip = false;
volatile bool flagTop = false;
//volatile bool flagVUp = false;
//volatile bool flagVDown = false;
<修正後②>
・loop()冒頭にある以下はコメントアウト(削除)
// --- 割り込みフラグの監視と処理(音量SW) ---
if (flagVUp) {
flagVUp = false;
if (Volume > 0) Volume--; // 0dBmax
ChgFlag = 1; // 変更フラグオン
}
if (flagVDown) {
flagVDown = false;
if (Volume < 64) Volume++;
ChgFlag = 1; // 変更フラグオン
}
-------------------------------------------------------------
<修正前③>
void IRAM_ATTR handleVUp() { // V_UP SW
unsigned long currentTime = millis();
if (currentTime - lastInterruptTime > debounceDelay) {
flagVUp = true;
lastInterruptTime = currentTime;
}
}
void IRAM_ATTR handleVDown() { // V_DOWN SW
unsigned long currentTime = millis();
if (currentTime - lastInterruptTime > debounceDelay) {
flagVDown = true;
lastInterruptTime = currentTime;
}
}
<修正後③>
void IRAM_ATTR handleVUp() { // V_UP SW
unsigned long currentTime = millis();
if (currentTime - lastInterruptTime > debounceDelay) {
if (Volume > 0) Volume--; // 0dBmax
ChgFlag = 1; // 変更フラグオン
lastInterruptTime = currentTime;
}
}
void IRAM_ATTR handleVDown() { // V_DOWN SW
unsigned long currentTime = millis();
if (currentTime - lastInterruptTime > debounceDelay) {
if (Volume < 64) Volume++;
ChgFlag = 1; // 変更フラグオン
lastInterruptTime = currentTime;
}
}
<メインループ内:ここは修正なし>
/******** ファイルオープンに成功した場合の処理 *******************/
do {
/// 更新フラグチェックと制御(音量ボタン)
if (ChgFlag) { // 更新ありか?
ChgFlag = 0; // フラグクリア
SetVolume(Volume, Volume); // 音量設定更新
/// 音量表示更新
itostring(2, 64-Volume, str_V+1);
digitalWrite(SD_CS, HIGH);
digitalWrite(D_CS, LOW);
LCD_DrawString(21, 0, str_V, WHITE,
BLACK);// 'Vxx'表示
digitalWrite(D_CS, HIGH);
digitalWrite(SD_CS, LOW);
}
・
・
・
if (remainingBytes == 0) break; //
全て読込み終了時(EOF検出・再生終了時)このループを強制脱出
/// SDカードから512バイト読み出す
musicFile.read(buffer, 512); // 512byte read
/// VS1011のXDCSをアクティブにする前にSD_CSをノンアクティブにする。
digitalWrite(SD_CS, HIGH);
/// SDIバスに512byteブロックのデータを32byte単位でDREQチェックしながら流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < 512; i += 32) {
while (!digitalRead(DREQ)); //
DREQがHighになるまで待つ
/// このbufferアドレスから始まる32byteのデータを一度に送信し、
/// 同時に受信した32バイトを同じ場所に上書きする。
SPI2.transfer(&buffer[i], 32);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
digitalWrite(SD_CS, LOW); //
次の読み取り位置取得・次の32バイトを読むためにSD_CSをアクティブにしておく。
} while (!flagSkip && !flagTop); // 再生途中にSW割込みが無く1曲分の再生が終わった時と
// 音量SW以外のSW割込みが入った場合はループを抜け以下を実行
<修正前④>
/// 音楽ファイルが見つからない場合
if (!musicFile) {
digitalWrite(SD_CS, HIGH);
digitalWrite(D_CS, LOW);
LCD_DrawString(0, 16, "File not found ", CYAN, BLACK);
}
<修正後④>
/// 音楽ファイルが見つからない場合
if (!musicFile) {
digitalWrite(SD_CS, HIGH);
digitalWrite(D_CS, LOW);
LCD_DrawString(0, 16, "File not found ", CYAN, BLACK);
while(1);
}
<結果>
・音量SW切り替え時、気になるノイズが発生するが音量は変化し音量表示も正常に変化してる。
→ 更新フラグチェックと制御(音量ボタン)の処理位置をvs1011へのmp3データ送信前から、送信後に修正したら
綺麗に音量調整が追従した。ノイズは無しOK
→ これでやっとESP32S3_MP3_PLAYER_TEST_1.inoプロジェクトOKになる。

<最終回路図>
・実験テーマ169と同じ回路です。
<最終スケッチとフォントデータ>
・こちらから、どうぞ→
ESP32S3_MP3_PLAYER_TEST_1.ino
ASCII12dot.h