当前位置: 首页 > news >正文

VS1053B的非阻塞式播放

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非阻塞式播放了......

http://www.jsqmd.com/news/1128602/

相关文章:

  • Fate/Grand Automata:终极Android自动化工具,告别FGO重复刷本
  • 鸿蒙物理 108 篇 第六十八篇 五行反向相克机理
  • Power BI Report Builder实战指南:快速生成合规分页报表
  • leecodecode【面试150】【2026.7.2打卡-java版本】
  • YOLO11改进 - C3k2融合 | C3k2融合CKconv中国结卷积(Chinese Knot Convolution)增强网络对暗弱小目标的特征表达能力 | TGRS 2026
  • 微服务架构下API网关的4种安全方案对比,哪种最适合你的系统?
  • 拼多多推广全攻略高效引流玩法
  • Windows经典游戏联机救星:5分钟让红警、星际、暗黑等老游戏重获新生
  • VLC Android电视版深度配置:打造专业级智能电视媒体中心的7个关键步骤
  • UI自动化测试方案调研:从概念到落地的完整决策指南
  • 为什么内向者会“话题终结者”?
  • 外贸独立站不是门面工程,而是获客引擎
  • 从《中国统计年鉴》到可比数据:手把手教你计算不变价GDP
  • Java程序设计(第3版)第四章——静态代码块
  • Codex + Figma:从零构建高保真 UI 的终极指南
  • Devin工程化落地:AI协作者如何嵌入CI/CD与测试流水线
  • vs调试技巧+宏定义+动态内存
  • 一线老师傅经验谈:选对海绵喷胶源头厂家,粘接寿命延长8年
  • linux x_86_64动态链接,gdb理解link_map参数
  • 内向者和别人聊天缺少共同话题的庖丁解牛
  • 数学艺术图案画-曼陀罗(39)
  • YouTube AI 助手存在提示注入风险,点击链接或致创作者私人视频标题泄露!
  • Dify 本地化部署指南(全平台)
  • 终极精简指南:使用PowerShell脚本让Windows 11瘦身50%
  • 『物流翻译+支付说明多语言』跨境国际化再升级 | VortMall微服务商城系统v1.3.8版本正式发布
  • Claude Code Auto mode 的成本与延迟,别只看模型价格,还要看每一次动作背后的安全往返
  • Audacity终极指南:3小时从零到精通的免费音频编辑完整教程
  • AI让我们啥时候失业。
  • 一站式Android固件解包工具:20+厂商格式的终极解决方案
  • 建站工具测评:BBWEYY/比文云/Framer/Make/Brevo(2026年7月更新)含零代码SAAS、AI编程、源码定制交付