03-时钟与电源管理.md 21 KB


title: "I.MX6ULL 时钟与电源管理" tags: [embedded-linux, clock, power-management, imx6ull, arm] created: 2026-09-17 updated: 2026-09-17

pdf_ref: "正点原子 I.MX6U 嵌入式Linux驱动开发指南 V2.0.1 — 第16章 主频和时钟配置实验"

I.MX6ULL 时钟与电源管理

1. 时钟系统概述

1.1 时钟源

I.MX6ULL 的系统时钟来源于两部分外部晶振:

晶振 频率 用途
32.768 KHz RTC 时钟源 实时时钟、看门狗低功耗定时器
24 MHz 系统主时钟源 ARM 内核、所有外设、PLL 输入

24 MHz 晶振是整个时钟树的根,所有 PLL 均由此倍频产生。内部 Boot ROM 默认将主频设置为 396 MHz,但 I.MX6ULL 标准工作频率为 528 MHz,部分型号可跑 696 MHz。

1.2 七路 PLL 时钟源

NXP 将外设时钟源分为 7 组,全部由 24 MHz 晶振 PLL 倍频而来:

graph TD
    OSC["24 MHz 晶振"] --> PLL1["PLL1 (ARM_PLL)<br/>可编程 54~108 倍频<br/>最高 1.3 GHz"]
    OSC --> PLL2["PLL2 (528_PLL / System_PLL)<br/>固定 22 倍频 = 528 MHz"]
    OSC --> PLL3["PLL3 (USB1_PLL)<br/>固定 20 倍频 = 480 MHz"]
    OSC --> PLL5["PLL5 (VIDEO_PLL)<br/>可编程 650~1300 MHz"]
    OSC --> PLL4["PLL4 (AUDIO_PLL)<br/>可编程 650~1300 MHz"]
    OSC --> PLL6["PLL6 (ENET_PLL)<br/>固定 20+5/6 倍频 = 500 MHz"]
    OSC --> PLL7["PLL7 (USB2_PLL)<br/>固定 20 倍频 = 480 MHz"]

    PLL2 --> PFD2_0["PLL2_PFD0 (352 MHz)"]
    PLL2 --> PFD2_1["PLL2_PFD1 (594 MHz)"]
    PLL2 --> PFD2_2["PLL2_PFD2 (396 MHz)"]
    PLL2 --> PFD2_3["PLL2_PFD3 (297 MHz)"]

    PLL3 --> PFD3_0["PLL3_PFD0 (720 MHz)"]
    PLL3 --> PFD3_1["PLL3_PFD1 (540 MHz)"]
    PLL3 --> PFD3_2["PLL3_PFD2 (508 MHz)"]
    PLL3 --> PFD3_3["PLL3_PFD3 (455 MHz)"]
PLL 名称 倍频方式 输出频率 主要用途
PLL1 ARM_PLL 可编程 (54~108) 最高 1.3 GHz ARM 内核主频
PLL2 System_PLL 固定 ×22 528 MHz 系统总线、DDR、NAND/NOR
PLL3 USB1_PLL 固定 ×20 480 MHz USB1 PHY + 4 路 PFD
PLL4 AUDIO_PLL 可编程 650~1300 MHz 音频外设 (ESAI 等)
PLL5 VIDEO_PLL 可编程 650~1300 MHz 显示外设 (LCD 等)
PLL6 ENET_PLL 固定 ×(20+5/6) 500 MHz 以太网时钟 (25/50/100/125 MHz)
PLL7 USB2_PLL 固定 ×20 480 MHz USB2 PHY

1.3 时钟树

I.MX6ULL 的时钟树分为三部分:

