目录
[PLL(Phase Locked Loop)锁相环:](#PLL(Phase Locked Loop)锁相环:)
[二、I.MX6U 的所有外设时钟源](#二、I.MX6U 的所有外设时钟源)
[1. ARM_PLL(PLL1):](#1. ARM_PLL(PLL1):)
[2. 528_PLL(PLL2):](#2. 528_PLL(PLL2):)
[7.USB2_PLL(PLL7):](#7.USB2_PLL(PLL7):)
[CCM 与 CCGR 寄存器的作用](#CCM 与 CCGR 寄存器的作用)
一、I.MX6U时钟概念介绍
不同于51单片机,IMX有着一套复杂的时钟系统。IMX考虑到不同的外设需要的时钟频率是不一样的。IMX使用的方法是分离出多个不同的时钟出来,分别加以处理 ,产生出多路不同频率时钟以提供给不同的外设使用。这个概念被称之为时钟树 ,这是因为无论哪种Soc通常都是由一个Soc外部的晶体振荡器提供最基本的频率,之后通过Soc内部复杂的电路进行分离的。形式上很像一棵树的样子,所以被称为时钟树。在开始介绍IMX的时钟之前,我们必须先搞清楚这个概念:PLL、PFD和divider:锁相环和分频器。
PLL(Phase Locked Loop)锁相环:
**PLL是一种电路,简单来说就是用来倍频的特殊电路。**因为晶体振荡器从加工技术上来说压根做不到1GHZ这样高的频率,我们使用的开发板上使用的只是一个24MHz的晶体振荡器,通过PLL把一个低频升到较高的一个频率
PFD和分频器:
PFD :Phase Fractional Dividers,即相位分数分频器 ,我们可以理解为先倍频,再分频然后输出
分频器,顾名思义,和PLL的作用是相反的,它能够把一个高频降低成一个低频。可以把分频器理解为输入的频率需要除以一个数再输出。100MHz经过4分频之后就变成了25MHz。
二、I.MX6U 的所有外设时钟源
从我们的开发板 参考手册可以看出,我们的硬件有7路PLL和8路PFD
注意:PLL6和PLL7在这个图上没有标注出来,因为PLL6和PLL7是为特定外设服务的专用锁相环,没有像PLL2和PLL3那样分出多路PFD(分数分频器)去供给系统总线或通用外设,因此在很多概述性的框图中被省略了,这里只做简单的介绍

黄色框里面的数值是官方给的建议值,除了PLL1之外,我们就按这个建议值来配置
简要介绍一下每路的PLL和PFD:
1. ARM_PLL(PLL1):
此路 PLL 是供 ARM 内核使用的,ARM 内核时钟就是由此 PLL生成的,此 PLL 通过编程的方式最高可倍频到 1.3GHz。
2. 528_PLL(PLL2):
此路 PLL 也叫做 System_PLL,此路 PLL 是固定的 22 倍频,不可编程修改。因此,此路 PLL 时钟=24MHz * 22 = 528MHz,这也是为什么此 PLL 叫做 528_PLL 的原因。此 PLL 分出了 4 路 PFD,分别为:PLL2_PFD0~PLL2_PFD3,这 4 路 PFD 和 528_PLL共同作为其它很多外设的根时钟源。通常 528_PLL 和这 4 路 PFD 是 I.MX6U 内部系统总线的时钟源,比如内处理逻辑单元、DDR 接口、NAND/NOR 接口等等。
3.USB1_PLL(PLL3):
此路 PLL 主要用于 USBPHY,此 PLL 也有四路 PFD,为:PLL3_PFD0~PLL3_PFD3,USB1_PLL 是固定的 20 倍频,因此 USB1_PLL=24MHz *20=480MHz。USB1_PLL 虽然主要用于USB1PHY,但是其和四路PFD 同样也可以作为其他外设的根时钟源。
4.AUDIO_PLL(PLL4):
此路 PLL 用于音频相关的外设,此路 PLL 的倍频可以调整,PLL的输出范围同样也是 650MHz~1300MHz,此路 PLL 在最终输出的时候也可以进行分频,可选1/2/4 分频。
7 路 PLL,I.MX6U 的所有外设时钟源都是从这 7 路 PLL 和有些 PLL 的PFD 而来的,这些外设究竟是如何选择 PLL 或者 PFD 的?这个问题就需要参考第18章,第629~630的设备树框图。这个图看起来很复杂的样子,我们来简单分个类。
5.VIDEO_PLL(PLL5):
此路 PLL 用于显示相关的外设,比如 LCD,此路 PLL 的倍频可以调整,PLL 的输出范围在 650MHz~1300MHz。此路 PLL 在最终输出的时候还可以进行分频,可选 1/2/4/8/16 分频
6.ENET_PLL(PLL6):
此路 PLL 固定为 (20+5/6)* 倍频,因此 ENET_PLL=24MHz * (20+5/6) = 500MHz。此路 PLL 用于生成网络所需的时钟,可以在此 PLL 的基础上生成 25/50/100/125MHz的网络时钟。
7.USB2_PLL(PLL7):
此路 PLL是给 USB2PHY 使用的。同样的,此路 PLL 固定为 20 倍频,因此也是 480MHz。此PLL其实相当于是USB1 PLL的旁路输出,一般是用于检测的。
三、时钟源设置
在默认配置下 I.MX6U 工作频率为 396MHz。我们之前的程序就运行在这个频率下。但是I.MX6U 系列标准的工作频率为 528MHz,有些型号甚至可以工作到 696MHz。其它的外设时钟我们也设置到官方在图上给的建议值。
1.PLL1设置(ARM_PLL)
分为四步:
(1)切换时钟源
在修改 PLL1 时钟频率前,我们需要先将内核时钟源改为其他的时钟源 ,因为PLL1是给ARM内核提供时钟的,在切换时钟时ARM内核有可能会由于时钟不稳定导致停摆。这里,PLL1 可选择的,我们可以先把PLL1切换到另外一个时钟源上,之后设置完528MHz之后再切换回来。

根据手册知道CCSR寄存器,切换时钟源:

代码为:
//切换时钟源
CCM->CCSR &=~(1<<8);
CCM->CCSR |= (1<<2);
(2)设置二分频:
为什么要先设置二分频在修改PLL1的值呢?
因为:当你修改PLL的倍频因子(比如从默认的396MHz改到1056MHz)后,硬件需要一段时间(通常几十到几百微秒)来完成"锁定"。
在这个锁定过程中,PLL的输出频率并非从396MHz平滑地上升到1056MHz 。它可能会瞬间跳变到一个远高于目标值的频率(例如,在内部环路滤波和振荡器调整时,可能会短暂输出1.5GHz甚至更高),这个过高的频率就会直接冲进CPU,造成致命故障。
根据手册上的时钟树可以得知这个二分频寄存器的名字:CACRR[ARM_PODF]

查找手册:

配置二分频的代码为:
unsigned int tmp = CCM->CACRR;
tmp &= ~( 0x7 << 0); //后三位全部置为0
tmp |= ( 1 << 0); //设置为二分频
CCM->CACRR = tmp;
注意:这里为什么定义一个tmp是因为如果在寄存器操作,需要写入两次才行,用tmp只需要一次写入,提高了效率
(3)配置PLL1
这个PLL1的设置是通过寄存器CCM_ANALOG_PLL_ARMn来设置的:

事实上其他的几个PLL和PFD都是通过CCM_ANALOG章节中介绍的几个寄存器来设置的。
BYPASS_CLK_SRC 表示ARM_PLL的时钟选择,按照原理图,我们只能选择24M晶体振荡器了。
DIV_SELECT代表倍频因子,是个数,这个数需要算出来,公式已经给出。这里我们的Fout=1056,Fin=24需要计算的就是div_select了,结果为88。1056 = 24 * (88/ 2.0)。
代码为:
tmp = CCM_ANALOG->PLL_ARM;
tmp &= ~(0x3<< 14);
tmp |= (1<<13);
tmp &= ~(0x7f <<0);
tmp |= (88<<0); //88是根据公式算出来的:1056=24*(88/2)
CCM_ANALOG->PLL_ARM = tmp;
(4)切换回时钟源
重新将内核的时钟源切换回PLL1,切换回来以后 I.MX6U 的内核主频就等于 528MHz(1056的二分频)。
代码为:
CCM->CCSR &= ~(1<<2);
这样PLL1就配置好了
2.PLL2设置(528PLL)
PLL2又叫528PLL因为PLL2的时钟固定为528MHz。那么想把528MHz转变为图中PLL2_PFD0的352MHz,那肯定需要设置一个系数,这个系数由寄存器CCM_ANALOG_PFD_528n来设置,来看一下这个寄存器:

寄存器结构介绍:
PFDx_FRAC(分频值):控制该 PFD 输出的分数分频值。
PFDx_CLKGATE(时钟门控):控制该 PFD 的输出是否关闭(置1则关闭该 PFD 的时钟输出)。
PFDx_STABLE(稳定标志,只读):用于诊断,指示新写入的 FRAC 值是否已经生效,输出是否稳定
所以我们想要输出这样的值只需要根据公式(公式在寄存器描述图中已经画出)把对应的PFDx的分频值算出来并配置进去

PFD0:FRAC=27
PFD1:FRAC=16
PFD2:FRAC=24
PFD3:FRAC=48
代码为:
//PLL2 pfd0~3 352-27 594-16 400(396)-24 200-48
tmp = CCM_ANALOG->PFD_528;
tmp &= ~((0x3f << 0) | (0x3f << 8) | (0x3f << 16) | (0x3f << 24));
tmp |= ((27 << 0) | (16 << 8) | (24 << 16) | (48 << 24));
CCM_ANALOG->PFD_528 = tmp;
3.PLL3设置(480PLL)
PLL3的设置方法根PLL2设置方法类似,不同之处在于PLL3又被称为480PLL,因为PLL3的频率固定为480MHz
根据手册的公式(公式都是一样的,和PLL2的区别就是把528换成了480):

根据时钟树的推荐值算出分频值:

PFD0:FRAC=12
PFD1:FRAC=16
PFD2:FRAC=17
PFD3:FRAC=19
代码为:
// PLL3 pfd0~3 352-27 594-16 400(396)-24 200-48
tmp = CCM_ANALOG->PFD_480_CLR;
tmp &= ~((0x3f << 0) | (0x3f << 8) | (0x3f << 16) | (0x3f << 24));
tmp |= ((27 << 0) | (16 << 8) | (24 << 16) | (48 << 24));
CCM_ANALOG->PFD_528 = tmp;
PLL4和PLL5为后续LCD的时钟,本篇用不到就不配置了
四、外设根时钟设置
这里我们设置 AHB_CLK_ROOT 、 IPG_CLK_ROOT和PERCLK_CLK_ROOT的时钟
我们之所以要设置AHB_CLK_ROOT 、 IPG_CLK_ROOT和PERCLK_CLK_ROOT的时钟,是因为我们之后的外设实验需要使用这三个时钟 ,从时钟树图中可以看出:IPG_CLK_ROOT和PERCLK_CLK_ROOT分别给ADC和I2C还有EPIT定时器外设提供时钟:

这几个外设是我们之后要学习的重点,而这两个根时钟又是由AHB_CLK_ROOT 提供时钟的 。因此配置好这三个时钟是我们之后学习的基础保障。
这三个时钟的配置方法是多样的、灵活的。这里选择PLL2_PFD2(396MHz)作为输入时钟, 我们来分析以下整个时钟配置流程:首先查看手册找到外设的根时钟设置范围:

AHB_CLK_ROOT 最高可以设置 132MHz ,IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 最高可以设置 66MHz。那我们就将 AHB_CLK_ROOT、IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 分 别 设 置 为 132MHz 、 66MHz 、66MHz。
1.AHB_CLK_ROOT配置
IPG_CLK_ROOT和PERCLK_CLK_ROOT都是由这个时钟提供的,要先把它配置好

从手册的时钟树图中可以看出,我们要配置AHB_CLK_ROOT根时钟,PLL2_PFD2要经过一个CBCMR[PRE_PERIPH_CLK_SEL]选择器 和cg(时钟开关,不用管,后续会一起打开)一个CBCDR[PERIPH_CLK_SEL]选择器 一个CBCDR[AHB_PODF]分频器
查找手册找到CBCMR寄存器:

选择PLL2_PFD2时钟源:
tmp = CCM->CBCMR;
tmp &= ~(0x3 << 18);
tmp |= (0x2 << 18);
CCM->CBCMR = tmp;
查找手册找到CBCDR寄存器的结构介绍:
图中的分频器也在这个寄存器,我们这里一起配置


选择PLL2和3分频,代码为:
//AHB_CLK_ROOT 132M
tmp = CCM->CBCDR;
tmp &= ~(0x7 << 10);
tmp |= (0x2 << 10); //3分频
tmp &= ~(0x1 << 25);//选择时钟源
CCM->CBCDR = tmp;
这样AHB_CLK_ROOT根时钟就配置好了
2.IPG_CLK_ROOT
有手册的时钟树可以看出,要配置IPG_CLK_ROOT根时钟,AHB_CLK_ROOT时钟要经过一个分频器和时钟开关

不难看出这个分频器就是刚才我们配置的CBCDR寄存器里面的IPG_PODF:

为了方便,我们把这个配置一起放到CBCDR寄存器中,于是上面的代码可以改写成:
//AHB_CLK_ROOT 132M
tmp = CCM->CBCDR;
tmp &= ~(0x7 << 10);
tmp |= (0x2 << 10);
tmp &= ~(0x1 << 25);
tmp &= ~(0x3 << 8);
tmp |= (0x1 << 8); //IPG_PODF 1/2(二分频) 66M
CCM->CBCDR = tmp;
3.PERCLK_CLK_ROOT
由手册的时钟树可以看出,要配置PERCLK_CLK_ROOT时钟**,**AHB_CLK_ROOT时钟要经过一个分频器和一个选择器和一个CSCMR[PERCLK_PODF]时钟开关

这里第一个分频器我们在配置IPG_CLK_ROOT时钟时已经配好了,这时候只需要选择这一路时钟,再把后续的分频器配置成一分频即可。
问题是图中的选择没有写名字,这种情况我们怎么找呢,这里直接搜索这个根时钟即可,找关键词SEL(选择的意思):

注意:如果找到后还不确定的话,看它的描述,这里置1是OSC(外部晶体振荡器或外部时钟输入。它是芯片所有时钟的源头),置为0是IPG,符合图片的描述。所以一定是这个选择器
这里也把后续的分频器也找到了,它默认就是一分频,所以就不配置了,只配置选择器,代码为:
//PERCLK_CLK_ROOT 66M
CCM->CSCMR1 &= ~(0x7f << 0);
这样我们需要的根时钟也配好了,后续文章的实验就要用到这些时钟,比如下一篇要讲的定时器。
五、整体代码与补充
整体代码:
#include "clk.h"
#include "MCIMX6Y2.h"
#include "core_ca7.h"
#include "fsl_iomuxc.h"
static void clk_gate_allenable(void)
{
CCM->CCGR0 = 0xffffffff;
CCM->CCGR1 = 0xffffffff;
CCM->CCGR2 = 0xffffffff;
CCM->CCGR3 = 0xffffffff;
CCM->CCGR4 = 0xffffffff;
CCM->CCGR5 = 0xffffffff;
CCM->CCGR6 = 0xffffffff;
}
void clk_init(void)
{
// ARM clk 528
CCM->CCSR &= ~(1 << 8);
CCM->CCSR |= (1 << 2);
unsigned int tmp = CCM->CACRR;
tmp &= ~(0x7 << 0);
tmp |= (0x1 << 0);
CCM->CACRR = tmp;
tmp = CCM_ANALOG->PLL_ARM;
tmp &= ~(0x3 << 14);
tmp |= (0x1 << 13);
tmp &= ~(0x7f << 0);
tmp |= (88 << 0);
CCM_ANALOG->PLL_ARM = tmp;
CCM->CCSR &= ~(1 << 2); // ARM PLL 528M
// PLL2 pfd0~3 352-27 594-16 400(396)-24 200-48
tmp = CCM_ANALOG->PFD_528;
tmp &= ~((0x3f << 0) | (0x3f << 8) | (0x3f << 16) | (0x3f << 24));
tmp |= ((27 << 0) | (16 << 8) | (24 << 16) | (48 << 24));
CCM_ANALOG->PFD_528 = tmp;
// PLL33 pfd0~3 720-12 540-16 508.2-17 454.7-19
tmp = CCM_ANALOG->PFD_480;
tmp &= ~((0x3f << 0) | (0x3f << 8) | (0x3f << 16) | (0x3f << 24));
tmp |= ((12 << 0) | (16 << 8) | (17 << 16) | (19 << 24));
CCM_ANALOG->PFD_480 = tmp;
// AHB_CLK_ROOT 132M
tmp = CCM->CBCMR;
tmp &= ~(0x3 << 18);
tmp |= (0x2 << 18);
CCM->CBCMR = tmp;
tmp = CCM->CBCDR;
tmp &= ~(0x7 << 10);
tmp |= (0x2 << 10);
tmp &= ~(0x1 << 25);
tmp &= ~(0x3 << 8);
tmp |= (0x1 << 8); // IPG_PODF 1/2 66M
CCM->CBCDR = tmp;
// PERCLK_CLK_ROOT 66M
CCM->CSCMR1 &= ~(0x7f << 0);
clk_gate_allenable();
}
补充:
为了方便,这直接把全部时钟都使能了
CCM 与 CCGR 寄存器的作用
在 i.MX6ULL 中,CCM(Clock Controller Module,时钟控制模块) 负责管理所有外设的时钟供给。
其中 CCGR(Clock Gate Control Register,时钟门控寄存器) 是一组寄存器(CCGR0~CCGR6),专门用于控制各个外设模块的时钟是否被"门控"(即是否使能)。
-
每个 CCGR 寄存器是 32 位 ,通常每 2 位 控制一个外设模块的时钟。
-
2 位的编码意义(以 i.MX6ULL 参考手册为准)为:
-
00:时钟关闭(模块不工作) -
01:时钟仅在模块运行时开启(由硬件自动控制) -
10:时钟始终开启(忽略运行状态) -
11:时钟始终开启(和10功能相同,有的版本定义为"always on")
-
优点:
-
简单粗暴,不需要查每个外设对应的具体时钟位。
-
在裸机开发初期,可以避免因为某个外设时钟没开而导致功能异常,便于快速验证硬件。
缺点:
-
功耗大增:所有外设时钟一直运行,即使那些根本没使用的外设也在耗电。
-
不够规范:实际产品中应该只打开用到的外设时钟,以降低功耗。