
1. 光会算 BCK 频率没用先把 I2S 到 TDM 这条演化链捋清楚1.1 I2S 天生只有两个声道这件事是怎么被突破的I2S 是 Philips 在 1986 年定义的串行音频接口规范上就三条线BCLK位时钟、WS左右声道选择、SD串行数据。WS 为低表示左声道为高表示右声道数据在 WS 翻转后的下一个 BCLK 上升沿开始传输MSB 在前。它天生就是为“立体声”设计的一条数据线上只有左右两个声道通道信息靠 WS 的电平来区分。如果系统需要四个、六个、八个声道呢早期的做法很粗暴多加几根 I2S 线。主控有两条 I2S 控制器就接两个 codec或者接一个自带多根串行输入的 codec每根线单独传一对声道。硬件上每根线都要占用一组引脚PCB 上要走好几组信号线还要保证它们之间同步。通道越多布线越痛苦而且很多时候 SoC 根本没有那么多 I2S 控制器可用。这就是 TDM 出现的最直接动力既然采样率相对 BCLK 来说很“慢”——48kHz 的采样周期长达 20.8µs而一个 BCLK 周期只有几十纳秒——一帧里其实塞得下很多个声道的 PCM 数据。那不如就用同一根数据线把一个采样周期拆成若干个“时隙”每个时隙固定属于某一个声道轮流发送。这就是时分复用。好处很直观无论 8 声道还是 16 声道数据线依然只要 1~2 根引脚成本不变多路数据的同步天然一致。1.2 TDM 在时间上到底“分”了什么TDM 的“帧”这个概念非常重要。一个帧frame有些资料叫 round对应一次采样周期帧率就等于采样率。48kHz 采样率意味着每秒有 48000 个帧每帧 20.83µs。如果一个 TDM 链路配置成 16 个时隙、每个时隙 32 bit那每一帧里就有 16 × 32 512 个 BCLK 周期在 48kHz 下BCLK 就得跑到 48000 × 512 24.576MHz。这个数字是 TDM 调试的“第一道算术题”后面示波器验证要从它出发。时隙和声道的关系一般固定slot0 对应第一个声道slot1 对应第二个依次类推。至于 slot0 从哪一刻开始算则由 FSYNC 帧同步信号来标定。FSYNC 每帧发一个脉冲或一个电平周期告诉所有收听方“一帧开始了请对齐各自的位置”。之后每个 BCLK 上升沿就是一个 bit攒够一个 slot 的 bit 数就切换到下一个 slot。打个比方TDM 总线就像一条单车道的公交线路BCLK 是秒表FSYNC 是每天零点整的报时slot0~slot15 是 16 个固定时刻出发的班次数据线就是那条路。只要对齐了零点谁该出现在哪个时刻完全可以从波形上数出来。顺便说一下应用场景。TDM 多通道总线最常见的地盘是车载音频、多麦克风阵列和专业录音设备。比如车内 8 个麦克风采集路噪做 ANC 降噪或者一块 8 通道 codec 同时采集 8 路模拟信号一般都会在 SoC 和 codec 之间走 TDM。除此之外DSP 与 DSP 之间搬运多通道数据时也常用 TDM因为它的时序是完全确定性的且没有多余控制线的开销。理解了这些后面分析 bit 对齐、示波器波形才有意义。2. BCLK、FSYNC、DATA 的三角关系以及时隙到底怎么数2.1 三根线的分工以及长帧同步和短帧同步的区别先看三根线的角色BCLK位时钟每个 BCLK 周期传输一个 bit。频率由“采样率 × 时隙数 × 每时隙位数”决定。主模式下由本端产生从模式下由对端输入。一般要求占空比接近 50%但实际 SoC 输出通常能做到 50% 上下。FSYNC帧同步频率严格等于采样率负责标记每一帧的起点。DATA数据承载 PCM 数据。全双工时通常有两条分别是发送线和接收线半双工时只有一条需要靠方向切换。FSYNC 在模式上有重要分歧长帧同步long frame sync和短帧同步short frame sync。长帧同步下FSYNC 的有效电平会持续一个完整帧或半个帧。I2S 和左对齐格式都属于这一类I2S 里 WS 低半个帧是左声道高半个帧是右声道TDM 长帧模式下FSYNC 有效电平可能覆盖整个帧期间所有 slot 依次出现。短帧同步则完全不同FSYNC 只持续 1~2 个 BCLK 的脉冲像个“发令枪”枪响之后数据立刻按 slot 排开。短帧同步在 DSP 与 DSP 之间非常常见TI 的 McASP、ADI 的 SPORT 都大量使用。这两类模式如果两端配置不一致数据会整体错一个或多个 slot。SoC 配成长帧、codec 配成短帧的翻车现场我在论坛和实际项目里见过太多。所以在看示波器之前先确认两边用的是同一种帧同步风格。2.2 slot0 出现在哪决定了整个帧的对齐把时间轴展开一个理想的 TDM 长帧数据假设 FSYNC 高有效应该是这样FSYNC 上升沿表示帧开始如果配置是“0 位延迟/左对齐”slot0 的 MSB 在 FSYNC 上升沿那个时刻就有效如果配置是“1 位延迟/I2S 风格”slot0 的 MSB 要等下一个 BCLK 上升沿才有效。这“一位之差”在波形上很难一眼看出来但后果很严重所有声道的数据整体向右挪了一个 bit。MSB 掉到了 MSB-1 的位置音频出来就是刺耳噪声。更麻烦的是很多 codec 的数据手册里这个位叫“Data Delay”或者“TX Delay”不同厂商的默认值还不一样。调试时我的习惯是永远不依赖默认值先把 FSYNC 和 DATA 放大到能看清 bit 的角度确认 MSB 到底是从哪个沿开始的。这一步确认完声道错位类的问题基本能排除一半。2.3 时隙宽度和有效位宽是两回事一个时隙常被定义为 32 bit但真正有效的 PCM 数据可能只有 16 或 24 bit。标准 TDM 通常是 MSB 对齐在时隙最左边低位补 0。举个例子8 通道、32 bit slot、24 bit 有效位。此时 BCLK 48k × 8 × 32 12.288MHz。每次传输的 32 bit 里前 24 bit 是真实采样值后 8 bit 全是 0。在示波器上看如果采样值恰好让低位为零那最后 8 个 bit 就是一条平线但如果低位恰好不为零也会看到电平变化所以不能单凭“是不是直线”判断时隙宽度。在 Linux ALSA 里snd_soc_dai_set_tdm_slot(dai, tx_mask, rx_mask, slots, slot_width)中的slot_width是物理时隙宽度而样本位数是有效位宽两者由驱动分别约束。只配 slot_width 忘了配 sample bits或者反过来就会出现“能响但声音不对”的诡异情况。这一点后面实战部分还会再踩一次。3. 示波器实测 TDM 波形抓波、数数、验证3.1 先做算术再做连线预期频率是多少上示波器之前先把配置定下来算出预期波形。例如采样率 48kHz8 slotslot 32 bit那么预期 BCLK 48k × 8 × 32 12.288MHzFSYNC 48kHz一帧内应有 8 × 32 256 个 BCLK 周期。拿着这个预期值去量才能第一时间发现“实际 BCLK 只有 6.144MHz”这种配置漏了一半的经典问题。很多人一上来就把探头往板子上怼看到有波形就说“时钟是好的”结果后面全部白调。先算清楚再测量这是 TDM 调试省时间的第一原则。示波器探头接法也简单CH1 接 BCLKCH2 接 FSYNCCH3 接 DATA。如果示波器只有两通道就先看 BCLK FSYNC确认时钟域正常后再看 DATA。带宽建议 200MHz 以上采样率至少 250MS/s。24.576MHz 的 BCLK 虽然基频不高但你要在波形上数 bit谐波和上升沿必须看得干净带宽太低会把边沿抹糊。3.2 如何从波形上数出 8 个 slot数 slot 的方法我总结成一套固定流程把触发通道设为 FSYNC触发沿选上升沿或下降沿取决于配置的极性。把时基调到一个帧周期附近。48kHz 就是 20.83µs可以设成 10µs/div让屏幕上显示 1~2 个完整帧。先数 BCLK看 FSYNC 两次有效沿之间有多少个 BCLK 上升沿。256 个就对128 个就是只有一半。再看 DATA。这一步的关键是让数据线发出“已知模式”比如循环发送0xAAAAAAAA或0x55555555。此时 DATA 线会呈现非常规律的方波段在每个 slot 边界数据样本变化会出现明显的相位跳变点。从 slot0 开始每数 32 个 BCLK 就是下一个 slot。用示波器的光标标出来屏幕上就能画出一帧的 slot 划分。如果 DATA 线上没有任何数据会是一条静止平线这时没法数 slot。先解决数据来源问题比如检查 DMA 是否在跑、DAC 是否解除静音、发送的 PCM 数据是否全零。3.3 极性、对齐方式在波形上的体现FSYNC 极性高有效时 FSYNC 高电平是帧低有效时反之。如果主端高有效、从端低有效slot0 会跑到帧的尾部通道顺序就乱套了。数据延迟左对齐0 bit delay时slot0 MSB 紧贴 FSYNC 有效沿I2S 风格1 bit delay时MSB 在有效沿之后一个 BCLK。放大波形对比 FSYNC 沿和 DATA 第一个 bit 的位置一眼就能确认。有的 codec 还支持 2 bit delay这种一般出现在短帧同步的 DSP 模式下配置时尤其要看手册。slot 宽度如果配置每个 slot 32 bit但波形上一段数据只有 16 个 BCLK 就切走了说明 slot width 被配成了 16。同理如果一段数据横跨 64 个 BCLK就是配成了 64 bit slot。这种错误在寄存器配置里很常见波形上却非常清楚。3.4 没有协议解码时怎么“手撕”DATA 线有些示波器带 I2S 解码直接标出 slot 序号非常省事。没有的话手动确认也不难关键是发送已知模式。比如一个 24 bit MSB first 的 codec往 slot0 写0xAAAAAA波形就应该是10101010101010101010101024 位后面跟着 8 个补位 bit。如果同时向 slot0 写0xAAAAAA、slot1 写0x555555两个值交替出现就能非常清楚地看到 slot 边界。自己写一个循环播放的测试程序把每个 slot 填上不同模式然后用示波器光标量从 FSYNC 有效沿开始每 32 BCLK 一个 slot标完再与预期模式对照。这就是最可靠的“人肉协议解码”。这个步骤做一次你对 TDM 数据时序的理解会比看十篇文档都深。4. Codec 实战把 8 通道 ADC/DAC 在一根 DATA 线上跑起来4.1 选型与硬件连接TDM 接线到底怎么接市面上常见的多通道 codec 有 TI PCM3168A8 ADC 8 DAC、Cirrus Logic CS424486 ADC 8 DAC、AK44588 通道 DAC等。选型时看三点通道数够不够、数字接口是否支持 TDM、主从模式是否灵活。还有一点容易忽略确认 codec 的 ADC 串行输出和 DAC 串行输入是独立引脚还是共用一个数据口。共用的虽然省线但方向切换和 slot 分配要特别小心。以“主控为 master、codec 为 slave”的常见接法为例MCLK主控输出主时钟一般取采样率的 256 倍或 512 倍。48kHz 就是 12.288MHz 或 24.576MHz。BCLK主控输出频率 采样率 × slot 数 × slot 宽度。FSYNC主控输出频率 采样率。DAC_DIN主控的 TX_DATA 接到 codec 的 DAC 串行输入引脚。ADC_DOUTcodec 的 ADC 串行输出引脚接到主控的 RX_DATA。别忘了 I2C/SPI 控制接口、复位引脚和电源。硬件上最容易踩的坑是 MCLK。很多 codec 必须等 MCLK 稳定后才能通过 I2C 写寄存器上电立即写配置很容易失败且不报错。我一般会在代码里把复位释放和首次 I2C 配置之间加几十毫秒延时同时在配置后做一次读回确认。电源时序也要看手册有些 codec 要求模拟电源先起来数字电源后起来搞反了会导致内部状态机异常。4.2 寄存器配置主从、格式、时隙分配一个都不能少不同 codec 寄存器名称不一样但 TDM 模式下需要确认的字段大致是这几块数字接口格式选 TDM/I2S/左对齐/右对齐不要用默认的 I2S 然后以为能直接跑多通道。帧同步模式长帧还是短帧极性高还是低。时隙数量与位宽比如 TDM8、slot 32 bit有些 codec 还支持 slot 使能掩码可以单独关掉某个 slot。主从模式codec 设成 slave 还是 master必须和 SoC 相反。两边都当 master 会互相打架两边都等对方给时钟则完全没有 BCLK/FSYNC。ADC/DAC 数据通路使能、通道音量、去加重。注意很多 codec 上电默认处于 mute 或 standby 状态寄存器算配完了但没解除静音也会表现为“没有声音”。我习惯把 I2C 寄存器读回作为配置成功的唯一凭据。曾经遇到过 I2C 地址高位写错导致寄存器写到一个不存在的设备驱动层还完全正常最后是读回才发现总线根本没响应。不要相信“驱动没报错”就等于“配置成功”。4.3 Linux ALSA 侧的 TDM 配置set_tdm_slot 是关键嵌入式 Linux 下TDM 配置分散在设备树、machine driver、codec driver 三层。最常见的方式是在 machine driver 的 hw_params 回调里同时把 CPU DAI 和 Codec DAI 都配一遍。伪代码大概是这样static int board_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd snd_pcm_substream_to_rtd(substream); int ret; /* 给 CPU 侧配置主时钟例如 12.288MHz */ ret snd_soc_dai_set_sysclk(rtd-cpu_dai, 12288000, 0); /* CPU 作为 master短帧同步 TDM 模式 */ ret snd_soc_dai_set_fmt(rtd-cpu_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_DSP_A); ret snd_soc_dai_set_fmt(rtd-codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_DSP_A); /* 8 个 slot每个 slot 32 bit全部启用 */ ret snd_soc_dai_set_tdm_slot(rtd-cpu_dai, 0x00ff, 0x00ff, 8, 32); ret snd_soc_dai_set_tdm_slot(rtd-codec_dai, 0x00ff, 0x00ff, 8, 32); return ret; }这里有两个细节要特别说明。第一SND_SOC_DAIFMT_CBS_CFS表示 CPU 是 master、codec 是 slave。如果你用的是 codec 主模式就换成SND_SOC_DAIFMT_CBM_CFM而且两端都要一致。别一边写 CBS、一边写 CBM那等于告诉两条 DAI 两个不同的时钟方向结果就是 BCLK/FSYNC 混乱。第二DSP_A和DSP_B的差别在不少 SoC 里就是“数据比帧同步晚一拍”还是“早一拍”的问题。不同内核版本、不同厂家的 BSP 对这两个宏的解释略有出入不要死记。最可靠的办法是配置完后到第 5 章的“波形验证”里确认 MSB 位置如果错了一位就切换成另一个宏。有些平台用设备树也能配置 TDM具体属性名取决于 BSP。如果机器驱动里已经调用了set_tdm_slot设备树里就不用重复配。我的经验是先搞清楚 BSP 的 machine driver 是否已经做了这件事再做设备树修改否则重复配置反而会冲突。4.4 验证通路先让 DAC 出声再让 ADC 录音验证顺序建议先 DAC、后 ADC因为 DAC 可以从物理输出直接量到波形反馈最快。生成一个 8 通道测试 WAV每个声道放不同频率的正弦比如 ch0 997Hz、ch1 1229Hz、ch2 1567Hz……播放后逐通道用示波器或频谱看能立刻确认声道顺序和通道数量。用aplay -D hw:0,0 -c 8 -f S32_LE -r 48000 test8ch.wav播放。如果 codec 支持多路 DAC 差分输出输出端要接负载或适当衰减否则测出来的波形幅度和原始预期差很多。验证 ADC给 8 个模拟输入分别接不同频率的信号源执行arecord -D hw:0,0 -c 8 -f S32_LE -r 48000 rec.wav录完再逐通道做 FFT。每个声道应该分别出现对应的频率尖峰。在 Linux 侧快速检查多声道文件可以直接丢给 ffmpeg 解码ffmpeg -i rec.wav -f null -或者用ffplay播放。注意 ffmpeg 里的 time_base 只用于容器时间戳换算跟 TDM 总线上 BCLK 的物理时序没有任何关系。有人把两者混为一谈去“调整 ffmpeg 的 time base”试图修复硬件声道错位纯属走弯路。如果自己写 Python 脚本解析 wav 时碰到 UnicodeDecodeError 这样的编码报错那是文件标签或路径的问题跟 PCM 数据本身无关别往 TDM 上想。5. 那些让人怀疑人生的 TDM 翻车现场以及怎么把它迅速定位5.1 只有前两个声道有数据后面全是静音slot 掩码的锅这个现象典型到几乎每个调 TDM 的人都遇到过。录音文件里前两个声道有波形后六个全是一条直线。原因通常有三个驱动里set_tdm_slot的 tx/rx_mask 配成了0x0003而不是0x00ff只启用了前两个 slot或者压根没调用set_tdm_slot驱动默认按 2 slot 工作或者 hw_params 里的 channels 只设成 2DMA 只搬运了两声道的数据。定位方法很直接示波器看 DATA 线上一个帧内有没有 8 段不同数据。如果只有 2 段问题在发送端如果 8 段全都有但 codec 只有两路输出问题在 codec 侧的 slot 映射。再顺手读一下 codec 寄存器确认 slot 使能字段。Linux 下还可以用arecord -D hw:0,0 --dump-hw-params确认声卡实际支持的通道数。5.2 声道顺序错了slot0 和通道 1 的“哲学问题”很多 codec 手册的通道编号从 1 开始但 ALSA 的 mask 和 SoC 寄存器是从 0 开始的。配置时如果按手册写成 channel1 slot1整体就会错一位声道顺序变成 ch2、ch3……ch0。定位方法给某个模拟输入接一个特征频率录音后看它落到哪个声道偏移量立刻清楚。比如给 ADC 的第 3 路输入接 2kHz结果录出来的第 4 路slot3有这个频率说明整体偏了一位。修正办法是调整 slot0 与物理通道的映射或者在 SoC 侧配置 slot offset。还有一种隐蔽情况FSYNC 极性配反slot0 跑到帧尾通道顺序直接循环偏移。这类问题从波形上看 FSYNC 有效沿和 slot0 的位置就能判断不必反复猜。5.3 能响但爆音、刺耳MSB 偏移一位的隐蔽表现这个坑最气人因为声道数量对、频率也对就是声音像砂纸一样。原因是数据线与帧对齐差了一个 BCLK。发送端用“左对齐/0 位延迟”接收端用“I2S/1 位延迟”或者反过来。波形上放大 FSYNC 有效沿看 DATA 的第一个 bit 是否紧贴着沿开始。如果明显晚了一个 BCLK所有 PCM 数据的 MSB 就掉到了第二位数值完全错误。修正办法就是统一数据延迟在 ALSA 里表现为DSP_A和DSP_B的切换在 codec 寄存器里表现为 data delay 字段。两边设成一致爆音立刻消失。5.4 BCLK 频率差一半算错 slot 数或位宽我接手过一个案子播放人声像开了变速齿轮语速快得离谱。查到最后是驱动里 slot_width 写成了 16而实际 slot 是 32。BCLK 只有预期的一半数据被压缩在半个帧里每个声道的采样周期错乱。这时候不要只看频率计数器直接在示波器上数 FSYNC 两次有效沿之间的 BCLK 个数预期 256实际 128那 slot 数或者 slot 宽度一定少了一半。反过来如果 BCLK 频率翻倍则可能是 codec 把 8 个声道分成两组、每组 4 slot 走两条数据线而 SoC 却配成了 8 slot 全走一根线这时候满帧塞不下两组数据也会出现严重的错位。5.5 用“已知模式 数字回环”快速区分问题在哪一端如果总线问题定位到这一步还糊着我一般会用数字回环来收口。思路是先让主控发出一段已知模式的 TDM 数据在示波器上确认发送侧格式正确然后打开 codec 的数字回环Digital Loopback让 DAC 数据直接回到 ADC 通路录音后检查数据是否还是同样的模式。还有更粗暴但有效的一招把 CPU 的 TX_DATA 直接短接到 RX_DATA在 codec 之前绕开 codec 录音。如果短接后能录到和播放一致的数据说明 CPU 侧 TDM 发送和接收链路没问题问题在 codec 配置如果短接后格式也乱那就是 CPU 侧 DAI 配置有误。这样一来问题边界就被压缩到了“Pre-Codec”还是“Codec”两端排查范围缩小一大半。6. 调试工具链与我习惯的验证顺序6.1 工具选择示波器不是越贵越好关键是够用调 TDM 不需要追求上万的设备但有几个参数不能省。带宽BCLK 到 24.576MHz 时想看清晰边沿200MHz 带宽比较稳最低也别低于 100MHz。采样率至少 250MS/s。数 256 个 BCLK 要在屏幕上放大后还能看清单个 bit采样率不够会看到“台阶”一样的假波形。存储深度最好有深存储或大余辉方便暂停后慢慢数 slot。协议解码有 I2S 解码当然好没有就用手动模式法也不难。逻辑分析仪加 Sigrok 也能干这个活采样率够就行。但逻辑分析仪看不了模拟幅度MCLK 幅值不足、信号振铃这类问题它发现不了所以示波器仍是主力逻辑分析仪适合长时间录制后离线分析。6.2 我的验证顺序从电源到数据一层一层剥我这些年调过的多通道总线基本都按下面这个顺序走很少翻车电源和 MCLK先量电压、MCLK 幅值和频率。MCLK 不对后面全是白搭。BCLK/FSYNC量频率、极性、占空比确认与配置一致。数据线静态电平不发数据时 DATA 线应该是确定电平不能是浮空乱跳。已知模式动态测试发送0xAAAAAA之类的固定模式数 slot、确认 MSB 位置。读回 codec 寄存器确认配置真的写进去了。模拟功能验证DAC 播放多音 WAVADC 录多频信号逐通道检查。长时间稳定性测试跑一晚上观察有没有偶发爆音或通道错位。偶发问题多半是时钟不稳或供电纹波不是配置而是硬件层面的问题。第 4 步是我最看重的因为它在最短时间内把“数字接口配置”和“模拟通道”彻底隔离。只要已知模式在 DATA 线上对得上剩下就是模拟/驱动侧的事。6.3 一张排查表把常见波形异常和配置问题对应起来波形现象大概率问题优先检查点完全没有 BCLK主从配置冲突 / 引脚复用错主从模式、MCLK、GPIO muxBCLK 频率不对slot 数或 slot 宽度配错set_tdm_slot、codec 寄存器FSYNC 频率不等于采样率采样率分频配置错sysclk、FSYNC 分频设置FSYNC 高电平很窄长帧/短帧同步不匹配DSP_A/DSP_B 与 codec 帧同步设置DATA 只有两段数据slot mask 或 DMA 通道数不够tx/rx_mask、PCM channels数据段整体偏一个 BCLK帧同步延迟不一致左对齐 / I2S 延迟设置能响但声道乱序slot0 位置或 FSYNC 极性错极性、slot offset、通道映射爆音 / 刺耳MSB 偏移或极性反相Data Delay、BCLK 极性与采样沿这张表是我自己整理的工作习惯每次碰到新板卡都先对着它查一遍大部分问题能在十分钟内定位不用重新踩一遍之前的坑。调试 TDM 多通道音频总线我最大的体会是它其实不复杂难的是把波形上的每一位和配置里的每一个 bit 对应起来。先让数据线“说”出确定的话再改代码顺序对了问题通常半天内能定位。希望这篇能帮你在调 codec 的时候少走一段弯路。