graph LR
    subgraph CLOCK_SWITCHER["时钟源 (CLOCK SWITCHER)"]
        PLL1["PLL1 1056MHz"]
        PLL2["PLL2 528MHz"]
        PLL3["PLL3 480MHz"]
        PLL4["PLL4 650MHz"]
        PLL5["PLL5 650MHz"]
        PLL6["PLL6 500MHz"]
        PLL7["PLL7 480MHz"]
        PFD["8路 PFD"]
    end

    subgraph ROOT_GEN["时钟根生成器 (CLOCK ROOT GENERATOR)"]
        SEL["时钟源选择器"]
        DIV["分频器 (PODF)"]
    end

    subgraph SYS_CLK["系统时钟 (SYSTEM CLOCKS)"]
        ARM["ARM 内核"]
        AHB["AHB 总线"]
        IPG["IPG 总线"]
        PERCLK["PERCLK"]
        UART["UART"]
        SPI["SPI"]
        I2C["I2C"]
        LCD["LCD"]
    end

    PLL1 --> SEL
    PLL2 --> SEL
    PLL3 --> SEL
    PLL4 --> SEL
    PLL5 --> SEL
    PFD --> SEL
    SEL --> DIV
    DIV --> ARM
    DIV --> AHB
    DIV --> IPG
    DIV --> PERCLK
    DIV --> UART
    DIV --> SPI
    DIV --> I2C
    DIV --> LCD

外设时钟配置三步曲(以外设 ESAI 为例):

  1. 时钟源选择 — 寄存器 CCM->CSCMR2ESAI_CLK_SEL 位,从 PLL4/PLL5/PLL3_PFD2/pll3_sw_clk 中选一路
  2. 前级分频 — 寄存器 CCM_CS1CDRESAI_CLK_PRED 位,1~8 分频
  3. 二级分频 — 寄存器 CCM_CS1CDRESAI_CLK_PODF 位,1~8 分频

例:PLL4=650 MHz → 前级 2 分频 = 325 MHz → 二级 8 分频 = 40.625 MHz


2. 时钟分频与门控

2.1 分频器

I.MX6ULL 使用 PODF (Prescale Output Divider) 进行分频,PODF 值与分频比的映射关系:

PODF 值 分频比
0 ÷1
1 ÷2
2 ÷4
3 ÷8

关键分频器及其寄存器:

分频器 寄存器 位域 范围 目标频率
ARM_PODF CCM_CACRR[2:0] 0~7 1~8 分频 内核主频
AHB_PODF CCM_CBCDR[12:10] 0~7 1~8 分频 AHB 总线 ≤132 MHz
IPG_PODF CCM_CBCDR[9:8] 0~3 1~4 分频 IPG 总线 ≤66 MHz
PERCLK_PODF CCM_CSCMR1[2:0] 0~7 1~8 分频 PERCLK ≤66 MHz

2.2 门控时钟

I.MX6ULL 通过 7 组 CCGR 寄存器 (CCM->CCGR0 ~ CCM->CCGR6) 控制外设时钟的使能与关闭。每个外设时钟有 2 位控制:

CCGR 值 含义
00 关闭 (off)
01 部分使能 (defined by CCM clock gate)
10 保留
11 运行时始终使能 (on during run)
/* 使能所有外设时钟 (开发阶段) */
void clk_enable(void)
{
    CCM->CCGR0 = 0xFFFFFFFF;
    CCM->CCGR1 = 0xFFFFFFFF;
    CCM->CCGR2 = 0xFFFFFFFF;
    CCM->CCGR3 = 0xFFFFFFFF;
    CCM->CCGR4 = 0xFFFFFFFF;
    CCM->CCGR5 = 0xFFFFFFFF;
    CCM->CCGR6 = 0xFFFFFFFF;
}

注意:量产代码应按需使能时钟,未使用的外设应关闭时钟以降低功耗。

2.3 时钟使能/关闭注意事项

修改以下分频器或选择器时会触发与 MMDC (Memory Controller) 的握手:

  • mmdc_podf
  • periph_clk_sel
  • periph2_clk_sel
  • arm_podf
  • ahb_podf

握手信号在 CCM_CDHIPR 寄存器中,相应位为 1 表示握手未完成,为 0 表示完成。修改 arm_podfahb_podf 时需先关闭时钟输出,修改完成后再打开,否则可能导致无时钟输出。


3. 时钟配置寄存器详解

3.1 CCM 寄存器

CCM_CCSR — 时钟控制状态寄存器

位域 名称 功能
[2] PLL1_SW_CLK_SEL pll1_sw_clk 源选择:0=pll1_main_clk, 1=step_clk
[8] STEP_SEL step_clk 源选择:0=osc_clk(24MHz), 1=secondary_clk

CCM_CACRR — ARM 根时钟分频寄存器

位域 名称 功能
[2:0] ARM_PODF ARM 内核时钟分频:0~7 对应 1~8 分频

CCM_CBCDR — 总线时钟分频寄存器

位域 名称 功能
[25] PERIPH_CLK_SEL periph_clk 源:0=pre_periph_clk, 1=periph_clk2_clock
[19] PERIPH2_CLK_SEL periph2_clk 源:0=PLL2, 1=periph2_clk2_clk
[12:10] AHB_PODF AHB 分频:0~7 对应 1~8
[9:8] IPG_PODF IPG 分频:0~3 对应 1~4
[7:0] AXI_PODF AXI 分频:0~7 对应 1~8

CCM_CBCMR — 总线时钟模式寄存器

位域 名称 功能
[19:18] PRE_PERIPH_CLK_SEL pre_periph_clk 源:00=PLL2, 01=PLL2_PFD2, 10=PLL2_PFD0, 11=PLL2_PFD2/2
[27:26] PRE_PERIPH2_CLK_SEL pre_periph2 源:00=PLL2, 01=PLL2_PFD2, 10=PLL2_PFD0, 11=PLL4

CCM_CSCMR1 — 时钟源选择寄存器 1

位域 名称 功能
[6] PERCLK_CK_SEL PERCLK 源:0=IPG_CLK, 1=OSC_CLK
[2:0] PERCLK_PODF PERCLK 分频:0~7 对应 1~8

3.2 CCM_ANALOG 寄存器

CCM_ANALOG_PLL_ARMn — PLL1 (ARM PLL) 控制寄存器

位域 名称 功能
[13] ENABLE PLL1 输出使能:1=使能, 0=关闭
[6:0] DIV_SELECT 倍频因子:54~108,Fout = Fin × div_select / 2.0

CCM_ANALOG_PFD_528n — PLL2 PFD 控制寄存器

位域 名称 功能
[5:0] PFD0_FRAC PLL2_PFD0 分频因子:528×18/PFD0_FRAC
[6] PFD0_CLKGATE PFD0 门控:1=关闭, 0=使能
[13:8] PFD1_FRAC PLL2_PFD1 分频因子
[14] PFD1_CLKGATE PFD1 门控
[21:16] PFD2_FRAC PLL2_PFD2 分频因子
[22] PFD2_CLKGATE PFD2 门控
[29:24] PFD3_FRAC PLL2_PFD3 分频因子
[30] PFD3_CLKGATE PFD3 门控

CCM_ANALOG_PFD_480n — PLL3 PFD 控制寄存器

结构与 PFD_528n 相同,频率公式为 480 × 18 / PFDx_FRAC

3.3 NXP 推荐 PFD 频率

PFD 推荐频率 FRAC 值 计算公式
PLL2_PFD0 352 MHz 27 528×18/27
PLL2_PFD1 594 MHz 16 528×18/16
PLL2_PFD2 396 MHz 24 528×18/24
PLL2_PFD3 297 MHz 32 528×18/32
PLL3_PFD0 720 MHz 12 480×18/12
PLL3_PFD1 540 MHz 16 480×18/16
PLL3_PFD2 508 MHz 17 480×18/17
PLL3_PFD3 455 MHz 19 480×18/19

4. 时钟配置代码

4.1 系统时钟初始化函数

