学习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循环处理的太快导致示波器测不到那么多正弦波。后面继续调整程序,以期获得理想的效果。或者播放音频文件试试,这样更容易判断处理是否正常。