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 之间的对齐关系。
2.6 主从模式配置实战建议
配置主从模式时,有几个细节容易忽略:
- 时钟极性(CPOL):飞利浦标准通常用低电平空闲,但有些芯片要求高电平空闲。WM8960 支持两种极性,我建议统一用低电平。
- MCLK 输出:STM32 可以输出主时钟(MCLK)给 WM8960,频率通常是 256 × 采样率。开启 MCLK 能提高音质,但会增加功耗。
- 全双工引脚:STM32 的 I2S 全双工需要额外的引脚(如 I2S2ext_SD 用于接收)。记得在 CubeMX 里正确映射。
我的习惯:调试阶段先用 16 位数据、16 位帧长度、飞利浦标准、主模式。这套组合最稳定,出问题容易定位。等基本功能通了,再优化位深和帧长度。