void imx6u_clkinit(void)
{
    unsigned int reg = 0;

    /* ========================================
     * 步骤 1:设置 ARM 内核时钟为 528 MHz
     * ======================================== */

    /* 1.1 判断当前 pll1_sw_clk 的时钟源
     * pll1_sw_clk 有两个来源:pll1_main_clk 和 step_clk
     * 要修改 PLL1 频率,必须先切到 step_clk (24MHz)
     */
    if ((((CCM->CCSR) >> 2) & 0x1) == 0)
    {
        /* 当前使用 pll1_main_clk,需要切换 */
        CCM->CCSR &= ~(1 << 8);  /* step_clk 源 = 24MHz OSC */
        CCM->CCSR |= (1 << 2);   /* pll1_sw_clk = step_clk */
    }

    /* 1.2 设置 PLL1 = 1056 MHz
     * 公式:Fout = Fin × div_select / 2.0
     * 1056 = 24 × div_select / 2.0 → div_select = 88
     */
    CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0x7F);
    /* bit13=1 使能时钟输出, bit[6:0]=88 倍频因子 */

    /* 1.3 将 pll1_sw_clk 切换回 pll1_main_clk */
    CCM->CCSR &= ~(1 << 2);

    /* 1.4 设置 ARM_PODF = 2 分频
     * 内核主频 = 1056 / 2 = 528 MHz
     * 注意:此处实际有隐式 2 分频,文档中明确标注
     */
    CCM->CACRR = 1;  /* ARM_PODF=1 表示 2 分频 */

    /* ========================================
     * 步骤 2:设置 PLL2 (SYS PLL) 各 PFD
     * ======================================== */
    reg = CCM_ANALOG->PFD_528;
    reg &= ~(0x3F3F3F3F);  /* 清除所有 PFD_FRAC 位 */

    reg |= 32 << 24;  /* PLL2_PFD3 = 528×18/32 = 297 MHz */
    reg |= 24 << 16;  /* PLL2_PFD2 = 528×18/24 = 396 MHz */
    reg |= 16 << 8;   /* PLL2_PFD1 = 528×18/16 = 594 MHz */
    reg |= 27 << 0;   /* PLL2_PFD0 = 528×18/27 = 352 MHz */

    CCM_ANALOG->PFD_528 = reg;

    /* ========================================
     * 步骤 3:设置 PLL3 (USB1) 各 PFD
     * ======================================== */
    reg = CCM_ANALOG->PFD_480;
    reg &= ~(0x3F3F3F3F);

    reg |= 19 << 24;  /* PLL3_PFD3 = 480×18/19 = 454.7 MHz */
    reg |= 17 << 16;  /* PLL3_PFD2 = 480×18/17 = 508.2 MHz */
    reg |= 16 << 8;   /* PLL3_PFD1 = 480×18/16 = 540 MHz */
    reg |= 12 << 0;   /* PLL3_PFD0 = 480×18/12 = 720 MHz */

    CCM_ANALOG->PFD_480 = reg;

    /* ========================================
     * 步骤 4:设置 AHB 时钟 (最大 132 MHz)
     * ======================================== */

    /* pre_periph_clk = PLL2_PFD2 = 396 MHz */
    CCM->CBCMR &= ~(3 << 18);  /* 清除 PRE_PERIPH_CLK_SEL */
    CCM->CBCMR |= (1 << 18);   /* 选择 PLL2_PFD2 */

    /* periph_clk = pre_periph_clk = 396 MHz */
    CCM->CBCDR &= ~(1 << 25);  /* PERIPH_CLK_SEL = 0 */
    while (CCM->CDHIPR & (1 << 5));  /* 等待握手完成 */

    /* AHB_PODF = 3 分频 → AHB_CLK_ROOT = 396/3 = 132 MHz
     * 注意:内部 Boot ROM 已设置为 3 分频
     * 修改 AHB_PODF 需先关闭 AHB_CLK_ROOT 输出
     */

    /* ========================================
     * 步骤 5:设置 IPG 时钟 (最大 66 MHz)
     * ======================================== */
    CCM->CBCDR &= ~(3 << 8);   /* IPG_PODF 清零 */
    CCM->CBCDR |= 1 << 8;      /* IPG_PODF = 1 → 2 分频
                                 * IPG_CLK_ROOT = 132/2 = 66 MHz */

    /* ========================================
     * 步骤 6:设置 PERCLK 时钟
     * ======================================== */
    CCM->CSCMR1 &= ~(1 << 6);  /* PERCLK 源 = IPG_CLK */
    CCM->CSCMR1 &= ~(7 << 0);  /* PERCLK_PODF = 1 分频
                                 * PERCLK = 66 MHz */
}

4.2 主函数调用

