详细介绍: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
    };
    // 使用所有默认参数挂载SPIFFS
    esp_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 
#include 
#include 
#include 
#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,  // 位深匹配16bit
            I2S_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
    };
    // 使用所有默认参数挂载SPIFFS
    esp_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);
}

posted @ 2025-10-23 16:15  yjbjingcha  阅读(2)  评论(0)    收藏  举报