VS1053B的非阻塞式播放
2026/7/5 13:51:30 网站建设 项目流程

2026年6月,突然有了一种想听MP3音乐的冲动,连续买了几个简单小模块,如YX5300等,然后再Arduino IED中搜库,搞Arduino的都知道,必须有库,不然买回来也白搭。

这种模块,自带耳机插孔和TF卡,自己买了32G的TF卡,按要求下载了几个MP3歌曲,并在文件名前加上001XXX.mp3,002YYY.mp3...,都是用串口控制(波特率9600)的,程序也简单:

串口播放源代码(需下载YX5300_ESP32库):

#include <YX5300_ESP32.h>//MP3头文件 #define RX2 16 #define TX2 17 YX5300_ESP32 mp3;//the mp3 object void setup() { Serial.begin(115200); mp3 = YX5300_ESP32(Serial2, RX2, TX2);//用串口2控制,波特率9600 mp3.playTrack(1);//第1文件 } void loop() { while (Serial.available()){ char theChar = (char)Serial.read(); Serial2.write(theChar); } String strTxd = ""; if (Serial2.available()){ strTxd = Serial2.readString(); } if (strTxd != ""){ Serial.write(strTxd.c_str(), strTxd.length()); switch (strTxd[3]){ case 0x3a://插入TF卡 mp3.playTrack(1);//播放第1文件 break; case 0x3b://拔出TF卡 break; case 0x3c: case 0x3d: case 0x3e://系统不忙 //mp3.playTrack(strTxd[6]); mp3.next(); break; } strTxd.clear(); } }

用ESP32下载试试,果然发出了声音,呵呵,不错。

玩了几天,感觉不爽,歌曲名、歌手都显示不出来,又在网上搜了VS1053B

看到有SPI接口的几个线,应该是读TF卡的,可以读出文件目录,先买回来试试。

连接SPI,应使用VSPI,程序中也是默认这个,HSPI不能用,同时其他引脚应避开D12、D13脚,以前踩了多次坑。

VS1053第一次尝试:

#include <UTF8ToGB2312.h>//UTF8到GB2312码 #include <Adafruit_VS1053.h>//VS1053 #include <vector>//vector对象记录文件名 using namespace std;//使用std命名空间 typedef struct { int nNo;//文件序号 String strFileName;//GB2312的文字 String strOrgName;//UTF8格式的文字 } file;//文件结构 // 全局变量 vector<file> files;//文件名VECTOR int nCur = -1;//播放序号 uint8_t volLeft = 30;//左声道 uint8_t volRight = 30;//右声道 #define CARDCS 5 //SPI chip select #define MP3_DREQ 27//VS1053 Data request, ideally an Interrupt pin #define MP3_XCS 14//VS1053 reset pin (output) #define MP3_XDCS 2//VS1053 chip select pin (output) #define MP3_RESET 4//VS1053 Data/command select pin (output) Adafruit_VS1053_FilePlayer MP3player = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, CARDCS);//create mp3Player object! void setup() { Serial.begin(115200); if (!SD.begin()) { Serial.println(F("SD failed, or not present")); } printDirectory(SD.open("/"), 0); sort(files.begin(), files.end(), [](const file & a, const file & b) { return a.nNo < b.nNo; });//文件名升序 for (int i = 0; i < files.size(); i ++) { Serial.println(files[i].strFileName);//打印文件目录 } // VS1053初始化 if (!MP3player.begin()) { Serial.print(F("Error code: mp3Player isn't begin")); } MP3player.setVolume(volLeft, volRight); } void loop() { if (MP3player.stopped()) { String str = "/"; nCur = nCur < files.size() - 1 ? ++ nCur : 0; str += files[nCur].strOrgName; Serial.println(files[nCur].strFileName); MP3player.playFullFile(str.c_str());//将文件扔给VS1053 } if (Serial.available()) { Serial.println(Serial.read()); } } // File listing helper void printDirectory(File dir, int numTabs) { while (true) { File entry = dir.openNextFile(); if (!entry) { break; } for (uint8_t i = 0; i < numTabs; i++) { Serial.print('\t'); } file f; f.strOrgName = entry.name(); f.strFileName = GB.get(entry.name()); f.nNo = f.strFileName.toInt(); files.push_back(f); //Serial.print(f.strFileName); if (entry.isDirectory()) { Serial.println("/"); printDirectory(entry, numTabs + 1); } else { // files have sizes, directories do not } entry.close(); } }

