前言
I2S 通訊協定(Inter-IC Sound,也寫作 I²S)是飛利浦在 1986 年制定的數位音訊序列匯流排標準。不同於 SPI 或 I2C 的通用性,I2S 是專門為數位音訊設計的,用於在音訊編解碼晶片(Audio Codec)、MEMS 麥克風、DSP 和 MCU 之間傳輸 PCM 音訊資料。ESP32 內建了兩組 I2S 控制器,STM32 也有多組 SPI/I2S 可配置的周邊,這使得 I2S 成為嵌入式音訊專案的首選介面。本文將從時序、模式、硬體設計到實際程式碼,幫你一次搞懂 I2S。
一、I2S 通訊協定基本概念
1.1 訊號線
I2S 使用三條訊號線(加上可選的 MCLK):
- SCK(Serial Clock / Bit Clock):位元時脈,每個 SCK 週期傳送一個位元
- WS(Word Select / Frame Sync):聲道選擇,0=左聲道,1=右聲道
- SD(Serial Data):序列資料線,可配置為輸入(SDI)或輸出(SDO)
- MCLK(Master Clock,可選):主時脈,通常為 256×FS 或 512×FS
圖 1:I2S 基本時序(16-bit,標準 Philips 模式) — SCK 持續輸出,WS 在左/右聲道之間切換,SD 在 WS 變換後延遲一個 SCK 開始輸出 MSB。
1.2 時序特性
I2S 的核心特性是 MSB 先行(MSB First)且在 WS 變換後延遲一個 SCK 週期開始傳送(標準 Philips 模式)。這與 SPI 的 CPOL/CPHA 概念類似,但專門為連續的立體聲 PCM 資料做了優化。
- WS 變換表示聲道切換(左→右→左→右...)
- SD 資料在 SCK 的 rising edge 鎖存
- 發送端在 SCK 的 falling edge 更新資料
1.3 常見模式比較
| 模式 | MSB 起始點 | 相容性 |
|---|---|---|
| I2S Philips(標準) | WS 變換後延遲 1 SCK | 最廣泛支援 |
| Left-Justified | WS 變換後立即輸出 MSB | 某些老 Codec 使用 |
| Right-Justified | WS 變換後延遲到 LSB 對齊幀尾 | 日本廠商常見 |
| TDM(Time Division Multiplexed) | 多聲道分時共用一條 SD 線 | 多聲道系統(8ch+) |
圖 2:I2S 標準模式 vs Left-Justified 模式 — I2S 標準模式 MSB 延後 1 SCK;Left-Justified 模式下 MSB 在 WS 變換後立即輸出。
二、取樣率與 SCK 計算
I2S 的 SCK 頻率由取樣率和資料寬度決定:
SCK = FS × Bits × Channels
例如 CD 音質(44.1 kHz、16-bit、立體聲):
SCK = 44100 × 16 × 2 = 1.4112 MHz
如果 Codec 要求 32-bit 幀(即使實際只用 16-bit data 也補 0):
SCK = 44100 × 32 × 2 = 2.8224 MHz (= 64 × FS)

圖 4:常見取樣率與對應的 SCK 頻率 — FS 越高、位元深度越大,SCK 越高。
實務上注意:ESP32 的 I2S clock 來自 APLL(Audio PLL),頻率解析度有限。使用非標準取樣率(如 22050 Hz)時務必驗證實際 SCK 與目標誤差。
三、I2S 硬體設計
3.1 系統架構

