0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 未分類
  3. 正文

I2S 通訊協定完整教學:從時序到 STM32/ESP32 音訊實作

2026 年 5 月 26 日 6點熱度 0人點贊 0條評論

前言

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

I2S 基本時序

圖 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+)

I2S vs Left-Justified

圖 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 系統架構

I2S 系統架構

圖 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 接線注意事項

  1. MCLK 問題:某些 Codec(如 WM8960)需要 MCLK = 256×FS。ESP32 可輸出 MCLK,STM32 某些系列無獨立 MCLK 輸出
  2. 電壓位準:3.3V 為共識;5V Codec 需電平轉換
  3. SCK 頻率上限:ESP32 I2S 最高約 40 MHz(實際受 APLL 限制);STM32 SPI/I2S 最高約 37.5 MHz(@150 MHz HCLK)
  4. 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 配置

  1. 設定 SPI2 / I2S2 為 Full-Duplex Master
  2. Standard = I2S Philips
  3. Data format = 16-bit
  4. Audio frequency = 44.1 kHz
  5. 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 沒有聲音?

  1. BCK 頻率錯誤:用邏輯分析儀或示波器量測 SCK 頻率是否等於 FS × bits × 2
  2. WS 極性:某些 Codec 的 WS 極性與 I2S 標準相反(如左聲道=1),程式需設定 WS polarity
  3. MCLK 缺失:Codec 需要 MCLK 但未提供,某些 Codec 可配置為不使用 MCLK
  4. I2S 模式不符:Codec 是 Left-Justified 但程式設成 I2S Philips

8.2 爆音或雜音

  1. 音量太大:正規化振幅不要超過 ±16383(16-bit),保留 headroom
  2. Pop Noise:Codec 電源開啟時先 mute,等輸出穩定後再播放
  3. Buffer Underrun:I2S 寫入速度趕不上播放速度,加大 buffer 或改善 Wi-Fi 延遲
  4. 時脈抖動: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 提供了專為音訊設計的可靠傳輸協定。重點回顧:

  1. 時序核心:MSB First + WS 延遲 1 SCK(I2S Philips)
  2. SCK 計算:FS × bits × channels;64×FS 是最常見配置
  3. 硬體考慮:MCLK 需求、電壓位準、BCK 上限
  4. 程式實作:ESP32 Arduino/ESP-IDF 與 STM32 HAL 都有完善支援
  5. 除錯方法:量測 SCK 頻率、檢查 WS 極性、觀察 DMA 狀態

無論是做語音辨識、藍牙音箱、還是音訊分析,I2S 都是你不可或缺的工具。

標籤: RS485 工業通訊
最後更新:2026 年 5 月 26 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang