4. I2S/PCM 总线协议:数字音频的“高速公路”

各位同学,今天我们聊聊数字音频的传输基石——I2S 和 PCM 总线。说白了,Codec 和 DSP 之间怎么“说话”,全靠这条总线。我做了这么多年音频,见过太多因为总线配置不对导致的“无声”或“杂音”问题。你想想看,两边数据格式都没对上,能出声才怪。

I2S(Inter-IC Sound)是飞利浦在 80 年代制定的标准,专门用来连接数字音频设备。PCM 接口其实和 I2S 非常像,只是时序上略有差异。咱们今天就把它的时序、数据格式、TDM 模式和主从配置彻底讲透。

核心要点:I2S 总线只有三条线——位时钟(BCLK)、帧时钟(LRCK/FS)、串行数据(SDATA)。搞定这三根线,就搞定了 90% 的音频传输问题。

4.1 I2S 时序:时钟与帧同步

先看时序。BCLK 是位时钟,每个脉冲传输一个 bit。LRCK 是帧时钟,用来区分左右声道。我习惯把 LRCK 叫做“声道选择信号”——高电平是右声道,低电平是左声道(标准 I2S 协议是这样,但有些芯片会反过来)。

举个例子,采样率 48kHz、位深 16bit、双声道:

  • BCLK 频率 = 48k × 16 × 2 = 1.536MHz
  • LRCK 频率 = 48kHz(等于采样率)

这里有个坑:BCLK 必须大于等于 采样率 × 位深 × 声道数。如果 BCLK 不够,数据传不完,就会出现“丢位”现象。我曾经在一个项目里遇到过,DSP 输出的 BCLK 只有 1MHz,但 Codec 需要 1.536MHz,结果声音断断续续,查了两天才发现是时钟没配够。

我的习惯:BCLK 频率尽量留 10%-20% 余量,特别是多声道场景。有些 Codec 对时钟抖动很敏感,余量能帮你避免很多奇怪的问题。

4.2 数据格式:左对齐、右对齐、I2S 标准

数据格式是初学者最容易搞混的地方。三种格式的区别在于:数据相对于 LRCK 边沿的起始位置不同。

格式 数据起始位置 MSB 位置 常见场景
I2S 标准 LRCK 变化后的第 2 个 BCLK 上升沿 左声道 MSB 在 LRCK 变化后延迟 1 位 飞利浦 Codec、主流 DSP
左对齐 LRCK 变化后的第 1 个 BCLK 上升沿 MSB 紧贴 LRCK 边沿 部分老式 Codec、ADI 芯片
右对齐 数据在 LRCK 变化后延迟到帧末尾 MSB 在帧的最后一位 日本厂商 Codec(如 AKM)

嗯,这里要注意:右对齐格式下,如果位深小于帧长度(比如 16bit 数据放在 32bit 帧里),数据会靠右对齐,左边补 0。我刚开始做音频时,用了一个 AKM 的 Codec,默认是右对齐,但 DSP 配成了 I2S 标准,结果出来的声音全是噪声——因为 DSP 把补的 0 当成了有效数据。

避坑指南:我曾经因为 Codec 数据手册里写的是“I2S compatible”,就默认配成了 I2S 标准,结果发现它其实是左对齐。后来我学乖了:永远以数据手册的时序图为准,不要只看文字描述。

4.3 TDM 模式:多声道传输

当声道数超过 2 个时(比如 5.1 环绕声、麦克风阵列),I2S 标准的两声道就不够用了。这时候需要 TDM(时分复用)模式。

TDM 的原理很简单:把一帧(LRCK 周期)分成多个时隙,每个时隙传输一个声道的数据。比如 8 声道 TDM,一帧里就有 8 个 slot,每个 slot 传一个声道的 16bit 或 24bit 数据。

我做过一个 8 麦克风阵列的项目,用的就是 TDM 模式。DSP 通过一条 SDATA 线就能接收 8 个声道的音频数据,省掉了 3 条数据线。但代价是 BCLK 频率要翻 4 倍(8 声道 / 2 声道 = 4 倍)。

TDM 配置的关键参数:

  • Slot 数量:等于声道数
  • Slot 宽度:每个 slot 的 bit 数(通常等于位深,但也可以更大)
  • 帧同步信号:有些 TDM 用 LRCK 做帧起始,有些用单独的 FS 信号

