ESP32-S3(ESP IDF-5.4.1) audio NS4168
I2S(Inter-IC Sound,集成电路内置音频总线)是一种同步串行通信协议,通常用于在两个数字音频设备之间传输音频数据。ESP32-S3 包含 2 个 I2S 外设。通过配置这些外设,可以借助 I2S 驱动来输入和输出采样数据。标准和 TDM 模式下的 I2S 总线包含以下几条线路:MCLK:主时钟线。该信号线可选,具体取决于从机,主要用于向 I2S 从机提供参考时钟。BCLK:位时钟线。
简介
I2S(Inter-IC Sound,集成电路内置音频总线)是一种同步串行通信协议,通常用于在两个数字音频设备之间传输音频数据。
ESP32-S3 包含 2 个 I2S 外设。通过配置这些外设,可以借助 I2S 驱动来输入和输出采样数据。
标准和 TDM 模式下的 I2S 总线包含以下几条线路:
-
MCLK:主时钟线。该信号线可选,具体取决于从机,主要用于向 I2S 从机提供参考时钟。
-
BCLK:位时钟线。用于数据线的位时钟。
-
WS:字(声道)选择线。通常用于识别声道(除 PDM 模式外)。
-
DIN/DOUT:串行数据输入/输出线。如果 DIN 和 DOUT 被配置到相同的 GPIO,数据将在内部回环
I2S通信模式 - 标准模式
标准模式中有且仅有左右两个声道,驱动中将声道称为 slot。这些声道可以支持 8/16/24/32 位宽的采样数据。
代码
初始化
这段代码主要用于初始化NS4168模块的I2S传输通道。首先,通过调用宏 I2S_CHANNEL_DEFAULT_CONFIG 自动生成一个I2S通道配置结构,此处传入 I2S_NUM_AUTO 表示自动分配通道编号,并设置角色为主模式(I2S_ROLE_MASTER)。接着,调用 i2s_new_channel 函数创建新的I2S通道,并使用 ESP_ERROR_CHECK 宏进行错误检查,确保通道创建成功。
随后,代码构造了一个标准的I2S配置结构 std_cfg。在该结构中,clk_cfg 字段利用 I2S_STD_CLK_DEFAULT_CONFIG(16000) 设置了采样率为16kHz的时钟配置;而 slot_cfg 则通过 I2S_STD_MSB_SLOT_DEFAULT_CONFIG 来指定数据传输格式,这里采用32位数据宽度(I2S_DATA_BIT_WIDTH_32BIT)以及单声道模式(I2S_SLOT_MODE_MONO)。这决定了数据传输时每个样本的比特数和通道数。
接下来的 gpio_cfg 字段则为I2S信号分配具体的GPIO引脚。代码中将 mclk(主时钟)标记为未使用(I2S_GPIO_UNUSED),说明此示例中不需要使用主时钟信号,而 bclk(位时钟)、ws(字选择信号)和 dout(数据输出)分别对应预定义的 NS4168_IIS_BCK、NS4168_IIS_WS 和 NS4168_IIS_DATA。din(数据输入)同样未被使用。对于invert_flags结构,所有相关时钟信号的反转均设置为false,表示信号不做反转处理。
最后,通过调用 i2s_channel_init_std_mode 函数,将先前创建的I2S通道与定义好的标准配置结构 std_cfg 进行绑定,并再次通过 ESP_ERROR_CHECK 检查初始化是否成功。如果所有配置和初始化过程都顺利完成,该函数最终返回 ESP_OK,表明NS4168模块的I2S传输通道已经成功初始化,可以进行后续的音频数据传输。
esp_err_t ns4168_init() {
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 std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO),
.gpio_cfg =
{
.mclk = I2S_GPIO_UNUSED, // some codecs may require mclk signal,
// this example doesn't need it
.bclk = NS4168_IIS_BCK,
.ws = NS4168_IIS_WS,
.dout = NS4168_IIS_DATA,
.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, &std_cfg));
return ESP_OK;
}
播放wav音频
函数参数是一个指向字符数组的指针,代表要播放的音频文件的路径。函数首先检查传入的文件路径是否为空,如果为空则打印错误信息并终止任务。
接下来,函数为写入数据分配了一个2048字节的缓冲区。如果内存分配失败,同样会打印错误信息并删除任务。然后函数尝试以二进制读模式打开指定的文件,如果文件打开失败,则释放之前分配的缓冲区并终止任务。
一旦文件成功打开,函数使用fseek将文件指针移动到偏移量为44的位置,这通常用于跳过WAV文件的头部信息。之后,函数启用I2S传输通道,并进入一个无限循环。在每次循环中,通过fread从文件中读取最多2048字节的数据到缓冲区。如果读到的数据字节数为0,则说明文件已到末尾,循环退出。
在读取到数据后,函数先调用i2s_channel_preload_data将数据预加载到I2S通道,然后调用i2s_channel_write将数据写入I2S通道,并根据写入结果打印相应信息。如果本次读取的数据少于2048字节,也会认为文件播放完毕,从而退出循环。最后,函数关闭文件,释放缓冲区,并调用vTaskDelete删除当前任务。
static void i2s_play_task(void *args) {
const char *file_path = (const char *)args;
if (file_path == NULL) {
printf("File path is NULL\n");
vTaskDelete(NULL);
return;
}
uint8_t *w_buf = (uint8_t *)calloc(1, 2048);
if (w_buf == NULL) {
printf("Failed to allocate memory for write buffer\n");
vTaskDelete(NULL);
return;
}
FILE *fp = fopen(file_path, "rb");
if (fp == NULL) {
printf("Failed to open file: %s\n", file_path);
free(w_buf);
vTaskDelete(NULL);
return;
}
fseek(fp, 44, SEEK_SET);
size_t w_bytes = 0;
size_t read_bytes = 0;
ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
while (1) {
read_bytes = fread(w_buf, 1, 2048, fp);
if (read_bytes == 0) {
printf("End of file reached\n");
break;
}
i2s_channel_preload_data(tx_chan, w_buf, read_bytes, &w_bytes);
if (i2s_channel_write(tx_chan, w_buf, read_bytes, &w_bytes,
portMAX_DELAY) == ESP_OK) {
printf("I2S write %d bytes\n", (int)w_bytes);
} else {
printf("I2S write failed\n");
}
if (read_bytes < 2048) {
printf("Partial buffer written, file playback complete\n");
break;
}
}
fclose(fp);
free(w_buf);
vTaskDelete(NULL);
}
格式化音频文件
不是任何一个wav音频文件都能够正确播放,需要根据I2S的配置,格式化音频文件;使用GoldWave编辑文件
如下图所示,格式为:
Wave PCM signed 16 bit, 16000Hz, 512kbps, stereo
修改sampling


格式化
格式化是在保存的时候
破音问题
遇到破音的问题,可能与喇叭有关系,可能需要将音频的音量调低
Reference
更多推荐






所有评论(0)