遇到的问题时,使用同一块电路板的从SD卡播放相同wav文件的情况下:
1. 使用IDF5.5编译的程序在 播放wav时,会有严重的破音,只要音乐中有部件音量稍大,就会破音。使用ffmpeg工具将音乐音量改到原来的40%以下时,破音会消除,但此时音量太小了听着不舒服。 正常音量的mp3文件转换成wav格式后均破音,在电脑、手机上播放均不破音。
2.使用arduino 2.3.6 ,使用乐鑫官方的ESP_I2S.h头文件提供的播放功能完全正常,大音量的情况下,完全没有破音。arudino下即使将gain从硬件上配置成12db的增益也完全没有破音。
这就奇怪了,相同的电路板,在arduino下编译运行正常,在IDF5.5之下编译的固件就不行了!!!
以下是mpeg输出的音频信息:
Code: Select all
[aist#0:0/pcm_s16le @ 000001dfe3e70d40] Guessed Channel Layout: stereo
Input #0, wav, from 'fc.wav':
Metadata:
encoder : Lavf61.7.100
Duration: 00:04:45.74, bitrate: 1411 kb/s
Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, stereo, s16, 1411 kb/s
IDF程序:
Code: Select all
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "sdkconfig.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#define EXAMPLE_STD_BCLK_IO1 16 // I2S bit clock io number
#define EXAMPLE_STD_WS_IO1 17 // I2S word select io number
#define EXAMPLE_STD_DOUT_IO1 18 // I2S data out io number
#define EXAMPLE_STD_DIN_IO1 6 // I2S data in io number
#define SDMODE 8
#define EXAMPLE_BUFF_SIZE 10240
#define PIN_NUM_MISO 39
#define PIN_NUM_MOSI 40
#define PIN_NUM_CLK 41
#define PIN_NUM_CS 42
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO
#define MOUNT_POINT "/sdcard"
static i2s_chan_handle_t tx_chan; // I2S tx channel handler
#define TAG "i2s_std_example"
// 打印目录内容的函数实现
void list_directory_contents(const char *path)
{
DIR *dir;
struct dirent *entry;
// 打开目录
dir = opendir(path);
if (dir == NULL)
{
perror("无法打开目录");
printf("请检查路径是否正确或是否有权限。\n");
return;
}
printf("\n目录 '%s' 中的文件和子目录:\n", path);
printf("----------------------------------------\n");
// 读取目录内容
while ((entry = readdir(dir)) != NULL)
{
// entry->d_name 包含文件名或目录名
// entry->d_type 可以用来判断类型 (DT_DIR 目录, DT_REG 普通文件等)
// 为了简化,这里只打印名称
printf("%s\n", entry->d_name);
}
printf("----------------------------------------\n");
// 关闭目录
closedir(dir);
}
static void i2s_example_write_task(void *args)
{
char songs[4][30] = {"/sdcard/yueliang.wav", "/sdcard/rlgt.wav", "/sdcard/NFC.wav","/sdcard/njohn.wav"};
int idx = 0;
while (1)
{
uint16_t *w_buf = (uint16_t *)calloc(1, EXAMPLE_BUFF_SIZE);
assert(w_buf); // Check if w_buf allocation success
size_t w_bytes = EXAMPLE_BUFF_SIZE;
FILE *file;
printf("Opening file: %s\n", songs[idx]);
file = fopen(songs[idx], "rb");
if (file == NULL)
{
printf("Error opening file\n");
vTaskDelay(pdMS_TO_TICKS(2000));
idx++;
continue;
}
fseek(file, 44, SEEK_SET);
// 循环读取文件内容
size_t readed = 0;
// 预加载数据
readed = fread(w_buf, 1, EXAMPLE_BUFF_SIZE, file);
if (readed > 0)
{
ESP_ERROR_CHECK(i2s_channel_preload_data(tx_chan, w_buf, readed, &w_bytes));
}
printf("预加载数据--》 Readed %d bytes, I2S writed %d bytes\n", readed, w_bytes);
// 没能把读到的数据全部写入DMA
if (w_bytes < readed)
{
fseek(file, w_bytes+44, SEEK_SET);
}
/* Enable the TX channel */
ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
while ((readed = fread(w_buf, 1, EXAMPLE_BUFF_SIZE, file)) > 0)
{
// printf("Readed %d bytes\n", readed);
ESP_ERROR_CHECK(i2s_channel_write(tx_chan, w_buf, readed, &w_bytes, 1000));
printf("Readed %d bytes, I2S writed %d bytes\n", readed, w_bytes);
//vTaskDelay(pdMS_TO_TICKS(1000));
}
ESP_ERROR_CHECK(i2s_channel_disable(tx_chan));
fclose(file);
free(w_buf);
vTaskDelay(pdMS_TO_TICKS(1000));
idx++;
if(idx>=4){
idx = 0;
}
}
}
static void i2s_example_init_std_simplex(void)
{
i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL));
i2s_std_config_t tx_std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = EXAMPLE_STD_BCLK_IO1,
.ws = EXAMPLE_STD_WS_IO1,
.dout = EXAMPLE_STD_DOUT_IO1,
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &tx_std_cfg));
}
void confgSdMode(void)
{
// 1. 配置 GPIO
gpio_config_t io_conf = {
// 禁用中断
.intr_type = GPIO_INTR_DISABLE,
// 设置为输出模式
.mode = GPIO_MODE_OUTPUT_OD, // <--- 关键:设置为开漏输出模式
// 设置要配置的 GPIO 引脚
.pin_bit_mask = (1ULL << SDMODE),
// 禁用下拉
.pull_down_en = GPIO_PULLDOWN_DISABLE,
// 启用上拉 (可选,但通常建议,以防外部没有上拉)
// 注意:GPIO 内部上拉能力有限,对于 I2C 等总线,通常需要外部上拉电阻。
.pull_up_en = GPIO_PULLUP_DISABLE,
};
// 应用配置
esp_err_t err = gpio_config(&io_conf);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to configure GPIO: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "GPIO %d configured as Open-Drain output.", SDMODE);
// --- 关键操作 ---
// 2. 设置输出电平为高 (1)
// 对于开漏输出,写 1 会使引脚进入高阻态 (Hi-Z),由外部上拉电阻拉高电平。
err = gpio_set_level(SDMODE, 1);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set GPIO level: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "GPIO %d set to HIGH (Open-Drain Hi-Z state).", SDMODE);
}
void configSDCard(void)
{
esp_err_t ret;
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024};
sdmmc_card_t *card;
const char mount_point[] = MOUNT_POINT;
ESP_LOGI(TAG, "Initializing SD card");
ESP_LOGI(TAG, "Using SPI peripheral");
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
host.slot = SPI3_HOST;
spi_bus_config_t bus_cfg = {
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = PIN_NUM_MISO,
.sclk_io_num = PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to initialize bus.");
return;
}
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = PIN_NUM_CS;
slot_config.host_id = host.slot;
ESP_LOGI(TAG, "Mounting filesystem");
ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
ESP_LOGE(TAG, "Failed to mount filesystem. "
"If you want the card to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
}
else
{
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.",
esp_err_to_name(ret));
}
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
}
void app_main(void)
{
confgSdMode();
configSDCard();
vTaskDelay(pdMS_TO_TICKS(200));
i2s_example_init_std_simplex();
/* Step 3: Create writing and reading task, enable and start the channels */
// xTaskCreate(i2s_example_read_task, "i2s_example_read_task", 4096, NULL, 5, NULL);
xTaskCreate(i2s_example_write_task, "i2s_example_write_task", 4096, NULL, 5, NULL);
}
Code: Select all
//输出音频用的I2S接口
const uint8_t LRCLK = 17, BCLK = 16, DIN = 18, SDMODE = 8;
I2SClass I2S;
SPIClass *sdspi = NULL;
uint8_t keyPressed = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
delay(2000);
Serial.println("Hello s3!");
sdspi = new SPIClass(HSPI);
sdspi->begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS, *sdspi)) {
Serial.println("Card Mount Failed");
return;
}
setupAudio();
}
void setupAudio() {
pinMode(SDMODE, OUTPUT_OPEN_DRAIN);
digitalWrite(SDMODE, 1);
I2S.setPins(BCLK, LRCLK, DIN, -1, -1); //SCK, WS, SDOUT, SDIN, MCLK
I2S.begin(I2S_MODE_STD, 44100, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);
}
void loop() {
// put your main code here, to run repeatedly:
delay(200);
// if(keyPressed){
// readKey();
// }
if (!SD.begin(SD_CS,*sdspi)) {
Serial.println("Card Mount Failed");
SD.end();
delay(1000);
return;
}
String fname = "/FCOLD.wav";
File file = SD.open(fname, "rb");
if (!file) {
Serial.print("Failed to open audio file:");
Serial.println(fname);
SD.end();
delay(1000);
return;
}
Serial.println("opened audio file.");
uint8_t *buffer = (uint8_t *)malloc(BUFFER_SIZE);
size_t bytes_read;
bool playing = true;
while (playing && (bytes_read = file.read(buffer, BUFFER_SIZE)) > 0) {
Serial.print(".");
I2S.write(buffer, bytes_read);
}
Serial.println();
free(buffer);
file.close();
SD.end();
}