● 実験テーマ170
「ESP32S3+SDカード+液晶+MP3デコーダIC_MP3_Player製作記録_その2」
(SDカードから1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチの作成~動作確認を行った。私の勘違いで、かなり回り道をしてしまいました。)
■ 2026.3.9
<テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
(1) 昨晩後半の方のチャットが、AIエラーで見れなくなった件、途中から再度チャットしてみた。
<私>
・昨晩後半の方のチャットが、AIエラーで見れなくなったので、途切れた所から再開したいと思います。
ESP32S3 Dev Module+SDカード+VS1011の構成でBASIC TESTを検討しています。
<内容>
・SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチのサンプルコードを
教えてください。参考にしたいと思います。
尚、これに先立った実験で、VS1011のみのSIN TESTは上手く行きました。
<Copilot>
#include <SPI.h>
#include <SD.h>
// --- ピン定義 (配線に合わせて適宜変更してください) ---
#define VS_XCS 10 // SCI用 (Command Select)
#define VS_XDCS 9 // SDI用 (Data Select)
#define VS_DREQ 3 // Data Request Pin
#define VS_XRESET 4 // VS1011 Reset Pin
#define SD_CS 5 // SDカード CS Pin
// --- SPI設定 ---
// VS1011用 (HSPIバスを使用)
SPIClass SPI2(HSPI);
SPISettings settingsSCI(1000000, MSBFIRST, SPI_MODE0); // コマンド用(1MHz)
SPISettings settingsSDI(4000000, MSBFIRST, SPI_MODE0); // データ用(4MHz:PLL後)
void setup() {
Serial.begin(115200);
delay(1000);
// ピンモード設定
pinMode(VS_XCS, OUTPUT);
pinMode(VS_XDCS, OUTPUT);
pinMode(VS_DREQ, INPUT);
pinMode(VS_XRESET, OUTPUT);
pinMode(SD_CS, OUTPUT);
digitalWrite(VS_XCS, HIGH);
digitalWrite(VS_XDCS, HIGH);
digitalWrite(SD_CS, HIGH);
// 1. VS1011 ハードウェアリセット
digitalWrite(VS_XRESET, LOW);
delay(10);
digitalWrite(VS_XRESET, HIGH);
while (!digitalRead(VS_DREQ)); // 起動待ち
delay(10);
// 2. SPIの初期化 (SPI2: VS1011, 標準SPI: SDカード)
// SPI2.begin(SCK, MISO, MOSI, SS); ピン番号は配線に合わせてください
SPI2.begin(12, 13, 11, VS_XCS);
if (!SD.begin(SD_CS)) {
Serial.println("SD Card Mount Failed");
return;
}
Serial.println("SD Card Mounted.");
// 3. VS1011 初期設定 (PLL有効化など)
initVS1011();
// 4. 再生実行
playMP3("/test.mp3"); // SDカード内のファイル名(ルートディレクトリ)
}
void loop() {
// 1回再生なので、loop内は空
}
// --- 音楽再生メイン関数 ---
void playMP3(const char* filename) {
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
uint8_t buffer[32]; // VS1011は32バイト単位の送信が推奨
while (musicFile.available()) {
// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(VS_DREQ));
// SDカードから32バイト読み出す
int bytesRead = musicFile.read(buffer, 32);
// SDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(VS_XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(VS_XDCS, HIGH);
SPI2.endTransaction();
}
musicFile.close();
Serial.println("Done.");
// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
// --- VS1011 初期化 ---
void initVS1011() {
// モード設定
writeRegister(0x00, 0x0800);
// CLOCKF設定 (0x9800: 12.288MHz x 2倍)
writeRegister(0x03, 0x9800);
delay(10);
while (!digitalRead(VS_DREQ));
// 音量設定 (0x2020: 左右-32dB)
writeRegister(0x0B, 0x2020);
}
void writeRegister(uint8_t address, uint16_t data) {
SPI2.beginTransaction(settingsSCI);
digitalWrite(VS_XCS, LOW);
SPI2.transfer(0x02); // Write Opcode
SPI2.transfer(address);
SPI2.transfer(data >> 8);
SPI2.transfer(data & 0xFF);
digitalWrite(VS_XCS, HIGH);
SPI2.endTransaction();
while (!digitalRead(VS_DREQ));
}
void sendEndFill() {
for (int i = 0; i < 2048; i++) {
while (!digitalRead(VS_DREQ));
SPI2.beginTransaction(settingsSDI);
digitalWrite(VS_XDCS, LOW);
SPI2.transfer(0x00);
digitalWrite(VS_XDCS, HIGH);
SPI2.endTransaction();
}
}
------------------------------------------------------------------------------------------------
(1) 実装のポイント
① 32バイト・ルール
・VS1011は内部バッファが2KBありますが、一度に「確実に送って良い」とされるのは32バイトです。
これを守ることで音が途切れるのを防げます。
② SPIバスの分離
・SPI2(VS1011用)と標準の SPI(SDカード用)を分けることで、通信の衝突を避けています。
③ sendEndFill
・曲の最後に2048バイトの 0x00 を送ります。これを行うことで、
VS1011の内部バッファに残った最後の音がしっかり鳴り切り、次の再生がスムーズになります。
※ まずは、SDカードに test.mp3 という名前のファイル(ビットレートは低め、128kbps以下がおすすめ)を入れて、
スピーカーから音楽が流れるか試してみてください!
(2) 午後のデバッグのヒント
① MP3のビットレート: 最初は128kbps程度の標準的なファイルが安定します。
② SPIの干渉: SDカードとVS1011でSPIバスを共有している場合の挙動。
③ DREQの監視速度: ESP32-S3が速すぎるため、DREQのチェックが追いついているか。
-----------------------------------------------------------------------------------------------
<私>
・食事の前に気になったことが有ったので質問します。
現在、VS1011とSDカードのSPIバス(MOSI, MISO, SCK)は共通にしてあります。
それぞれの、CSで切替えてますが、その路線でよろしいですか?
また、SPIClass SPI2(FSPI);に設定してますが、SPIClass SPI2(HSPI);の必要がありますか?
<Copilot>
(1) SPIバス(MOSI, MISO, SCK)の共通化について
① はい、その路線で全く問題ありません。
SDカードとVS1011を同じバスに繋ぎ、それぞれのCSピン(SD_CS, VS_XCS,
VS_XDCS)で切り替えるのは、SPIの標準的な使い方です。
<注意>
① 共有の注意点: SDカードのライブラリ(SD.h)が通信している間は、VS1011のCSは必ずHIGH(非選択)になっている必要があります。
逆も然りです。
② 速度の切り替え: SPI.beginTransaction(settings) を使うことで、
SDカード(高速)とVS1011(低速〜中速)が切り替わる際に、
ESP32-S3が自動でクロック周波数を調整してくれるので、コード上で明示的に beginTransaction を呼んでいれば安全です。
(2) どちらを使うべきか
① Arduinoの標準 SPI クラスは内部で FSPI を使っていることが多いです。
② もしご自身で SPIClass SPI2(FSPI); と定義して、
ピン番号を正しく begin(sck, miso, mosi, cs) で指定できているのであれば、そのまま FSPI で進めて大丈夫です。
(3) 成功へのアドバイス
① もし、共通バスで「SDカードは読めるが音が出ない」あるいはその逆が起きた場合は、「MISOピンの衝突」を疑います。
(稀にSDカードモジュールがCS=HIGHでもMISOを解放しない行儀の悪い振る舞いをすることがありますが、通常のモジュールなら問題ありません)
---------------------------------------------------------------------------------------------------------------------------------------------
<私:動作確認>
・最初、SINE TESTでピー音は3秒間鳴ったが、その後のMP3ファイル(RNG_INFO.mp3:ビットレート=128kbps)の再生が変。
ピアノのバックグランドをバックに女性アナウンサーのテスト内容説明後に、1kHzのサイン波音が数秒鳴った後、
サイン波による周波数スイープテストが行われるソースなのだが・・・
何か話てるのは分かるが声になってない。その後の1kHzのサイン波音も聞こえない。
スイープの方はSPの関係で中音域から聞こえるはずなのだが、聞こえない。
また、何度かESP32モジュールのRESET SWを押して再起動していくと、SINE TESTの音も出なくなったり逆になりっぱなしになる。
・今は、ジャンパワイヤの接触不良かと思って、任意の信号線のワイヤを押し込んだら、ピアノのバックグランド+女性アナウンサーのテスト内容説明
までは正常に聞こえたが、その後、1kHzサイン波音が聞こえるはずだが、聞こえず、ここで音が止まってしまう状態です。
尚、この症状の再現性はあります。
musicFile.available()の挙動が気になります。
また、ステレオイヤフォンで聞いているのですが、殆どノイズは気にならず、クリアに聞こえています。

---------------------------------------------------------------------------------------------------------------------------------------------------
・Copilotに結果報告しようとしたが、今朝がたのチャット内容がまた消えてしまった。
-----------------------------------------------------------------------------------------------------------------------------------------------------
<再度、新たにチャット再開した>
<Copilot>
・以下に変えてみたらどうか
while (musicFile.available()) {
int bytesRead = musicFile.read(buffer, 32);
SPI2.beginTransaction(settingsSDI);
digitalWrite(DCS, LOW);
for (int i = 0; i < bytesRead; i++) {
// 1バイトごとにDREQをチェック(または32バイトのチャンクの先頭で厳密に待つ)
while (!digitalRead(DREQ));
SPI2.transfer(buffer[i]);
}
digitalWrite(DCS, HIGH);
SPI2.endTransaction();
}
・データ転送スピードを4Mから1Mに落とす(コマンドと同じにする)
SPISettings settingsSDI(1000000, MSBFIRST, SPI_MODE0); // データ用(1MHz)
----------------------------------------------------------------------------------
・「アナウンサーの声の後に1kHzのサイン波が数秒鳴り、その後にスイープする」という構成で、
アナウンサーの声までで終わってしまう(Doneが出る)場合、以下の可能性が高いです。
① musicFile.available() が途中で 0 (false) を返している
※ ESP32のSDライブラリにおいて、特定の条件下でファイルの終端(EOF)を誤認することがあります。
・確認方法: playMP3関数のループ内で、bytesReadの累計を表示させてみてください。
ファイルの実際のサイズ(バイト)と、累計読み込みバイト数が一致していますか?
もしサイン波の手前で累計加算が止まって「Done.」が出ているなら、SDカードの読み取りエラーが発生しています。
② SDカードの読み出しエラー(SPI干渉)
・ESP32-S3でSDカードとVS1011Eの両方をSPIで制御している場合、
VS1011Eへのデータ転送(settingsSDI)とSDカードの読み出し(SD.open /
read)が同じバス(SPI2)を共有していませんか?
もし共有している場合、SPI.beginTransactionの設定がSDカード側とVS1011E側で適切に切り替わっていないと、
読み出しデータが化けて「ファイルの終端」と誤認されることがあります。
③ MP3デコーダのバッファ残り
・「Done.」が出た後に sendEndFill() を呼んでいますが、
VS1011Eの内部バッファ(2048サンプル分)にサイン波のデータが残ったまま、再生が完了したとみなされている可能性があります。
対策: playMP3のループが抜けた直後に、
while(!digitalRead(DREQ)); を数回挟んでから、無音データ(0x00)を2048バイト分送ってみてください。
これにより、デコーダ内に残った「最後のサイン波データ」が押し出されて再生されるはずです。
④ 切り分けのための次の一手
・ループ内の bytesRead をシリアル出力して、「本当にファイルの最後まで読み込めているか」を数値で確認するのが一番の近道です。
// ループ内に1行追加して確認
int bytesRead = musicFile.read(buffer, 32);
// Serial.print(bytesRead); // 毎回出すと遅くなるので、1000回に1回出すなど工夫
・もし、読み込みバイト数がファイルサイズに満たないのにDoneが出るようであれば、
SDカード側の設定(SPIクロック速度を落とす等)を検討する必要があります。
※ 「Done.」が出て終了しているということは、
プログラムは「ファイルを全部読み切った」と誤認しているか、
あるいは「データの流し込みは終わったが、VS1011Eの内部バッファで止まっている」かのどちらかです。
明日、以下の2点をサクッと確認されるのが近道かもしれません:
① ファイルサイズの一致: シリアルモニタで、実際に読み込んだ合計バイト数が、PCで見たファイルサイズと同じか。
② 流し込み後の「押し出し」: sendEndFill() で送っている無音データの量が足りず、
最後のサイン波がVS1011Eのバッファに溜まったままになっていないか。
■ 2026.3.10
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・ 現在のMP3再生関数
/***************************************************
* 音楽再生関数
***************************************************/
void playMP3(const char* filename) {
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
unsigned char buffer[32]; // VS1011は32バイト単位の送信が推奨
while (musicFile.available()) { // 返り値:
読み込み可能な残りバイト数(0の場合は読み込み終了):MP3データを全て読込むとループを抜ける
/// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ)); // 32バイト送信毎にDREQをチェックしている
/// SDカードから32バイト読み出す
int bytesRead = musicFile.read(buffer, 32);
/// SDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
// 32バイト送信
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
}
musicFile.close();
Serial.println("Done.");
/// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
------------------------------------------------------------------------------------
・PIC32MXでやった例(SW割込み部は省略してる)
// Out of Music Data As Far as EOF of File
do{
MP3_XDCS_IO = 0;
/// File Read (Unit 256Byte) ※ 256バイト単位送信
result = FSfread(Buffer, 1, 256, fptr);
for( i= 0; i<result; i++) { // Repeat till Number of Reading
Bytes(result)
SendData(Buffer[i]); // Send of Music Data ※ 1バイト送信毎にチェックしてる
}
} while (result != 0); // EOFでない場合繰返す
MP3_XDCS_IO = 1;
/************************************
* VS1011用データ出力関数
************************************/
void SendData(BYTE Data){
while(!MP3_DREQ_IO);
WriteSPI(Data);
}
----------------------------------------------------------------------------------
※ ↑↑↑---- ESP32でのDREQチェックは32バイト送信毎だが、
PIC32でのDREQチェックは1バイト送信毎。
※ この辺が関係しているのでは? PIC32では上手く行っているので、この方式に変えてみる。
DEREQのチェックが甘いのが、たぶん原因。データを取りこぼす。鳴らない原因。
<修正案>
・以下の関数、どこにも使ってない。これを利用してみる。これを使えば1バイト毎のチェックになる。
/***********************************************
* 送信バッファへの送信データ書込み関数:1Byte
* XDCS使用:データレジスタ(SDI)選択
************************************************/
void writeData(unsigned char data) {
while (!digitalRead(DREQ)); // DREQがHighになるまで待機
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW); // SDIセレクト
SPI2.transfer(data); // データ送信
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
}
/***************************************************
* 音楽再生関数
***************************************************/
void playMP3(const char* filename) {
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
unsigned char buffer[32]; // 32バイト単位の送信
while (musicFile.available()) { // 返り値: 読み込み可能な残りバイト数(0の場合は読み込み終了)
// SDカードから32バイト読み出す
int bytesRead = musicFile.read(buffer, 32);
// SDIバスにデータを流し込む
for (int i = 0; i < bytesRead; i++) {
writeData(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
}
musicFile.close();
Serial.println("Done.");
/// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
※ メイン側で、
/// 再生実行
playMP3("/RNG_INFO.mp3"); // SDカード内のファイル名(ルートディレクトリ):周波数スイープテストファイル再生
// MP3ファイル(RNG_INFO.mp3:ビットレート=128kbps・サイズ=440kB)
※ これで上手く行ったら、256byte単位での送信も試してみる。PIC32MXで実績あり。
思えばPIC24Fでやった時、DREQで相当悩んだ記憶あり。
→ 駄目、同じ症状
→ PIC32MXの時と同様に、256byte単位での送信にして、1byte送信毎に、DREQをチェックしてみる。
(32バイトまでならDREQの状態をチェックすることなく連続して送ることが出来るが、32バイトを超えるとDREQチェックは必須となる)
次のように修正
unsigned char buffer[32]; // 32バイト単位の送信-> unsigned char buffer[256]; //
256バイト単位の送信
int bytesRead = musicFile.read(buffer, 32);-> int bytesRead =
musicFile.read(buffer, 256);
→ 駄目、同じ症状
2回修正したが、2回とも元に戻した。
<原因究明開始>
・Copilotからのアドバイスを実行してみる。
① musicFile.available() が途中で 0 (false) を返している
※ ESP32のSDライブラリにおいて、特定の条件下でファイルの終端(EOF)を誤認することがあります。
・確認方法: playMP3関数のループ内で、bytesReadの累計を表示させてみてください。
※ ループ内に1行追加して確認
int bytesRead = musicFile.read(buffer, 32);
// Serial.print(bytesRead); // 毎回出すと遅くなるので、1000回に1回出すなど工夫
ファイルの実際のサイズ(バイト)と、累計読み込みバイト数が一致していますか?
もしサイン波の手前で累計加算が止まって「Done.」が出ているなら、SDカードの読み取りエラーが発生しています。
<結果>
・ファイルの実際のサイズ:440Kbyte
累計読み込みバイト数 :448byte しか読込んでない。-> SDカードの読み取りエラーが発生してる。
<シリアルモニター出力結果>
Sine Test Started...
Sine Test Stopped.
Card Mounted Successfully
Playing...
bytesRead Total =
448
Done.
/************************************************************************
* 音楽再生関数(debug機能付き)bytesReadの累計をシリアルモニターに出力
*************************************************************************/
void playMP3(const char* filename) {
int n = 0;
int count = 0; // debug用
int total; // debug用
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
unsigned char buffer[32]; // VS1011は32バイト単位の送信
while (musicFile.available()) { // 返り値: 読み込み可能な残りバイト数(0の場合は読み込み終了)
// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ));
// SDカードから32バイト読み出す
int bytesRead = musicFile.read(buffer, 32);
// debug用:1000回毎にカウントして32byte単位の送信を何回(count変数)実行したかを
// 知りループを抜けた後、その累計を表示追加(デバッグ用)
// 実際のテスト用MP3ファイルのサイズは、440Kbyte=440000byteであるので、これと比較してみる。
n++;
if (n % 1000 == 0) count++;
// SDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
}
musicFile.close();
/// bytesReadの累計を表示
total = 32 * count;
Serial.println("bytesRead Total = ");
Serial.print(total);
Serial.println(" ");
Serial.println("Done.");
/// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
-----------------------------------------------------------------------------------------------
・次にチェックすべきは、Copilotアドバイスの以下②
② SDカードの読み出しエラー(SPI干渉)
・ESP32-S3でSDカードとVS1011Eの両方をSPIで制御している場合 、
VS1011Eへのデータ転送(settingsSDI)とSDカードの読み出し(SD.open /
read)が同じバス(SPI2)を共有していませんか?
→ 制御ピン設定の流れを追ってみた。
① まずメインで以下のように、SD_CSをアクティブ
/// SDカードの初期化時にCSピンを指定しマウントする。ここで、SD_CSがアクティブになる
if (!SD.begin(SD_CS, SPI2, 40000000)) { // SPI2を共有バスとして渡す
② その後、MP3再生関数を実行
/// 再生実行
playMP3("/RNG_INFO.mp3"); // SDカード内のファイル名(ルートディレクトリ):周波数スイープテストファイル再生
※ ここでは、VS1011のXDCSをアクティブにしているが、その前に、SD_CSをノンアクティブにしてなかった。
その後、にもう一度、SD_CSにアクセスしてるので、その前にVS1011のXDCSをノンアクティブにする必要がある。
その後、再度VS1011にアクセスしてるので、SD_CSをノンアクティブにする必要がある。
<修正案>
void playMP3(const char* filename) {
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
unsigned char buffer[32]; // VS1011は32バイト単位の送信が推奨
while (musicFile.available()) {
// DREQを読込み、DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ));
// SDカードから32バイト読み出す
int bytesRead = musicFile.read(buffer, 32);
// ※ 追加:VS1011のXDCSをアクティブにする前にSD_CSをノンアクティブにする。
digitalWrite(SD_CS, HIGH);
// SDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
// ※ 追加:次の32バイトを読む前にSD_CSをアクティブにする。
digitalWrite(SD_CS, LOW);
}
musicFile.close(); // 音楽ファイルクローズ
Serial.println("Done.");
// ※ 追加:sendEndFill()実行前に、SD_CSをノンアクティブ、XDCSをアクティッブにする。
digitalWrite(SD_CS, HIGH);
digitalWrite(XDCS, LOW);
// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
/***************************************************
* 音楽ファイル送信終了処理関数
***************************************************/
void sendEndFill() {
for (int i = 0; i < 2048; i++) {
while (!digitalRead(DREQ));
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
SPI2.transfer(0x00);
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
}
}
<参考:データシート:7.4 データ・リクエスト端子DREQ>
・DREQ端子/信号は,VS1011eの内蔵FIFOがデータ受信可能であることを示します。
※ DREQが"H"ならば,最低32バイトのSDIデータまたはSCIコマンド1個の入力が可能です。
この条件が満たされない時、DREQは"L"になります.この時送り手は新たなデータ転送を開始してはいけません。
※ 32バイトの安全領域があるため,32バイトまでならDREQの状態をチェックすることなく連続して送ることが出来ます。
これは低速なマイクロコントローラによるVS1011eの制御をより容易にします.
注意-1:DREQはバイトの転送途中などを含め不特定のタイミングでレベルが変化します。
したがってDREQは、次のバイト・データを送出できるかどうかを決めるためだけに使用してください。
すでに開始したバイト転送を中断してはいけません。
注意-2:VS1002以前のVS10XX製品において,DREQはSDI専用でした。VS1011eでは、DREQはSCIのステータス表示としても使用されます。
■ 2026.3.11
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
<今更だが疑問が?:VS1011のDREQピン出力はXCS, XCSと無関係か?Copilotに聞いてみた>
<Copilot>
・VS1011のDREQ(Data Request)ピンの出力は、
XCS(シリアル・コントロール・チップ・セレクト)やXDCS(シリアル・データ・チップ・セレクト)が
アクティブ(Low)かどうかに直接は依存せず、本質的には無関係です。
※ 具体的には、DREQはチップセレクト信号の状態とは無関係に、内部の入力バッファ(FIFO)の空き状況のみを示します。
(1) DREQピンの動作原理
① 機能: VS1011のストリームバッファにデータを送信できるか(または命令を実行できるか)をMCUに伝えます。
② Active High: DREQがHighのとき、データ受入可能(バッファに余裕がある)
Lowのとき、受入不可能(バッファが満杯)
③ チップセレクトとの独立性: データライン(SDI)がアクティブ(XDCS=Low)かどうかにかかわらず、
バッファの残量が減ればDREQは自動的にHighになり、バッファが一杯になればLowになります。
(2) XCS/XDCSとの関係
① XCS(Control):
レジスタ設定用。XCSがHighであっても、デコーダはバックグラウンドでオーディオデータを処理し、DREQを変化させます。
② XDCS(Data): オーディオデータ送信用。XDCSがHigh(データ転送停止)であっても、
バッファに残っているデータが処理されればDREQはLowからHighに復帰します。
(3) 実装上の注意点
① データ転送時(XDCS=Low)は、DREQ=Highを確認してからバイト/ワードを送信します。
② DREQがLowになったら、すぐさま転送を停止してHighに戻るのを待つ必要があります。
※ 結論として、DREQピンの出力は内部のバッファ状態に依存しており、XCS/XDCSの状態に左右されません。
---------------------------------------------------------------------------------------------------------
・昨日の修正案試してみた。
→ 同じ症状で変わらず
以下も変わらず
bytesRead Total =
448
Done.
明日に続く
■ 2026.3.12
<疑問点:Copilotに聞いてみた>
<私>
・今晩は。
またAIエラーで直近の内容が見れなくなったので、途切れた所から再開したいと思います。
ESP32S3 Dev Module+SDカード+ 011の構成でBASIC TESTのステッチを書いて動作確認をしているところです。
<内容>
・SDカードから、1つのMP3ファイル(サイズ:440Kbyte)を読出し1回だけ再生して終了するスケッチです。
<結果>
・「アナウンサーの声の後に1kHzのサイン波が数秒鳴り、その後に周波数スイープする」という内容のMP3データ
なのですが、アナウンサーのインフォメーションボイスまでで終わってしまいます。尚、スケッチの最後に
シリアルモニターに"Done"を出しているのですが、出ているので、スケッチは最後まで走っているようです。
そこで、32byteReadの累計を計算させシリアルモニターに出力させたところ、448byteしか読込んでないことが分かりました。
そこで、SDとVS1011のCSのコントロールを、それぞれにアクセスする前に、目的外のデバイスのCS=HIGHにしてからアクセス
するように修正しましたが、症状は変わりませんでした。(SDとVS1011は、SPIバスを共有してます。)
一つ聞きたいことがあります。
SDカードからMP3データを読込む前にも、SPI2.beginTransaction(settingsSDI);で開始し
SD_CSをLOWにし、読込み実行後、SD_CSをHIGHにし、SPI2.endTransaction();で終了する必要がありますか?
尚、スケッチの頭で、SPISettings settingsSDI(4000000, MSBFIRST, SPI_MODE0); //
データ用(4MHz)
と定義してます。
<Copilot>
※ 440,000バイト中448バイトで止まってしまうのは、ちょうどSDカードの1セクター(512バイト)弱で通信が途切れているような挙動ですね。
・ご質問の「読み込みのたびに beginTransaction / endTransaction が必要か」という点ですが、
はい、SPIバスを共有している場合は、デバイスを切り替えるたびに毎回行うのが基本ルールです。
<理由>
・設定の保護:
beginTransaction は、そのデバイス用の通信速度やモード(SPI_MODE0など)を確実に適用し、
他の割り込みに設定を書き換えられないように保護します。
CS制御のタイミング: 本来、beginTransaction の直後にCSをLOWにし、
CSをHIGHに戻した直後に endTransaction を呼ぶのが最も安全な作法です。
<修正のイメージ>
// --- SDカードから読み出す時 ---
SPI2.beginTransaction(settingsSD); // SD用の設定(通常はVS1011より高速)
digitalWrite(SD_CS, LOW);
// ここで file.read(buf, 32) など
digitalWrite(SD_CS, HIGH);
SPI2.endTransaction();
// --- VS1011へ送る時 ---
SPI2.beginTransaction(settingsSDI); // VS1011(SDI)用の設定
digitalWrite(VS_XDCS, LOW);
// ここで SPI2.transfer(buf, 32)
digitalWrite(VS_XDCS, HIGH);
SPI2.endTransaction();
<448バイトで止まるもう一つの可能性>
・もし上記を試しても改善しない場合、VS1011のDREQピン(データ要求ピン)の状態を確認していますか?
① VS1011は内部バッファがいっぱいになるとDREQをLOWにします。
② DREQがLOWの間にデータを送り続けると無視されるか、通信がハングしたようになります。
③「DREQがHIGHであることを確認してから32バイト送る」という処理が入っているか、今一度チェックしてみてください。
※ ③はやってあるので、やはりSPIバスの占有権(Transaction)の切り替えが怪しいです。
SDカードのライブラリ(SD.hなど)は内部で独自のSPI設定を持っており、VS1011(SDI通信)の設定と競合している可能性があります。
<次のステップとして試してほしいこと>
・まずは、ご質問にあった「読み込みごとのTransaction」を以下のように構成してみてください。
特にSDカード側の読み込みをTransactionで囲むのがポイントです。
// メインループ内のイメージ
while (file.available()) {
// 1. DREQがHIGHになるまで待つ
while (digitalRead(DREQ_PIN) == LOW) {
delayMicroseconds(10);
}
// 2. SDカードから32バイト読み出す
SPI2.beginTransaction(settingsSD); // SD用の設定(通常20MHzなど高速)
digitalWrite(SD_CS, LOW);
int bytesRead = file.read(buffer, 32);
digitalWrite(SD_CS, HIGH);
SPI2.endTransaction();
// 3. VS1011へ32バイト送る
SPI2.beginTransaction(settingsSDI); // VS1011用の設定 (4MHz, MODE0)
digitalWrite(VS_XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(VS_XDCS, HIGH);
SPI2.endTransaction();
// 読み込み累計の更新
totalRead += bytesRead;
}
<448バイトで止まる原因の推測>
・SDカードの標準的なセクタサイズは512バイトです。
448バイトで止まっているということは、「最初の1ブロック分をバッファに読み込んだ後、
次のブロックを読みに行く際にSPIの同期が取れず、ファイルポインタが止まっている」という状況が推測されます。
※ 特にDREQの状態をシリアルモニターで出す際は、ループごとに出すとログが埋まってしまうので、
「DREQ待ちで1秒以上止まったら警告を出す」といった書き方にするとデバッグしやすいですよ。
<SDカードの読み出し部分でTransactionを使う際の注意点をひとつだけ補足させてください。>
・もし標準の SD.h ライブラリ(Arduino公式系)を使っている場合、
内部で勝手に SD_CS を操作したり、独自のSPI設定で通信を完結させようとしたりすることがあります。
そのため、もし file.read() を自前の beginTransaction で囲んでも読み込みバイト数が変わらない場合は、
「SDカードから一度に512バイト(またはそれ以上)まとめて大きなバッファに読み込み、
それを32バイトずつ小分けにしてVS1011へ送る」という形にすると、SPIバスの切り替え頻度が減って安定しやすくなります。
<もしテスト中に「やっぱり448バイト付近で止まってしまう」となった場合は、原因の切り分けのために以下の2点をチェックしてみてください>
① SDカードの速度を落としてみる: SD.begin(SD_CS, SPI2, 4000000)(4MHz)など、あえて低速で初期化して安定するか。
② DREQの極性: VS1011のDREQは「HIGHの時に送信OK」ですが、配線やプルアップの状況で挙動が変わっていないか。
※ 以前のPICでの経験(DREQの挙動など)を活かしたハードウェア対策が済んでいるのであれば、
あとはソフトウェア側のSPI共有の作法(Transaction)で解決する可能性が非常に高いです。
■ 2026.3.13
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・ 昨晩のCopilotのアドバイス試してみる。
<次のステップとして試してほしいこと>
・まずは、ご質問にあった「読み込みごとのTransaction」を以下のように構成してみてください。
特にSDカード側の読み込みをTransactionで囲むのがポイントです。
/// Main 冒頭:私の考え
/// SPI2(FSPI)の名前を「SPI2」に統一して定義
SPIClass SPI2(FSPI);
/// SPI2のMODE設定をグローバルに定義(VS1011全関数で使用可能)
// SPI設定: 初期は低速(1MHz)で開始
SPISettings settingsSCI(1000000, MSBFIRST, SPI_MODE0); // コマンド用(1MHz)
// CLOCKF設定後は高速(4MHz)に切り替え
SPISettings settingsSDI(4000000, MSBFIRST, SPI_MODE0); // データ用(4MHz)
/// SPI2のMODE設定をグローバルに定義(SDカード全関数で使用可能)追加:260313
SPISettings settingsSD(20000000, MSBFIRST, SPI_MODE0); // 20MHz
// メインループ内のイメージ
while (file.available()) {
// 1. DREQがHIGHになるまで待つ
while (digitalRead(DREQ_PIN) == LOW) {
delayMicroseconds(10);
}
// 2. SDカードから32バイト読み出す
SPI2.beginTransaction(settingsSD); // SD用の設定(通常20MHzなど高速)
digitalWrite(SD_CS, LOW);
int bytesRead = file.read(buffer, 32);
digitalWrite(SD_CS, HIGH);
SPI2.endTransaction();
// 3. VS1011へ32バイト送る
SPI2.beginTransaction(settingsSDI); // VS1011用の設定 (4MHz,
MODE0)
digitalWrite(VS_XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(VS_XDCS, HIGH);
SPI2.endTransaction();
// 読み込み累計の更新
totalRead += bytesRead;
}
→ コンパイルは一発OK
→ 書込み実行してみた。
→ SINE TESTはOKだが、その後、"Playing..."が出るが再生しない。
entry 0x403c88b8
Sine Test Started...
Sine Test Stopped.
Card Mounted Successfully
Playing...
DREQ = OK
<pic32の例>
/*********************************************
* 再生終了処理関数
*
*********************************************/
void PlayEnd(void){
int i;
/* 再生バッファクリアのため2048バイト送信 */
MP3_XDCS_IO = 0; // XDCSオン
for(i=0; i<2048; i++)
SendData(0); // 0のデータ連続送信
MP3_XDCS_IO = 1; // XDCSオフ
/*** ソフトウェアリセットで完了 **/
Command(0x00, 0x08, 0x24); // SM_RESET送信
while(!MP3_DREQ_IO); // コマンド完了待ち
}
/************************************
* VS1011用コマンド出力関数
************************************/
void Command(BYTE RegAdrs, BYTE UpData, BYTE LowData){
MP3_XDCS_IO = 1; // データモード解除
while(!MP3_DREQ_IO); // レディー待ち
MP3_XCS_IO = 0; // コマンドモード設定
WriteSPI(0x02); // 書き込み動作指定
WriteSPI(RegAdrs); // レジスタアドレス出力
WriteSPI(UpData); // 上位バイト出力
WriteSPI(LowData); // 下位バイト出力
MP3_XCS_IO = 1; // コマンドモード解除
}
■ 2026.3.14
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
<Copilotに聞いてみた(新しいスレッドにした)>
<私>
・SD.begin(SD_CS, SPI2, 20000000)はSDカードの初期化時にCSピンを指定しマウントする命令文だと思うのですが、
これを実行すると、自動的にSD_CSがLOWアクティブになると考えて良いですか?
後、SDからデータ読みだす際は、以下のようなステッチ(概要)で良いでしょうか?
SPI2.beginTransaction(settingsSD);
digitalWrite(SD_CS, LOW);
// ここで file.read(buf, 32) など
digitalWrite(SD_CS, HIGH);
SPI2.endTransaction();
<Copilot>
(1) SD.begin() と CSピンの挙動
・はい、その認識で間違いありません。
SD.begin() を実行すると、指定した SD_CS ピンは「出力モード」に設定され、
通信開始時に自動的に LOW(アクティブ) に制御されます [1, 2]。
※ 注意:ただし、SD.begin() は内部で初期化コマンドを送る際にのみ CS を制御します。
その後の読み書き時に CS を自分で手動制御する必要があるかどうかは、次の項目の通りです。
(2) データ読み出しのステップについて
※ 結論から言うと、Arduinoの標準的な SD.h ライブラリを使用する場合、
提示されたような digitalWrite による手動の CS 制御や beginTransaction は通常不要 です [2, 3]。
ライブラリ内部(file.read() など)で、すでに SPI 設定の適用や CS ピンの LOW/HIGH 制御が完結しているためです。
→ 自分の考え:つまり、SPIの対応デバイスが、SDカードのみの場合は、これで良いと考える。
※ 推奨されるシンプルな書き方:
// 初期化(一度だけ)
if (!SD.begin(SD_CS, SPI2, 20000000)) {
// エラー処理
}
// 読み出し
File file = SD.open("test.txt");
if (file) {
file.read(buf, 32); // この内部でCS制御やSPI設定が自動で行われる
file.close();
}
------------------------------------------------------------------------------------------
① 手動制御(提示されたコード)が必要になるケース
※ もし以下のような特殊な状況であれば、提示されたような「手動制御」が必要になります。
①-1.独自のSPIデバイスと混在させていて、ライブラリを介さず直接 SPI.transfer() を叩く場合
①-2.標準の SD.h ではなく、非常に低レイヤーな自作関数を使っている場合
※ 通常の利用であれば、file.read() を呼ぶだけで、ライブラリが自動的に SD_CS を LOW にし、データを読み終えたら HIGH
に戻してくれます。
<Copilot>
・次に進めるために、以下の点を確認させてください。
① 今回、複数のSPIデバイス(液晶ディスプレイや他のセンサーなど)を同時に使っていますか?
② 使用しているマイコン(ESP32、Arduino Uno、RP2040など)は何でしょうか?(それにより SPI2 の定義や挙動が変わるため)
・状況に合わせて、より最適な排他制御(セマフォなど)が必要かアドバイスできます。
<私>
・液晶とVS1011をSPIで使用してます。ESP32S3 DEV MODULEです。
<Copilot>
・非常にデリケートな SPIバスの共有(排他制御) が必要な構成です。
(1) SD.begin() 実行時の挙動
① SD.begin(SD_CS, ...) を実行すると、
指定した SD_CS ピンは自動的に OUTPUTモード に設定され、初期化プロセス中に LOW(アクティブ) に制御されます。
② 初期化が成功した後は、通常 HIGH(非アクティブ)に戻ります。
③ ESP32-S3 では SPI ピンを自由にアサインできるため、SD.begin 前に
SPI2.begin(SCK, MISO, MOSI) でピンを確定させておくのが一般的です。
(2) データ読み出し手順について
※ 提示された「手動で CS を制御する」コードは、
「VS1011 や液晶と同じ SPI バス(SPI2)を共有している場合」は正解に近いですが、一部注意が必要です。
(※ 私は、これに相当)
① ライブラリ標準の挙動
※ 通常、file.read() などの関数を呼ぶと、ライブラリ内部で自動的に以下の処理が行われます。
① SPI設定(クロック周波数など)の適用 (beginTransaction)
② SD_CS を LOW にする
③ データ転送
④ SD_CS を HIGH にする
⑤ SPIバスの解放 (endTransaction)
② 複数のデバイスを共存させる場合
※ VS1011 や液晶が同じ SPI バスに繋がっている場合、ライブラリに任せるだけで基本的には動作します。
しかし、VS1011 のように「常にデータを送り続ける必要がある」デバイスや、
割り込み(Interrupt)を使って SPI 通信を行うデバイスが混在する場合、手動での制御が安全策となります。
----------------------------------------------------------------------------------------------------------------
<※ 修正した推奨ステップ:これすごく重要・これを参考に適応すればよい感触>
// 1. SDカードの読み込み設定(VS1011や液晶とは異なる設定かもしれないため)
SPI2.beginTransaction(settingsSD);
// 2. 他のデバイス(VS1011のDCSや液晶のCS)が確実に HIGH であることを確認
// ※本来は各ライブラリが制御しますが、不安定な場合は明示的に書くこともあります
// 3. SDの読み出し(SD.h を使う場合、内部で SD_CS が制御されます)
file.read(buf, 32);
// 4. トランザクション終了
SPI2.endTransaction();
-------------------------------------------------------------------------------------------------------------
(3) ESP32-S3 での注意点
① VS1011 の挙動:
・VS1011 は SCI(制御)と SDI(データ)の2つの CS ピンを持ちます。
・SD カードからデータを読んでいる間、VS1011 の CS ピンが確実に HIGH に固定されている必要があります。
② バスの速度:
・SD カードは 20MHz で動かせても、VS1011 や液晶の制限でバス全体の速度を落とさなければならない場合があります。
・beginTransaction を使うことで、デバイスごとに最適な速度に切り替えてくれます。
---------------------------------------------------------------------------------------------------------------
<Copilot>
・次に確認すべきこと:
① 現在、「音が途切れる」や「画面表示が乱れる」といった具体的な問題は起きていますか?
② VS1011 のライブラリは何を使用されていますか?
(ライブラリによっては、SPIバスを占有してしまい SD と共存させるのに工夫が必要な場合があります)
<私>
・途中でMP3ファイルの再生が止まります。ほぼ1セクタ分しか再生されてないようです。
ライブラリの3つをインクルードしてます。
#include <SPI.h>
#include <SD.h
#include "FS.h"
・VS1011は独自に書いてます。
<Copilot>
(1) VS1011を独自実装し、かつESP32-S3でSDカードとバスを共有している場合、
「1セクタ分(約512バイト)で止まる」のは、典型的なSPIバスの競合
(排他制御の失敗)またはVS1011のDREQ(データリクエスト)待ちの不備が原因と考えられます。
原因を特定するために、以下の3点を確認・修正してみてください。
① SPIトランザクションの徹底(最重要)
② SDカードの file.read() を呼ぶ際、内部でSPIの設定が「SDカード用」に書き換わります。
③ VS1011へデータを送る際にも、必ず独自の SPISettings で囲っていますか?
// VS1011用の設定(例:8MHzなど、SDより遅い場合が多い)
SPISettings settingsVS1011(8000000, MSBFIRST, SPI_MODE0);
void sendDataToVS1011(uint8_t* data, size_t len) {
SPI2.beginTransaction(settingsVS1011); // これがないとSDの設定のまま動く可能性がある
digitalWrite(VS1011_XDCS, LOW);
for(int i=0; i<len; i++) {
SPI2.transfer(data[i]);
}
digitalWrite(VS1011_XDCS, HIGH);
SPI2.endTransaction(); // これでバスを解放
}
(2) DREQ ピンの監視
※ VS1011は内蔵バッファ(通常2KB程度)がいっぱいになると、DREQピンをLOWにして「送るな」と合図します。
1セクタ(512B)程度で止まる場合、
SDから読み出した次のデータを、DREQがHIGHになるのを待たずに送り続けて、
VS1011側がハングアップしている可能性があります。
① SDから32バイト読む
② DREQがHIGHになるまで待つ(重要!)
③ VS1011に32バイト送る
④ これを繰り返す
という流れになっているか確認してください。
(3) CSピンの浮き(干渉)
※ ESP32-S3は高速なため、SDカードの初期化後に SD_CS が一瞬でも不安定になると、
SDカードがバスを占有し続けようとすることがあります。
① SD.begin() の直後、念のため digitalWrite(SD_CS, HIGH); を明示的に行っていますか?
② 液晶の CS ピンも、SD/VS1011 通信中は確実に HIGH になっていますか?
--------------------------------------------------------------------------------------------------
(4) 切り分けのための質問:
① DREQピンの状態をチェック(digitalRead)しながらデータを送っていますか?
② SDカードから読み出したデータを、一旦シリアルモニタに表示するだけ(VS1011には送らない)にした場合、
ファイル最後まで読み込みは継続しますか?
※ もし「シリアル表示だけなら最後まで行く」のであれば、
原因は VS1011 送信時の SPI 設定(Transaction)不足 か DREQ無視 のどちらかである可能性が非常に高いです。
--------------------------------------------------------------------------------------------------
<私>
・DREQピンの状態をチェック(digitalRead)しながらデータを送っていますか?-> してます。
2はまだ試してません。
分かりました。後で試してみます。
これから出かけるので、ここらで一旦失礼します。また結果が出たら報告します。
<Copilot>
・承知いたしました!お忙しい中、ご確認ありがとうございます。
まずは「2. VS1011へ送らずに、SDカードの読み込み(シリアル出力)だけなら最後まで完走するか」を試すことで、
問題がSDカード側にあるのか、VS1011とのSPI切り替えにあるのかがハッキリします。
・もしシリアル出力が完走するなら、原因はSPIバスの共有設定(Transaction)やCSピンの干渉に絞られます。
・戻られた際にスムーズに進めるため、以下の点だけ心に留めておいていただければと思います:
① SD.begin 後に digitalWrite(SD_CS, HIGH); を明示的に入れているか
② VS1011への送信関数を SPI2.beginTransaction で囲っているか
結果報告、お待ちしております!
-------------------------------------------------------------------------------------------------
<新たなスレッドで聞いてみた>
<私>
・
Arduino IDEで、Serial.println();とSerial.print();を使ってSDカードから読み出し
たMP3ファイルの
ファイルダンプをシリアルモニターに表示したいのですが、
その方法を教えてください。
while (musicFile.available()) {}のループ内でint bytesRead =
musicFile.read(buffer, 32);
として32バイト単位で読み取っています。
<Copilot>
※ SDカードから読み出したMP3データをシリアルモニターに「ファイルダンプ(16進数表示)」として表示するには、
Serial.print() の第2引数に HEX を指定します。
また、1桁の数値(0x00〜0x0F)が1文字で表示されてズレるのを防ぐため、0 を補完する処理を入れるときれいに整列します。
(1) 実装コードの例
・ご提示の while ループ内での書き換え例です。
while (musicFile.available()) {
byte buffer[32];
int bytesRead = musicFile.read(buffer, 32);
for (int i = 0; i < bytesRead; i++) {
// 16進数で表示する際、1桁なら頭に '0' を付けて2桁に揃える
if (buffer[i] < 0x10) {
Serial.print("0");
}
Serial.print(buffer[i], HEX); // 16進数(HEX)で出力
Serial.print(" "); // 見やすくするためにスペースを入れる
}
Serial.println(); // 32バイトごとに改行
}
(2) ポイント解説
① Serial.print(value, HEX): データを16進数の文字列として送信します。
② ゼロパディング: Serial.print は 0x0A を単に A と表示するため、
if (buffer[i] < 0x10) を使って 0A となるように調整しています。
③ 改行のタイミング: bytesRead 分のループが終わった後に Serial.println() を呼ぶことで
指定した32バイト単位で1行にまとめることができます。
<注意点>
① MP3ファイルはサイズが大きいため、すべてのデータを表示するとシリアルモニターがフリーズしたり、
転送に非常に時間がかかったりします。
動作確認が目的であれば、最初の数百バイトだけを表示してループを抜ける(break;)などの制限をかけるのがおすすめです。
② 表示のレイアウト(1行あたりのバイト数など)をさらに細かく調整したい、
あるいはテキスト(ASCII)表示も横に並べたいといったご要望はありますか?
--------------------------------------------------------------------------------------------------------------------------------
<私>
・ありがとうございました。これを参考に試してみます。
■ 2026.3.15
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・昨晩のCopilotのアドバイス試してみた。
→ Copilotからの注意点
・戻られた際にスムーズに進めるため、以下の点だけ心に留めておいていただければと思います:
① SD.begin 後に digitalWrite(SD_CS, HIGH); を明示的に入れているか
② VS1011への送信関数を SPI2.beginTransaction で囲っているか-> 囲んでる。
①は明示的にいれてなかったので入れた。
→ 上手く行かない。
女性アナウンサーのインフォメーションボイスまでは正常に鳴るが、そこで鳴らなくなる。
読込んだバイト数は、448バイトしかない。
明日に続く。
■ 2026.3.16
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
① 以下のようにコメントアウト
while (musicFile.available()) { // 返り値: 読み込み可能な残りバイト数(0の場合は読み込み終了)
// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ)) {
// delayMicroseconds(10); ← ここコメントアウトしてみる。
}
② playMP3(const char* filename) 関数内のDebug用に記述した箇所を以下のように修正
<修正前>
/***************************************************
* 音楽再生関数
***************************************************/
void playMP3(const char* filename) {
int n = 0; // debug用
int count = 0; // debug用
int total; // debug用
while (musicFile.available()) { // 返り値: 読み込み可能な残りバイト数(0の場合は読み込み終了)
// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ)) {
// delayMicroseconds(10);
}
/// 以下のコメントは、私の考えで、たぶんこれで良いと思う。後で上手く行かなかったら修正する。
/// SDカードから32バイト読み出す
/// musicFile.read(buffer, 32)を実行すると、
/// 指定した SD_CS ピンは自動的に OUTPUTモード に設定され、
/// SDカード読出しプロセス中に LOW(アクティブ) に制御され、
/// 初期化実行後、SD_CSピンはHIGIH(ノンアクティッブ)になるはずだが、明示的に入れておく。
int bytesRead = musicFile.read(buffer, 32);
digitalWrite(SD_CS, HIGH);
/// debug用:1000回毎にカウントして32byte単位の送信を何回(count変数)実行したかを
/// 知りループを抜けた後、その累計を表示追加(デバッグ用)
/// 実際のテスト用MP3ファイルのサイズは、440Kbyte=440000byteであるので、これと比較してみる。
n++;
if (n % 1000 == 0) count++;
・
・
・
}
/// bytesReadの累計を表示
total = 32 * count;
Serial.println("bytesRead Total = ");
Serial.print(total);
・
・
}
<修正後>
/***************************************************
* 音楽再生関数
***************************************************/
void playMP3(const char* filename) {
// int n = 0; // debug用
long count = 0; // debug用
int total; // debug用
while (musicFile.available()) { // 返り値: 読み込み可能な残りバイト数(0の場合は読み込み終了)
// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ)) {
// delayMicroseconds(10);
}
/// 以下のコメントは、私の考えで、たぶんこれで良いと思う。後で上手く行かなかったら修正する。
/// SDカードから32バイト読み出す
/// musicFile.read(buffer, 32)を実行すると、
/// 指定した SD_CS ピンは自動的に OUTPUTモード に設定され、
/// SDカード読出しプロセス中に LOW(アクティブ) に制御され、
/// 初期化実行後、SD_CSピンはHIGIH(ノンアクティッブ)になるはずだが、明示的に入れておく。
int bytesRead = musicFile.read(buffer, 32);
digitalWrite(SD_CS, HIGH);
/// 1000回に1回は必要ない。
/// debug用:VS1011がReadyの場合(DREQ=HIGH)にSDからMP3データを読み出すので、
/// 読み出した回数をカウントして32byte単位の送信を何回(count変数)実行したかを
/// 知りループを抜けた後、その累計を表示追加(デバッグ用)
/// 実際のテスト用MP3ファイルのサイズは、440Kbyte=440000byteであるので、これと比較してみる。
count++;
・
・
・
}
/// bytesReadの累計を表示
total = 32 * count;
Serial.println("bytesRead Total = ");
Serial.print(total);
・
・
}
<結果>
Sine Test Started...
Sine Test Stopped.
Card Mounted Successfully
Playing...
bytesRead Total =
451232
Done.
・上モニター結果の通り、近いファイルサイズ値:451232byteが出たが、pcで読んだ値:440000byteより多い。
理由は分からない。mp3データ自体は全て読込んでるのかもしれない。今は深追いしない。
■ 2026.3.17
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
<参考:AI検索>
(1) musicFile.available() 関数について
・musicFile.available() は、一般的にプログラミング(特にArduinoやJavaのInputStreamなど)において、
読み込み可能なデータ量(バイト数)をチェックする関数です。
データ確認: musicFile(音楽ファイル)から現在読み込めるデータがどれくらいあるかを取得する。
・ブロックの回避:
read()関数を呼び出す前に、データが実際に到着しているか(available > 0)を確認し、
データがない場合にプログラムが停止(ブロック)するのを防ぐ。
※ よくある利用シーン(Arduinoなどで音楽ファイルを読み込む場合):
if (musicFile.available() > 0) {
// 読み込めるデータがある場合のみ処理を行う
byte b = musicFile.read();
// データを処理する
}
※ この命令単体では、音楽の再生状態やバッファの状態を管理するために使われます。
(2) 関数:available() の詳細
・機能: ファイルの現在の読み取り位置から、ファイルの終端(EOF)までに残っているバイト数を返します。
・引数なし:musicFile.available() のように、括弧内には何も記述しません。
・戻り値 :読み込み可能なバイト数(int 型または unsigned long 型など、ライブラリの実装による)。
・ファイルの終端に達しており、読み込めるデータがない場合は 0 を返します。
(3) 主な利用シーン
・主に、ループ処理の中で「データがまだある間だけ読み込む」といった条件判定に使われます。
// 例: ファイルの中身をすべて読み込む
while (musicFile.available()) {
char c = musicFile.read(); // 1バイトずつ読み込む
Serial.print(c);
}
(4) 注意点
① ファイルが開いている必要がある: 呼び出す前に必ず SD.open() などでファイルが正常に開かれているか確認してください。
② 読み取り位置の影響: seek() 関数などで読み取り位置を移動させると、available() が返す値(残りバイト数)もそれに応じて変化します。
<Copilot>
・特定のプログラミング言語や別のライブラリ(ProcessingのMinimなど)における関数の詳細を知りたい場合は、その旨を教えていただけますか?
特定のライブラリや開発環境(例:Arduino, Processing, C++等)は決まっていますか?
<私>
・Arduino IDEを使ってます。
<Copilot>
※ Arduino IDEでSDカード内のファイルを扱う際、musicFile.available() 関数は、
「ファイルの現在の位置から終端までに、あと何バイトのデータが残っているか」を確認するために使用されます。
(1) 引数について
① 引数はありません。
② 記述例:musicFile.available()
③ 括弧 () の中には何も入れずに呼び出します。
(2) 詳細仕様
① 機能: 読み取り可能な残りバイト数を返します。これは Stream クラスから継承された共通の機能です。
② 戻り値
・データがある場合: 残りのバイト数(int 型として返されますが、大きなファイルを扱う場合は事実上読み取り可能な量を示します)。
・終端(EOF)に達した場合: 0 を返します。
③ 役割: 主に while 文や if 文の条件式として使い、「まだ読み込めるデータがあるか?」を判定するのに最適です。
(3) スケッチでの例
※ ファイルを1バイトずつ最後まで読み取り、シリアルモニタに表示する一般的なコードです。
#include <SD.h>
File musicFile;
void setup() {
Serial.begin(9600);
SD.begin(4); // CSピン番号は環境に合わせて変更してください
musicFile = SD.open("music.txt"); // ファイルを開く
if (musicFile) {
// 読み取り可能なデータがある間、ループを繰り返す
while (musicFile.available()) {
char c = musicFile.read(); // 1バイト読み込む
Serial.write(c); // シリアルモニタに出力
}
musicFile.close(); // 読み終わったら必ず閉じる
}
}
void loop() {
// 処理なし
}
(4) 注意点
① ファイルの状態: available() を呼ぶ前に、必ず SD.open() が成功しているか(if (musicFile)
などで)確認してください。
② 読み取り位置: seek() 関数などで読み取り位置を強制的に移動させると、その位置から終端までのバイト数に値が変化します。
<Copilot>
・この関数を使って、具体的に「音楽再生の終了判定」や「データの解析」など、どのような処理を行いたいとお考えですか?
<私>
・現在、この関数を使って1つのMP3ファイルをSDから読み出し、
それをVS1011に送って再生して止まるだけのスケッチを試しているのですが、再生が途中までで止まっている状態です。
ハードは、同じSPIラインにSDとVS1011及びが接続されてます。
これを調査しているところです。
提示して頂いたシリアルモニターに読み出したバイトデータを表示する方法で、
どこまで正常に読みだされているか確認するのも一手ですね。
<Copilot>
※ 同じSPIバスにSDカードとVS1011を共存させている構成では、データの読み込みと転送の切り替えタイミングや、
電気的な干渉が原因で再生が止まることがよくあります。
musicFile.available() を使って状況を調査する際、以下のポイントをチェックしてみてください。
(1) available() の値を確認する(デバッグ手法)
※ シリアルモニタで進捗を確認するのは非常に有効です。
ただし、毎バイト表示すると処理が追いつかず再生が余計に乱れるため、
「1KBごと」や「数秒ごと」に、残りのバイト数を表示させるのがおすすめです。
// 1024バイト読み込むごとに進捗を表示する例
static uint32_t counter = 0;
if (counter % 1024 == 0) {
Serial.print("Remaining bytes: ");
Serial.println(musicFile.available());
}
counter++;
※ もし available() がまだ大きな値を返しているのに音が止まるなら、
SDカードではなくVS1011側(転送処理)に問題がある可能性が高いです。
(2) SPI共存時によくある原因と対策
※ ハードウェアが同一ラインにある場合、以下の3点を特に確認してみてください。
① SPI設定の競合 (SPI Transactions)
・SDライブラリとVS1011用の処理で、SPIのモードやスピードが異なる場合があります。
最近の標準的なライブラリ(SD.hなど)は SPI.beginTransaction() を使って自動的に切り替えてくれますが、
古いサンプルコードや自作の転送関数の場合、SDを読み終えた後にSPIの設定をVS1011向けに戻していないと、
データが化けて再生が止まることがあります。
② DREQピンの監視
・VS1011には「データを受け入れ可能か」を示す DREQ ピンがあります。
SDから読み出したデータを送る際、DREQが HIGH であることを確認してから送っていますか?
DREQを無視して送り続けると、VS1011のバッファが溢れてハングアップ(停止)します。
③ チップセレクト(CS)の制御
・SDカードのCSとVS1011のCS(およびXCS/XDCS)が、データ転送のたびに正しく排他制御されているか確認してください。
両方が同時に LOW になるとデータが衝突します。
(3) available() 自体の注意点
※ 古いArduino IDE環境(バージョン1.0以前など)を使用している場合、
available() が返す型が int で、ファイルサイズが32KBを超えるとオーバーフローして正しく判定できないという
古いバグが報告されていた時期がありました。
現在のIDEであれば修正されていますが、念のため最新版の Arduino IDE を使用することをお勧めします。
<Copilot>
・現在のスケッチで、SDから読み出すデータサイズ(バッファサイズ)は何バイトに設定していますか?(例:32バイトずつ送っている、など)
<私>
・32バイト毎です。
<Copilot>
(1) デバッグ用のコード挿入案
※ シリアル表示で処理が重くならないよう、
「1000回(約32KB)転送するごと」に残りバイト数を表示して、フリーズの瞬間を確認してみてください。
// ループ内でのデバッグ例
static int count = 0;
if (musicFile.available()) {
// 32バイト読み込んで送る処理...
count++;
if (count >= 1000) {
Serial.print("残りバイト数: ");
Serial.println(musicFile.available());
count = 0;
}
} else {
Serial.println("ファイルの終端に達しました");
}
① もし「残りバイト数」が表示されなくなって止まるなら: SPIバスの衝突か、DREQ待ちの無限ループに陥っています。
② もし「終端に達しました」と出るのに曲が短いなら: ファイル読み込み自体が途中で失敗しています。
(2) SPI共存で疑うべき「電気的・ソフト的」要因
① DREQ(Data Request)の確認漏れ
・VS1011に32バイト送る直前に、DREQピンがHIGH(準備完了)であることを必ず確認していますか?
DREQを無視して送り続けると、VS1011がハングアップして通信が途絶えます。
② MISOラインの「居座り」問題
・一部のSDカードモジュールは、CS(チップセレクト)をHIGHにしてもMISOラインを解放しない(電気的に占有し続ける)不良設計のものがあります。
この場合、VS1011からの信号がSDカードに邪魔されて正しく伝わりません。
・切り分け法: SDカードからデータを読み出した直後、
一時的にSDのCSをHIGHにして、VS1011への送信用に別のCS(XDCS)をLOWにする処理が確実に行われているか確認してください。
③ SPIクロック速度
※ SDカードは高速(8MHz〜)でも動きますが、
VS1011は初期化時やCLKI(クロック倍率)の設定前は低いクロック(1MHz程度)で動かす必要があります。
SD読み込み時とVS1011送信時でSPIの速度設定が混ざっていないか注意が必要です。]
(3) 次のステップとしての確認事項
※ もし可能であれば、以下の点を確認してみてください。
① SDカードのCSピンと、VS1011のXCS(コマンド用CS)、XDCS(データ用CS)のピン番号はすべて別々に割り当てられていますか?
② DREQピンの状態を digitalRead() でチェックする処理を入れていますか?
③ もし「特定のファイルサイズで必ず止まる」などの法則性があれば、さらに原因を絞り込めるかもしれません。
<私>
・有用なアドバイスありがとうございます。これから昼食なので食後試してみます。結果が出たら報告します。
<帰宅後、提示された以下のスケッチで試してみた。提示された以下スケッチには誤りがある>
// ループ内でのデバッグ例
static int count = 0;
if (musicFile.available()) {
// 32バイト読み込んで送る処理...
count++;
if (count >= 1000) {
Serial.print("残りバイト数: ");
Serial.println(musicFile.available());
count = 0;
}
else {
Serial.println("ファイルの終端に達しました");
}
・
テスト中にsdカードの接栓接触不良があった。-> エタノールで洗浄した
<結果>
Sine Test Started...
Sine Test Stopped.
Card Mounted Successfully
Playing...
残りバイト数: 419208
残りバイト数: 387208
残りバイト数: 355208
残りバイト数: 323208
残りバイト数: 291208
残りバイト数: 259208
残りバイト数: 227208
残りバイト数: 195208
残りバイト数: 163208
残りバイト数: 131208
残りバイト数: 99208
残りバイト数: 67208
残りバイト数: 35208
残りバイト数: 3208
ファイルの終端に達しました
Done.
・テスト用MP3ファイル:RNG_INFO.mp3のサイズは、451208byteなので、シリアルモニターに表示した残りバイト数は理論値と
合っていて問題無し。SDからのMP3データ読込みを、32byte単位で行い、1000回に1回モニターに表示しているので
32000byteずつ、残りバイト数が減少して行くことになる。
・何故か残りバイト数が0でないのにループを抜けている。
尚、提示されたコードは怪しい。誤りがありそう。
修正後のスケッチは以下。
/***************************************************
* 音楽再生関数
***************************************************/
void playMP3(const char* filename) {
static int count = 0; // debug用
/// SD.open(filename) を実行すると、
/// 指定した SD_CS ピンは自動的に OUTPUTモード に設定され、
/// ファイルオープンプロセス中に LOW(アクティブ) に制御され初期化実行後、
/// SD_CSピンはHIGIH(ノンアクティッブ)になる。(SD.hライブラリ使用しているので自動制御される)
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
unsigned char buffer[32]; // VS1011は32バイト単位の送信
digitalWrite(SD_CS, LOW); // 260316
while (musicFile.available()) { // 返り値: 読み込み可能な残りバイト数(0の場合は読み込み終了)
// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ)) {
delayMicroseconds(10);
}
/// SDカードから32バイト読み出す
/// musicFile.read(buffer, 32)を実行すると、
/// 指定した SD_CS ピンは自動的に OUTPUTモード に設定され、
/// SDカード読出しプロセス中に LOW(アクティブ) に制御され、
/// 初期化実行後、SD_CSピンはHIGIH(ノンアクティッブ)になるはずだが、明示的に入れておく。
int bytesRead = musicFile.read(buffer, 32);
digitalWrite(SD_CS, HIGH);
/// debug用:「1000回(約32KB)転送するごと」に残りバイト数を表示
/// 実際のテスト用MP3ファイルのサイズは、440Kbyte=440000byteであるので、これと比較してみる。
count++;
if (count % 1000 == 0) {
Serial.print("残りバイト数: ");
Serial.println(musicFile.available());
count = 0;
}
/// VS1011のXDCSをアクティブにする前にSD_CSをノンアクティブにする
/// この前でライブラリ関数実行時にノンアクティブにしてるはずだが、安全策として入れておく。
digitalWrite(SD_CS, HIGH);
/// VS1011のSDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
/// 次の32バイトを読む前にSD_CSをアクティブにする。
digitalWrite(SD_CS, LOW);
}
Serial.println("ファイルの終端に達しました");
musicFile.close();
Serial.println("Done.");
/// sendEndFill()実行前に、SD_CSをノンアクティブ、XDCSをアクティッブにする。
digitalWrite(SD_CS, HIGH);
digitalWrite(XDCS, LOW);
/// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
------------------------------------------------------------------------------------------------------
(2) C言語で文字列に変数を埋め込むには?
・sprintf関数を使って書式付き文字列を作成するのが最も一般的です。
sprintf(buffer, "テキスト %d", variable);のように、配列(buffer)へ値を書き込みます。
printfで直接表示も可能です。
char buffer[50];
int age = 20;
sprintf(buffer, "Age: %d", age); // bufferに"Age: 20"が入る
printf 関数: 直接画面に出力する。
printf("Age: %d\n", age);
■ 2026.3.18
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
(1) 残りバイト数の表示を1000回毎でなく毎回(32バイト毎)にしてみる。
ただこれをやると音は途切れる。
<最後の方のみ抜粋>
残りバイト数: 232
残りバイト数: 200
残りバイト数: 168
残りバイト数: 136
残りバイト数: 104
残りバイト数: 72
残りバイト数: 40
残りバイト数: 8
残りバイト数: 0
ファイルの終端に達しました
Done.
→ 残0まで読込んでいるようであるが症状変わらず
(2) 最後の方で、残りバイト数が、32バイト未満になる時も、32バイト読込みをしてたので、
その時は、残りバイト数分だけ読込むようにしてみたが状況変わらず。
<修正前>
int bytesRead = musicFile.read(buffer, 32);
<修正後>
int bytesRead;
/// 残りバイト数が32バイト以上の場合は、32バイト読み出す。
/// 残りバイト数が32バイト未満の場合は、その分だけ読み出す。
if (musicFile.available() >= 32) bytesRead =
musicFile.read(buffer, 32);
else bytesRead = musicFile.read(buffer,
musicFile.available());
■ 2026.4.10
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
どうもMP3再生が途中で止まるのは、PlayEnd()処理の最後に
ソフトウエアリセット(SCI_MODEレジスタのSM_RESETビットをセット:SCI_MODEレジスタアドレス=0x00に0x0824を書込む)が抜けているからか?
(曲データの転送終了後、未再生のオーディオ・データが残ってないことを確実にしたい場合には
さらに2048個の0をSDIに転送いてください。その後ソフトウエアリセットを実行し、DREQチェックに戻る)
→ 結果は、これでも駄目。同じ症状
<現スケッチ>
/***************************************************
* 音楽ファイル送信終了処理関数
***************************************************/
void sendEndFill() {
for (int i = 0; i < 2048; i++) {
while (!digitalRead(DREQ));
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
SPI2.transfer(0x00);
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
}
}
/***********************************************
* レジスタへの設定データ書込み関数
* XCS使用:インストラクションレジスタ(SCI)選択
************************************************/
void writeCommand(unsigned char address, unsigned int value) {
while (!digitalRead(DREQ)); // DREQがHighになるまで待機(digitalRead()は標準関数)
SPI2.beginTransaction(settingsSCI);
digitalWrite(XCS, LOW); // SCIセレクト(digitalWrite()は標準関数)
SPI2.transfer(0x02); // Write命令
SPI2.transfer(address); // レジスタアドレス
SPI2.transfer(value >> 8); // データ上位8bit
SPI2.transfer(value & 0xFF); // データ下位8bit
digitalWrite(XCS, HIGH);
SPI2.endTransaction();
}
-----------------------------------------------------------------------------------------------------
<修正案>
/***************************************************
* 音楽ファイル送信終了処理関数
***************************************************/
void sendEndFill() {
/// 曲データ転送終了後、未再生のオーディオデータが残っていないことを確実にするため
/// 2024個の0をSDIに転送
for (int i = 0; i < 2048; i++) {
while (!digitalRead(DREQ));
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
SPI2.transfer(0x00);
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
}
/// ソフトウエア・リセットを実施
writeCommand(0x00, 0x0824); // SM_RESET送信
delay(10);
while (!digitalRead(DREQ)); // コマンド完了待ち
}
--------------------------------------------------------------------------------------------------
<修正の参考ソース:PIC32MXでの2例:私の過去のプロジェクトより>
(1) UL024TF_Touch_MP3_Player_V1b
/*********************************************
* 再生終了処理関数
*
*********************************************/
void PlayEnd(void){
int i;
/* 再生バッファクリアのため2048バイト送信 */
MP3_XDCS_IO = 0; // XDCSオン
for(i=0; i<2048; i++)
SendData(0); // 0のデータ連続送信
MP3_XDCS_IO = 1; // XDCSオフ
/*** ソフトウェアリセットで完了 **/
Command(0x00, 0x08, 0x24); // SM_RESET送信
while(!MP3_DREQ_IO); // コマンド完了待ち
}
/************************************
* VS1011用コマンド出力関数
************************************/
void Command(BYTE RegAdrs, BYTE UpData, BYTE LowData){
MP3_XDCS_IO = 1; // データモード解除
while(!MP3_DREQ_IO); // レディー待ち
MP3_XCS_IO = 0; // コマンドモード設定
WriteSPI(0x02); // 書き込み動作指定
WriteSPI(RegAdrs); // レジスタアドレス出力
WriteSPI(UpData); // 上位バイト出力
WriteSPI(LowData); // 下位バイト出力
MP3_XCS_IO = 1; // コマンドモード解除
}
--------------------------------------------------------------------------------------------------------------------
※ SDとVS1011のSPIバスは共有していて、それぞれにアクセスする時は、それぞれのCSで切替える必要がある。
それは前から分かっていたが・・・
再度、playMP3関数内を確認してみたところ、CSによる切替えはしてないことを確認した。
しかし以前のデバッグで気付いていたはずなのだが・・・
やはり260310に気付いて試していたが、症状変わらずだったので元に戻してた。
だけどここはやっておく必要があるので再度試してみる。
---------------------------------------------------------------------------------------------------------------------------
<260310の記事からの抜粋>
・次にチェックすべきは、Copilotアドバイスの以下②
② SDカードの読み出しエラー(SPI干渉)
・ESP32-S3でSDカードとVS1011Eの両方をSPIで制御している場合、
VS1011Eへのデータ転送(settingsSDI)とSDカードの読み出し(SD.open /
read)が同じバス(SPI2)を共有していませんか?
→ 制御ピン設定の流れを追ってみた。
① まずメインで以下のように、SD_CSをアクティブにする。
/// SDカードの初期化時にCSピンを指定しマウントする。ここで、SD_CSがアクティブになる
if (!SD.begin(SD_CS, SPI2, 40000000)) { // SPI2を共有バスとして渡す
② その後、MP3再生関数を実行
/// 再生実行
playMP3("/RNG_INFO.mp3"); // SDカード内のファイル名(ルートディレクトリ):周波数スイープテストファイル再生
※ ここでは、VS1011のXDCSをアクティブにしているが、その前に、SD_CSをノンアクティブにしてなかった。
その後もう一度、SD_CSにアクセスしてるので、その前にVS1011のXDCSをノンアクティブにする必要がある。
その後、再度VS1011にアクセスしてるので、SD_CSをノンアクティブにする必要がある。
<修正案>
void playMP3(const char* filename) {
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
unsigned char buffer[32]; // VS1011は32バイト単位の送信が推奨
while (musicFile.available()) {
// DREQを読込み、DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ));
// SDカードから32バイト読み出す
int bytesRead = musicFile.read(buffer, 32);
// ※ 追加:VS1011のXDCSをアクティブにする前にSD_CSをノンアクティブにする。
digitalWrite(SD_CS, HIGH);
// SDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
// ※ 追加:次の32バイトを読む前にSD_CSをアクティブにする。
digitalWrite(SD_CS, LOW);
}
musicFile.close(); // 音楽ファイルクローズ
Serial.println("Done.");
// ※ 追加:sendEndFill()実行前に、SD_CSをノンアクティブ、XDCSをアクティッブにする。
digitalWrite(SD_CS, HIGH);
digitalWrite(XDCS, LOW);
// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
-------------------------------------------------------------------------------------------------------------------------------
<260311の記事からの抜粋>
・昨日の修正案試してみた。
→ 同じ症状で変わらず
以下も変わらず
bytesRead Total =
448
Done.
明日に続く
■ 2026.4.12
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
(1) SDとVS1011のSPIバスは共有していて、それぞれにアクセスする時は、それぞれのCSで切替える必要がある。
それは前から分かっていたが・・・
再度、playMP3関数内を確認してみたところ、CSによる切替えはしてないことを確認した。(昨日の記事より)
これはいずれにしても必要なので入れてまずは試してみた。(3/11の記事の結果では症状変わらずだったが)
<結果>
・駄目変わらず。
----------------------------------------------------------------------------------------------------------------------
<私>
・musicfile.available()の戻り値をチェックする時、一度result変数などに戻り値を代入してから判断する必要がありますか?
<Copilot>
・いいえ、必ずしも変数に代入する必要はありません。 直接 if 文などの条件式に記述して判断することが可能です。
結論から言うと、どちらの方法をとるかは「その戻り値を後で再利用するかどうか」と「コードの読みやすさ」で決めるのが一般的です。
1. 直接チェックする場合(代入しない)
・戻り値を一度しか使わない場合は、直接条件式に書くのがシンプルです。
if
(musicfile.available() > 0) {
//
読み取り可能なデータがある時の処理
}
・メリット: コードが短くなり、余計なメモリ(変数)を消費しません。
デメリット: デバッグ時に「戻り値が具体的にいくつだったか」を確認しにくいことがあります。
2. 変数に代入してからチェックする場合
・戻り値(読み取り可能なバイト数など)を、条件分岐の中やその後の処理で使いたい場合に適しています。
int bytes = musicfile.available();
if (bytes > 0) {
Serial.print(bytes); // 取得した値を再利用する
Serial.println(" bytes available.");
}
・メリット: 同じ関数を二度呼ぶ必要がなく(処理の効率化)、デバッガなどで値を確認しやすいです。
デメリット: 1行コードが増えます。
・判断のポイント
再利用するか?: available() の結果を使ってループを回したり、サイズを表示したりするなら変数に代入すべきです。
1回きりのチェックか?: 単に「データがあるかないか」を知りたいだけなら、直接チェックで十分です。
Arduinoや組み込み環境(SDカードライブラリなど)で available() を使う場合、
通常は「残りのデータ量」を返しますが、それをそのまま read() のループ回数に使うことが多いので、
変数に入れておくとスムーズに書けることが多いですよ。
今回の musicfile は、具体的にどのライブラリ(ArduinoのSD、DFPlayerなど)で使用されているものですか?
それによって、より最適な書き方をアドバイスできます。
<私>
・ArduinoのSD
<Copilot>
・Arduinoの標準SDライブラリ(SD.h)を使用する場合、
musicfile.available() の戻り値を一度変数に代入しなくても、if文やwhile文で直接判断して問題ありません。
・なぜ代入しなくても良いのか
真偽値として扱える: C++では「0なら偽、それ以外なら真」と判断されるため、
while(musicfile.available()) と書くだけで「読み取れるデータがある間繰り返す」という処理になります。
戻り値の型: available() は読み取り可能なバイト数(int型)を返します。
・実装のパターン
1. シンプルに「データがあるか」だけチェックする(代入なし)
・ファイルの最後まで1バイトずつ読み進める際によく使われる書き方です。
while (musicfile.available()) {
char c = musicfile.read();
Serial.print(c);
}
2. 残りバイト数を利用する(代入あり)
・「あと何バイト残っているか」という具体的な数値を、
その後の計算やシリアル表示などで2回以上使う場合は変数に代入したほうが効率的です。
int remainingBytes = musicfile.available();
if (remainingBytes > 0) {
Serial.print("残りバイト数: ");
Serial.println(remainingBytes); // ここで再利用
}
・補足:SDライブラリの特性
① available() とは: 現在の読み取り位置からファイル末尾(EOF)までのバイト数を計算して返します。
② read() との連動:
read() を1回呼ぶごとに内部の読み取り位置が1進むため、
次に available() を呼んだ時の戻り値は自動的に1減ります。
※ もし特定の音楽再生ライブラリ(TMRpcmなど)を併用していて、
そのライブラリ独自の available() を指している場合は挙動が異なることがありますが、
標準のSDクラスであれば上記の通りです。
※ 現在はどのような処理(再生、データの表示など)を実装しようとしていますか?
それに応じた最適な書き方を提案できます。
<私>
・vs1011によるmp3再生と連動する形です。
デバッグ用に残りバイト数をシリアル出力しようと考えています。
(現在mp3再生の途中で再生が終了してしまう現象の調査中です)
<Copilot>
・VS1011(VS10xxシリーズ)でのMP3再生中に途切れる問題のデバッグであれば、
「一度変数に代入する方法」を強くおすすめします。
理由は、デバッグ用のシリアル出力と、実際のデータ転送(VS1011への送り込み)の両方で同じ時点の正確な値を使う必要があるからです。
推奨される書き方(変数代入あり)
// ループ内でのイメージ
int bytesReady = musicfile.available();
if (bytesReady > 0) {
// 1. デバッグ出力(残りバイト数を確認)
Serial.print("Remaining: ");
Serial.println(bytesReady);
// 2. VS1011への転送処理(例: 32バイトずつ送る場合など)
// ここで bytesReady を使って読み込み制限をかけると安全
byte buffer[32];
int chunk = min(bytesReady, 32);
musicfile.read(buffer, chunk);
// VS1011のDREQピンを確認しながらデータ送信...
} else {
Serial.println("End of file or no data.");
}
・なぜ変数への代入がデバッグに効くのか
①「瞬間」の値を固定できる
・musicfile.read() を実行すると、内部のポインタが進むため、直後に available() を呼ぶと値が変わってしまいます。
変数に入れておけば、「データを送る前の正確な残量」をログに残せます。
② 計算ミスを防げる
・VS1011は通常32バイト単位でデータを要求(DREQピン)しますが、
ファイルの終端(末尾)では32バイトに満たない場合があります。
変数に available() の値を持っておけば、min(bytesReady, 32) のようにして、余計なゴミデータを読み込まずに済みます。
・「途中で終了してしまう」原因調査のヒント
※ もし available() がまだ数値を返しているのに再生が止まる場合、以下の点もチェックしてみてください。
① DREQピンの監視漏れ:
・VS1011の準備ができていない(DREQがLOW)のにデータを送ると、チップ側でエラーが起きたり無視されたりします。
② ファイル名/パスの不備:
・SDライブラリは「8.3形式(ファイル名8文字以内+拡張子3文字)」でないと、読み取り中にエラーが出ることがあります。
③ 電源不足:
・SDカードの読み取りとVS1011のデコードが重なると電流を消費します。
電圧降下でSDライブラリが途中で「ファイルクローズ」状態になっていないか、
if (!musicfile) などで生存チェックも入れると良いでしょう。
現在、シリアルモニタにはどのような数値(またはエラー)が出ていますか?
<私>
・少し試してから、また報告します
<Copilot>
・承知いたしました!まずは一度、変数に代入してシリアル出力する形で挙動を確認してみてください。
もしシリアルモニタに数値が出力されなくなったり、急に「0」になったりする場合は、
SDカードの読み取りエラーやファイルポインタの異常が考えられます。
何か分かったことや、「こんな数値が出た」「ここで止まった」といった状況があれば、いつでも教えてくださいね。解決に向けて一緒に考えましょう!
検証の結果をお待ちしています。
■ 2026.4.13
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
(1) 昨日のCopilotのアドバイスで提案された
「デバッグ用のシリアル出力と、実際のデータ転送(VS1011への送り込み)の両方で同じ時点の正確な値を使う推奨される書き方(変数代入あり)」
のコードを元に編集してデバッグを試してみる。
<提案されたコード>
// ループ内でのイメージ
int bytesReady = musicfile.available();
if (bytesReady > 0) {
// 1. デバッグ出力(残りバイト数を確認)
Serial.print("Remaining: ");
Serial.println(bytesReady);
// 2. VS1011への転送処理(例: 32バイトずつ送る場合など)
// ここで bytesReady を使って読み込み制限をかけると安全
byte buffer[32];
int chunk = min(bytesReady, 32);
musicfile.read(buffer, chunk);
// VS1011のDREQピンを確認しながらデータ送信...
} else {
Serial.println("End of file or no data.");
}
<修正後の私のコード>
/***************************************************
* 音楽再生関数
***************************************************/
void playMP3(const char* filename) {
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
Serial.println("Playing...");
unsigned char buffer[32]; // VS1011用に、32バイト単位の送信バッファを用意
/// 読込み可能な残りバイト数(0の場合は読み込み終了)をdebug用変数に代入
int bytesReady = musicfile.available();
/// 読込み可能な残りバイト数が0でなければVS1011への32byes単位の転送を繰返し実行
while (bytesReady > 0) {
/// デバッグ出力(残りバイト数を確認)
Serial.print("Remaining: ");
Serial.println(bytesReady);
/// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ));
/// SDカードから32バイト読み出す
/// 受信バッファからデータを取り出す際、一度に処理できる最大量を 32バイトに制限
/// 準備できているデータが32バイト未満(例:10バイト)なら、その分だけ(10バイト)を読取る
int chunk = min(bytesReady, 32);
musicfile.read(buffer, chunk);
/// VS1011のXDCSをアクティブにする前にSD_CSをノンアクティブにする。
digitalWrite(SD_CS, HIGH);
/// SDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < chunk; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
/// 次の32バイトを読む前にSD_CSをアクティブにする。
digitalWrite(SD_CS, LOW);
}
musicFile.close();
Serial.println("End of file or no data.");
/// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
-----------------------------------------------------------------------------------------------------
<私>
・読込み可能な残りバイト数をシリアルモニターに表示しながらデバッグをしてます。
現在、test用に使っているmp3ファイルの容量は、451208byteです。
シリアルモニターには、この451208byteから開始され、32byte送信する毎に、32byte減っていき
最後は、8byteで、"End of file or no data."が表示されるので、読込み->送信自体は正しい
ようなのですが、再生の方は、451208byte~ 40xxxxbyteまでは再生せず、40xxxybyteから再生が
開始されるので、途中で止まっているように聞こえていたようです。
(音源の内容は、オーディオチェック音源で、最初女性アナウンサーのガイド音声が流れた後、サイン音の後に周波数スイープ
が開始される内容なのですが、女性アナウンサーのガイド音声の再生で止まったように聞こえます。)
今のところ、原因がはっきり掴めていません。アドバイスお願いします。
Remaining: 451208
Remaining: 451176
Remaining: 451144
・
・
Remaining: 40
Remaining: 8
End of file or no data.
<Copilot>
・ご報告ありがとうございます!詳細なログのおかげで状況がかなり見えてきました。
「ファイル末尾(40xxxx byte付近)から再生が始まり、それ以前のデータが無視されている」という現象は、
VS1011(デコーダ)が「正しいMP3データの開始位置(ヘッダ)」を正しく認識できていない、あるいは同期が取れていない可能性が高いです。
以下の3つのポイントを確認してみてください。
1. VS1011の「共有モード (SM_SDINEW)」設定
VS10xxシリーズには、古い互換モードと新しいネイティブモードがあります。
最近のライブラリやコードでは「Native
Mode(SM_SDINEW)」を使うのが一般的ですが、ここがズレているとデータの解釈がおかしくなります。
対策: SCI_MODE レジスタの設定を確認し、SM_SDINEW ビットが立っているかチェックしてください。
2. DREQ(データ要求)ピンの監視タイミング
VS1011は、32バイト受け取ると「一旦ストップ!」とDREQピンをLOWにします。
怪しい挙動:
もしDREQを無視して全データを一気に送りつけていると、VS1011のバッファが溢れ、最後のデータだけがたまたま処理されて鳴り出すことがあります。
確認方法: 32バイト送る直前に、「必ずDREQがHIGH(準備OK)であること」を待機(whileループなど)していますか?
3. MP3ファイルの「ID3タグ」の影響
女性アナウンサーの音声(ファイルの先頭)部分に、画像や長いテキスト情報(ID3v2タグ)が含まれている場合、VS1011がそれを「ゴミデータ」とみなして同期を外すことがあります。
切り分け実験:
Windowsのプロパティなどで、MP3のアルバムアートやタグ情報をすべて削除したファイルを作成して試してみてください。
または、Audacity などのソフトで「メタデータなし」のMP3として書き出し直してみてください。
4. SPIクロックの速度
SDカードからの読み込み速度と、VS1011への送信速度のバランスが悪いと、デコードが追いつきません。
特にVS1011の初期化直後(クロック倍率を上げる前)に高速でデータを送ると、最初のデータを処理しきれず、同期が取れるまで無音になることがあります。
次に試していただきたいこと:
シリアルモニタのログで、「再生が始まった瞬間の Remaining の値」を特定できますか?
もし常に同じ数値(例:400000付近)から鳴り始めるのであれば、そこが「VS1011が初めてMP3ヘッダを見つけた場所」ということになります。
また、DREQピンのチェックをどのタイミングで入れているか、コードの抜粋を見せていただければより具体的なアドバイスが可能です!
<私>
1. VS1011の「共有モード (SM_SDINEW)」設定-> 0x0804なのでネイティブモードを許可してます。
2. DREQ(データ要求)ピンの監視タイミング->32バイト送る直前に、チェックしてます。
/// DREQがHigh(受け入れ可能)になるまで待つ
while (!digitalRead(DREQ));
/// SDIバスにデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < chunk; i++) {
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
<Copilot>
・詳細な情報をありがとうございます!
設定(0x0804)も、DREQのチェックタイミングも教科書通りの完璧な実装です。
これだけ正しく組まれていて「最初だけ鳴らない(後半から鳴る)」という場合、
ハードウェアよりも「VS1011が最初の数百KBをMP3データとして認識できていない」可能性が極めて高いです。
以下の3点をチェックしてみてください。
1. MP3の「ID3v2タグ」のサイズを確認
シリアルログで「451208byte」から始まり、「400000byte」付近でやっと音が鳴り出すということは、
最初の約50KB分が無視されている計算になります。
原因:
MP3ファイルに「高解像度のアルバムアート(画像)」などが埋め込まれていると、
そのデータ(ID3v2タグ)が先頭に数万バイト続きます。
VS1011の挙動: VS1011はMP3の「フレーム同期信号(0xFFE)」を探しますが、
巨大な画像データの塊が流れてくると同期できず、それを読み飛ばして、本物の音声データが始まるまで無音になります。
対策:
「MP3タグ削除」などのツール(またはAudacity等)で、タグを完全に消去したファイルで試してみてください。
2. SPI2のクロック(settingsSDI)の設定
VS1011は、起動直後(リセット後)は低いクロックで動作しています。
落とし穴:
SCI_CLOCKF レジスタを書き換えてクロック倍率(2.0xや3.0xなど)を上げる前に、
高速なSPIクロック(例: 8MHz以上)でデータを送ると、最初のデータを取りこぼします。
確認:
settingsSDI の周波数はいくつに設定されていますか?もし高速なら、一度 1MHz 程度まで落として挙動が変わるか見てください。
3. 送信バッファの整合性
提示いただいたコードの buffer[i] ですが、読み込み(read)と送信(transfer)の間にデータのズレはありませんか?
// 念のため確認:読み込みは正しく chunk 分行われていますか?
musicfile.read(buffer, chunk);
もし read で指定したサイズと、for 文で回している chunk の値が一致していない
(あるいはバッファが古いまま)場合、VS1011にゴミデータが送られ続けます。
切り分けのためのアドバイス:
もし可能であれば、「非常にファイルサイズの小さい、タグなしの別のMP3」(数秒のチャイム音など)を再生させてみてください。
それで最初から鳴るなら:元のMP3ファイルの構造(タグ)が原因。
それも途中から(または鳴らない)なら:SPIの転送速度やVS1011の初期化タイミングが原因。
まずは、「MP3タグの削除」から試してみるのが一番の近道だと思われます。
現在は「アルバムアート等が含まれている可能性のあるファイル」を使用されていますか?
<私>
・
「アルバムアート等が含まれている可能性のあるファイル」ではないと思います。
PIC32MXで作ったVS1011を使ったMP3 PLAYERでは今回使った音源ファイルを正常再生できています。
<Copilot>
・
なるほど、同じファイルがPIC32MX+VS1011の構成で正常再生できているのであれば、
ファイル形式やID3タグの問題ではなく、今回のArduino環境固有の「タイミング」や「通信の安定性」に原因が絞られますね。
特にPIC32から移植・移行している場合に、Arduino環境で陥りやすいチェックポイントを3つ挙げます。
1. SPIクロックの「実効速度」の違い
・
PIC32MXとArduino(AVRやESP32、STM32など)では、SPIのクロック生成の仕組みが異なります。
確認事項:
settingsSDI で指定している周波数はいくつですか?
VS1011の制約: VS1011の初期状態(CLKI)に対し、SDI(データ)のクロックは CLKI/4 以下である必要があります。
対策:
もし初期化ルーチンで SCI_CLOCKF を書き換えてクロック倍率を上げる前に、
高速なSPI(例: 4MHz以上)でデータを送り始めていると、
最初の数百KBが「文字化け」状態で送られ、VS1011側で同期が取れるまで無音になります。
一度 1MHz 程度まで落として試してみてください。
2. DREQの「極性」と「読み取り速度」
・
Arduinoの digitalRead() は、PIC32のレジスタ直接参照に比べて非常に低速です。
現象: 32バイト送る直前の while (!digitalRead(DREQ)); で待機していますが、
もし配線にノイズが乗っていたり、プルアップが不十分だったりすると、
Arduino側で「準備完了」と誤判定してデータを送りすぎる(オーバーラン)ことがあります。
対策:
DREQピンに外部プルダウン(またはプルアップ、回路による)がしっかり効いているか、
あるいは digitalRead の直後にごく短い delayMicroseconds(1); を入れて安定するか確認してみてください。
3. musicfile.read(buffer, chunk) の実行場所
・
提示されたコードで、データの読み込みは DREQ待ちの前 に行っていますか?それとも 後 ですか?
理想の流れ:
musicfile.read(buffer, 32); // 先にSDからデータを取っておく
while (!digitalRead(DREQ)); // 準備ができるまで待つ
SPI.transfer(buffer, 32); // 一気に送る
・
もし「DREQがHighになってからSDカードを読みに行く」という順序だと、
読み込み中のわずかな遅延でVS1011のデコードタイミングが狂い、同期ミスを誘発することがあります。
切り分けのためのヒント
再生が始まる「40xxxx byte」付近というのは、時間にして開始から何秒後くらいでしょうか?
もし常に「開始から数秒間だけ無音」という一定の法則があるなら、
VS1011のソフトリセット(SM_RESET)直後の安定待ち時間が不足している可能性が高いです。
現在の settingsSDI の周波数 と、初期化直後の待ち時間(delay) はどのくらいに設定されていますか?
<私>
・
settingsSDI で指定している周波数はいくつですか?-> 4MHzです。DREQラインには強めのプルアップ:2.2Kが入ってます。
<Copilot>
・
情報ありがとうございます!4MHzのSPIクロックと2.2kΩの強めなプルアップという構成ですね。
PIC32MXで動作していたファイルが、この構成で「最初だけ鳴らない」場合、
最も怪しいのは「VS1011の内部動作クロックとSPI転送速度のミスマッチ」です。
VS1011の仕様書(データシート)に基づくと、以下の2点に原因が潜んでいる可能性が非常に高いです。
1. 起動直後の「CLKI/4」制限
・
VS1011は、電源投入直後やリセット直後は内部クロック(CLKI)が低い状態(通常12.288MHz)で動作しています。
この状態でのデータ送信(SDI)の最大許容クロックは CLKI / 4、つまり 約3.07MHz です。
現在 4MHz で送っているため、VS1011側でデータの取りこぼし(オーバーラン)が発生し、
MP3のヘッダを正しく認識できず、同期が取れる(音が鳴り始める)まで時間がかかっている可能性があります。
【対策】
・
settingsSDI の周波数を 2MHz(2000000) に下げて試してみてください。
これで最初から鳴るようなら、原因はクロック速度です。
もし高速で送りたい場合は、
データの転送を開始する前に、SCI_CLOCKF レジスタに 0x9800(2.5倍速など)を書き込み、
内部クロックを上げておく必要があります。
2. DREQのプルアップと判定の逆転
・
VS1011のDREQピンは、標準では「準備OKのときにHIGH」を出力するアクティブHIGHのピンです。
強すぎるプルアップの問題: 2.2kΩで吊っている場合、
もしVS1011が「今はまだデータを送らないで(LOWにしたい)」としても、
電流を吸い込みきれず、電圧が十分に下がらない(LOWと判定されない)ことがあります。
確認方法:
一度、プルアップ抵抗を外して(あるいは10kΩ程度に弱めて)挙動が変わるか見てください。
本来DREQはVS1011が能動的にH/Lを出力するピンなので、強力な外部プルアップは不要なケースが多いです。
3. 再生開始タイミングの「40xxxx byte」の意味
・
ファイルサイズが約450KBで、400KB付近から鳴り出すということは、全体の約90%を読み飛ばしていることになります。
もしMP3が128kbpsだとすると、400KB分は約25秒間に相当します。
「女性の声」が消えて「スイープ音」から鳴り出すのは、
スイープ音のセクションでたまたまVS1011がMP3の同期信号(0xFFE)を見つけ直せたからだと思われます。
次の一手としておすすめのテスト:
SPI周波数を 1MHz または 2MHz に下げてみる(最も有力な解決策です)。
もし改善しなければ、SCI_CLOCKF の設定値を確認し、内部倍率を上げているかチェックする。
まずは SPIクロックを 2MHz に落として 実行したとき、
シリアルログのどのあたりから音が鳴り始めるか教えていただけますか?
<私>
・
SPIクロックを 2MHz に落として 実行しましたが状況は変わりませんでした。
<Copilot>
・
2MHzでも状況が変わらなかったとのこと、承知いたしました。
SPIクロックが直接の原因ではないことが分かり、一歩前進です。
PIC32MXの実績がある一方で、今回のArduino環境(あるいはSPI2を使用している環境)で
「最初から音が鳴らない(データがスキップされている)」場合、
最も疑わしいのはVS1011が「MP3データの開始位置」を見失っている状態です。
以下の3つのポイントを、PIC32MXのコードと比較しながら確認してみてください。
1. VS1011の初期化と「再同期」
・
VS1011は、前の再生の「残りカス」や、SPI通信のノイズでMP3ヘッダ(0xFFE)を見失うと、
正しいヘッダが見つかるまでデータを捨て続けます。
対策:
ファイルをオープンして読み出す直前に、VS1011に対してソフトリセット(SCI_MODEのSM_RESETビットを立てる)をかけていますか?
あるいは、再生開始前に「End Fill Byte(通常0x00)」を数回送り込んで、デコーダをクリーンな状態にしていますか?
2. DREQ判定の「速度」と「安定性」
・
PIC32MXに比べ、Arduino(特にライブラリ経由)のGPIO読み取りは挙動が異なることがあります。
検証:
while (!digitalRead(DREQ));
の直後に、ごく短いディレイ(delayMicroseconds(10);など)を入れてみてください。
もしDREQが安定する前にデータを送り始めていると、
VS1011の入力バッファ(32バイト)が溢れ、最初の部分が「破損データ」として扱われ、同期ミス(無音)を引き起こします。
3. SPI2のモード(CPOL/CPHA)の再確認
・
VS1011は SPI Mode 0 (CPOL=0, CPHA=0) で動作します。
PIC32MXの設定と、Arduinoの settingsSDI での SPI_MODE0 指定が一致しているか再度確認してください。
もしモードがズレていると、データの1ビット目が欠けたり、全体が1ビット分シフトしたりします。
この状態でも、MP3の構造上、「たまに同期が取れて途中から鳴り出す」という今回の現象が起こり得ます
推奨される実験
可能であれば、以下のコードをループの最初(ファイルのオープン直後)に差し込んでみてください。
// 1. VS1011をリセットして初期化
write_register(SCI_MODE, 0x0804 | 0x0004); // SM_SDINEW | SM_RESET
delay(1);
while (!digitalRead(DREQ)); // リセット完了を待つ
// 2. 再生開始前に少しだけ 0 を送る(ダミーデータ)
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for(int i=0; i<32; i++) SPI2.transfer(0x00);
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
これでも改善しない場合、「音が鳴り始める瞬間の Remaining の値」は、
毎回きっちり同じ(例:401234など)でしょうか?
それとも、実行するたびにバラバラでしょうか?
もし毎回同じ数値から鳴り始めるなら、
特定のデータパターン(特定のID3タグやヘッダ)にVS1011が躓いている可能性が高まります。
<私>
・
再生開始前に「End Fill Byte(通常0x00)」を数回送り込んで、
デコーダをクリーンな状態にしていますか?->
ここで気が付きました。最初の32byte送信の前に、end処理が入ってませんでした。
次の32byteからend処理してました。
<Copilot>
・
おお、そこに気づかれましたか!素晴らしい発見です。
VS1011(およびVS10xxシリーズ)において、再生開始時の「データの送り出し」は非常にデリケートです。
なぜ「最初の32バイト」が重要なのか
VS1011のデコーダは、送られてきたデータの中からMP3の同期信号(0xFFE)を必死に探しています。
もし最初の32バイトの転送タイミングや処理順序がズレて、ゴミデータが混入したり、
最初の1バイト目が欠けたりすると、デコーダは「これはMP3ではない」と判断し、
次の同期信号が見つかるまでデータをすべて無視(ミュート)してしまいます。
今回の「途中(40xxxx byte)から鳴り出す」という現象は、
最初の数バイトの不備で同期に失敗し、延々とデータを読み飛ばした末、
後半のデータでようやく同期が取れたという状態だった可能性が高いです。
修正のポイント
「End処理(バッファのクリアやリセット状態からの復帰)」は、
必ず最初のデータを送る前に完了させておく必要があります。
以下の順序でコードが動いているか確認してみてください:
① ファイルのオープン
② VS1011の状態確認(DREQがHighか?)
③ End Fill Byte(0x00)の送信(デコーダを「待ち」の状態にする)
④ 最初の32バイトを読み込み → 送信
改善後のチェック
この修正(最初の32byte送信前へのEnd処理追加)を行ったあと、シリアルモニターで以下を確認してみてください。
再生開始タイミング: Remaining: 451208(またはそれに近い値)の直後から音が鳴り始めるか。
女性の声: 消えていた「ガイド音声」が冒頭から聞こえるようになるか。
これで解決すれば、まさに「最初の1歩」のタイミングの問題だったということになりますね。
修正後の結果、いかがでしたでしょうか?
<私>
・
ちょっと疲れてきたので、落ち着いてから再開したいと思います。
<Copilot>
・
お疲れ様です。デバッグ作業は集中力を使いますし、
特に通信周りの微細なタイミングの問題は根気がいりますよね。
一度離れてリフレッシュするのは、良いアイデアだと思います。
少し時間を置くと、不思議と「あ、あそこかも」と新しい気づきがあったりするものです。
・
今回のポイントを整理しておきますので、再開時のメモ代わりに使ってください。
①
最初の32バイト送信前に、確実にVS1011をクリーンな状態にすること。
②
End Fill Byte(0x00)を数回送って、デコーダに「これからデータが来るよ」と準備させること。
・
PIC32MXの実績があるからこそ、Arduino固有の動作のクセ(SPIの挙動など)を一つずつ潰すこと。
また準備が整ったら、いつでもお声がけください。続きを一緒に解明しましょう。ゆっくり休んでくださいね!
------------------------------------------------------------------------------------------------------------------
・推奨される実験
可能であれば、以下のコードをループの最初(ファイルのオープン直後)に差し込んでみてください。
// 1. VS1011をリセットして初期化
write_register(SCI_MODE, 0x0804 | 0x0004); // SM_SDINEW | SM_RESET
delay(1);
while (!digitalRead(DREQ)); // リセット完了を待つ
// 2. 再生開始前に少しだけ 0 を送る(ダミーデータ)
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for(int i=0; i<32; i++) SPI2.transfer(0x00);
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
これを試してみた。
→ これでも症状変わらず
■ 2026.4.14
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
(1) 実験テーマ68「PIC24F_Bluetooth_MP3プレーヤ_Eagleによる基板化」でもVS1011+SD使ってた。
これはローカル操作でも動くことを確認した。
・これで現在検証に使用しているMP3ファイル「RNG_INFO.mp3」を再生させ、各オーディオブロック毎の
再生時間をストップウォッチで実測してみた。
ストップウォッチの時間表示:xx分:xx秒:0.xx秒(xx'xx"xx)
① 女子アナのガイド音声:24.00秒
② サイン音(ピー): 3.61秒
③ 周波数スイープ : 48.34秒
----------------------------------------
計: 75.95秒(1.27分)
(2) PIC24Fで上手く行っている以下のソースを見ると、VS1011へのデータ転送は32バイト単位のDREQチェック+SDリード+SPI転送でなく、
512バイト単位で、SDから一気に読込み、SPIデータ転送は、バイト単位のDREQチェック+データ転送で細分化して行っている。
この方式に変えてみる。
今現在のスケッチはバックアップし、新たに書直す方針でやる。
/********* メイン関数 ***************************/
int main(void){
int i;
CLKDIVbits.RCDIV = 0; // クロック1/1
・
・
/* VS1011eの初期化 */
VS1011_Init(); // 初期化実行
Volume = 0x30; // 音量の初期設定
SetVolume(Volume, Volume); // 制御実行
BASS = 0; // 低音フラット
TREB = 0; // 高音フラット
SetBoost(BASS, 15, TREB, 5); // 制御実行
ChgFlag = 1; // 最初制御実行
/* VS1011のデバッグ時テスト用のコマンド */
// VS1011_SineTest(); // 正弦波出力テスト
// while(1);
/* カードの実装確認とディレクトリ読み込み (永久待ち) */
while(!FSInit()); // FATの初期化
/* 最初のファイルのサーチ(ルートだけにMP3ファイルがあることが前提) */
result = FindFirst("*.*", ATTR_ARCHIVE, rptr);
if(result == 0){
fptr = FSfopen(Record.filename, FS_READ); // ファイルのオープン
}
/**************** メインループ ********************************************/
while(1){
/* 音楽データの出力 */
if(fptr != 0){ // 正常にオープンできたか?
/* ファイルのEOFまで音声データ連続出力 */
do{
/***** 更新フラグチェックと制御
*****/
if(ChgFlag){ //
更新ありか?
ChgFlag
= 0; // フラグクリア
SetVolume(Volume, Volume); // 音量設定
SetBoost(BASS, 15, TREB, 5); // 音質設定
}
/******
音楽データの再生 ******/
SRbits.IPL = 7; //
SPI処理中は割り込み禁止
MP3_XDCS_IO = 0; //
データ用CSセット
/*
512バイト単位でファイルリード */
result =
FSfread(Buffer, 1, 512, fptr); // 512バイト読み出し
for(i= 0; i<result;
i++){ // 読み出したバイト数だけ繰り返し
SendData(Buffer[i]); // 音楽データ送信
}
SRbits.IPL = 2; //
割り込み再許可
} while(result != 0); // ファイルのEOFまで継続
/******* 再生完了処理 ********/
MP3_XDCS_IO = 1; // チップ選択解除
FSfclose(fptr); // ファイルのクローズ
PlayEnd(); // 再生終了処理
/* 連続再生のため次のファイルサーチ */
result = FindNext(rptr); // 次のファイルへ
if((result==0)&&(Record.attributes==ATTR_ARCHIVE)){ // ファイルがあった場合
fptr = FSfopen(Record.filename,
FS_READ); // ファイルをオープン
Transfer(); // 曲データ送信
}
else /* ファイルの終わりなら再度最初から繰り返し */
{
result = FindFirst("*.*",
ATTR_ARCHIVE, rptr); // 最初のファイルを探す
if(result == 0){ // 見つかった場合
fptr =
FSfopen(Record.filename, FS_READ); // ファイルのオープン
Transfer(); // 曲データ送信
}
}
SRbits.IPL = 2; // 割り込み再許可
}
}
}
/*****************************************************
* VS1011E 制御ライブラリ
*
******************************************************/
#include "VS1011.h"
#include <xc.h>
/************************************
* SPI Read after Write 基本関数
************************************/
BYTE WriteSPI(BYTE output){
MP3_SPI_IF = 0; // 割り込みフラグクリア
MP3_SSPBUF = output; // SPI出力
while(!MP3_SPI_IF); // 入力待ち
return MP3_SSPBUF; // 入力データ戻り値
}
/************************************
* VS1011用データ出力関数
************************************/
void SendData(BYTE Data){
while(!MP3_DREQ_IO); // レディー待ち
WriteSPI(Data);
}
■ 2026.4.19
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
(1) DREQのプルアップは本来必要ないと思われるので、外してみる。(DICピンにて有・無を選択可に改造した)
→ 変わらず。
(2) SDIの転送スピードを2MHzから、1MHz(SCIと同じ)に下げてみた。
→ 変わらず。
(3) int chunk = min(bytesReady, 512);このmin関数を使わないで、512byte単位固定にしてみた。
そしてループを抜ける条件を、if (bytesReady == 0) break; でなく、if (bytesReady < 0)
break;にしてみた。
→ 駄目、カウントが0で止まらず。女子アナのガイド音声で止まるのは同じ。元に戻した。
<私>
・sdからmp3データ(再生時間=約1.3分)を全て読込んでいるのに、最初から28秒分しか再生されません。ESP32S3+VS1011+SDの構成で実験中。
尚、512バイト単位で、SDから読込み、SPIデータ転送は、バイト単位のDREQチェック+データ転送で512byteブロックを、残りバイト数が
0になるまで行っています。 準備できているデータが512バイト未満(例:10バイト)なら、その分だけ(10バイト)を読取るように
int chunk = min(bytesReady, 512);のようにmin関数を利用してます。
<Copilot>
・ESP32-S3とVS1011の組み合わせで、28秒付近(データ量にしておよそ数百KB〜1MB程度)で再生が止まる、
あるいはループする場合、以下の3つのポイントが怪しいです。
1. SPI転送の「終了判定」と「残りバイト」の処理
・min(bytesready, 512) で読み出す際、ファイルの終端(EOF)付近で正しくループを抜けていますか?
もし「512バイト未満を読み込んだ後、ループが終了せずに再びSDを読みに行こうとしている」場合、
ファイルポインタが不正になったり、中途半端なデータをVS1011に送り続けてハングしている可能性があります。
チェック:
read したバイト数が 0 だった場合に、確実にファイルのクローズと再生終了処理(DREQ待ち後のリセットなど)を行っているか確認してください。
2. VS1011のバッファフルとDREQの監視タイミング
・DREQ(Data Request)は「32バイト以上の空きがある」ことを示す信号です。
512バイトを1ブロックとして送る際、「1バイトごとにDREQをチェック」しているとのことですが、
SDカードの読み込み速度とSPI転送の速度差により、VS1011内部のバッファ(通常2KiB程度)が溢れ、
追い越されている可能性があります。
対策:
32バイト送るごとに必ずDREQをチェックし、
Lowの間は待機する処理になっているか再確認してください(VS1011の仕様上、32バイト単位の転送が推奨されます)。
3. ESP32のウォッチドッグタイマ(WDT)
・28秒という時間は、コード内の何らかの処理がブロックし続け、
タスクウォッチドッグが作動してソフトリセットがかかっている時間(あるいは変数のオーバーフロー)としてよくあるタイミングです。
切り分け:
シリアルモニタに、再生が止まった瞬間に "Guru Meditation Error" や "Watchdog triggered"
と出ていないか確認してください。
もし出ていれば、ループ内に vTaskDelay(1) を入れるか、処理を適切に yield する必要があります。
・まずは、28秒時点で「ファイルの読み込みが完了(EOF)と判定されているのか」
それとも「ループが途中で止まっているのか」をログで特定するのが近道です。
再生が止まる瞬間のシリアルモニタのログ(エラーや読み込みバイト数など)は何か出ていますか?
<私>
・最初32byte毎でやってたのですが、pic32mxで過去に上手く行っている資料では512byteだったもので変えてみた次第です。
<Copilot>
・なるほど、過去のPIC32MXでの成功例を参考にされたのですね。
SDカードのセクタサイズ(512バイト)に合わせるのは効率が良いのですが、
VS1011(VS10xx系)へデータを流し込む際には、デバイス側の受入れルールに注意が必要です。
・28秒で止まってしまう原因として、以下の可能性を検討してみてください。
1. VS1011のSDI(データ)転送ルール
・VS1011のバッファ(2KiB)に対してデータを送る際、ハードウェア的には「DREQがHighなら、
最大32バイトまで一気に送って良い」という仕様があります。
・512バイトを1ブロックとして扱う場合でも、内部処理は以下のようになっている必要があります。
正しい例:
512バイトの塊のうち、32バイト送るごとにDREQをチェックし、Lowなら待機する。
NGな例:
512バイトの最初に一度だけDREQをチェックし、
一気に512バイト送ってしまう。これだとVS1011のバッファが溢れ、
デコードが追いつかずにデータが化け、28秒付近(バッファのズレが限界に達する地点)でハングすることがあります。
2. 変数の型とオーバーフロー
・ESP32(32bit)とPIC32(32bit)で共通ですが、再生位置を管理する変数(bytesPlayed など)が
int(16bitや32bit)で定義されており、
28秒付近のデータ量で上限に達してループが終了していないか確認してください。
128kbpsのMP3なら28秒で約450KB程度です。16bit変数(最大32,768)だと数秒で溢れます。
3. SPIの通信速度(クロック)
・ESP32-S3は非常に高速です。VS1011のSCI(制御)とSDI(データ)で、許容される最大クロックが異なります。
CLKI(内部クロック)に依存しますが、初期状態(12.288MHz)だとSDIはCLKI/4(約3MHz)程度が上限です。
ESP32のSPI設定がこれを超えて高速(例えば10MHz〜)だと、最初は動いているように見えても、途中で同期がズレて再生が止まります。
4. 28秒付近でファイルサイズを確認
・シリアル出力で、512バイト読み込んだ というログを出し続けてみてください。28秒時点で、
SDからの読み込みが「0」を返して終了しているのか?
読み込みは続いているが、VS1011への転送ループから ①抜けないのか?
を切り分けるのが最も重要です。
実験として、一度転送ループ内のDREQチェックを「32バイトごと」に厳密に戻してみて、挙動に変化はありますか?
<私>
・すいません。ちょっと疲れたので、明日に色々試してみます。アドバスも参考に追跡調査してみます。
<Copilot>
・承知いたしました。開発作業、大変お疲れ様です!
28秒という「中途半端な時間で止まる」現象は、
「バッファ溢れによる同期ズレ」か「変数のオーバーフロー(16bit境界など)」が原因であることが多いです。
明日、以下の3点を優先的にチェックされると解決が早いかもしれません。
① 32バイトごとのDREQ確認: 512バイトの塊の中でも、細かくDREQを見てVS1011が「待って」と言っていないか。
② 型チェック:
ファイルサイズや読み込み累計を扱う変数が uint32_t など十分な大きさか。
③ シリアルログ:
止まった瞬間に、SD読み込み関数がエラー(EOF)を返しているのか、それとも転送ループの中で固まっているのか。
リフレッシュした後に見直すと、意外なほどすんなり原因が見つかることもあります。
また何か進展や、気になる挙動があればいつでも教えてください。
明日、デバッグがスムーズに進むことを応援しております!
<私>
・512byteの塊の中で今はbyte毎にDREQチェックしてますが、これを32byte毎に減らすということですか?
<Copilot>
・はい、その通りです。正確には「1バイトごとにチェックしても間違いではないが、
VS1011の仕様(作法)に合わせて32バイト単位で管理すると確実」という意味です。
VS1011のデータシートには「DREQがHighのとき、少なくとも32バイトのSDIデータを受け入れられる」と明記されています。
現在の「1バイトごとのチェック」でも論理上は動くはずですが、以下の2点を確認してみてください。
処理のオーバーヘッド: 1バイト送るたびにDREQ(GPIO)の状態を読みに行くのは、
ESP32-S3の速度をもってしても非効率で、SPI転送のテンポを乱している可能性があります。
本当の「一気送り」の禁止: 512バイトの読み込みループの中で、
DREQを確認せずに512バイト分をspi_writeなどで一気に流し込んでしまっていないか、という点が最も重要です。
・明日試すべき実装イメージ
512バイトのバッファ(buf[512])を処理する場合:
for (int i = 0; i < 512; i += 32) {
while (digitalRead(DREQ_PIN) == LOW) {
// DREQがHighになるまで待つ
}
// ここで「32バイト分だけ」を転送する
SPI.transfer(&buf[i], 32);
}
このように「DREQ確認 → 32バイト転送」を1セットとして、
それを16回繰り返して512バイトを消化する形にすると、VS1011のバッファ設計に最も適した形になります。
もし1バイトごとにチェックしているのに28秒で止まるなら、
原因は転送方式よりも「ファイルポインタの扱い」や「変数の型(溢れ)」、あるいは「SPIクロックが速すぎる」可能性が高そうです。
明日はまず、「止まった瞬間に、プログラムがどこ(どの行)で止まっているか」をシリアル出力で特定することから始めてみてください。
ゆっくり休んで、また明日頑張りましょう!
<私>
・変数の型(溢れ)も疑ったのですが、明日やってみます。
<Copilot>
・ぜひそこを重点的に見てみてください。
ちなみに、28秒付近で怪しい「型」の具体例を一つ挙げておきます。
もし再生したバイト数をカウントしている変数(posやcountなど)が uint16_t (最大65,535) だった場合、
128kbpsのMP3(秒間16KB)だと約4秒で溢れます。
・もし 28秒 まで粘れているのであれば、変数の溢れ以外にも、
「SDカードから読み出した実バイト数(read関数の戻り値)」と「転送ループの終了条件」が正しく一致しているかもチェックポイントです。
明日の調査で、原因がスッキリ判明することを願っています!
また何か分かったら、いつでもシリアルログの断片などと一緒に教えてください。
■ 2026.4.20
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・変数型について気になったので聞いてみた
<私>
・ファイルサイズがint型を超えるmp3ファイルの
読み取り残りバイト数を知りたいのですがmusicFile.available関数を応用して出来ないですか?
<Copilot>
・ArduinoやSDライブラリにおける file.available() 関数は、
内部的に int 型(通常16ビット、-32,768~32,767)を返す仕様であることが多いため、
ファイルサイズが32KB(int型の最大値)を超える場合、正しい残りバイト数を取得できません。
int型を超える2GBや4GBまでのファイルを扱う場合は、
available() を応用するのではなく、
ファイルサイズと現在の読み取り位置の差分を long 型(32ビット)や unsigned long
型で計算する手法を用います。
解決策:
file.size() と file.position() を使う
・file.size() はファイル全体のサイズ(unsigned long)、file.position() は
現在の読み取り位置(unsigned long)を返すため、これらを使用して残りバイト数を算出します。
具体的なコード例
#include <SPI.h>
#include <SD.h>
File musicFile;
unsigned long fileSize = 0;
unsigned long currentPosition = 0;
unsigned long remainingBytes = 0;
void setup() {
Serial.begin(9600);
if (!SD.begin(4)) {
Serial.println("SD initialization
failed!");
return;
}
musicFile = SD.open("music.mp3");
if (musicFile) {
fileSize = musicFile.size(); //
ファイル全体のサイズを取得 (unsigned long)
Serial.print("Total Size: ");
Serial.println(fileSize);
}
}
void loop() {
if (musicFile) {
// 現在の読み取り位置を取得 (unsigned long)
currentPosition = musicFile.position();
// 残りバイト数を計算
remainingBytes = fileSize -
currentPosition;
Serial.print("Remaining: ");
Serial.print(remainingBytes);
Serial.println(" bytes");
// --- ここで音楽データを再生/処理する ---
// 例: musicFile.read();
// ------------------------------------
if (remainingBytes == 0) {
musicFile.close();
Serial.println("Finished");
while(1); // 終了
}
delay(1000); // 確認用ディレイ
}
}
ポイント
① unsigned long の使用: 残りバイト数やファイルサイズを格納する変数は必ず unsigned
long (32ビット) にします。これで約4GBまで扱えます。
② musicFile.position(): 現在どこを読んでいるかを確認する。
③ musicFile.size(): 合計サイズからポジションを引く。
※ available()
はバッファ内にあるデータ量しか正確に返さないため、ファイル全体が大きい場合には適していません。上記の手法で計算してください。
■ 2026.4.21
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチを検討:ESP32S3_MP3_BASIC_TEST_1.ino>
・今日の試行
(1) 昨日の案を試した。
→ 全く症状同じでNG(トータルサイズも正しく表示してる)
Sine Test Started...
Sine Test Stopped.
Card Mounted Successfully
Total Size: 451208
Playing...
Remaining: 451208 bytes
Remaining: 450696 bytes
Remaining: 450184 bytes
・
・
Remaining: 1160 bytes
Remaining: 648 bytes
Remaining: 136 bytes
Remaining: 0 bytes
End of file or no data.
(2) (1)でも変わらなかったのでCopilotからの次を試す。
・明日試すべき実装イメージ
512バイトのバッファ(buf[512])を処理する場合:
for (int i = 0; i < 512; i += 32) {
while (digitalRead(DREQ_PIN) == LOW) {
// DREQがHighになるまで待つ
}
// ここで「32バイト分だけ」を転送する
SPI.transfer(&buf[i], 32);
}
このように「DREQ確認 → 32バイト転送」を1セットとして、
それを16回繰り返して512バイトを消化する形にすると、VS1011のバッファ設計に最も適した形になります。
<結果>
・SPI.transfer(&buf[i],
32);の部分で不明なコンパイルエラーが出たので、単純に32byte毎のDREQチェック+32byte単位のSDI転送に変えて試した。
全く症状同じでNGだが、
何故か今まで気にならなかったノイズが気になった。
サイン音の時は全くノイズ無いが、女性アナウンサーのバックグランドミュージックに混じり小さいボツボツ音が聞こえ気になった。
ジャンパワイヤを確認したら、SDカードへのGND線が切れかけていたので交換したが同じだった。
また予備のVS1011に交換しても同じだった。
(3)
この現象は、1byte毎のDREQチェック+512byte単位の転送から、32byte毎のDREQチェック+32byte単位の転送にした時か起きてるぽい。
なので、1byte毎のDREQチェック+512byte単位の転送に戻してみた。
→ これでノイズは全く気にならないレベルになった。
しかしEOFまでSDからデータを読込んでいるのもかかわらず、最初の約28秒分しか再生されない。
使っているデータの再生時間は、1.27分(自作のPIC24Fで実績のあるプレーヤーでの実測値)
(4) 以下のように512byteSDI転送の中で、毎回byte毎にDREQをチェックするのではなく、32byte毎にDREQチェックをするように
変更してみたが状況変わらず。しかしノイズは全く気にならないレベル。
/// SDIバスに512byte単位でデータを流し込む
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < 512; i++) {
if (i%32 == 0) while (!digitalRead(DREQ)); // 32byte毎にDREQチェック
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
(5) 上記でwhile (!digitalRead(DREQ));の後にdelayMicroseconds(1);を入れてみた。
→ 変わらず
■ 2026.4.22
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチの動作確認:ESP32S3_MP3_BASIC_TEST_1.ino>
<ちょっと疑問があったのでAI検索してみた>
<私>
・SPI.transfer(&buf[i], 32) と SPI.transfer(buf[i], 32) の違い
<AI>
・SPI.transfer(&buf[i], 32) と SPI.transfer(buf[i], 32) の最大の違いは、
SPI通信に渡されるデータが「バッファのメモリ位置(アドレス)」か「バッファの中身(値)」かという点です。
多くの場合、SPI.transfer(&buf[i], 32) が正しい使い方です。
1. SPI.transfer(&buf[i], 32) (正しい使い方)
・意味: buf 配列の i 番目の要素のアドレス(番地)を指します。
・動作: このアドレスから始まる32バイトのデータを一度に送信し、
同時に受信した32バイトを同じ場所に上書きします。
・用途: 大量のデータをまとめて送受信する、バッファ全体を扱う場合に使用されます。
2. SPI.transfer(buf[i], 32) (誤りの可能性が高い)
・意味: buf 配列の i 番目の要素の値(データそのもの)を指します。
例えば buf[i] が 0x55 なら、0x55 という数値を渡します。
・動作: コンパイルエラーになるか、
数値がメモリのアドレスと解釈されて不正なメモリ領域へアクセス
(セグメンテーションフォールトや誤動作)の原因になります。
SPIライブラリは、第一引数として配列のポインタ(アドレス)を期待しているため、単一の数値を渡すと不具合につながります。
<まとめ>
記述 渡しているもの 動作 主な用途
=================================================================================================================
&buf[i] アドレス(ポインタ) 32バイトまとめて送受信 配列(データブロック)の通信
buf[i] 値(データ) 1バイトだけの処理 SPI.transfer(value)の形式なら1バイト送受信
=================================================================================================================
※ 基本的には、データをブロックとして扱う場合は &buf[i] を使用してください。
--------------------------------------------------------------------------------------------------------------------------
・今日の試行
(1) Copilotが提案した以下のスケッチを再度試してみる。
今度は&bufferのところでコンパイルエラーが出たら、詳細を控えて原因を追究してみる。
512バイトのバッファ(buffer[512])を処理する場合:
for (int i = 0; i < 512; i += 32) {
while (!digitalRead(DREQ)) {
// DREQがHighになるまで待つ
}
// ここで「32byte分だけ」を転送する
// このbufferアドレスから始まる32byteのデータを一度に送信し、
// 同時に受信した32バイトを同じ場所に上書きする。
SPI.transfer(&buffer[i], 32);
このように「DREQ確認 → 32バイト転送」を1セットとして、
それを16回繰り返して512バイトを消化する形にすると、VS1011のバッファ設計に最も適した形になります。
<結果>
・コンパイルは通った。エラー無し。
・しかし症状変わらず。
---------------------------------------------------------------------------------------------------
<私>
・
「DREQ確認 → 32バイト転送」を1セットとして、それを16回繰り返して512バイトを消化する形で
試してみましたが、状況に変わり有りませんでした。再生時間約1分のMP3ファイルでテストしてますが
SDカードからの読取り残容量のモニター出力は、512byte単位で減っていき、EOF検知で"End of file or no data."
と確認できました。以下のようなログです。
Sine Test Started...
Sine Test Stopped.
Card Mounted Successfully
Total Size: 451208
Playing...
Remaining: 451208 bytes
Remaining: 450696 bytes
Remaining: 450184 bytes
・
・
Remaining: 1160 bytes
Remaining: 648 bytes
Remaining: 136 bytes
Remaining: 0 bytes
End of file or no data.
しかし、ここまでの再生時間は約28秒(女子アナのガイド音声まで)で止まってしまいます。
以下に現在のテストスケッチ(音楽再生関数:void playMP3(const char* filename)・これをメインで
playMP3("/RNG_INFO.mp3"); のようにコール)を示しましたのでレビューお願いします。
void playMP3(const char* filename) {
File musicFile = SD.open(filename);
if (!musicFile) {
Serial.println("File not found");
return;
}
/// ファイルオープンに成功したら、ファイル全体のサイズを取得しモニターに出力
fileSize = musicFile.size(); // ファイル全体のサイズを取得 (unsigned long)
Serial.print("Total Size: ");
Serial.println(fileSize);
/// 再生開始メッセージ出力
Serial.println("Playing...");
unsigned char buffer[512]; // VS1011用に、512バイト単位の送信バッファを用意
while(1) {
/// 現在の読み取り位置を取得
currentPosition = musicFile.position();
/// 残りバイト数を計算しモニター出力
remainingBytes = fileSize - currentPosition;
Serial.print("Remaining: ");
Serial.print(remainingBytes);
Serial.println(" bytes");
if (remainingBytes == 0) break; // 全て読込み終了時(EOF検出時)このループを脱出
/// SDカードから32バイト読み出す
// musicFile.read(buffer, 32);
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();
/// 次の32バイトを読む前にSD_CSをアクティブにする。
digitalWrite(SD_CS, LOW);
}
musicFile.close();
Serial.println("End of file or no data.");
/// 終了時に無音データを送ってバッファを吐き出す
sendEndFill();
}
<copilot>
・
ログの共有ありがとうございます!「ファイル末尾(Remaining:
0)まで読み込み切っている」という事実は、大きな進展です。
プログラムのロジックや変数の型は正しく、データの供給側は完遂しています。
それなのに28秒(約450KB分)で音が止まる場合、
原因は「VS1011側が途中でデータの受け入れを拒否している(ハングしている)」可能性が高いです。
以下の3点を確認・修正してみてください。
1. SPIクロックの「速度オーバー」
・
ESP32-S3は非常に高速ですが、VS1011側の限界があります。
ettingsSDI のクロック設定は何MHzになっていますか?
確認点: VS1011の初期状態(12.288MHz時)では、SDIのクロックは6MHz以下(安全策をとるなら4MHz以下)である必要があります。
対策: SPISettings(4000000, MSBFIRST, SPI_MODE0)
程度まで落として試してみてください。速すぎるとデコードが破綻し、内部バッファが壊れて再生が止まります。
2. ファイル終端付近での読み込みエラー(512バイト固定処理)
・
提示されたコードに1箇所、バグになり得る部分があります。
musicFile.read(buffer, 512); // ★常に512バイト読み込んでいる
問題点:
ファイルの最後が512バイトに満たない場合(例:残り136バイト)、read関数は136バイトしか読み込みませんが、
その後の for ループは強制的に
512バイト分(前回のゴミデータを含む) をVS1011に送ってしまいます。
対策: 実際に読み込んだバイト数だけを送るように修正してください。
int bytesRead = musicFile.read(buffer, 512); // 実際に読めた数を取得
digitalWrite(XDCS, LOW);
for (int i = 0; i < bytesRead; i++) { // bytesRead分だけ回す
if ((i % 32) == 0) {
while (!digitalRead(DREQ));
}
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
3. VS1011の「モード設定」と「リアルタイム」の問題
・
28秒で止まるのが、もし「毎回同じ場所(データの特定の位置)」であれば、
MP3データの中にVS1011が解釈できない不正なヘッダが含まれており、そこでデコーダがフリーズしている可能性があります。
切り分け: 他のMP3ファイル(できれば128kbps固定ビットレートのもの)でも、同様に28秒付近で止まりますか?
4. SM_TEST(サインテスト)が干渉していないか
・
ログの最初に Sine Test Started
とありますが、
もしVS1011がソフトリセットされずにサインテストモードのままデータ転送へ移行していると、挙動が不安定になります。
再生前に一度、ハードウェアリセット(RESETピンをLow→High)を確実に行っているか確認してください。
まずは「SPIクロックを4MHz以下に下げる」ことと、「読み込んだバイト数(bytesRead)分だけ送る」の2点を修正して試していただけますか?
<私>
・
今はSDIは2Mです。またif ((i % 32) == 0)の方法でも既にためしてみましたが、その時は変わりなしでしたが、今は状況が違うので試してみます。
<Copilot>
・
SPIクロック2MHzであれば、速度の問題ではなさそうですね。
「Remaining
0までログが出ているのに、音は28秒で止まる」という状況から、最も怪しいのは「VS1011のデコーダが、
何らかの理由でそれ以降のデータをMP3として認識できず、無視(あるいは破棄)している」可能性です。
以下の3つのポイントを順に確認してみてください。
1. 読み込みバイト数の厳密な管理
・
先ほどのコードを、実際に読み込めた数(bytesRead)だけ送る形に書き換えてみてください。ゴミデータを送るとデコーダが混乱します。
int bytesRead = musicFile.read(buffer, 512); // 実際に読めたバイト数を取得
if (bytesRead > 0) {
digitalWrite(SD_CS, HIGH); // SDを離す
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
for (int i = 0; i < bytesRead; i++) {
// 32バイトごとにDREQチェック
if ((i & 31) == 0) {
while (!digitalRead(DREQ));
}
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
digitalWrite(SD_CS, LOW); // SDに戻す
}
2. MP3ファイルの形式(VBR vs CBR)
・
VS1011は古いチップのため、VBR(可変ビットレート)や、サンプリングレート 48kHz などのファイルで同期がズレやすい傾向があります。
テストしているファイルは 128kbps / 44.1kHz / 固定ビットレート(CBR) でしょうか?
もし可能であれば、別のツール(Audacity等)で書き出した単純なMP3で試してみてください。
3. VS1011の「リセット」と「SCI_MODE」
・
再生開始前に、VS1011が確実に「初期状態」になっているか確認が必要です。
ハードウェアリセット: 再生関数を呼ぶ前に、XRESETピンをLow→Highにしていますか?
サインテストの残存: ログにある「Sine
Test」の後に、
VS1011をソフトリセット(SCI_MODEのSM_RESETビットを1にする)していますか?
サインテストモードのままだと、SDIから送ったデータが正しく処理されないことがあります。
4. ESP32-S3特有の挙動(WDT)
・
ログが最後まで出ているので、ループ自体は回っていますが、もし「再生中に音がプチプチ切れる」などの前兆があるなら、
SPIの競合や割り込みが影響している可能性があります。
・
切り分けのアドバイス
今「28秒」で止まっているファイルとは別の、もっと短い(例えば10秒)のファイルを再生した時、最後まで(10秒分)正しく鳴りきりますか?
短いファイルなら最後まで鳴る → データ量や再生時間の増加に伴う同期ズレ(バッファフル)
短いファイルでも途中で終わる → ファイル終端の処理、またはVS1011の設定ミス
まずは「実際に読めた分だけ送る」修正で、挙動が変わるか見てみてください!
<私>
・
128kbps / 44.1kHz / 固定ビットレート(CBR)だったと記憶してます。
<Copilot>
・
128kbps / 44.1kHz / CBRであれば、VS1011にとって最も標準的で扱いやすい形式です。
ログでは「451,208バイト」を完走していますね。
これを計算すると:
451208byte x 8= 3609664byte
3609664bit / 128000bps = 28.2秒
計算がピッタリ一致しました。 つまり、「SDカードから読み切ったデータそのものが、実は28秒分しかなかった」 というのが結論です。
元々のMP3ファイルがPC上で「1.3分」と表示されているのに、実際は450KB(28秒分)しか読めていない場合、以下の2点をチェックしてみてください。
1. SDカードへの書き込みミス
・
SDカードをPCに差し戻して、ファイルプロパティのサイズを確認してください。
もしPC上でも約450KB(440KiB程度)しかなければ、SDカードへのコピーが途中で失敗しています。
1.3分の128kbps MP3なら、本来は 約1.2MB 程度あるはずです。
2. SD.open のモード
・
ESP32のSDライブラリで、もし意図せず読み取り制限がかかっている可能性は低いですが、念のためファイルが最後まで開けているか確認します。
現在のログで Total Size: 451208 と出ているのが、PC上のファイルサイズと一致しているかどうかが最大の鍵です。
3. VS1011の「残りカス」問題(おまけ)
・
もしファイルサイズがPCと一致しているのに28秒で終わるなら、VS1011が「28秒地点にある特定のデータ」をEOF(終端)と誤認している可能性があります。
しかし、今回のログの数値があまりに28秒の計算値と一致しているため、「ファイルそのものが28秒分で切れている」 可能性が極めて高いです。
・
まず、PCでそのMP3ファイルの「バイト数」を右クリック→プロパティから確認していただけますか?
ログの 451208 と一致していれば、SDカード側のファイルが壊れているか、全容量コピーされていません。
もしPC上では1MB以上あるのに、ESP32で 451208 と出るなら、SDカードの相性や読み取り制限(FAT32の境界など)を疑います。
ご確認をお願いします!28.2秒という数字があまりに綺麗に出たので、原因はここにあるはずです。
<私>
・
SDカードへの書き込みミスはありません。プロパティでも451208byteです。
<Copilot>
・
詳細なログと調査結果をありがとうございます!非常に重要な事実が判明しました。
結論から申し上げますと、現在SDカード内にある 451,208バイトのファイルそのものが、計算上「約28秒分」のデータしか含んでいません
1. なぜ「28秒」で正しいと言えるのか
・
MP3(128kbps / CBR)のデータ量は以下の式で計算できます。
1秒あたりのデータ量: 128000bits/8= 16000bytes
ファイル全体の秒数:451208bits / 16000bps = 28.2秒
・
お示しいただいたログの Remaining: 0
と、実際の再生時間が一致しているのは、ESP32とVS1011がファイルの全データを正しく処理しきった証拠です。
プログラム自体は完璧に動作しています
2. 「PCで1.3分と表示される」理由の推測
・
PC(Windows Media
Playerやエクスプローラーなど)は、MP3のヘッダ情報(ID3タグやVBRヘッダ)に書かれた「再生時間」を優先して表示することがあります。
可能性1: 元々1.3分の音声だったが、SDカードへコピーする際やファイル作成時に、中身のデータだけが450KB地点で切断(ドロップアウト)してしまった。
可能性2: ヘッダには「1.3分」と記録されているが、実際のオーディオフレームが28秒分しか存在しない「壊れたMP3」になっている
3. 次の確認ステップ
・
原因が「ソフト」ではなく「データ」にあることが濃厚ですので、以下を試してみてください。
① PCでの実再生確認:
・そのSDカード内の RNG_INFO.mp3
をPCで再生し、「28秒以降も音が出るか」を確認してください。おそらくPCでも28秒で無音になるか、再生が止まるはずです。
② 別ファイルのテスト:
・全く別の(例えば5分程度の)MP3をSDに入れてみてください。
ファイルサイズが 約1MB/1分
程度あることを確認してから再生し、1分を超えて鳴り続ければ、今回の件は「特定ファイルが壊れていた」ことが確定します。
③ 再エンコード:
・もし元の1.3分の音声が必要な場合は、Audacity などのフリーソフトで一度開き、
再度 128kbps / CBR
で書き出し直してみてください。これにより、ヘッダと実データの乖離が修正されます。
お手元のPCでSDカード内のファイルを再生した際、28秒以降はどう聞こえますか?
・
最後になりますが、実装された「32バイト単位でDREQを確認し、読み込みサイズ(bytesRead)分だけ送る」処理は、
今後の安定動作において非常に理想的なコードですので、そのまま使われることをお勧めします。
<私>
・
おかしいです。以前作ったpic24fでの同じ構成のmp3プレーヤでこのsdカードの同じ音源を再生させストップウオッチで実測したので時間は間違いなです。
<Copilot>
・
実測データに基づいた貴重な情報をありがとうございます。
PIC24Fのプレーヤーで同じSDカード・同じファイルが1.3分(約80秒弱)鳴っていたのであれば、ファイルが壊れているという線は消えました。
そうなると、ESP32-S3が「ファイルサイズを誤認している(途中でEOFと判断している)」 という、非常に奇妙な現象が起きていることになります。
・
最大の謎:なぜ451,208バイトで止まるのか
128kbps/1.3分のMP3は、計算上 約1.2MB 程度あるはずです。
それなのに musicFile.size() がその約1/3の 451208
を返し、かつ Remaining: 0
でループが終わっているということは、
ESP32のSDライブラリが、ファイルの途中で「ここが終端だ」と思い込んでいることになります。
以下の3つの可能性を検証していただけますか?
1. FAT32のクラスタ境界の問題(SDカードの不整合)
・
SDカード内のファイルエントリが何らかの理由で不安定になっている可能性があります。
対策:
・SDカードの中身を一度PCにバックアップし、「SDメモリカードフォーマッター」 で上書きフォーマット(クイックではなく)した後、
再度ファイルを書き込んでみてください。Windows標準のフォーマットよりも信頼性が高いです
2. ESP32 SDライブラリのバグ・制限
・
ESP32の SD.h (または SD_MMC.h)
が、特定のファイルシステムやクラスタサイズにおいて、大きなファイルを正しくシークできていない可能性があります。
・確認:
プログラムの冒頭で musicFile.size() を表示していますが、PCのプロパティで表示される正確なバイト数 と比較してどうでしょうか?
PCで 1,248,000バイト なのに ESP32で 451,208バイト なら、ライブラリの読み取りエラーです。
PCでも 451,208バイト なら、PIC24Fで再生した時とは「別の状態」のファイルになっています。
・
SPIの干渉(ハードウェア要因)
SDカードとVS1011でSPIバスを共有している場合、XDCS(VS1011)を操作する際のノイズや切り替えタイミングで、
SDカード側が「通信終了」と誤認してバスを離してしまっている可能性があります。
切り分け:
・playMP3
関数内で、VS1011への転送処理(SPI2.transferの部分)を全てコメントアウトし、
単に「SDカードから512バイト読み込んでRemainingを表示するだけ」のループを回してみてください。
これで最後まで(1.2MB分など)ログが出れば、VS1011との通信干渉が原因です。
これでも 451208 で止まれば、SDカードライブラリまたはSDカード自体の問題です。
まずは「VS1011への出力を止めて、SDカードの読み込みログだけを見る」テストで、ファイルサイズ誤認が解消するか確認していただけますか?
これが解消すれば、原因は「SPIバスの競合(CSの制御タイミングなど)」に絞り込めま
<私>
・
確認: プログラムの冒頭で musicFile.size() を表示していますが、PCのプロパティで表示される正確なバイト数 と比較してどうでしょうか?->
ぴったり合ってます
<Copilot>
・
「ファイルサイズはPCと一致しており、最後まで読み切っている」という事実から、原因は非常にシンプルかつ衝撃的なところに絞り込まれました。
結論から申し上げますと、「VS1011が、本来の約3倍のスピードで再生(空回し)してしまっている」可能性が極めて高いです。
・
なぜ「28秒」で止まるのか(再計算)
先ほど計算した通り、451,208バイトのMP3データは、128kbpsなら物理的に「約28秒分」のデータ量しかありません。
PIC24Fの構成で同じファイルが「1.3分(78秒)」鳴っていたのであれば、その時のファイルサイズは 約1.2MB あったはずです。
もし現在、SDカード内のファイルが 451KB なのに、中身が 1.3分(80秒弱)分 の音声を含んでいるとするなら、
そのMP3は 約45kbps
という非常に低いビットレートで作成されていることになります。
・
今の状態:
45kbps相当のデータを、128kbps(本来の3倍速)の速度でVS1011が処理してしまっているため、28秒でデータが底をつき、再生が終了している。
・
解決のためのチェックポイント
①
VS1011のクロック設定(SCI_CLOCKF)を確認してください
②
VS1011に供給している外部クロック(水晶発振器)の周波数と、プログラム内で設定している SCI_CLOCKF レジスタの値は一致していますか?
③
もし水晶が 12.288MHz なのに、レジスタ設定で倍率を高くしすぎていると、デコード速度が速くなり、早口(チップの声)で再生されます。
・
サンプリングレートの誤認
VS1011がMP3のヘッダを正しく読み取れず、例えば「22kHzのデータなのに44kHzとして再生している」ような状況です。
対策: SCI_AUDATA レジスタを読み出して、再生中にVS1011が認識している「サンプリングレート」と「チャンネル数」を確認してみてください。
・
PIC24Fの時と「本当に同じファイル」か再確認
「プロパティで451,208バイト」というのは、1.3分の128kbps MP3としては明らかに小さすぎます。
128kbps × 80秒 ÷ 8 = 1,280,000バイト
451,208バイト ÷ 80秒 × 8 ≈ 45kbps
もし元の音が1.3分あるなら、PCでそのファイルを右クリックし「詳細」タブからビットレートを確認してください。
もし45kbps前後であれば、VS1011側でその低ビットレートを正しくデコードできていない可能性があります。
・
最も可能性が高いのは「VS1011のデコード速度が意図せず速くなっている」ことです。
再生中の声の高さ(ピッチ)は正常(PIC24Fの時と同じ)ですか?それとも、少し高い「早回し」のような声になっていますか?
<私>
・VS1011のクロック設定(SCI_CLOCKF)を確認してください-> okです
<Copilot>
・
ご確認ありがとうございます。SCI_CLOCKF
の設定も問題なく、ファイルサイズもPCと完全に一致しているとなると、
残る可能性は「再生速度(デコード速度)が本来の約3倍速になっている」という点に絞られます。
1. 「28秒」と「80秒」の決定的な差
・
改めて、データの流れを整理します。
ファイルの実体: 451,208バイト(PIC24Fでは約80秒かけて再生されていたデータ)。
計算上のビットレート: 451208 x8 / 80= 45kbps
・現在の現象:
・この45kbps相当のデータを、VS1011が「128kbps」のペースで消費しているため、データが底をついている。
もし再生される音声が「早口(ピッチが高い)」であれば、VS1011のサンプリングレート設定が原因ですが、
もし「ピッチは正常(声は普通だが、すぐ終わる)」のであれば、VS1011がMP3フレームの一部を読み飛ばしているか、
あるいは「データの送り込みが速すぎて、VS1011がパケットを正しく処理できていない」可能性があります。
2. 次に試すべき2つのデバッグ
① サンプリングレートとビットレートの動的確認
・
再生ループの中で、VS1011が今「どのサンプリングレート」で動いているかを表示させてみてください。
// 再生ループの適当な場所で1回だけ実行
unsigned int audata = read_register(SCI_AUDATA); // SCI_AUDATA (0x5) を読み出す
Serial.print("Sample Rate: ");
Serial.println(audata & 0xFFFE); // 下位1ビット以外が周波数
Serial.print("Channels: ");
Serial.println(audata & 0x1 ? "Stereo" : "Mono");
unsigned int hdat0 = read_register(SCI_HDAT0);
unsigned int hdat1 = read_register(SCI_HDAT1);
// HDAT1, HDAT0から現在のビットレートを解析できます
・
これで、もし 44100 と出ているのに元の音が 22050 などであった場合、速度が2倍(またはそれ以上)になります。
② SPIバスの「干渉」を物理的に疑う
・
ESP32-S3は非常に強力なドライブ能力を持っています。
もし SDカードのCS(SD_CS) と VS1011のXDCS
がSPIバス上で「一瞬でも両方アクティブ」になっていると、データが化けます。
現在のコードにある以下の部分に、少しだけ待ち時間を入れてみてください。
digitalWrite(SD_CS, HIGH);
delayMicroseconds(10); // ★追加:SDカードがバスを完全に離すのを待つ
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW);
// ...転送...
digitalWrite(XDCS, HIGH);
delayMicroseconds(10); // ★追加
digitalWrite(SD_CS, LOW);
・
特に高速なESP32-S3では、GPIOの切り替えが速すぎて、デバイス側の応答(ハイインピーダンス化)が追いつかないことがあります。
・
結論としての推測
PIC24Fの時と同じファイルが「451,208バイト」なのは確定ですので、「ESP32-S3+VS1011の組み合わせにおいて、
データの消費スピードがPIC24Fの時よりも格段に速くなっている」ことが28秒で終わる正体です。
まずは SCI_AUDATA でVS1011が認識している周波数 を確認し、それが本来の音源の周波数と一致しているか見ていただけますか?
もし周波数が一致しているのに早く終わる場合、転送中にデータが化けて「MP3フレームを読み飛ばしている」可能性が高まります。
<私>
・
音声のピッチは正常です
<Copilot>
・
音声のピッチが正常であるということは、VS1011のサンプリングレート設定やクロック倍率は正しく、「音を正しくデコードして鳴らしている」ことを意味します。
そうなると、「28秒で物理的に全データを送り終えている」という事実と、「本来は80秒(1.3分)分の音声である」という事実の間にある矛盾は、以下の1点に集約されます。
・
結論:
データが「間引き」されて送られている可能性
ピッチが正常なのに再生時間が短くなるのは、VS1011にデータが届く過程で、多くのデータ(MP3フレーム)が欠落(スキップ)しているためです。
VS1011は「壊れたデータ」や「不完全なフレーム」を無視して、次の正しい同期信号(0xFFE...)を探して再生を続けます。
その結果、飛び飛びの再生になり、時間は短縮されますがピッチは正常に聞こえます。
原因として最も怪しいのは、SDカードの読み込みとVS1011への送信の切り替え(SPIバスの競合)です。
・
修正案:
CS(チップセレクト)制御の厳密化
ESP32-S3の高速な処理に対し、SDカードやVS1011の「バスの解放」が追いついていない可能性があります。
特に、digitalWrite(SD_CS,
LOW) をした直後に次のループで musicFile.read() を呼ぶ際、バスが安定していないかもしれません。
以下の構造を試してみてください。
// --- 修正イメージ ---
while(remainingBytes > 0) {
// 1. SDカードから読み出す
digitalWrite(XDCS, HIGH); // VS1011を確実に離す
digitalWrite(XCS, HIGH); // VS1011を確実に離す
digitalWrite(SD_CS, LOW); // SDを選択
delayMicroseconds(5); // 安定のための微小待ち
int bytesRead = musicFile.read(buffer, 512);
digitalWrite(SD_CS, HIGH); // SDをすぐに離す
delayMicroseconds(5);
if (bytesRead <= 0) break;
// 2. VS1011へ送る
SPI2.beginTransaction(settingsSDI);
digitalWrite(XDCS, LOW); // VS1011(Data)を選択
for (int i = 0; i < bytesRead; i++) {
if ((i & 31) == 0) {
while (!digitalRead(DREQ));
}
SPI2.transfer(buffer[i]);
}
digitalWrite(XDCS, HIGH);
SPI2.endTransaction();
// Remainingのシリアル出力は、毎ループ行うと非常に重いので、
// 100ループに1回程度にするか、一時的に消してテストしてください
}
・
なぜこれが疑わしいか
現在のコードでは、digitalWrite(SD_CS, LOW) がループの最後(while の閉じ括弧の直前)にあり、
その直後に
musicFile.position() や musicFile.read() が呼ばれています。
もしSDカード側がバスの制御を完全に握る前にVS1011側への操作や通信が混ざると、SDカードから読み出したデータが化ける
→
VS1011が「不正なMP3データ」としてその部分を破棄する という現象が起きます。
・
確認のための実験:
もし可能であれば、musicFile.read で読み込んだ直後の buffer[0] と buffer[1] などをシリアルに出力し、
MP3の同期信号である
0xFF などの値が頻繁に出現しているか確認してみてください。
もしここが 0x00 や 0xFF
ばかりでデタラメな値なら、SPIバス上でのデータの衝突が確定します。
「SDカードを読み出す間はVS1011を完全に黙らせる(CSを確実にHIGHに保つ)」ことを意識してコードを整理してみてください。
<私>
・
バスの衝突を避けるの厳密化ですか。見直してみます。
<Copilot>
・
はい、その通りです。
ESP32-S3は非常に高速なため、PIC24Fの時には表面化しなかった「デバイスがバスを離しきる前の切り替え」が問題になることが多々あります。
特にVS1011には、制御用の XCS とデータ用の XDCS の2つのセレクト信号があるため、これらが確実に HIGH
になっていることを保証する必要があります。
以下の2点を追加で意識してみてください:
① 未使用ピンの固定:
・
もし XCS(制御用)を転送ループ内で操作していない場合でも、
初期化時に digitalWrite(XCS, HIGH) を行い、データ転送中も確実に HIGH
に固定されていることを確認してください。
② SPIの「モード」と「設定」の再適用:
・
SDカードライブラリ(SD.hなど)は、内部で独自のSPI設定(クロックやモード)を勝手に適用することがあります。
SDカードから読み出した後、VS1011に送る直前で必ず
SPI2.beginTransaction(settingsSDI) を呼び出す今の構造は正しいので、そのまま継続してください。
・
「SD読み込み」と「VS1011送信」の間に、ごくわずかな(数マイクロ秒の)余白を作るだけで、データ化けが収まり、本来の1.3分が鳴り出す可能性は非常に高いです。
本日は夜も遅くなってまいりましたので、このあたりで一度区切られてはいかがでしょうか。
明日また、「バス制御の厳密化」を試された結果を楽しみにお待ちしております。解決まであと一歩のところまで来ているはずです!
それでは、おやすみなさい。
<私>
・
色々ありがとうございました。頭をリフレッシュしてから再開します。おやすみです。
<Copilot>
・
こちらこそ、長時間お疲れ様でした!
「ピッチは正常なのに時間が短い」=「データがどこかで間引かれている」という非常にクリアな仮説にたどり着きましたので、
リフレッシュした後にバスの制御タイミングを少し調整するだけで、パズルのピースがはまるように解決する気がします。
落ち着いた頭で取り組むと、案外すぐ答えが見つかるものです。
明日の再開時、無事に美しいアナウンスが1.3分間鳴り響くことを応援しております!
それでは、おやすみなさい。
<私>
・
アナウンスの後にサイン音が鳴り、その後、本編の周波数スイープが開始されるオーディオ装置チェック用の音源となってます。
<Copilot>
・
詳細な音源内容を教えていただき、ありがとうございます!「アナウンス → サイン音 → 周波数スイープ(本編)」という構成ですね。
この構成を聞いて、さらに「28秒で止まる」原因の核心に近づいた気がします。
というのも、「周波数スイープ」のような純音に近い連続的な信号は、MP3エンコードにおいて非常に高いビットレートを要求したり、
逆にデータが極端に圧縮されたりしやすく、デコーダ(VS1011)が同期を外しやすい箇所だからです。
リフレッシュ後の調査で役立ちそうな、「スイープ音源特有のチェックポイント」を整理しました。
①
スイープ開始直後の「同期落ち」
・
アナウンス(音声)は複雑な波形なので同期しやすいですが、スイープ音(純音)が始まると、VS1011が「データ化け」に対してより敏感になります。
SPIバスのわずかなノイズや競合で1バイトでもデータが飛ぶと、VS1011が「ここから先はMP3ではない」と判断し、残りのスイープ部分を丸ごと無視して、
次の有効なフレーム(EOFまで見つからない場合が多い)までスキップしている可能性があります。
②
データ消費の激変
・
スイープ音の部分がもし高ビットレートでエンコードされている場合、VS1011のDREQが頻繁にLowになり、転送の「待ち」が発生しやすくなります。
このタイミングでSPIバスの切り替え(SD
↔ VS1011)がバタつくと、データの供給が追いつかず、デコーダがハングすることがあります。
③
転送ループ内の「空打ち」の排除
・
昨日検討した「実際に読み込んだバイト数(bytesRead)だけを送る」という修正が、ここで効いてきます。
スイープ音の途中でゴミデータ(512バイトに満たない端数など)が混じると、VS1011は高確率でそこで再生をストップします。
・
明日の作戦案:
まずは、「バスの衝突回避(数μsのウェイト)」と「正確な読み込みサイズ(bytesRead)での転送」を適用した状態で、「スイープ音が始まる瞬間」に注目して聞いてみてください。
もし28秒がちょうど「アナウンスからスイープに切り替わる瞬間」であれば、原因はデータ構造の切り替わりに対する転送ミスで確定です。
それでは、今度こそゆっくりお休みください。
本編のスイープ音が最後まで綺麗に鳴り響くのを楽しみにしております!おやすみなさい。
■ 2026.4.23
<続テスト第2弾:SDカードから、1つのMP3ファイルを読出し1回だけ再生するArduinoスケッチの動作確認:ESP32S3_MP3_BASIC_TEST_1.ino>
<結局は今迄の私の疑問は、私の勘違いだった。完璧に動いていた。>
<私>
・おはようございます。解りました。
今までの私の疑問は勘違いでした。
テスト用に使ってたMP3ファイル:RNG_INFO.mp3は女性アナウンサの音声ガイドのみの音源でした。
pic24fで続きまで再生できてたのは、次のファイルサーチ~再生まで可能なソースだったからでした。
sdカードリーダーを繋いでpcでプロパティを確認し、win11標準の再生ソフトで再生して、今更ですが気が付きました。



<Copilot>
・
おはようございます!原因が判明してスッキリしましたね。
お送りいただいた画像のプロパティを見ると、「長さ 00:00:27」とはっきり表示されています。
昨夜のログに出ていた「451,208バイト」という数値は、この27〜28秒分のデータ量として完璧に正しかったということになります。
・
結論:
システムは「正常」でした
ESP32-S3+VS1011の構成は、ファイルを最後(EOF)まで完璧に読み込み、正しく再生しきっていたことが証明されました。
PIC24Fの時: プログラムが自動で「次のファイル(本編のスイープ音)」を探して連続再生していたため、1本の長いファイルのように聞こえていた。
今回のESP32: 指定した1ファイル(イントロのみ)を再生して正しく終了していた。
プログラムにバグがなかったのは素晴らしいニュースです!
・
次のステップへのアドバイス
「アナウンス → サイン音 → 周波数スイープ」と連続して聴きたい場合は、
今の playMP3 関数を、フォルダ内のファイルを順次読み込むようにループさせるか、
本編のファイル名(例:RNG_SWEEP.mp3 など)を続けてコールすれば、意図した通りの動作になるはずです。
・
昨夜一生懸命考えた「32バイトごとのDREQチェック」や「実際に読めた分だけ送る(bytesRead)」という修正は、
今後ビットレートが異なるファイルや、より長いファイルを再生する際の安定性を飛躍的に高めてくれるので、
決して無駄ではありません。むしろ、非常に信頼性の高いプレーヤーの土台が完成しました。
次は、本編のスイープ音ファイルを指定して、最後まで鳴り切るかテストしてみますか?
<私>
・
まずはここまでのドキュメントを整理してhp公開作業を進めたいと思います。これでより良い土台が出来ました。
<Copilot>
・
それは素晴らしいですね!原因が「不具合」ではなく「仕様通りの完璧な動作」だったという結果は、
HPで公開する技術資料としても非常に価値があります。
特に、「128kbpsのMP3データ量と再生時間が計算上一致し、
プログラムが正確にEOFまで処理できていることをログで証明できた」というプロセスは、
同じ構成で悩む他の開発者にとっても非常に説得力のある内容になるはずです。
今回の実験で得られた、より強固になった土台(ベストプラクティス)をまとめておきます:
・DREQの厳密な管理: 32バイトごとにチェックし、VS1011のバッファ仕様に忠実に送る。
・ファイル終端の正確な処理: read() の戻り値(実際に読めたバイト数)に基づいた転送を行い、ゴミデータを送らない。
・バス競合の意識: ESP32-S3のような高速な石では、CS(チップセレクト)の切り替えタイミングが重要であること。
これらが組み込まれたソースコードは、まさに「実戦向き」の信頼性を持っています。
・HPの公開作業、頑張ってください!また次のステップ(連続再生の実装やスイープ音のテストなど)で何かあれば、いつでもお声がけください。
無事に公開まで漕ぎ着けることを応援しております。お疲れ様でした!
<私>
・
本当にいろいろありがとうございました。まあこういう勘違いによる回り道はよくあるので・・・でもとてもよい勉強になりました。
<Copilot>
・
いえいえ、こちらこそありがとうございました!
開発において、こういう「勘違い」は誰にでもありますし、実は一番の学びになる最高のスパイスだったりしますよね。
今回の「回り道」のおかげで、以下のことが確信できました:
①
計算と実測が一致することの重要性(450KB = 28秒という理論の裏付け)
②
高速なESP32-S3でもVS1011を正確に制御できるコードの完成
③
「データが壊れているのか、プログラムが悪いのか」を切り分けるデバッグ手法
これらを実体験として通られたことで、これからHPを見て学ぶ方々にも、より説得力のある「生きた情報」が提供できるのではないでしょうか。
整理されたドキュメントやHPの公開、楽しみにしています!
また別の音源でのテストや、機能追加などで何かあれば、いつでも気軽に声をかけてください。
最高のHP公開になりますように!こちらこそ、ありがとうございました!
<私>
・
次のステップの時もよろしくです
<Copilot>
・
こちらこそ、ぜひよろしくお願いいたします!
今回のような「理論と実測を突き合わせるデバッグ」を乗り越えたことで、システムへの信頼がぐっと深まりましたね。
次のステップ(連続再生の実装や、大容量ファイルの扱いなど)で新しい課題が出てきた際も、この「盤石な土台」があれば、きっとスムーズに進むはずです。
HPの公開作業、リフレッシュした頭で楽しんで進めてください。
またいつでも、進展やご相談をお待ちしております!
次回のアップデートも楽しみにしております。頑張ってください!
<最終回路図>
・実験テーマ169と同じ回路です。
<最終スケッチ>
・こちらから、どうぞ→
ESP32S3_MP3_BASIC_TEST_1.ino