连接好ESP32和VS1053,下载程序,运行:

哈哈,不错,文件名全读出来了。文件也能播放,但按“发送”键,屏幕无反应,应该发什么,串口回什么。搜了一下:MP3player.playFullFile();是阻塞式播放,程序运行到此就会在函数内运行,直至函数运行完(即歌曲播放完),网上也有解决办法,即在初始化时:MP3player.useInterrupt(VS1053_FILEPLAYER_PIN_INT);

在循环里不要用MP3player.playFullFile();要用MP3player.startPlayingFile();看了好多文章,都是这样说的,于是就改代码,改了代码,就不断重启。

到网上查rst:0x8,有人说是溢出了,有人说是中断执行太长,我认为也是中断执行时间过长,改了很多次,包括改SPI的速度、默认一次拷贝32个字节,换是16、甚至改成1个,还是不行,换头文件等等也不行;看了不是代码执行速度的问题,应该是代码以前不是针对ESP32 DEVKIT-V1开发的,或是咱们使用的问题。最后把Adafruit_VS1053.cpp文件的相关代码逐一拷贝到源文件里测试,试了一天,终于让我发现秘密了。先用startPlayingFile(String strName)函数,做打开文件的准备工作,包括打开文件、定位到MP3数据的起始位置,保证MP3_DREQ引脚变成高电平。

关键代码:

while (playingResume && digitalRead(MP3_DREQ)) {
feedBuffer();//从文件读出数据送到播放缓冲区
}

这个循环不可能一直在循环,数据拷到1024或1056左右时(跟踪过),MP3_DREQ引脚就变成低电平了,则退出循环了(速度很快),我们的其他代码就能继续执行了。数据用完了,MP3_DREQ引脚就变成高低电平了,同样再进入循环...

最终非阻塞式播放代码:

#include <UTF8ToGB2312.h>//UTF8到GB2312头文件 #include <Adafruit_VS1053.h>//VS1053必须头文件 #include <vector>//vector对象记录文件名 using namespace std;//使用std命名空间 typedef struct { int nNo; String strFileName; String strOrgName; } file; // 全局变量 vector<file> files;//记录文件容器 int nCur = -1;//播放序号 int nSize = 0;//歌曲总数 File mp3File;//当前播放的MP3文件 uint8_t mp3buffer[32] = {0};//数据缓冲区 uint8_t nLoopMode = 0;//正向播放0/逆序播放1/随机播放2/单曲3/停止4 bool bPlay = false;//正在播放 bool bPlayResume = false;//播放/暂停 bool bMuted = false;//播放/暂停 uint8_t volLeft = 30;//左声道 uint8_t volRight = 30;//右声道 #define CARDCS 5 //SPI chip select #define MP3_DREQ 27//VS1053 Data request, ideally an Interrupt pin #define MP3_XCS 14//VS1053 reset pin (output) #define MP3_XDCS 2//VS1053 chip select pin (output) #define MP3_RESET 4//VS1053 Data/command select pin (output) Adafruit_VS1053_FilePlayer MP3player = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, CARDCS);//create mp3Player object! void setup() { Serial.begin(115200); if (!SD.begin()) {//TF文件初始化 Serial.println(F("SD failed, or not present")); } printDirectory(SD.open("/"), 0);//储存文件目录 sort(files.begin(), files.end(), [](const file & a, const file & b) { return a.nNo < b.nNo; });//文件名升序 nSize = files.size(); for (int i = 0; i < nSize; i ++) { Serial.println(files[i].strFileName);//打印文件目录 } // VS1053初始化 if (!MP3player.begin()) { Serial.print(F("Error code: mp3Player isn't begin")); } MP3player.setVolume(volLeft, volRight);//初始化音量 } void loop() { //播放完毕自动循环的判断 if (!bPlay) { switch (nLoopMode){ case 0: nCur = nCur < nSize - 1 ? ++ nCur : 0; break;//正序循环 case 1: nCur = nCur < 1 ? nSize - 1 : -- nCur; break;//逆序循环 case 2: nCur = random(nSize); break;//随机循环 case 3: break;//单曲循环 } if (nLoopMode < 4){ Serial.println(files[nCur].strFileName); startPlayingFile(files[nCur].strOrgName);//打开文件的准备工作 } } //关键代码 while (bPlayResume && digitalRead(MP3_DREQ)) { feedBuffer();//从文件读出数据送到播放缓冲区 } //播放时的控制 if (Serial.available()) { char c = Serial.read(); switch (c) { case '+': if (volLeft <= 0) { volLeft = 0; } else { -- volLeft; -- volRight; MP3player.setVolume(volLeft, volRight);//加音量 Serial.println(volLeft); } break; case '-': if (volLeft >= 255) { volLeft = 255; } else { ++ volLeft; ++ volRight; MP3player.setVolume(volLeft, volRight);//加音量 Serial.println(volLeft); } break; case 'p': bPlayResume = !bPlayResume;//播放\暂停 break; case 'a': nCur = nCur < nSize - 1 ? ++ nCur : 0; Serial.println(files[nCur].strFileName); startPlayingFile(files[nCur].strOrgName);//下一首 break; case 'b': nCur = nCur < 1 ? nSize - 1 : -- nCur; Serial.println(files[nCur].strFileName); startPlayingFile(files[nCur].strOrgName);//上一首 break; case 'c': MP3player.setPlaySpeed(2);//快一倍 break; case 'd': MP3player.setPlaySpeed(1);//正常速度 break; case 'm': bMuted = !bMuted; bMuted ? MP3player.setVolume(255, 255) : MP3player.setVolume(volLeft, volRight);//选静音 break; case 'n': nLoopMode = nLoopMode >= 4 ? 0 : ++ nLoopMode;//选循环模式 Serial.println(nLoopMode); break; } } } //打开文件的准备工作 boolean startPlayingFile(String strName) { String str = "/"; str += strName; mp3File = SD.open(str); if (!mp3File) { return false; } if (MP3player.isMP3File(str.c_str())) { mp3File.seek(MP3player.mp3_ID3Jumper(mp3File)); } bPlay = true; bPlayResume = true; // wait till its dreq pin for high while (!digitalRead(MP3_DREQ)) { delay(1); #if defined(ESP8266) ESP.wdtFeed(); #endif } return true; } //从文件读出数据送到播放缓冲区 void feedBuffer(void) { if (!bPlay || (!mp3File) || (!digitalRead(MP3_DREQ))) { return; // paused or stopped } // Feed the hungry buffer! :) while (digitalRead(MP3_DREQ)) {//9344 // Read some audio data from the SD card file int bytesread = mp3File.read(mp3buffer, 32); if (bytesread == 0) { // wrap it up! bPlay = false; bPlayResume = false; mp3File.close(); break; } MP3player.playData(mp3buffer, bytesread); } } // File listing helper void printDirectory(File dir, int numTabs) { while (true) { File entry = dir.openNextFile(); if (!entry) { break; } for (uint8_t i = 0; i < numTabs; i++) { Serial.print('\t'); } file f; f.strOrgName = entry.name(); f.strFileName = GB.get(entry.name()); f.nNo = f.strFileName.toInt(); files.push_back(f); if (entry.isDirectory()) { Serial.println("/"); printDirectory(entry, numTabs + 1); } else { // files have sizes, directories do not } entry.close(); } }

这下完美搞定VS1053非阻塞式播放了......

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询