03-时钟与电源管理.md 28 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 等),最终输出可选 1/2/4 分频
PLL5 VIDEO_PLL 可编程 650~1300 MHz 显示外设 (LCD 等),最终输出可选 1/2/4/8/16 分频
PLL6 ENET_PLL 固定 ×(20+5/6) 500 MHz 以太网时钟 (25/50/100/125 MHz)
PLL7 USB2_PLL 固定 ×20 480 MHz USB2 PHY

原书第 16.1.2 节列举的 7 路 PLL 顺序为:① ARM_PLL(PLL1) ② 528_PLL(PLL2) ③ USB1_PLL(PLL3) ④ USB2_PLL(PLL7,序号为 4 但实际是 PLL7) ⑤ ENET_PLL(PLL6) ⑥ VIDEO_PLL(PLL5) ⑦ AUDIO_PLL(PLL4)。

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) 进行分频。原书第 16.1.4/16.1.6 节明确:PODF 值 0~7 分别对应 1~8 分频(线性关系,即分频比 = PODF 值 + 1):

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

💡 注意:PODF 是线性映射,分频比 = PODF 值 + 1(0~7 依次对应 1~8 分频),不是 0→÷1、1→÷2、2→÷4、3→÷8 的指数映射。例如设置 ARM_PODF=1 时为 2 分频(1056/2=528MHz),与"值+1"关系一致。

关键分频器及其寄存器(CCM 基地址 = 0x020C4000):

分频器 寄存器 位域 范围 目标频率
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 个连续位(CGn),可设置为 4 种状态:

CCGR 位设置 含义
00 所有模式下都关闭外设时钟
01 只在运行(Run)模式下打开,等待(Wait)和停止(Stop)模式下均关闭
10 未使用(保留)
11 除停止模式以外,其他所有模式下时钟都打开

CCGR 寄存器地址(CCM 基地址 0x020C4000):

寄存器 地址 说明
CCM_CCGR0 0x020C4068 如 bit[31:30] 控制 GPIO2 时钟
CCM_CCGR1 0x020C406C 如 bit[27:26] 控制 GPIO1 时钟
CCM_CCGR2 0x020C4070
CCM_CCGR3 0x020C4074
CCM_CCGR4 0x020C4078
CCM_CCGR5 0x020C407C UART 等相关
CCM_CCGR6 0x020C4080

位域规律:CGn 占据 bit[2n+1 : 2n]。例如 CCGR0 的 bit[31:30]=GPIO2,CCGR1 的 bit[27:26]=GPIO1。

/* 使能所有外设时钟 (开发阶段) */
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 — 总线时钟分频寄存器

位域 名称 功能
[29:27] PERIPH_CLK2_PODF periph_clk2 时钟分频:0~7 对应 1~8 分频
[26] PERIPH2_CLK_SEL periph2_clk 源:0=PLL2, 1=periph2_clk2_clk(触发 MMDC 握手)
[25] PERIPH_CLK_SEL periph_clk 源:0=pre_periph_clk, 1=periph_clk2_clock(触发 MMDC 握手)
[21:19] MMDC_CH0_PODF mmdc_ch0 时钟分频:0~7 对应 1~8 分频
[18:16] AXI_PODF AXI 分频:0~7 对应 1~8
[12:10] AHB_PODF AHB 分频:0~7 对应 1~8(触发 MMDC 握手)
[9:8] IPG_PODF IPG 分频:0~3 对应 1~4
[7] AXI_ALT_CLK_SEL axi_alt 时钟选择:0=PLL2_PFD2, 1=PLL3_PFD1
[6] AXI_CLK_SEL axi 时钟源选择:0=periph_clk, 1=axi_alt
[5:3] FABRIC_MMDC_PODF fabric/mmdc 时钟分频:0~7 对应 1~8(即 mmdc_podf)
[2:0] PERIPH2_CLK2_PODF periph2_clk2 时钟分频:0~7 对应 1~8

CCM_CBCMR — 总线时钟模式寄存器

位域 名称 功能
[25:23] LCDIF1_PODF lcdif1 时钟分频:0~7 对应 1~8 分频
[22:21] PRE_PERIPH2_CLK_SEL pre_periph2 源:00=PLL2, 01=PLL2_PFD2, 10=PLL2_PFD0, 11=PLL4
[20] PERIPH2_CLK2_SEL periph2_clk2 源:0=pll3_sw_clk, 1=OSC
[19:18] PRE_PERIPH_CLK_SEL pre_periph_clk 源:00=PLL2, 01=PLL2_PFD2, 10=PLL2_PFD0, 11=PLL2_PFD2/2
[13:12] PERIPH_CLK2_SEL periph_clk2 源:00=pll3_sw_clk, 01=osc_clk, 10=pll2_bypass_clk

原书代码 CCM->CBCMR |= (1 << 18) 即把 PRE_PERIPH_CLK_SEL 设为 01(PLL2_PFD2),与 bit[19:18] 一致。

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(ANATOP)基地址 = 0x020C8000

寄存器 地址 说明
CCM_ANALOG_PLL_ARMn 0x020C8000 PLL1 (ARM PLL) 控制寄存器
CCM_ANALOG_PLL_528n 0x020C8030 PLL2 (528_PLL)
CCM_ANALOG_PLL_USB1n 0x020C8010 PLL3 (USB1_PLL)
CCM_ANALOG_PLL_USB2n 0x020C8020 PLL7 (USB2_PLL)
CCM_ANALOG_PFD_480n 0x020C80F0 PLL3 PFD 控制
CCM_ANALOG_PFD_528n 0x020C8100 PLL2 PFD 控制

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 控制寄存器

