4、时钟树与PLL配置:MCLK、BCLK、LRCLK关系、PLL分频系数计算、采样率与时钟频率匹配
在嵌入式Linux音频系统中,Codec芯片的时钟树是数字音频信号能够正确采样、传输和转换的基石。MCLK(主时钟)、BCLK(位时钟)和LRCLK(帧时钟)三者之间的频率关系,以及PLL(锁相环)的分频系数计算,直接决定了音频采样率是否准确、有无时钟抖动导致的失真。本节将深入剖析时钟树的拓扑结构、频率匹配公式以及PLL配置的实战计算方法。
4.1 三大时钟信号的角色与关系
音频Codec通常需要三个外部或内部生成的时钟信号协同工作:
| 时钟信号 | 全称 | 频率特征 | 主要作用 |
|---|---|---|---|
| MCLK | Master Clock(主时钟) | 最高频,通常为 256×Fs、384×Fs 或 512×Fs | 驱动Codec内部Σ-Δ调制器、数字滤波器及PLL参考源 |
| BCLK | Bit Clock(位时钟) | 每个音频数据位一个脉冲,BCLK = 声道数 × 位深 × LRCLK | 控制串行数据接口(I²S/PCM)的位传输节奏 |
| LRCLK | Left-Right Clock(帧时钟) | 等于采样率 Fs,即 LRCLK = Fs | 标识左右声道数据帧的起始,决定采样率 |
三者之间的核心约束关系可用以下公式表达:
BCLK = LRCLK × 声道数 × 采样位深
MCLK = LRCLK × 过采样率(OSR)
例如,对于立体声(2声道)、16位深、48kHz采样率的I²S标准配置:
- LRCLK = 48 kHz
- BCLK = 48 kHz × 2 × 16 = 1.536 MHz
- MCLK 若采用 256×Fs,则为 48 kHz × 256 = 12.288 MHz
4.2 时钟树拓扑与PLL分频链路
现代音频Codec内部通常集成一个或多个PLL,用于将外部提供的MCLK(或其它参考时钟)转换为内部各模块所需的精确时钟。下图展示了典型的Codec时钟树结构:
PLL配置的核心在于:输入参考频率 × 倍频系数 / 总分频系数 = 目标时钟频率。通常Codec数据手册会提供一张“PLL分频系数查找表”,开发者根据目标采样率Fs和外部MCLK频率,查表或计算得到寄存器值。
4.3 PLL分频系数计算实战
以常见的WM8960 Codec为例,其PLL输出频率计算公式为:
PLL输出频率 = (MCLK × PLL_N) / (PLL_K × 2^PLL_P)
其中:
- PLL_N:整数倍频因子(通常 6~12)
- PLL_K:小数分频因子(24位精度)
- PLL_P:后置分频指数(0、1、2)
计算步骤:
- 确定目标PLL输出频率: 根据所需采样率Fs和过采样率OSR计算。例如,需要48kHz采样率,内部Σ-Δ调制器需要256×Fs = 12.288 MHz。
- 计算总倍频比: 总倍频比 = 目标PLL输出频率 / MCLK输入频率。假设MCLK = 12 MHz,则总倍频比 = 12.288 / 12 = 1.024。
- 分解为整数和小数部分: 1.024 = 1 + 0.024。选择PLL_N = 1,PLL_K = 0.024 × 2^24 ≈ 402653。
- 选择后置分频P: 若PLL输出频率过高,可通过PLL_P进行2的幂次分频。本例中PLL输出12.288 MHz,无需后置分频,PLL_P = 0。
snd_soc_component_set_pll() 和 snd_soc_component_set_sysclk() 接口完成。驱动开发者只需提供目标频率和参考时钟,底层Codec驱动会自动计算并写入PLL寄存器。但理解计算原理对于调试时钟失锁、采样率不准等问题至关重要。
4.4 采样率与时钟频率匹配表
下表列出了常见采样率下,MCLK、BCLK和LRCLK的标准频率对应关系(以立体声、16位深为例):
| 采样率 Fs (kHz) | LRCLK (kHz) | BCLK (MHz) | MCLK 256×Fs (MHz) | MCLK 384×Fs (MHz) | MCLK 512×Fs (MHz) |
|---|---|---|---|---|---|
| 8 | 8 | 0.256 | 2.048 | 3.072 | 4.096 |
| 16 | 16 | 0.512 | 4.096 | 6.144 | 8.192 |
| 44.1 | 44.1 | 1.4112 | 11.2896 | 16.9344 | 22.5792 |
| 48 | 48 | 1.536 | 12.288 | 18.432 | 24.576 |
| 96 | 96 | 3.072 | 24.576 | 36.864 | 49.152 |
| 192 | 192 | 6.144 | 49.152 | 73.728 | 98.304 |
4.5 驱动中时钟配置的典型流程
在嵌入式Linux音频驱动中,时钟树的配置通常遵循以下步骤:
- 设置系统时钟源: 通过
clk_set_rate()或Codec专用API配置外部MCLK频率。 - 配置PLL参数: 调用
snd_soc_component_set_pll()设置PLL输入源、输出频率和分频系数。 - 设置音频接口格式: 通过
snd_soc_dai_set_fmt()配置I²S/PCM格式,隐含BCLK和LRCLK的极性。 - 设置系统时钟分频: 调用
snd_soc_dai_set_sysclk()将PLL输出分配给各模块。 - 启动音频流: 触发DMA传输,Codec内部自动根据配置生成BCLK和LRCLK。
以下是一个简化的驱动代码片段,展示PLL配置过程:
static int codec_clock_config(struct snd_soc_component *component, int fs)
{
int ret;
unsigned int pll_out = fs * 256; // 目标PLL输出频率
// 1. 设置PLL参考时钟为MCLK,目标输出为 pll_out
ret = snd_soc_component_set_pll(component, WM8960_PLL,
WM8960_SYSCLK_MCLK, 12000000, pll_out);
if (ret < 0) {
dev_err(component->dev, "Failed to set PLL\n");
return ret;
}
// 2. 将系统时钟源切换为PLL输出
ret = snd_soc_component_set_sysclk(component, WM8960_SYSCLK_PLL,
0, pll_out, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(component->dev, "Failed to set sysclk\n");
return ret;
}
// 3. 配置音频接口位深和声道数(隐含BCLK/LRCLK关系)
ret = snd_soc_dai_set_tdm_slot(component->dai, 0x3, 0x3, 2, 16);
if (ret < 0) {
dev_err(component->dev, "Failed to set TDM slot\n");
return ret;
}
return 0;
}
上述代码中,snd_soc_component_set_pll() 内部会根据输入频率(12 MHz)和目标输出频率(12.288 MHz)自动计算并写入PLL_N、PLL_K和PLL_P寄存器。驱动开发者无需手动计算分频系数,但必须确保输入频率在Codec允许的PLL参考频率范围内。
4.6 时钟抖动与去抖策略
时钟抖动(Jitter)是影响音频信噪比(SNR)和总谐波失真(THD)的关键因素。PLL虽然可以倍频,但也会引入相位噪声。在驱动配置中,以下策略有助于降低抖动影响:
- 选择低抖动晶振: 优先使用温补晶振(TCXO)或恒温晶振(OCXO),避免使用普通陶瓷谐振器。
- 启用PLL环路带宽优化: 部分Codec提供PLL环路带宽寄存器,窄带宽可抑制高频抖动,但锁定时间变长。
- 使用异步采样率转换器(ASRC): 当MCLK与LRCLK不同源时,ASRC可消除时钟域间的抖动传递。
- 避免时钟树级联过多: 每经过一级PLL或分频器,抖动都会累积。尽量让MCLK直接驱动Codec,减少中间时钟芯片。
/sys/kernel/debug/clk/clk_summary 查看各时钟树的使能状态和频率,确认PLL是否锁定。若发现时钟频率与预期偏差超过0.1%,应检查PLL分频系数计算或外部晶振精度。