实战经验:TDM 模式下,DSP 和 Codec 的 slot 宽度必须一致。如果 Codec 每个 slot 是 32bit,但 DSP 配成了 24bit,那么第 4 个声道的数据会跑到第 3 个 slot 里,声道就全乱了。

4.4 主从模式配置

主从模式决定了谁提供时钟(BCLK 和 LRCK)。

模式 时钟提供方 适用场景 注意事项
主模式 本设备产生 BCLK 和 LRCK Codec 做主、DSP 做从 时钟精度取决于本设备晶振
从模式 外部提供 BCLK 和 LRCK DSP 做主、Codec 做从 需要外部时钟源稳定

我个人习惯让 DSP 做主设备。为什么?因为 DSP 的时钟通常来自 PLL,频率可以灵活配置。而 Codec 的晶振往往是固定的(比如 12.288MHz 或 24.576MHz),如果让它做主,DSP 就得去同步它的时钟,反而麻烦。

但有一种情况例外:如果 Codec 内部有 PLL 可以生成多种采样率,让它做主也可以。我记得有个项目用了 TI 的 TLV320AIC3104,它内部 PLL 可以自动适配各种采样率,我就让它做主,DSP 只负责收数据,省了不少配置工作。

配置步骤:

  1. 确定谁做主设备(通常 DSP 做主)
  2. 配置主设备的 BCLK 和 LRCK 频率
  3. 配置从设备的时钟输入引脚为 slave 模式
  4. 确保两边的主从设置一致(不能都配成主模式)

4.5 知识体系总览

下面这张图总结了 I2S/PCM 总线的核心知识点。你可以把它当作调试时的检查清单。

I2S/PCM 总线知识体系 I2S/PCM 总线知识体系 I2S/PCM 总线 时序 数据格式 TDM 模式 主从模式 BCLK 频率计算 LRCK = 采样率 时钟抖动容限 I2S 标准(延迟 1 位) 左对齐(MSB 紧贴边沿) 右对齐(数据靠右) Slot 数量 = 声道数 Slot 宽度配置 帧同步信号选择 主模式(本设备提供时钟) 从模式(外部提供时钟) 主从一致性检查 调试口诀:先查时钟频率,再对数据格式, 最后确认主从模式——三步搞定 I2S

4.6 实战:I2S 配置代码示例

最后给一段实际配置代码。假设我们用 STM32 的 I2S 外设驱动一个 Codec(比如 WM8960),配置为 DSP 做主、Codec 做从、I2S 标准格式、24bit 位深、48kHz 采样率。

// I2S 主模式配置(STM32 HAL 库)
void MX_I2S2_Init(void)
{
    hi2s2.Instance = SPI2;
    hi2s2.Init.Mode = I2S_MODE_MASTER_TX;    // 主模式,发送
    hi2s2.Init.Standard = I2S_STANDARD_PHILIPS; // I2S 标准格式
    hi2s2.Init.DataFormat = I2S_DATAFORMAT_24B; // 24bit 数据
    hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE; // 使能 MCLK
    hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_48K;  // 48kHz 采样率
    hi2s2.Init.CPOL = I2S_CPOL_LOW;            // 时钟极性
    hi2s2.Init.ClockSource = I2S_CLOCK_PLL;     // 使用 PLL 时钟
    hi2s2.Init.FirstBit = I2S_FIRSTBIT_MSB;     // MSB 先传
    
    if (HAL_I2S_Init(&hi2s2) != HAL_OK)
    {
        Error_Handler();  // 初始化失败处理
    }
}

这段代码里,I2S_STANDARD_PHILIPS 就是 I2S 标准格式。如果你用的是左对齐或右对齐的 Codec,把 Standard 改成对应的宏就行。嗯,这里要注意:STM32 的 I2S 外设只支持 I2S 标准和左对齐,不支持右对齐。如果你遇到右对齐的 Codec,得用 SPI 模拟或者加一个 FPGA 做格式转换。

最后提醒一句:I2S 配置完成后,一定要用示波器或逻辑分析仪抓一下 BCLK 和 LRCK 的波形。我见过太多“代码看起来没问题,但就是不出声”的案例,最后发现是时钟极性配反了,或者 LRCK 频率算错了。示波器不会骗人,它比你的眼睛可靠得多。