前面的文章介绍了 DAC 方式播放和用更高精度的 PWM 方式直接播放音频文件,但是很明显我们遇到了的2个问题:
- 存储空间有限,APP 中最大只能存放2.7MB的音频;
- 每次都需要手工将数据转化为 .h ,比较麻烦。
这次就介绍如何在 FireBeetle上使用更大的空间。从介绍中可以看到FireBeetle Flash 为16MB,但是 APP 最大只能用到3MB,余下的空间要么分配给 SPIFFS,要么分给 FATFS。

SPIFFS 和 FATFS 都是一种文件系统。其中SPIFFS 是一个用于 SPI NOR flash 设备的嵌入式文件系统,支持磨损均衡、文件系统一致性检查等功能【参考1】。同样的 FATFS也是一种文件系统【参考2】。很明显 ESP32 上我们可以使用更大的空间,因此这里尝试使用 FATSFS来存储音频文件。
对于 ESP32 来说,每次上传的 APP 和 FATFS 是分开的。比如,编译之后生成了一个 3MB 的APP, 那么上传时只会烧写更新 3MB APP那一段的SPI NOR中的内容,余下的部分不会改变(加上压缩以及快速的串口通讯,我们并不会感觉上传太慢)。因此,我们还需要一个额外的工具来完成上传 FATFS 的部分。这个工具就是arduino esp32fs 插件,这个插件能将文件传输到ESP32 的SPIFFS, LittleFS 或者FatFS分区上,项目地址如下:
https://github.com/lorol/arduino-esp32fs-plugin
下载到编译好的文件是一个 JAR文件。

安装方法是在你 Arduino.exe 的目录中,Tools 下创建 ESP32FS/Tool 目录,然后将上面这个文件放置进去:

重启 Arduino 之后在 Tools 菜单中会出现 “ESP32 Sketch Data Upload”的选项。

为了给 FATFS 分区上传,我们还需要2个额外的工具 mklittlefs.exe 和 mkfatfs.exe。这两个工具需要放在C:\Users\用户名\AppData\Local\Arduino15\packages\firebeetle32\hardware\esp32\0.1.1\tools 目录下。
上面准备妥当之后,先编写一个测试程序。这个程序会列出当前 FATFS 上面的文件名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #include "FS.h" #include "FFat.h" void listDir(fs::FS & fs, const char * dirname, uint8_t levels){ Serial.printf( "Listing directory: %s\r\n" , dirname); File root = fs.open(dirname); if ( ! root){ Serial.println ( "- failed to open directory" ); return ; } if ( ! root.isDirectory()){ Serial.println ( " - not a directory" ); return ; } File file = root.openNextFile(); while (file){ if (file.isDirectory()){ Serial.print ( " DIR : " ); Serial.println (file.name()); if (levels){ listDir(fs, file.name(), levels - 1 ); } } else { Serial.print ( " FILE: " ); Serial.print (file.name()); Serial.print ( "\tSIZE: " ); Serial.println (file.size()); } file = root.openNextFile(); } } void setup () { Serial.begin ( 115200 ); if ( ! FFat.begin()){ Serial.println ( "FFat Mount Failed" ); return ; } Serial.printf( "Total space: %10u\n" , FFat.totalBytes()); Serial.printf( "Free space: %10u\n" , FFat.freeBytes()); } void loop () { listDir(FFat, "/" , 0 ); delay ( 5000 ); } |
此外,我们还要在程序目录下创建一个 data 目录,然后将3支歌曲的文件放在里面。

直接使用菜单上传FATFS分区。

选择 FATFS:

看到下面的字样就表示已经成功:

接下来,像普通 Arduino 代码一样上传我们的程序,打开串口就能看到结果:

有了上面的代码,我们可以很容易编写出来播放代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #include "FS.h" #include "FFat.h" #include "data\1990.h" #include "soc/sens_reg.h" // For dacWrite() patch, TEB Sep-16-2019 void listDir(fs::FS & fs, const char * dirname, uint8_t levels){ Serial.printf( "Listing directory: %s\r\n" , dirname); File root = fs.open(dirname); if ( ! root){ Serial.println ( "- failed to open directory" ); return ; } if ( ! root.isDirectory()){ Serial.println ( " - not a directory" ); return ; } File file = root.openNextFile(); while (file){ if (file.isDirectory()){ Serial.print ( " DIR : " ); Serial.println (file.name()); if (levels){ listDir(fs, file.name(), levels - 1 ); } } else { Serial.print ( " FILE: " ); Serial.print (file.name()); Serial.print ( "\tSIZE: " ); Serial.println (file.size()); } file = root.openNextFile(); } } void playFile(fs::FS & fs, const char * path){ Serial.printf( "Playing file: %s\r\n" , path); File file = fs.open(path); if ( ! file){ Serial.println ( "- failed to open file for reading" ); return ; } uint8_t buffer[ 1024 * 4 ]; size_t bytessend; while (file.available()){ bytessend = file.read(buffer, 1024 * 4 ); for ( int i = 0 ;i< 1024 * 4 ;i + + ) { CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M); SET_PERI_REG_BITS(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_DAC, buffer[i], RTC_IO_PDAC1_DAC_S); SET_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_XPD_DAC | RTC_IO_PDAC1_DAC_XPD_FORCE); ets_delay_us( 125 ); } } file.close(); } void setup () { Serial.begin ( 115200 ); if ( ! FFat.begin()){ Serial.println ( "FFat Mount Failed" ); return ; } Serial.printf( "Total space: %10u\n" , FFat.totalBytes()); Serial.printf( "Free space: %10u\n" , FFat.freeBytes()); listDir(FFat, "/" , 0 ); } void loop () { playFile(FFat, "/1st.wav" ); playFile(FFat, "/10years.wav" ); playFile(FFat, "/1990.wav" ); } |
参考:
- https://docs.espressif.com/projects/esp-idf/zh_CN/release-v4.1/api-reference/storage/spiffs.html
- https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/storage/fatfs.html