int main(void)
{
    unsigned int led_state = OFF;

    imx6u_clkinit();   /* 初始化系统时钟,必须最先调用 */
    clk_enable();       /* 使能所有外设时钟 */
    led_init();
    beep_init();
    key_init();

    /* 时钟配置后 LED 闪烁频率加快:
     * 396 MHz → 528 MHz,代码执行速度提升 33%
     */

    while (1)
    {
        led_state = !led_state;
        led_switch(LED0, led_state);
        delay(500);
    }
}

4.3 时钟配置流程总结

flowchart TD
    A["开始"] --> B["切换 pll1_sw_clk → step_clk (24MHz)"]
    B --> C["设置 PLL1 = 1056 MHz<br/>CCM_ANALOG_PLL_ARMn"]
    C --> D["切回 pll1_sw_clk = pll1_main_clk"]
    D --> E["设置 ARM_PODF = 2<br/>内核 = 1056/2 = 528 MHz"]
    E --> F["设置 PLL2 PFD0~3"]
    F --> G["设置 PLL3 PFD0~3"]
    G --> H["设置 AHB = 132 MHz"]
    H --> I["设置 IPG = 66 MHz"]
    I --> J["设置 PERCLK = 66 MHz"]
    J --> K["完成"]

5. 电源管理概述

5.1 电源域

I.MX6ULL 的电源管理由 CCM (Clock Controller Module) 和 PMU (Power Management Unit) 协同完成。主要电源域:

电源域 供电范围 说明
ARM core 0.9V~1.3V ARM 内核逻辑供电
SOC 1.1V~1.3V 片上外设逻辑供电
NVCC_xxx 1.8V/3.3V I/O 引脚供电 (可独立控制)
RTC 3.3V RTC 域独立供电

5.2 功耗模式

I.MX6ULL 支持多种运行模式:

模式 CPU 状态 功耗 唤醒源 典型应用
Run 全速运行 最高 N/A 正常工作
Wait 时钟停止 中断 等待事件
Stop PLL 关闭 中断/GPIO 深度休眠
VLLS SRAM 保持 极低 RESET/WAKEUP 电池备份

5.3 DVFS (动态电压频率调节)

DVFS 允许在运行时动态调整 CPU 频率和电压:

graph LR
    A["应用需求"] --> B{"负载检测"}
    B -->|"高负载"| C["提高频率<br/>增加电压"]
    B -->|"低负载"| D["降低频率<br/>降低电压"]
    C --> E["PLL1 配置<br/>CCM_ANALOG_PLL_ARMn"]
    D --> E
    E --> F["电压调节器<br/>外部 PMIC"]

DVFS 调节流程:

  1. 监测 CPU 利用率 (通过 performance counters 或 timer tick)
  2. 查找目标频率-电压对 (OPP 表)
  3. 若升频:先升压,再升频
  4. 若降频:先降频,再降压
  5. 等待 PLL 稳定后继续执行

6. 跨平台对比

6.1 I.MX6ULL vs STM32 vs RK3568 时钟差异

特性 I.MX6ULL STM32F4 RK3568
主频 528 MHz 168 MHz 1.8 GHz
外部晶振 24 MHz 8~25 MHz 24 MHz
PLL 数量 7 路 3 路 (PLLM/PLLN/PLLQ) 4 路 (APLL/DPLL/CPLL/NPLL)
PLL 类型 固定 + 可编程混合 通用 PLL (M/N/P/Q) 独立 APLL + 通用 PLL
分频方式 PODF + PFD 分频因子 (M/N/P/Q/R) 分频器 + 门控
PFD 机制 有 (8路)
时钟门控 CCGR (7组×32位) AHBxENR/APBxENR CRU gating
DVFS 支持 有 (软件实现) 无 (固定电压) 硬件 DVFS
时钟树复杂度 高 (三层选择)
总线时钟 AHB(132M)/IPG(66M) AHB(168M)/APB1(42M)/APB2(84M) AHB(150M)/APB(100M)

6.2 关键差异分析

PLL 配置方式

  • I.MX6ULL:PLL2/PLL3/PLL7 固定倍频,PLL1/PLL4/PLL5 可编程
  • STM32F4:所有 PLL 均可通过 M/N/P/Q 因子灵活配置
  • RK3568:APLL (ARM 核心) 独立,其他 PLL 共用参考时钟