寄存器分为四组(PFD0~PFD3),每组 8 个 bit;FRAC 可设置范围 12~35

位域 名称 功能
[5:0] PFD0_FRAC PLL2_PFD0 分频因子:PLL2_PFD0 = 528×18/FRAC
[6] PFD0_STABLE 只读,读此位判断 PLL2_PFD0 是否稳定
[7] PFD0_CLKGATE PFD0 输出使能:1=关闭, 0=使能
[13:8] PFD1_FRAC PLL2_PFD1 分频因子
[14] PFD1_STABLE PLL2_PFD1 稳定标志(只读)
[15] PFD1_CLKGATE PFD1 门控:1=关闭, 0=使能
[21:16] PFD2_FRAC PLL2_PFD2 分频因子
[22] PFD2_STABLE PLL2_PFD2 稳定标志(只读)
[23] PFD2_CLKGATE PFD2 门控
[29:24] PFD3_FRAC PLL2_PFD3 分频因子
[30] PFD3_STABLE PLL2_PFD3 稳定标志(只读)
[31] PFD3_CLKGATE PFD3 门控

CCM_ANALOG_PFD_480n — PLL3 PFD 控制寄存器

结构与 PFD_528n 完全相同,只是频率公式变为 PLL3_PFDx = 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 = 0;                    /* 清零 */
    reg = CCM_ANALOG->PFD_480;
    reg &= ~(0x3F3F3F3F);

    reg |= 19 << 24;  /* PLL3_PFD3 = 480×18/19 = 454.74 MHz */
    reg |= 17 << 16;  /* PLL3_PFD2 = 480×18/17 = 508.24 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 时钟 (最小 6 MHz,最大 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 需要先禁止 AHB_CLK_ROOT 输出,但原书作者没有找到
     * 关闭 AHB_CLK_ROOT 输出的寄存器,所以下面设置 AHB_PODF 的代码
     * 仅供学习参考,不能直接使用(用 #if 0 屏蔽)!
     * 内部 boot rom 已将 AHB_PODF 设置为 3 分频,不设置时
     * AHB_ROOT_CLK 也等于 396/3 = 132 MHz。
     */
#if 0
    /* 要先关闭 AHB_ROOT_CLK 输出,否则时钟设置会出错 */
    CCM->CBCDR &= ~(7 << 10);       /* CBCDR 的 AHB_PODF 清零 */
    CCM->CBCDR |= 2 << 10;          /* AHB_PODF 3分频,AHB_CLK_ROOT=132MHz */
    while (CCM->CDHIPR & (1 << 1)); /* 等待握手完成 */
#endif

    /* ========================================
     * 步骤 5:设置 IPG 时钟 (最小 3 MHz,最大 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["完成"]

4.4 实验步骤(原书 16.2~16.4 节)

硬件原理:时钟原理图分析参考 16.1.1 小节(32.768KHz 接 RTC,24MHz 作内核与外设时钟源)。

例程路径:开发板光盘 -> 1、裸机例程 -> 8_clk。本实验在上一章实验 7_key 基础上完成,直接修改 bsp_clk.c

Makefile 修改:本实验没有新增源文件,只需把变量 TARGET 改为 clk,链接脚本保持不变:

CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET        ?= clk

编译烧写

chmod 777 imxdownload            # 给予 imxdownload 可执行权限,一次即可
./imxdownload clk.bin /dev/sdd   # 烧写到 SD 卡,不能烧到 /dev/sda 或 sda1

验证现象:将 SD 卡插入开发板卡槽后复位。实验现象与 7_key 一样,但 LED 闪烁频率更快——因为 7_key 主频为 396MHz,本实验配置为 528MHz,代码执行延时函数更快,闪烁周期缩短。主频提升约 33%。

时钟修改五步法(原书 16.1.4 节):

  1. 设置 CCSRSTEP_SEL 位,令 step_clk 时钟源为 24M 晶振。
  2. 设置 CCSRPLL1_SW_CLK_SEL 位,令 pll1_sw_clk 时钟源为 step_clk=24MHz(先把主频降到 24MHz)。
  3. 设置 CCM_ANALOG_PLL_ARMn,将 pll1_main_clk(PLL1) 设为 1056MHz。
  4. 设置 CCSRPLL1_SW_CLK_SEL 位,切回 pll1_main_clk=1056MHz。
  5. 设置 CCM_CACRRARM_PODF 为 2 分频,内核主频 = 1056/2 = 528MHz。

5. 电源管理概述

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第 16 章内容(该章只讲主频和时钟配置),为扩展知识。I.MX6ULL 的低功耗模式请以《I.MX6ULL 参考手册》电源管理章节为准。

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/RTC 深度休眠
DORMANT 内核掉电保持 极低 中断/GPIO 超低功耗待机
SNVS 仅 SNVS 域运行 最低 RTC/SNVS 唤醒引脚 电池备份/RTC

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. 跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

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. 面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

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 每次修改都需要重新锁定,不适合频繁切换频率的场景。

内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第16章 主频和时钟配置实验(16.1 时钟系统详解、16.1.2 七路 PLL、16.1.5 PFD、16.1.6 总线时钟、16.3 bsp_clk.c、16.4 编译下载验证);寄存器位域地址以《I.MX6ULL/I.MX6UL 参考手册》第 18 章 CCM 为准。