前面的文章介绍了 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 上面的文件名称。
#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 代码一样上传我们的程序,打开串口就能看到结果:
有了上面的代码,我们可以很容易编写出来播放代码:
#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