2. I2S协议深度解析:总线时序、飞利浦标准与左对齐格式、主从模式选择、数据帧格式配置

I2S,全称 Inter-IC Sound,是数字音频设备之间传输音频数据的串行总线标准。说白了,它就是让 STM32 和 WM8960 这样的音频芯片能“说上话”的桥梁。我刚开始接触音频项目时,总觉得 I2S 就是 SPI 换了个马甲,结果调试时吃了不少苦头。嗯,这里要注意,I2S 和 SPI 虽然长得像,但时序和协议细节差别很大。

2.1 I2S 总线信号与基本时序

I2S 总线只需要三根核心信号线:

  • SCK(连续串行时钟):也叫位时钟(BCLK),每个脉冲对应一个数据位。采样率 × 声道数 × 位深 = SCK 频率。比如 44.1kHz、立体声、16 位,SCK = 44.1k × 2 × 16 = 1.4112MHz。
  • WS(声道选择):也叫帧时钟(LRCK),高电平表示右声道,低电平表示左声道(飞利浦标准)。WS 频率 = 采样率。
  • SD(串行数据):按位传输音频数据,MSB 优先。

我见过不少新手把 SCK 和 WS 搞混。记住一个诀窍:WS 是“节拍器”,SCK 是“踩点”。WS 每切换一次,就代表一个声道的开始;SCK 每跳一次,就传输一个比特。

核心时序关系:数据在 SCK 的上升沿被发送端驱动,在下降沿被接收端采样。这个“上升沿发送、下降沿接收”的机制,保证了数据稳定传输。

2.2 飞利浦标准与左对齐格式

I2S 协议有几种标准格式,最常用的是飞利浦标准和左对齐格式。我在项目中遇到过客户要求兼容不同音频编解码器,结果发现格式不匹配导致无声,排查了半天。

飞利浦标准(Philips Standard)

  • 数据在 WS 切换后的 第二个 SCK 上升沿 开始传输。
  • MSB 对齐到 WS 边沿后的第二个时钟周期。
  • 数据位宽可以小于 SCK 周期数,不足部分自动填充 0。

左对齐格式(Left-Justified)

  • 数据在 WS 切换后的 第一个 SCK 上升沿 立即开始传输。
  • MSB 与 WS 边沿对齐,没有延迟。
  • 常用于一些老式或专用音频芯片。
特性 飞利浦标准 左对齐格式
数据起始位置 WS 边沿后第 2 个 SCK WS 边沿后第 1 个 SCK
MSB 对齐方式 延迟 1 个 SCK 立即对齐
兼容性 最广泛 部分专用芯片
STM32 配置 I2S_Standard_Phillips I2S_Standard_MSB

我的经验:WM8960 默认使用飞利浦标准。如果你用左对齐格式,记得在 STM32 的 I2S 初始化结构体里把 Standard 改成 I2S_Standard_MSB。我曾经因为没改这个,折腾了一整天。

2.3 主从模式选择

I2S 通信中,必须有一个设备产生 SCK 和 WS 时钟,这个设备就是主设备。另一个设备接收时钟,就是从设备。

  • 主模式:STM32 产生 SCK 和 WS。适合 STM32 作为音频系统核心的场景。
  • 从模式:STM32 接收外部时钟。适合 STM32 作为协处理器或与外部时钟源同步的场景。

我个人习惯把 STM32 设为主模式,WM8960 设为从模式。这样时钟完全由 MCU 控制,调试起来更可控。但要注意,主模式下 SCK 和 WS 的频率必须精确匹配采样率和位深,否则会出现杂音或丢帧。

避坑指南:我曾经在项目里把 STM32 设为从模式,但外部时钟源不稳定,导致音频断断续续。后来改用内部 PLL 生成时钟,问题才解决。如果你用从模式,务必确保外部时钟质量。

2.4 数据帧格式配置

数据帧格式决定了每个声道传输多少位数据,以及数据在帧内的排列方式。STM32 的 I2S 支持以下配置:

参数 可选值 说明
数据位宽 16 位、24 位、32 位 每个声道的有效数据位数
帧长度 16 位、32 位 每个声道的总 SCK 周期数
声道数 单声道、立体声 WS 频率 = 采样率 × 声道数
数据格式 飞利浦、左对齐、右对齐 数据在帧内的起始位置

举个例子,16 位数据、32 位帧长度:每个声道占用 32 个 SCK 周期,但只有前 16 位是有效数据,后 16 位自动填充 0。这样虽然浪费带宽,但兼容性更好。我一般用 16 位数据 + 16 位帧长度,节省时钟频率。

// STM32 I2S 初始化配置示例(HAL 库)
hi2s.Instance = SPI2;
hi2s.Init.Mode = I2S_MODE_MASTER_TX;      // 主模式发送
hi2s.Init.Standard = I2S_STANDARD_PHILLIPS; // 飞利浦标准
hi2s.Init.DataFormat = I2S_DATAFORMAT_16B; // 16 位数据
hi2s.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
hi2s.Init.AudioFreq = I2S_AUDIOFREQ_44K;   // 44.1kHz
hi2s.Init.CPOL = I2S_CPOL_LOW;             // 时钟极性低电平
hi2s.Init.ClockSource = I2S_CLOCK_PLL;     // 使用 PLL 生成时钟
hi2s.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_ENABLE; // 全双工

if (HAL_I2S_Init(&hi2s) != HAL_OK) {
  Error_Handler();
}

关键点:全双工模式下,STM32 的 I2S 需要同时配置发送和接收。HAL 库的 I2S_FULLDUPLEXMODE_ENABLE 会启用两个独立的 FIFO,一个用于发送,一个用于接收。这样录放音才能同时进行。

2.5 I2S 时序与数据帧结构图

下面这张图展示了飞利浦标准下,16 位立体声数据的完整时序。你可以看到 WS、SCK 和 SD 之间的对齐关系。

I2S 飞利浦标准时序图(16位立体声) WS 左声道 右声道 SCK 16 个 SCK 周期(左声道) SD MSB 数据位 14 ~ 0 MSB 数据位 14 ~ 0 时序说明: 1. WS 低电平 = 左声道,高电平 = 右声道(飞利浦标准) 2. 数据在 SCK 上升沿发送,下降沿接收 3. MSB 在 WS 边沿后的第 2 个 SCK 上升沿开始传输 4. 16 位数据占用 16 个 SCK 周期,不足部分自动补 0

2.6 主从模式配置实战建议

配置主从模式时,有几个细节容易忽略:

  • 时钟极性(CPOL):飞利浦标准通常用低电平空闲,但有些芯片要求高电平空闲。WM8960 支持两种极性,我建议统一用低电平。
  • MCLK 输出:STM32 可以输出主时钟(MCLK)给 WM8960,频率通常是 256 × 采样率。开启 MCLK 能提高音质,但会增加功耗。
  • 全双工引脚:STM32 的 I2S 全双工需要额外的引脚(如 I2S2ext_SD 用于接收)。记得在 CubeMX 里正确映射。

我的习惯:调试阶段先用 16 位数据、16 位帧长度、飞利浦标准、主模式。这套组合最稳定,出问题容易定位。等基本功能通了,再优化位深和帧长度。