spiffs分区文件系统在esp idf的创建
我用的是vscdoe,为了测试一个ns4168的音频播放,我使用了spiffs分区来存储wav文件。
创建分区开始。
首先要求在配置文件中打开spiffs分区功能。
这个要拉倒最下面才看到
然后就是有时候如果配置里看不到,
REQUIRES driver spiffs # 关键:添加 spiffs 依赖
这个是加载main目录里的cmake配置文件
如果配置文件没有选项可以通过CMakeLists设置
再下来就是把分区表设定为自定义的
分区表大家都懂,在根目录下面
内容我给个例子
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
spiffs, data, spiffs, , 3M ,
也可以指定分区地址哈
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
model, data, spiffs, 0x10000, 0xF0000,
ota_0, app, ota_0, 0x100000, 6M,
ota_1, app, ota_1, 0x700000, 6M,
spiffs, data, spiffs, 0xD00000, 1M
类似于这种。都行。
然后最关键的一步,要在根目录下面的CMakeLists.txt添加自动烧录的配置。放在project项目后面。
# 配置 SPIFFS 自动烧录(关键代码)
spiffs_create_partition_image(spiffs data FLASH_IN_PROJECT)
这个时候在根目录下创建data文件夹,把你要的内容放进去就行了。
然后,清空项目,烧录,写个测试代码看看
// -------------------------- SPIFFS初始化 --------------------------
esp_err_t spiffs_init(void) {ESP_LOGI(TAG, "Initializing SPIFFS");esp_vfs_spiffs_conf_t conf = {.base_path = "/spiffs",.partition_label = NULL,.max_files = 5,.format_if_mount_failed = true};// 使用所有默认参数挂载SPIFFSesp_err_t ret = esp_vfs_spiffs_register(&conf);if (ret != ESP_OK) {if (ret == ESP_FAIL) {ESP_LOGE(TAG, "Failed to mount or format filesystem");} else if (ret == ESP_ERR_NOT_FOUND) {ESP_LOGE(TAG, "Failed to find SPIFFS partition");} else {ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));}return ret;}size_t total = 0, used = 0;ret = esp_spiffs_info(NULL, &total, &used);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to get SPIFFS partition info (%s)", esp_err_to_name(ret));esp_vfs_spiffs_unregister(NULL);return ret;}ESP_LOGI(TAG, "SPIFFS initialized, total: %d, used: %d", total, used);return ESP_OK;
}// 在代码中添加列出文件的测试函数
void AudioCodec::list_spiffs_files() {DIR *dir = opendir("/spiffs");if (dir == NULL) {ESP_LOGE(TAG, "Failed to open directory");return;}struct dirent *ent;while ((ent = readdir(dir)) != NULL) {ESP_LOGI(TAG, "File: %s", ent->d_name);}closedir(dir);
}
如果有内容,说明ok了。
调用就直接调用
// 1. 打开WAV文件FILE *wav_fp = fopen(wav_path, "rb");if (!wav_fp) {ESP_LOGE(TAG, "无法打开文件: %s", wav_path);return ESP_FAIL;}// 2. 获取文件总大小fseek(wav_fp, 0, SEEK_END);size_t total_size = ftell(wav_fp);rewind(wav_fp);
------------------------------------------------------------
我贴出来整个音频测试项目文件。
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "esp_vfs.h"
#include "esp_spiffs.h" // 添加SPIFFS支持// -------------------------- 配置参数(根据硬件修改) --------------------------
#define TAG "WAV_PLAYER"
#define I2S_BCLK GPIO_NUM_15 // I2S时钟线引脚
#define I2S_WS GPIO_NUM_16 // I2S声道选择线(LRCK)引脚
#define I2S_DOUT GPIO_NUM_7 // I2S数据输出引脚
#define PLAY_BUFFER_SIZE 2048 // 播放缓冲区大小(2KB,可按需调整)
#define WAV_SAMPLE_RATE 16000 // 目标WAV采样率(16kHz,需与播放文件匹配)
#define WAV_BIT_DEPTH 16 // 目标WAV位深(16bit,需与播放文件匹配)// 全局I2S播放通道句柄(供初始化和播放任务共用)
static i2s_chan_handle_t tx_chan = NULL;// -------------------------- I2S初始化(仅播放配置) --------------------------
/*** @brief 初始化I2S硬件(适配16kHz/16bit WAV播放)* @return ESP_OK: 成功;其他: 失败*/
esp_err_t ns4168_init(void) {// 1. 配置I2S播放通道(主模式)i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);esp_err_t err = i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL);if (err != ESP_OK) {ESP_LOGE(TAG, "Failed to create I2S TX channel: %s", esp_err_to_name(err));return err;}// 2. 配置I2S标准模式(适配16kHz/16bit/立体声,若WAV是单声道需修改SLOT_MODE)i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(WAV_SAMPLE_RATE), // 采样率匹配WAV文件.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, // 位深匹配16bitI2S_SLOT_MODE_MONO // 若播放单声道WAV,改为I2S_SLOT_MODE_MONO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED, // 多数音频放大器无需MCLK,按需启用.bclk = I2S_BCLK,.ws = I2S_WS,.dout = I2S_DOUT,.din = I2S_GPIO_UNUSED, // 播放无需输入,禁用DIN引脚.invert_flags = {.mclk_inv = false,.bclk_inv = false,.ws_inv = false,},},};// 3. 初始化I2S模式并启用通道err = i2s_channel_init_std_mode(tx_chan, &std_cfg);if (err != ESP_OK) {ESP_LOGE(TAG, "Failed to init I2S std mode: %s", esp_err_to_name(err));return err;}ESP_LOGI(TAG, "I2S init success (16kHz/16bit)");return ESP_OK;
}// -------------------------- WAV播放任务 --------------------------
/*** @brief 读取WAV文件并通过I2S播放* @param args: WAV文件路径(SPIFFS中的路径)*/
static void i2s_play_task(void *args) {const char *wav_path = (const char *)args;if (wav_path == NULL || tx_chan == NULL) {ESP_LOGE(TAG, "Invalid play params (path=%p, tx_chan=%p)", wav_path, tx_chan);vTaskDelete(NULL);return;}// 1. 分配播放缓冲区(栈外分配,避免栈溢出)uint8_t *play_buf = (uint8_t *)malloc(PLAY_BUFFER_SIZE);if (play_buf == NULL) {ESP_LOGE(TAG, "Failed to malloc play buffer (%d bytes)", PLAY_BUFFER_SIZE);vTaskDelete(NULL);return;}memset(play_buf, 0, PLAY_BUFFER_SIZE);// 2. 打开WAV文件(从SPIFFS读取)FILE *wav_fp = fopen(wav_path, "rb");if (wav_fp == NULL) {ESP_LOGE(TAG, "Failed to open WAV file: %s", wav_path);free(play_buf);vTaskDelete(NULL);return;}ESP_LOGI(TAG, "Open WAV file success: %s", wav_path);// 3. 跳过WAV文件头(标准WAV头为44字节,若文件有扩展头需调整)fseek(wav_fp, 44, SEEK_SET);// 4. 启用I2S播放通道并开始播放esp_err_t err = i2s_channel_enable(tx_chan);if (err != ESP_OK) {ESP_LOGE(TAG, "Failed to enable I2S channel: %s", esp_err_to_name(err));fclose(wav_fp);free(play_buf);vTaskDelete(NULL);return;}size_t read_bytes = 0; // 从文件读取的字节数size_t written_bytes = 0;// I2S实际写入的字节数ESP_LOGI(TAG, "Start playing WAV...");// 循环读取WAV数据并播放while (1) {// 从文件读取数据到缓冲区read_bytes = fread(play_buf, 1, PLAY_BUFFER_SIZE, wav_fp);if (read_bytes == 0) {ESP_LOGI(TAG, "End of WAV file (read all data)");break;}// 将缓冲区数据通过I2S发送(阻塞等待发送完成)err = i2s_channel_write(tx_chan, play_buf, read_bytes, &written_bytes, portMAX_DELAY);if (err != ESP_OK) {ESP_LOGE(TAG, "I2S write failed: %s", esp_err_to_name(err));break;}ESP_LOGD(TAG, "Play progress: read=%d bytes, written=%d bytes", (int)read_bytes, (int)written_bytes);// 若读取的字节数小于缓冲区大小,说明文件已读完if (read_bytes < PLAY_BUFFER_SIZE) {ESP_LOGI(TAG, "Partial buffer read, play complete");break;}}// 5. 播放完成,清理资源i2s_channel_disable(tx_chan); // 禁用I2S通道fclose(wav_fp); // 关闭WAV文件free(play_buf); // 释放缓冲区ESP_LOGI(TAG, "WAV play task finished");vTaskDelete(NULL);
}// -------------------------- SPIFFS初始化 --------------------------
esp_err_t spiffs_init(void) {ESP_LOGI(TAG, "Initializing SPIFFS");esp_vfs_spiffs_conf_t conf = {.base_path = "/spiffs",.partition_label = NULL,.max_files = 5,.format_if_mount_failed = true};// 使用所有默认参数挂载SPIFFSesp_err_t ret = esp_vfs_spiffs_register(&conf);if (ret != ESP_OK) {if (ret == ESP_FAIL) {ESP_LOGE(TAG, "Failed to mount or format filesystem");} else if (ret == ESP_ERR_NOT_FOUND) {ESP_LOGE(TAG, "Failed to find SPIFFS partition");} else {ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));}return ret;}size_t total = 0, used = 0;ret = esp_spiffs_info(NULL, &total, &used);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to get SPIFFS partition info (%s)", esp_err_to_name(ret));esp_vfs_spiffs_unregister(NULL);return ret;}ESP_LOGI(TAG, "SPIFFS initialized, total: %d, used: %d", total, used);return ESP_OK;
}// 在代码中添加列出文件的测试函数
void list_spiffs_files() {DIR *dir = opendir("/spiffs");if (dir == NULL) {ESP_LOGE(TAG, "Failed to open directory");return;}struct dirent *ent;while ((ent = readdir(dir)) != NULL) {ESP_LOGI(TAG, "File: %s", ent->d_name);}closedir(dir);
}// -------------------------- 主函数 --------------------------
void app_main(void) {// 1. 初始化SPIFFS文件系统esp_err_t spiffs_err = spiffs_init();if (spiffs_err != ESP_OK) {ESP_LOGE(TAG, "SPIFFS init failed, exit app");return;}// 2. 初始化I2S播放硬件esp_err_t init_err = ns4168_init();if (init_err != ESP_OK) {ESP_LOGE(TAG, "I2S init failed, exit app");return;}list_spiffs_files(); // 列出 SPIFFS 中的文件// 3. 启动播放任务(使用SPIFFS中的文件路径)const char *target_wav = "/spiffs/hi_idf_audio.wav"; // SPIFFS中的WAV文件路径xTaskCreate(i2s_play_task, "i2s_play_task", 4096, (void *)target_wav, 5, NULL);
}