圖 3:I2S 系統架構 — ESP32/STM32 作為 Master 產生 SCK 和 WS,Audio Codec 和 MEMS 麥克風作為 Slave。
3.2 常見 I2S 元件
| 元件 | 類型 | 說明 |
|---|---|---|
| MAX98357 / I2S 放大器 | DAC | 3W 單聲道 D 類功放,I2S 輸入直驅喇叭 |
| WM8960 | Codec | 雙聲道 DAC+ADC,常見於 ESP32-Audio-Kit |
| INMP441 | MEMS Mic | 全向 MEMS 麥克風,I2S 輸出 |
| CS4344 | DAC | 低價立體聲 DAC,24-bit/192kHz |
3.3 接線注意事項
- MCLK 問題:某些 Codec(如 WM8960)需要 MCLK = 256×FS。ESP32 可輸出 MCLK,STM32 某些系列無獨立 MCLK 輸出
- 電壓位準:3.3V 為共識;5V Codec 需電平轉換
- SCK 頻率上限:ESP32 I2S 最高約 40 MHz(實際受 APLL 限制);STM32 SPI/I2S 最高約 37.5 MHz(@150 MHz HCLK)
- BCK 極性:I2S 標準要求 SCK idle = low,但某些 Codec 支援 idle = high
四、ESP32 I2S 實作(Arduino 框架)
4.1 I2S 音訊輸出(播放 WAV)
#include <I2S.h>
#define I2S_BCK 26
#define I2S_WS 25
#define I2S_DOUT 22 // 資料輸出
#define I2S_DIN 21 // 資料輸入(麥克風用)
void setup()
{
Serial.begin(115200);
// 配置 I2S
I2S.setPins(I2S_BCK, I2S_WS, I2S_DOUT, I2S_DIN);
if (!I2S.begin(I2S_PHILIPS_MODE, 44100, 16))
{
Serial.println("I2S init failed!");
while (1);
}
Serial.println("I2S OK - 44.1kHz 16-bit");
}
void loop()
{
// 產生 440 Hz 正弦波測試音
static float phase = 0;
int16_t sample[128];
const float freq = 440.0;
const float fs = 44100.0;
for (int i = 0; i < 128; i++)
{
float val = sin(2 * PI * freq * phase / fs);
sample[i] = (int16_t)(val * 16000); // 振幅 ±16000
phase++;
}
// 寫入 I2S(立體聲:左右聲道相同)
size_t written = I2S.write((uint8_t *)sample, sizeof(sample));
if (written != sizeof(sample))
Serial.printf("Underrun! Wrote %d / %d\n", written, sizeof(sample));
}
4.2 I2S 音訊輸入(MEMS 麥克風錄音)
#include <I2S.h>
#define I2S_BCK 26
#define I2S_WS 25
#define I2S_DIN 34 // INMP441 資料輸出腳
const int sample_rate = 16000; // 語音辨識常用 16 kHz
const int bits = 16;
const int buffer_size = 512;
int16_t samples[buffer_size];
void setup()
{
Serial.begin(115200);
I2S.setPins(I2S_BCK, I2S_WS, -1, I2S_DIN); // 僅輸入
if (!I2S.begin(I2S_PHILIPS_MODE, sample_rate, bits))
{
Serial.println("I2S init failed!");
while (1);
}
}
void loop()
{
size_t bytes_read = I2S.readBytes((char *)samples, buffer_size * 2);
int samples_read = bytes_read / 2;
// 計算 RMS 音量
float sum_sq = 0;
for (int i = 0; i < samples_read; i++)
sum_sq += (float)samples[i] * samples[i];
float rms = sqrt(sum_sq / samples_read);
Serial.printf("RMS: %.2f\tPeak: %d\n", rms, samples[0]);
delay(100);
}
4.3 使用 I2S 讀取 INMP441 並透過 Wi-Fi 串流
#include <WiFi.h>
#include <I2S.h>
const char *ssid = "SSID";
const char *pass = "PASSWORD";
WiFiServer server(8080);
void setup()
{
Serial.begin(115200);
WiFi.begin(ssid, pass);
I2S.setPins(26, 25, -1, 34);
I2S.begin(I2S_PHILIPS_MODE, 16000, 16);
server.begin();
Serial.printf("Server started: %s:8080\n", WiFi.localIP().toString().c_str());
}
void loop()
{
WiFiClient client = server.available();
if (!client) return;
// 持續串流 PCM 資料到客戶端
uint8_t buf[512];
while (client.connected())
{
size_t n = I2S.readBytes((char *)buf, 512);
if (n > 0) client.write(buf, n);
}
client.stop();
}
五、ESP32 I2S 實作(ESP-IDF 框架)
5.1 基本 I2S 初始化
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#define I2S_PORT I2S_NUM_0
i2s_chan_handle_t tx_handle;
void i2s_init(void)
{
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(
I2S_PORT, I2S_ROLE_MASTER);
i2s_std_config_t std_cfg = {
.clk_cfg = {
.sample_rate_hz = 44100,
.clk_src = I2S_CLK_SRC_DEFAULT,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
},
.slot_cfg = {
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_16BIT,
.slot_mode = I2S_SLOT_MODE_STEREO,
.slot_mask = I2S_STD_SLOT_BOTH,
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
.ws_pol = false,
.bit_shift = true, // I2S Philips: delay 1 SCK
},
.gpio_cfg = {
.mclk = GPIO_NUM_NC,
.bclk = GPIO_NUM_26,
.ws = GPIO_NUM_25,
.dout = GPIO_NUM_22,
.din = GPIO_NUM_21,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
i2s_new_channel(&chan_cfg, &tx_handle, NULL);
i2s_channel_init_std_mode(tx_handle, &std_cfg);
i2s_channel_enable(tx_handle);
}
void play_sine(void)
{
int16_t buf[256];
static float phase = 0;
for (int i = 0; i < 256; i += 2)
{
float val = sin(phase);
int16_t s = (int16_t)(val * 16000);
buf[i] = s; // 左聲道
buf[i+1] = s; // 右聲道
phase += 2 * 3.14159 * 440.0 / 44100.0;
}
size_t written;
i2s_channel_write(tx_handle, buf, 256 * 2, &written, portMAX_DELAY);
}
六、STM32 I2S 實作(HAL Library)
STM32 的 SPI 周邊可以配置為 I2S 模式(SPI_I2S)。以 STM32F407 為例,SPI2 可映射到 I2S2。
6.1 CubeMX 配置
- 設定 SPI2 / I2S2 為 Full-Duplex Master
- Standard = I2S Philips
- Data format = 16-bit
- Audio frequency = 44.1 kHz
- CK/Pin = PB13(SCK), WS = PB12, SD = PB15, MCK = PC6
6.2 I2S 初始化程式碼
#include "stm32f4xx_hal.h"
SPI_HandleTypeDef hspi2;
void MX_I2S2_Init(void)
{
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_2LINES;
hspi2.Init.DataSize = SPI_DATASIZE_16BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi2.Init.CRCPolynomial = 10;
HAL_SPI_Init(&hspi2);
}
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef gpio = {0};
__HAL_RCC_SPI2_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// PB13 = SCK, PB12 = WS, PB15 = SD
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_MEDIUM;
gpio.Alternate = GPIO_AF5_SPI2;
gpio.Pin = GPIO_PIN_13 | GPIO_PIN_12 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOB, &gpio);
}
void i2s_send_sample(uint16_t left, uint16_t right)
{
uint16_t frame[2] = {right, left}; // I2S: WS=0=左, WS=1=右
HAL_SPI_Transmit(&hspi2, (uint8_t *)frame, 2, HAL_MAX_DELAY);
}
void play_tone(void)
{
for (int i = 0; i < 44100; i++)
{
float t = (float)i / 44100.0;
int16_t s = (int16_t)(sin(2 * 3.14159 * 440 * t) * 16000);
i2s_send_sample(s, s); // 左右聲道相同
}
}
七、實戰專案:Wi-Fi 網路音訊播放器
結合 ESP32 的 Wi-Fi 和 I2S,可以實作一個簡單的網路音訊播放器:
// 伺服器端(發送 PCM 資料)
# ESP32 作為 HTTP 音訊串流伺服器
// 見 4.3 的 Wi-Fi 串流範例
// 客戶端(收聽端)
# 任何支援 HTTP 的裝置:
curl http://esp32-ip:8080 --output - | aplay -r16000 -fS16_LE -c1
如果要實現低延遲(<200ms)串流,改用 UDP/RTP 而非 TCP:
#include <WiFiUdp.h>
WiFiUDP udp;
const int port = 1234;
uint8_t pcm_buf[512];
void udp_stream_task(void *param)
{
while (1)
{
// 從 I2S 讀取音訊
size_t n = I2S.readBytes((char *)pcm_buf, 512);
// 透過 UDP 發送
udp.beginPacket(dest_ip, dest_port);
udp.write(pcm_buf, n);
udp.endPacket();
}
}
八、常見問題與除錯
8.1 沒有聲音?
- BCK 頻率錯誤:用邏輯分析儀或示波器量測 SCK 頻率是否等於 FS × bits × 2
- WS 極性:某些 Codec 的 WS 極性與 I2S 標準相反(如左聲道=1),程式需設定 WS polarity
- MCLK 缺失:Codec 需要 MCLK 但未提供,某些 Codec 可配置為不使用 MCLK
- I2S 模式不符:Codec 是 Left-Justified 但程式設成 I2S Philips
8.2 爆音或雜音
- 音量太大:正規化振幅不要超過 ±16383(16-bit),保留 headroom
- Pop Noise:Codec 電源開啟時先 mute,等輸出穩定後再播放
- Buffer Underrun:I2S 寫入速度趕不上播放速度,加大 buffer 或改善 Wi-Fi 延遲
- 時脈抖動:I2S 對 SCK 抖動敏感,避免在 I2S 正在播放時改變 APLL 參數
8.3 I2S DMA 配置(STM32)
// I2S + DMA 循環模式播放
#define BUF_SIZE 512
uint16_t audio_buf[BUF_SIZE];
// 初始化 DMA
__HAL_LINKDMA(&hspi2, hdmatx, hdma_tx);
// DMA 傳輸完成回呼
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI2)
{
// 填寫下一段音訊資料到 audio_buf
fill_next_buffer(audio_buf, BUF_SIZE);
}
}
// 啟動 DMA 循環播放
HAL_SPI_Transmit_DMA(&hspi2, (uint8_t *)audio_buf, BUF_SIZE);
九、總結
I2S 通訊協定是嵌入式音訊開發的必備技能。從簡單的 MEMS 麥克風錄音到高品質音訊串流,I2S 提供了專為音訊設計的可靠傳輸協定。重點回顧:
- 時序核心:MSB First + WS 延遲 1 SCK(I2S Philips)
- SCK 計算:FS × bits × channels;64×FS 是最常見配置
- 硬體考慮:MCLK 需求、電壓位準、BCK 上限
- 程式實作:ESP32 Arduino/ESP-IDF 與 STM32 HAL 都有完善支援
- 除錯方法:量測 SCK 頻率、檢查 WS 極性、觀察 DMA 狀態
無論是做語音辨識、藍牙音箱、還是音訊分析,I2S 都是你不可或缺的工具。
文章評論