4. 时钟系统配置:PLL锁相环原理、系统时钟树分析、为DDR与外设提供时钟
时钟,是嵌入式系统的“心跳”。
Bootloader 里如果时钟配错了,后面所有外设、DDR、CPU 频率全都会乱套。我见过太多工程师在移植 U-Boot 时,卡在时钟配置这一步好几天。说白了,时钟树没理清,后面全是白干。
4.1 PLL锁相环原理——从“抖”到“稳”
先聊聊 PLL 锁相环。很多同学觉得它很玄,其实没那么复杂。
PLL 的核心任务就一个:把低频的参考时钟,倍频成高频的稳定时钟。比如外部晶振只有 24MHz,但 CPU 要跑 1.2GHz,怎么办?靠 PLL。
一个典型的 PLL 结构包含四个部分:
- 鉴频鉴相器(PFD):比较参考时钟和反馈时钟的相位差。
- 电荷泵(CP):把相位差转换成电流信号。
- 环路滤波器(LF):滤掉高频噪声,输出平滑的控制电压。
- 压控振荡器(VCO):根据控制电压调整输出频率。
嗯,这里有个关键点:VCO 的输出频率 = 参考频率 × 倍频系数 / 分频系数。这个公式在配置寄存器时天天用。
我曾经在一个项目中,因为 PLL 的环路滤波器参数没配好,导致输出时钟抖动过大,DDR 读写偶尔出错。查了三天,最后用示波器看时钟波形才发现——嗯,从那以后,我每次配 PLL 都会先看一眼 VCO 的锁定状态寄存器。
4.2 系统时钟树分析——理清“谁给谁供时钟”
现代 SoC 的时钟树,说白了就是一张“时钟分发网”。
以 i.MX6ULL 为例,它的时钟树大概长这样:
你看,从外部晶振出发,经过 OSC 后分三路:
- PLL1:专供 ARM Core,频率最高。
- PLL2:系统 PLL,供给 AHB、IPG 等总线。
- PLL3:USB PLL,也常被外设借用。
每个 PLL 后面还有一堆分频器和选择器。你想想看,如果选错了时钟源,或者分频系数配错了,DDR 可能直接初始化失败。
4.3 为DDR提供时钟——时序的命脉
DDR 对时钟的要求极其苛刻。频率、相位、抖动,哪一项不达标,系统就跑不稳。
在 Bootloader 里配 DDR 时钟,我一般按这个步骤来:
- 确定 DDR 类型和频率:DDR3 还是 DDR4?最高支持多少 MHz?
- 选择 PLL 源:通常用系统 PLL(PLL2)或专门的 DDR PLL。
- 配置分频系数:算出目标频率对应的分频值。
- 设置 DDR 控制器时钟:包括 DRAM 时钟、PHY 时钟、参考时钟。
- 调整相位偏移:确保 DQS 与时钟对齐。
举个例子,某款芯片的 DDR 时钟配置代码片段:
/* 配置 DDR PLL 为 528MHz */
reg_write(PLL_DDR_CTRL, 0x0000200D); // 使能 PLL,设置倍频系数
/* 等待 PLL 锁定 */
while (!(reg_read(PLL_DDR_STATUS) & 0x01));
/* 设置 DDR 时钟分频,目标 264MHz */
reg_write(CCM_CSCDR1, (reg_read(CCM_CSCDR1) & ~0x1F) | 0x01);
/* 使能 DDR 时钟输出 */
reg_write(CCM_CCGR1, reg_read(CCM_CCGR1) | (0x3 << 12));
这段代码看着简单,但坑不少。我曾经因为忘记等待 PLL 锁定,直接往下配分频,结果 DDR 初始化死活过不去。后来加了 while 循环等锁定标志,问题就解决了。
4.4 为外设提供时钟——别让外设“饿着”
外设时钟配置,说白了就是“谁要用,就给谁开门”。
每个外设都有对应的时钟门控寄存器。比如 UART、I2C、SPI、Ethernet,它们各自挂在不同的时钟总线上。配置时要注意:
- 时钟源选择:有些外设可以选 PLL2 或 PLL3 作为时钟源。
- 分频系数:外设的工作频率不能超过其最大规格。
- 时钟门控:必须使能对应外设的时钟门控位,否则外设寄存器读出来全是 0。
我见过一个案例:同事移植 U-Boot 时,UART 怎么都不输出。查了半天,发现是时钟门控没开。嗯,这种问题其实很常见,因为芯片上电后默认大部分外设时钟是关闭的。
下面是一个典型的外设时钟配置表:
| 外设 | 时钟源 | 最大频率 | 门控寄存器 |
|---|---|---|---|
| UART1 | PLL3 / 24MHz | 5 MHz | CCM_CCGR0 |
| I2C1 | PLL2 / IPG | 400 kHz | CCM_CCGR1 |
| SPI1 | PLL2 / AHB | 30 MHz | CCM_CCGR2 |
| Ethernet | PLL2 / ENET | 125 MHz | CCM_CCGR3 |
4.5 避坑指南——时钟配置常见问题
做 Bootloader 时钟配置这些年,我踩过不少坑。挑几个典型的说说:
- PLL 未锁定就使用:后果是系统跑飞。解决办法:配完 PLL 后一定要轮询锁定标志。
- 时钟源切换顺序错误:先切到临时时钟,再切到新 PLL。顺序反了,系统直接挂。
- 分频系数计算错误:DDR 频率超了上限,导致数据损坏。建议用公式手动算一遍,别光靠工具。
- 忘记使能时钟门控:外设寄存器读出来全是 0。调试时先检查门控寄存器。
我曾经在一个项目中,因为时钟树配置里漏了一个分频器的使能位,导致 AHB 总线频率只有预期的一半。系统能跑,但性能差得离谱。查了两天才找到原因——嗯,从那以后我配时钟树都会对着参考手册逐位核对。