学习I2S有一段时间了,基本上理解了I2S的工作方式和逻辑波形构成。和预想的不一样,I2S与I2C的其区别还是挺大的,最明显的就是模式上,I2S有左对齐、飞利浦、右对齐以及PCM四种方式,方式不一样,输出数据波形就会有不同。按照这个理解,意味着对接的I2S音频转换芯片,一样会有不同的工作方式,而不是像音频放大器那样,简单对接音频输入就行。
为了进一步学习I2S音频转换芯片从输入到输出的变化,买了一个MAX98357模块。MAX98357模块已经有专门的帖子介绍了。前面说过了I2S数据有不同的工作方式,但MAX98357模块提供的几个引脚中,看不到能适配工作模式的引脚,也没有看类似于TLV320AIC23B那样的控制接口配置工作参数,看资料说MAX98357是固定工作于PCM编码模式的。但是如果不用飞利浦标准模式设置ESP32S3的I2S的话,输出的声音不正常。
为了快速体验I2S的整个工作效果,没继续花时间去了解MAX98357,而是直接上手试验。I2S的主体使用ESP32S3模块,在Arduino环境下进行编程测试。
ESP32S3模块:

MAX98357模块:

两个模块的连接方式:
ESP32S3模块 MAX98357模块
------------------------------------------------
3.3V Vin
GND GND
GPIO6 DIN
GPIO8 LRC
GPIO9 BCLK
3.3V SD
GAIN(控制增益,悬空即可)
------------------------------------------------
需要在Arduino下的开发板管理器中安装ESP32S3,我选择的是ESP32S3 Dev Module。
程序代码如下:
#include <Arduino.h>
#include "driver/i2s.h"
// I2S 配置参数
#define I2S_BCLK 9
#define I2S_LRC 8
#define I2S_DOUT 6
// 音频参数
#define SAMPLE_RATE 44100
#define SAMPLE_BITS 16
#define BUFFER_SIZE 1024
// 生成正弦波测试音频
void generateSineWave(int16_t* buffer, size_t samples, float frequency, float volume) {
static float phase = 0.0;
float phaseIncrement = 2 * PI * frequency / SAMPLE_RATE;
for (size_t i = 0; i < samples; i++) {
buffer[i] = volume * sin(phase) * 32767; // 16位有符号整数范围
//Serial.println(buffer[i]);
phase += phaseIncrement;
if (phase >= 2 * PI) phase -= 2 * PI;
}
}
void setup() {
Serial.begin(921600);
// 配置 I2S
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = (i2s_bits_per_sample_t)SAMPLE_BITS,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
//.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = BUFFER_SIZE,
.use_apll = false
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCLK,
.ws_io_num = I2S_LRC,
.data_out_num = I2S_DOUT,
.data_in_num = I2S_PIN_NO_CHANGE
};
// 初始化 I2S
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
}
void loop() {
int16_t audioBuffer[BUFFER_SIZE];
// 生成 1000Hz 正弦波
generateSineWave(audioBuffer, BUFFER_SIZE, 1000, 0.1);
// 通过 I2S 发送音频数据
size_t bytesWritten;
i2s_write(I2S_NUM_0, audioBuffer, sizeof(audioBuffer), &bytesWritten, portMAX_DELAY);
}
出于快速验证的目的,测试中没有使用播放MP3、WAV等音频文件的方式,而是使用程序生成正弦波的采样点(利用sin函数),用来模拟正弦波。频率设置为1KHz,保证用扬声器能听到这个频率的声音。
整个实验装置及实验效果:

使用的是8欧姆的喇叭,没想到声音还是挺大的,
演示视频文件:demo.zip
通过Arduino串口绘图仪获取的采样数据绘制的波形:

是正弦波。
注意:如果想从串口得到这个波形,需要在generateSineWave函数中去掉Serial.println(buffer[i]);的注释,但这样做会让声音变得不连续。
改变正弦波的频率和音量比例参数(generateSineWave函数中的第三个和第四个参数),喇叭声音会发生相应的变化。
在MAX98357的输出端(喇叭)获得的波形(由示波器测得):

黄色的是喇叭两端得到的波形(有正弦波的模样,但不全,嘿嘿)。
按照我的理解,以44.1KHz采样1KHz的正弦波,采样周期为1/44.1KHz = 0.022mS,1KHz的周期为1mS,也就是说,一个完整的1KHz的正弦波按照44.1KHz采样,只能采集到44个点。而1024个数据可以获得大约23个完整的正弦波形。不知道是不是因为loop循环处理的太快导致示波器测不到那么多正弦波。后面继续调整程序,以期获得理想的效果。或者播放音频文件试试,这样更容易判断处理是否正常。
22

