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 只负责收数据,省了不少配置工作。
配置步骤:
- 确定谁做主设备(通常 DSP 做主)
- 配置主设备的 BCLK 和 LRCK 频率
- 配置从设备的时钟输入引脚为 slave 模式
- 确保两边的主从设置一致(不能都配成主模式)
4.5 知识体系总览
下面这张图总结了 I2S/PCM 总线的核心知识点。你可以把它当作调试时的检查清单。
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 频率算错了。示波器不会骗人,它比你的眼睛可靠得多。