--- 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 倍频而来: ```mermaid graph TD OSC["24 MHz 晶振"] --> PLL1["PLL1 (ARM_PLL)
可编程 54~108 倍频
最高 1.3 GHz"] OSC --> PLL2["PLL2 (528_PLL / System_PLL)
固定 22 倍频 = 528 MHz"] OSC --> PLL3["PLL3 (USB1_PLL)
固定 20 倍频 = 480 MHz"] OSC --> PLL5["PLL5 (VIDEO_PLL)
可编程 650~1300 MHz"] OSC --> PLL4["PLL4 (AUDIO_PLL)
可编程 650~1300 MHz"] OSC --> PLL6["PLL6 (ENET_PLL)
固定 20+5/6 倍频 = 500 MHz"] OSC --> PLL7["PLL7 (USB2_PLL)
固定 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 的时钟树分为三部分: ```mermaid 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->CSCMR2` 的 `ESAI_CLK_SEL` 位,从 PLL4/PLL5/PLL3_PFD2/pll3_sw_clk 中选一路 2. **前级分频** — 寄存器 `CCM_CS1CDR` 的 `ESAI_CLK_PRED` 位,1~8 分频 3. **二级分频** — 寄存器 `CCM_CS1CDR` 的 `ESAI_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。 ```c /* 使能所有外设时钟 (开发阶段) */ 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_podf` 和 `ahb_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 系统时钟初始化函数 ```c 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 主函数调用 ```c 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 时钟配置流程总结 ```mermaid flowchart TD A["开始"] --> B["切换 pll1_sw_clk → step_clk (24MHz)"] B --> C["设置 PLL1 = 1056 MHz
CCM_ANALOG_PLL_ARMn"] C --> D["切回 pll1_sw_clk = pll1_main_clk"] D --> E["设置 ARM_PODF = 2
内核 = 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`,链接脚本保持不变: ```makefile CROSS_COMPILE ?= arm-linux-gnueabihf- TARGET ?= clk ``` **编译烧写**: ```bash 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. 设置 `CCSR` 的 `STEP_SEL` 位,令 step_clk 时钟源为 24M 晶振。 2. 设置 `CCSR` 的 `PLL1_SW_CLK_SEL` 位,令 pll1_sw_clk 时钟源为 step_clk=24MHz(先把主频降到 24MHz)。 3. 设置 `CCM_ANALOG_PLL_ARMn`,将 pll1_main_clk(PLL1) 设为 1056MHz。 4. 设置 `CCSR` 的 `PLL1_SW_CLK_SEL` 位,切回 pll1_main_clk=1056MHz。 5. 设置 `CCM_CACRR` 的 `ARM_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 频率和电压: ```mermaid graph LR A["应用需求"] --> B{"负载检测"} B -->|"高负载"| C["提高频率
增加电压"] B -->|"低负载"| D["降低频率
降低电压"] C --> E["PLL1 配置
CCM_ANALOG_PLL_ARMn"] D --> E E --> F["电压调节器
外部 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 为准。