一、基础概念:PLL、Prescaler、PFD 作用
1. PLL(锁相环 Phase Locked Loop)
外部晶振(IMX6ULL 默认 24MHz OSC)作为输入源,倍频生成高频稳定基准时钟。
IMX6ULL 内置 4 路独立 PLL:
- PLL1 (PLL_ARM):专门供给 ARM 内核;
- PLL2 (PLL_528):提供多媒体外设基准时钟;
- PLL3 (PLL_480):用于 USB、高速外设;
- PLL4 (PLL_AUDIO):音频专用时钟。
PLL 输出固定高频时钟,是整个 SOC 时钟系统的源头。
2. Prescaler(预分频器)
对上游时钟信号进行整数分频,降低时钟频率。
时钟框图中蓝色方框/1 /2 /4全部是分频器。
用途:同一个高频 PLL 源,经过不同分频得到多路低频时钟,适配不同外设频率要求。
3. PFD(Phase Fractional Divider 相位分数分频器)
搭载在 PLL2、PLL3 后端,分数分频模块。
传统分频只能整数分频,PFD 支持分数分频,一个 PLL 可以同时输出 4 路不同频率时钟(PFD0~PFD3)。
- PLL2(528MHz)经过 PFD 分出 4 路独立时钟;
- PLL3(480MHz)经过 PFD 分出 4 路独立时钟;
极大丰富了时钟源选择,不用搭建多个 PLL,节约芯片面积。
时钟信号基础链路:
24MHz晶振 → PLL倍频 → PFD多路分数分频 → 多路选择器MUX → Prescaler分频 → 根时钟ROOT → 时钟门CG → 外设
二、SOC 时钟初始化整体工作内容
我们编写clock_init()完成整套时钟配置,整体分为 4 大工作:
- ARM 内核时钟配置(PLL1)
临时切换系统时钟源到外部 24MHz 晶振(防止调 PLL 时系统死机),配置 PLL1 倍频参数,切换回 PLL1 作为 ARM 时钟源,配置后级分频得到 ARM_ROOT 时钟。 - 初始化 PLL2、PLL3 的 4 路 PFD 时钟
配置 PFD 寄存器,打开各路 PFD 输出,设置分频系数,生成多路高频备选时钟源,供给 AXI、AHB、外设根时钟。 - 配置三大根时钟源(时钟多路选择器 MUX + 分频器)
通过 CBCMR、CBCDR、CSCMR1 寄存器配置多路选择器,选定每一路根时钟的上游时钟源,并配置分频系数:
AHB_CLK_ROOT→ uSDHC 等总线外设PERCLK_CLK_ROOT→ EPIT、I2C 外设IPG_CLK_ROOT→ ADC、WDOG 看门狗外设
- 开启所有外设时钟门(CG 时钟门控)
操作 CCGR0~CCGR6 寄存器,打开全部外设时钟门。时钟门相当于外设电源开关,即使根时钟正常,如果时钟门关闭,外设依然不工作。
四、结合时钟树 + 源码逐段解析时钟初始化流程
#include "MCIMX6Y2.h" #include "core_ca7.h" #include "clock.h" // 打开所有外设时钟门控 CG void enable_clock(void) { CCM->CCGR0 = 0xFFFFFFFF; CCM->CCGR1 = 0xFFFFFFFF; CCM->CCGR2 = 0xFFFFFFFF; CCM->CCGR3 = 0xFFFFFFFF; CCM->CCGR4 = 0xFFFFFFFF; CCM->CCGR5 = 0xFFFFFFFF; CCM->CCGR6 = 0xFFFFFFFF; }CCGRx 寄存器每 2bit 控制一个外设时钟门:
00关闭、01/10/11开启。全部写入 0xFFFFFFFF,打开全部外设时钟。
// 时钟初始化 void clock_init(void) { unsigned int t = 0; // ===================== 1. ARM内核时钟 PLL1配置 ===================== // CCSR[8]:临时切换系统时钟源为 OSC 24MHz晶振 CCM->CCSR &= ~(1 << 8); // CCSR[2]:选择step_clk作为临时运行时钟 CCM->CCSR |= (1 << 2); // 配置PLL1(PLL_ARM)倍频参数 t = CCM_ANALOG->PLL_ARM; t &= ~(3 << 14); t |= (1 << 13); // 使能PLL输出 t &= ~(0x7F << 0); t |= (88 << 0); // 倍频系数,24MHz * 88 = 2112MHz CCM_ANALOG->PLL_ARM = t; // CACRR[0] ARM_PODF:PLL1输出二分频,得到1056MHz CCM->CACRR |= (1 << 0); // CCSR[2]清零,切回PLL1_MAIN_CLK作为ARM主时钟源 CCM->CCSR &= ~(1 << 2);ARM 时钟链路(对照时钟树)24MHz OSC → PLL1倍频(24*88=2112MHz) → CACRR二分频 → 1056MHz → ARM_DOMAIN再次二分频 → ARM_CLK_ROOT
⚠️关键技巧:修改 PLL 参数前,必须临时切到晶振时钟,否则 CPU 时钟不稳定直接死机。
// ===================== 2. PLL2(PLL_528) PFD0~PFD3配置 ===================== t = CCM_ANALOG->PFD_528; t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24)); // 清除PFD关闭位,开启四路PFD输出 t &= ~((1 << 7) | (1 << 15) | (1 << 23) | (1 << 31)); // 设置四路PFD分频值 t |= (27 << 0) | (16 << 8) | (24 << 16) | (32 << 24); CCM_ANALOG->PFD_528 = t; // ===================== 3. PLL3(PLL_480) PFD0~PFD3配置 ===================== t = CCM_ANALOG->PFD_480; t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24)); t &= ~((1 << 7) | (1 << 15) | (1 << 23) | (1 << 31)); t |= (12 <<0) | (16 << 8) | (17 << 16) | (19 << 24); CCM_ANALOG->PFD_480 = t;PLL2 基准 528MHz、PLL3 基准 480MHz,通过 PFD 分数分频输出 4 路独立时钟,作为 AXI、AHB 总线时钟备选源。
// ===================== 4. 三大根时钟配置 ===================== // 4.1 CBCMR:外设多路选择器,选择AHB_CLK_ROOT上游时钟源(供给uSDHC) t = CCM->CBCMR; t &= ~(3 << 18); t |= (1 << 18); CCM->CBCMR = t; // 4.2 CBCDR:AXI、AHB分频配置 t = CCM->CBCDR; t &= ~(1 << 25); t &= ~((7 << 10) | (3 << 8)); t |= (2 << 10) | (1 << 8); CCM->CBCDR = t; // 4.3 CSCMR1:PERCLK / IPG时钟配置(EPIT/I2C、ADC/WDOG) t = CCM->CSCMR1; t &= ~(1 << 6); t &= ~(0x3F << 0); CCM->CSCMR1 = t; // 打开全部外设时钟门 enable_clock(); }对照时钟图
- AHB_CLK_ROOT → uSDHC
时钟源经过 MUX 选择 → CBCDR [AHB_PODF] 4 分频 → AHB 根时钟,供给 SD 卡控制器。 - PERCLK_CLK_ROOT → EPIT、I2C
来自 AHB_CLK_ROOT,经过 PERCLK 选择器、1 分频,生成 PERCLK 根时钟,定时器、I2C 依赖该时钟。 - IPG_CLK_ROOT → ADC、WDOG
IPG 时钟通常为 AHB 时钟的 1/2,供给低速外设:看门狗、ADC。
五、总结
IMX6ULL 时钟系统完整信号链路:24MHz外部晶振 → PLL倍频 → PFD多路分频 → MUX多路选择器 → Prescaler预分频 → ROOT根时钟 → CG时钟门 → 片内外设
时钟初始化工作顺序严格不能颠倒:
切换临时时钟 → 配置 PLL1 (ARM) → 配置 PLL2/PLL3 的 PFD → 配置各路根时钟源与分频 → 打开外设时钟门。
只有时钟稳定配置完成,后续 LED、按键、串口、中断、定时器等外设驱动才能正常运行。