这个坑采的非常冤枉和巨大,非常大的冤枉路,只能一声叹息
说一下我是如何踩坑的,原本是打算用esp32 读取SD卡播放mp3,在esp32 读取自己打的SD卡已经踩了无数坑了,详情见:
少走弯路,ESP32 使用Micro SD(TF)的经验说明-CSDN博客
在好不容易解决了SD卡用1线sdmmc读取的问题,遇到了惊天大雷,esp32 s3 只支持ble 5.0 ,不支持a2dp,那就意味着esp32 s3不能用做蓝牙音频播放的接受端 ,除非哪天升级到ble 5.2的版本才有le audio.
没法,只能回到esp32 ,sdmmc的方式读取走不通,那就用SPI的方式吧。
之前在esp32 通过spi读取数据的代码如下:
SPIClass spi = SPIClass(HSPI); spi.begin(18 /* SCK */, 19 /* MISO */, 23 /* MOSI */, 5 /* SS */); if (!SD.begin(5 /* SS */, spi, 120000000,"/cdcard")) { Serial.println("Card Mount Failed"); return; }
这段代码是可以正常工作的,访问到TF卡对象,后面也能对文件和目录进行各种访问,结果坑就在这里啊。
于是,就在这段代码基础上引入了ESP8266Audio 进行音乐播放,这是个相对灵活的库,以前验证使用过。
if (file->open("/mp3/王杰 - 一场游戏一场梦 (2000版).mp3")) { // Serial.println(file->getPos()); // file->seek(100,100); // Serial.println(file->getPos()); Serial.printf_P(PSTR("Playing '%s' from SD card...\n"), mp3file.name()); id3 = new AudioFileSourceID3(file); id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG"); mp3->begin(file, out); } else { Serial.printf_P(PSTR("Error opening '%s'\n"), mp3file.name()); }
if (mp3->isRunning()) { if (!mp3->loop()) mp3->stop(); } else { Serial.println("MP3 done"); delay(1000); }
以上代码不全,思路是:
1,创建输出到I2S的AudioOutputI2S 对象;
2,AudioFileSourceSD 对象读取SD卡mp3文件;
3,AudioGeneratorMP3 负责解码,并输出到I2S;
4,AudioFileSourceID3 提供辅助信息,可以不用;
5,在loop代码中加上循环;
但是事实上,这代码只要烧录进单片机都会在setup执行完成后,一涉及到loop单片机重启。
各种跟踪后发觉问题出现在:
uint32_t AudioFileSourceID3::getPos() { return src->getPos(); }
uint32_t AudioFileSourceSD::getPos() { if (!f) return 0; return f.position(); }
这个
f.position();
只要调用就发生了异常,由于条件限制无法继续定位,怀疑是esp8266audio这个库有问题,换库,于是换成了audioI2S,其实这个库的操作更简单。
官方给出来的示例代码:
#include "Arduino.h" #include "Audio.h" #include "SPI.h" // Digital I/O used #define SD_CS 5 #define SPI_MOSI 23 #define SPI_MISO 19 #define SPI_SCK 18 #define I2S_DOUT 25 #define I2S_BCLK 27 #define I2S_LRC 26 Audio audio; void setup() { pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); SPI.setFrequency(1000000); Serial.begin(115200); SD.begin(SD_CS); audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); audio.setVolume(12); // 0...21 // audio.connecttoFS(SD, "test.mp3"); audio.connecttoFS(SD, "良い一日私の友達.mp3"); } void loop() { audio.loop(); } // optional void audio_info(const char *info){ Serial.print("info "); Serial.println(info); } void audio_id3data(const char *info){ //id3 metadata Serial.print("id3data ");Serial.println(info); } void audio_eof_mp3(const char *info){ //end of file Serial.print("eof_mp3 ");Serial.println(info); }
由于SD的操作已经完成,所以只取了部分代码:
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(12); // 0...21// audio.connecttoFS(SD, "test.mp3");
audio.connecttoFS(SD, "良い一日私の友達.mp3");
结果还是一样,只有涉及到loop,就会重启
检查官方示例发觉上面有一段注释:
/*
⒈ install SdFat V2 from https://github.com/greiman/SdFat
⒉ activate "SDFATFS_USED" in Audio.h
⒊ activate "#define USE_UTF8_LONG_NAMES 1" in SdFatConfig.h
*/
完成1,2后,重新把代码从原本的
SPIClass spi = SPIClass(HSPI); spi.begin(18 /* SCK */, 19 /* MISO */, 23 /* MOSI */, 5 /* SS */); if (!SD.begin(5 /* SS */, spi, 120000000,"/cdcard")) { Serial.println("Card Mount Failed"); return; }
换成了:
pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); SPI.setFrequency(1000000); Serial.begin(115200); SD.begin(SD_CS);
结果,居然可以了。
esp32 读取sd卡里面的mp3,然后通过i2s功放板接到喇叭正常播放。
这是sdFat库的功劳?
不服气啊,各种找原因,调整各种参数,后来发觉去掉sdfat库还是可以正常工作,问题出在spi上。
又挨个对比spi的参数,直到最后发觉
在
SPIClass spi = SPIClass(HSPI);
spi.begin(18 /* SCK */, 19 /* MISO */, 23 /* MOSI */, 5 /* SS */);
if (!SD.begin(5 /* SS */, spi, 120000000,"/cdcard")) {
Serial.println("Card Mount Failed");
return;
}
和
pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH);
SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
SPI.setFrequency(1000000);
前者是SPIClass,后者也是SPIClass,但是前者指向了esp32中的spi,后者指向了esp8266的spi,就是这个不同。
只要使用了esp32中的spi,则单片机一定会在loop中重启,
于是在用esp8266的spi下,从audioi2s换回esp8266audio,一样能够正常播放了。
因此从头到尾问题都出现在spi上,但是不管那个SPI,对于SD的一般访问读写,都是正常的,这TMD的谁能想到啊!
接着再往后走,最后发现以下通过audioi2s代码可以读取SD卡上的mp3进行播放。
#include <Arduino.h> #include <SD.h> #include "Audio.h" // put function declarations here: Audio ad; void setup() { // put your setup code here, to run once: SD.begin(); ad.setPinout(32, 33, 21); ad.setVolume(21); // 0...21 ad.connecttoFS(SD,"/马良 - 往后余生.mp3"); } void loop() { ad.loop(); // put your main code here, to run repeatedly: }
这是最简洁的代码,基本啥SPI配置在代码中都不需要,只有I2S的配置,这够简单吧?
上面这个能够正常运行的前提是按照esp32的默认访问SD卡进行SPI线路的连接
#define SD_CS 5 #define SPI_MOSI 23 #define SPI_MISO 19 #define SPI_SCK 18 #define I2S_DOUT 21 #define I2S_BCLK 32 #define I2S_LRC 33
你们看,从sd库本身的示例,到audioi2s的示例,到各种网络上关于sdmmc的示例都是在一大抄,如果没有这些错误的引导,也就不会产生错误的各种情况。
也就是说:
1,按照标准进行接线;
2,引入audioi2s库;
3,使用上面最简洁的代码,破开SPI各种配置和不同引用的影响;
4,运行,烧录单片机;成功正常播放mp3音乐。
当然这里面有一些关于VSPI,HSPI的配置在里面,只是应该按照最简洁的方式来啊!
兜兜转转,我的这个坑够大的啊!
写到这里的时候,又去折腾了一下:
在最简代码情况下,进入sd.begin方法,这时方法中的spiclass如果你按照ctrl跳转到esp8266上,而如果按照include 进行跳转又都在32的include上。
搞不懂!
反正是个坑,不知道是乐鑫的框架层面还是platformio层面;由于arduino ide引入audioi2s相对麻烦就懒得对比测试了。
考虑是否给乐鑫汇报一下。