PFD 机制 (I.MX6ULL 特有):

  • PFD 是 PLL 输出的二次分数分频,提供介于两个整数分频之间的非整数频率
  • 例:PLL2=528 MHz → PLL2_PFD2=396 MHz (528×18/24)
  • STM32 和 RK3568 均无此机制

时钟门控粒度

  • I.MX6ULL:每个外设 2 位,支持"始终使能/部分使能/关闭"三态
  • STM32F4:每外设 1 位,仅"使能/关闭"
  • RK3568:模块级门控,粒度较粗

7. 面试精选

Q1: I.MX6ULL 为什么使用 24 MHz 外部晶振而不是更高的?

  1. EMI 考量:低频晶振产生的电磁干扰更小,PCB 布线要求更低
  2. PLL 范围:I.MX6ULL 的 PLL 倍频范围 (最高 108 倍) 完全覆盖 24 MHz → 2.5 GHz,足够使用
  3. 成本与精度:24 MHz 晶振是工业标准频率,成本低、精度高 (±20ppm)
  4. 功耗:低频参考时钟在 PLL 未锁定时功耗更低

Q2: 修改 ARM 主频时为什么必须先切换到 step_clk?

PLL1 的输出 (pll1_main_clk) 直接连接到 ARM 内核时钟选择器。如果在 PLL1 输出不稳定时 ARM 内核仍在使用它,会导致:

  • 时钟毛刺:PLL 倍频因子修改瞬间输出频率不确定
  • 系统崩溃:内核在不稳定的时钟下执行指令可能导致死锁或数据损坏
  • PLL 锁定时间:PLL 需要约 100μs 稳定,期间输出不可靠

切换到 step_clk (24 MHz) 后,CPU 在安全的低频下等待 PLL1 稳定,再切回 PLL1,确保时钟切换全程可控。

Q3: PLL2_PFD2 为什么实际频率是 396 MHz 而不是推荐的 400 MHz?

PFD 频率公式为 Fpfd = Fpll × 18 / FRAC,其中 FRAC 必须是整数。

  • 目标 400 MHz:FRAC = 528×18/400 = 23.76 (非整数)
  • 最近整数 FRAC = 24 → 实际频率 = 528×18/24 = 396 MHz

这是 PFD 分数分频机制的固有局限:分频因子必须为整数,所以只能近似目标频率。396 MHz 与 400 MHz 偏差仅 1%,对绝大多数外设无影响。NXP 在官方推荐值中已注明"实际为 396 MHz"。

Q4: 修改 AHB_PODF 时为什么要先关闭时钟输出?如果不关闭会怎样?

AHB 总线连接着 MMDC (内存控制器)、GIC (中断控制器) 等关键部件。修改分频值时:

  1. 原子操作要求:分频器的更新需要在时钟稳定期间完成
  2. 亚稳态风险:如果时钟在分频值切换瞬间仍在运行,可能产生一个时钟周期宽度的毛刺
  3. MMDC 握手:AHB_PODF 修改会触发 MMDC 握手,握手期间总线时钟必须稳定

不关闭的后果

  • 可能产生时钟毛刺导致 MMDC 数据丢失
  • ARM 内核可能在不确定的时钟沿采样,导致取指错误
  • 严重时系统挂死

实际上 I.MX6ULL 内部 Boot ROM 已将 AHB_PODF 设置为 3 分频,很多场景下无需修改。

Q5: I.MX6ULL 的 7 路 PFD 和 STM32 的 PLL 倍频有什么本质区别?

维度 I.MX6ULL PFD STM32 PLL
本质 分数分频器 (÷(FRAC/18)) 整数倍频器 (×M/N)
输入 PLL 固定输出 外部晶振或 HSI
输出特点 一组固定比例的多频点 单一可配置频点
延迟 极低 (组合逻辑) 高 (PLL 重锁)
使用场景 多外设共享同一 PLL 的不同频点 单外设独立配置

PFD 设计思想:一个 PLL 输出通过 PFD 矩阵一次性生成多个衍生频率,避免为每个外设单独配置 PLL,降低时钟切换延迟和功耗。STM32 的 PLL 每次修改都需要重新锁定,不适合频繁切换频率的场景。