一. I.MX6ULL时钟系统详解
I.MX6ULL 的系统主频为 528MHz,有些型号可以跑到 696MHz,但是默认情况下内部 boot r om会将 I.MX6ULL 的主频设置为 396MHz。在使用 I.MX6ULL 的时候肯定是要发挥它的最大性能,那么主频肯定要设置到 528MHz(其它型号可以设置更高,比如 696MHz),其它的外设时钟也要设置到 NXP 推荐的值,如下表所示。
1.1 系统时钟的来源
从图中可以看出 I.MX6ULL 开发板的系统时钟来源于两部分:32.768KHz 和 2 4MHz 的晶振,其中32.768KHz 晶振是 I.MX6ULL 的 RTC 时钟源(实时时钟),24MHz 晶振是 I.MX6ULL 内核和其它外设的时钟源,也是重点
1.2 七路 PLL 时钟源
I.MX6ULL 的外设有很多,不同的外设时钟源不同,NXP 将这些外设的时钟源进行了分组, 一共有 7 组,这 7 组时钟源都是从24MHz 晶振 PLL而来的,因此也叫做 7 组 PLL
PLL:锁相环(Phase-Locked Loop)全称:相位锁定环路,模拟高频发生电路
功能:外部只有低速晶振(i.MX6ULL 为 24MHz),PLL 可以把 24MHz 成倍放大到几百 MHz,供给 CPU、DDR、高速外设。 普通分频器只能降频,只有 PLL 能升频。
这七个PLL如下表所示:
ARM PLL PLL 1 | 作用:给 Cortex-A7 CPU 内核提供主频时钟 该锁相环从 24 MHz 的参考时钟中合成出具有低抖动的时钟。 此锁相环的时钟输出频率范围在650 MHz 至 1.3 GHz之间。 输出频率由 7 位寄存器字段CCM_ANALOG_PLL_ARM[DIV_SELECT]来选择,地址:0x020C8000 PLL 输出频率 = Fref * DIV_SEL / 2 |
System PLL (528 PLL) PLL 2 | 固定倍频,不可软件修改,输出恒定 528MHz,固定22倍频 PFD 典型输出: PFD0 = 352 MHz PFD1 = 594 MHz PFD2 = 400(396) MHz PFD3 = 297 MHz 作用:SoC 绝大多数总线、内存、通用外设时钟所使用,通常 528_PLL 和这 4 路 PFD 是 I.MX6U 内部系统总线的 时钟源,比如内处理逻辑单元、DDR 接口、NAND/NOR 接口等等 |
USB1 PLL (480 PLL) PLL 3 | 固定倍频,不可软件修改,输出恒定 480 MHz,固定倍频20倍频 PFD 典型输出: PFD0 = 720 MHz PFD1 = 540 MHz PFD2 = 508 MHz PFD3 = 454.7 MHz 作用:此路 PLL 主要用于 USBPHY,但是其和四路 PFD 同样也可以作为其他外设的根时钟源 |
PLL 4 Audio PLL | 主供给音频使用 音频接口:SPDIF, SAI1 - SAI3 输出范围:650MHz ~ 1.3GHz 输出频率可通过编程 CCM_ANALOG_PLL_AUDIO、CCM_ANALOG_PLL_VIDEO 和 CCM_ANALOG_MISC2 寄存器组中的字段来设定,其依据以下公式计算得出: PLL output frequency = Fref * (DIV_SELECT + NUM/DENOM) |
PLL 5 Video PLL | 主供给视频外设,和图像处理有关的外设 eg:RGB LCD接口 输出范围:650MHz ~ 1.3GHz 输出频率可通过编程 CCM_ANALOG_PLL_AUDIO、CCM_ANALOG_PLL_VIDEO 和 CCM_ANALOG_MISC2 寄存器组中的字段来设定,其依据以下公式计算得出: PLL output frequency = Fref * (DIV_SELECT + NUM/DENOM) |
PLL 6 Ethernet PLL | 此路 PLL 固定为 20+5/6 倍频,因此 ENET_PLL=24MHz * (20+5/6) = 500MHz。 作用:用于生成网络所需的时钟 |
PLL 7 USB2 PLL | 用来调试 |
1.3 CCM clock tree 时钟树简介
在图中一共有三部分:CLOCK_SWITCHER、CLOCK ROOT GENERATOR和SYSTEM CLOCKS。其中左边的 CLOCK_SWITCHER 就是上一小节讲解的那 7 路 PLL 和 8 路 PFD,右边的 SYSTEM CLOCKS 就是芯片外设,中间的 CLOCK ROOT GENERATOR 是 最复杂的!这一部分就像“月老”一样, 给左边的 CLOCK_SWITCHER 和右边的 SYSTEM C LOCKS 进行牵线搭桥。外设时钟源是有多路可以选择的,CLOCK ROOT GENERATOR 就负 责从 7 路 PLL 和 8 路 PFD 中选择合适的时钟源给外设使用
1.4 有关时钟配置的器件
1.4.1 PLL
锁相环:phased locked loop
作用:倍频
倍频因子:N
输出:24*N
1.4.2 Prescaler
预分频器:Prescaler
作用:分频
分频因子:M
输出:24/M
1.4.3 PFD
相位分数分频器:phased fraction divder
作用:可以倍频也可以降频
倍频,分频因子:N M
输出:24 * N / M
N /M >1:倍频
N / M<1:降频
二. 系统时钟配置
2.1 内核时钟配置
使用到的寄存器:
CCM:Clock Controller Module,时钟控制器模块的各种寄存器
CCM_ANALOG系列寄存器
2.1.1 ARM内核时钟树
ARM内核时钟树如下图:
1、内核时钟源来自于 PLL1此时 PLL1 为 996MHz。
2、通过寄存器CCM_CACRR的ARM_PODF位对PLL1进行分频,可选择 1/2/4等分频,假如选择 2 分频,那么经过分频以后的时钟频率是 996/2=498MHz,也就是IMX6ULL的主频
3、设置内核主频为 528MHz,那么 PLL1 可以设置为 1056MHz,寄存器CCM_CACRR的ARM_PODF位设置为 2 分频即可。同理,如果要将主频设 置为 696MHz,那么 PLL1 就可以设置为 696MHz,CCM_CACRR的ARM_PODF设置为 1 分 频即可。接下来详细的看一下CCM_CACRR和CCM_ANALOG_PLL_ARMn这两个寄存器
4、寄存器CCM_CACRR(CCM ARM Clock Root Register) (内核根时钟寄存器)结构如下:
| ARM_PODF | 000 :一分频 001 :二分频 010 :三分频 011 :四分频 100 :五分频 101 :六分频 110 :七分频 111 :八分频 |
5、寄存器CCM_ANALOG_PLL_ARMn(ARM 锁相环控制寄存器)
| BYPASS_CLK_SRC | 0:选择24MHz晶振作为时钟源 |
| ENABLE | 1:使能 0:禁止 |
| DIV_SELECT | 此位设置 PLL1 的输出频率, 可设置范围为:54~108,可得出PLL1的范围:648 - 1296MHz 公式:PLL1 CLK = Fin * div_seclec/2.0,Fin=24MHz 如果 PLL1 要输出 1056MHz 的话,div_select 就要设置为 88 |
2.1.2 PLL1 时钟开关
配置内核前,boot rom默认配置主频为396MHz,在修改内核时钟频率的时候需要先将内核时钟源改为其他的时钟源,因为时钟频率突然的升高可能会导致内核工作状态死机等异常状态
修改过程如下图所示:
1、pll1_sw_clk也就是 PLL1 的最终输出频率
2、左边是一个选择器,选择pll1_sw_clk的时钟源,由寄存器CCM_CCSR的PLL1_SW_ CLK_SEL位决定pll1_sw_clk是选择pll1_main_clk还是step_clk。正常情况下应该选择pll1_main_clk,但是如果要对pll1_main_clk(PLL1)的频率进行调整的话,比如要设置PLL1=1056 MHz,此时就要先将pll1_sw_clk切换到step_clk上。等pll1_main_clk调整完成以后再切换回来
3、再下面也是一个选择器,选择step_clk的时钟源,由寄存器CCM_CCSR的STEP_SEL位来决定step_clk是选择osc_clk还是secondary_clk。一般选择osc_clk,也就是24MHz的晶振
4、寄存器CCM_CCSR(CCM Clock Switcher register)---时钟切换寄存器
STEP_SEL | 作用:选择step_clk的时钟源 0 :选择 osc_clk (24M) |
| PLL1_SW_CLK_SEL | 作用:选择pll1_sw_clk的时钟源 0:选择pll1_main_clk 1:选择step_clk |
2.1.3 配置内核时钟示例代码
/配置 ARM 时钟 CCM->CCSR &= ~(1 << 8); /选择 step_clk = osc_clk = 24MHz CCM->CCSR |= (1 << 2); /选择 pll1_sw_clk = step_clk = 24MHz t = CCM_ANALOG->PLL_ARM; t &= ~(3 << 14); /选择24MHz做为时钟源 t |= (1 << 13); /使能时钟 t &= ~(0x7F << 0); /先将低七位清零 t |= (88 << 0); /DIV_SELECT = 88 CCM_ANALOG->PLL_ARM = t; CCM->CACRR |= (1 << 0); /设置PLL1输出二分频 CCM->CCSR &= ~(1 << 2); /选择 pll1_sw_clk = pll1_main_clk2.2 配置PLL2与PLL3的8个PFD
设置好主频以后还需要设置好其他的 PLL 和 PFD 时钟,PLL1 上一节已经设置了,PLL2、PLL3 和 PLL7固定为528MHz、480MHz 和 480MHz,PLL4~PLL6 都是针对特殊外设的,用到的时候再设置。因此,接下来重点就是设置 PLL2 和 PLL3 的各自 4 路 PFD,NXP 推荐的这 8 路 PFD 频率如图所示:
这两个要通过寄存器CCM_ANALOG_PFD_528与CCM_ANALOG_PFD_480来设置
2.2.1寄存器CCM_ANALOG_PFD_528
其余位同理:PLL2_PFD1~PLL2_PFD3 设置类似
| PFD0_FRAC | PLL2_PFD0 的分频数, PLL2_PFD0 的计算公式为528*18/PFD0_FRAC 范围:此位可设置的范围为12~35 如果 PLL2_PFD0 的频率要设置为 352MHz 的话PFD0_FRAC=528* 18/352=27。 如果 PLL2_PFD0=352 MHz 的话,PFD0_FRAC=27 如果 PLL2_PFD1=594MHz 的话,PFD1_FRAC=16 如果 PLL2_P FD2=396MHz 的话,PFD2_FRAC=24 如果 PLL2_PFD3=297MHz 的话,PFD3_FRAC=32 |
| PFD0_STABLE | 此位为只读位,可以通过读取此位判断 PLL2_PFD0 是否稳定 |
| PFD0_CLKGATE | PLL2_PFD0 输出使能位, 为 1 的时候关闭 PLL2_PFD0 的输出, 为 0 的时候使能输出 |
2.2.2 寄存器CCM_ANALOG_PFD_480
从图可以看出,寄存器 CCM_ANALOG_PFD_480n 和 CCM_ANALOG_PFD_528n 的结构是一模一样的,只是一个是 PLL2 的,一个是 PLL3 的。寄存器位的含义也是一样的,只是频率计算公式不同,
比如 PLL3_PFDX=480*18/PFDX_FRAC(X=0~3)
如果 PLL3_PFD0=720 MHz 的话,PFD0_FRAC=12;
如果 PLL3_PFD1=540MHz 的话,PFD1_FRAC=16;
如果 PLL3_P FD2=508.2MHz 的话,PFD2_FRAC=17;
如果 PLL3_PFD3=454.7MHz 的话,PFD3_FRAC=19
2.2.3 示例代码
/配置 PLL2 的四路PFD:PFD0 PFD1 PFD2 PFD3 t = CCM_ANALOG->PFD_528; /配置PLL2的四路PFD输出指定频率时钟并且使能时钟 t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24)); t &= ~((1 << 7) | (1 << 15) | (1 << 23) | (1 << 31)); /启用分数分频器 t |= ((27 << 0) | (16 << 8) | (24 << 16) | (32 << 24)); CCM_ANALOG->PFD_528 = t; /配置 PLL3 的四路PFD:PFD0 PFD1 PFD2 PFD3 t = CCM_ANALOG->PFD_480; /配置PLL3的四路PFD输出指定频率时钟并且使能时钟 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;2.3 配置三个根时钟
这一节配置三个根时钟,分别是:
AHB_CLK_ROOT、PERCLK_CLK_ROOT、IPG_CLK_ROOT
由时钟树得知,很多外设如:EPIT、I2C、ADC、WDOG等,时钟都来自这三个根时钟,故要对这三个根时钟进行配置
IMX6ULL外设根时钟可设置范围如图所示:
又图可知:AHB_CLK_ROOT最高可以设置132MHz,IPG_CLK_ROOT和PERCLK_CLK_ROOT最高可以设置66MHz。那就将 AHB_CLK_ ROOT、IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 分别设置为 132MHz、66MHz、66MHz。
2.3.1 配置AHB_CLK_ROOT
配置步骤:
第一步:
此选择器用来选择 pre_periph_clk 的时钟源,可以选择PLL2、PLL2_PFD2、PLL2_PF D0 和 PLL2_PFD2/2。寄存器 CCM_CBCMR 的 PRE_PERIPH_CLK_SEL位决定选择哪一个,默认选择 PLL2_PFD2,因此 pre_periph_clk=PLL2_PFD2=396MHz
寄存器 CCM_CBCMR:
| PRE_PERIPH_CLK_SEL(19 - 18) | 作用:pre_periph 时钟源选择 00 :选择 PLL2 --- 528MHz 01 :选择 PLL2_PFD2 --- 396MHz 10 :选择 PLL2_PFD0 --- 352MHz 11 :选择 PLL2_PFD2/2 --- 396/2MHz |
第二步:
由寄存器 CCM_CBCDR的PERIPH_CLK_SEL来选择
| IPG_PODF(9 - 8) | IPG时钟分频 00 :一分频 01 :二分频 |
| AHB_PODF | AHB 时钟分频 000:1分频 |
| PERIPH_CLK_SEL | 0 :PLL2 (pll2_main_clk) 1 :derive clock from periph_clk2_clk clock source |
第三步:
设置3分频即可,也是使用寄存器 CCM_CBCDR
2.3.2 配置PERCLK_CLK_ROOT与IPG_CLK_ROOT
注意这里要使用寄存器CBCDR[IPG_PODF]来设置二分频,132/2=66,才能得到想要的频率
寄存器CCM_CSCMR1
| PERCLK_PODF(低5位) | 000000 divide by 1 000101 divide by 6 |
| PERCLK_CLK_SEL(bit 6) | 0 :选择 ipg clk 1 :选择 osc clk |
2.3.3 示例代码
/配置三个根时钟 /AHB_CLK_ROOT t = CCM->CBCMR; t &= ~(3 << 18); t |= (1 << 18); /选择PFD2 CCM->CBCMR = t; t = CCM->CBCDR; t &= ~(1 << 25); /选择PLL2 t &= ~((7 << 10) | (3 << 8)); /三分频输出 t |= (2 << 10); t |= (1 << 8); /配置IPG_CLK_ROOT = 132/2 =66MHz CCM->CBCDR = t; /PERCLK_CLK_ROOT t = CCM->CSCMR1; t &= ~(1 << 6); /选择 IPG_CLK_ROOT,配置PERCLK_CLK_ROOT = IPG_CLK_ROOT t &= ~(0x3F << 0); /选择一分频,PERCLK_CLK_ROOT = IPG_CLK_ROOT = 66MHz CCM->CSCMR1 = t;三. IMX6ULL配置与使能时钟完整代码
示例代码:
#include"MCIMX6Y2.h" #include"clock.h" //打开所有时钟门 void enable_clocks(void) { CCM->CCGR0 = 0xFFFFFFFF; CCM->CCGR1 = 0xFFFFFFFF; CCM->CCGR2 = 0xFFFFFFFF; CCM->CCGR3 = 0xFFFFFFFF; CCM->CCGR4 = 0xFFFFFFFF; CCM->CCGR5 = 0xFFFFFFFF; CCM->CCGR6 = 0xFFFFFFFF; } //初始化时钟 void clock_init(void) { unsigned int t; //配置 ARM 时钟 CCM->CCSR &= ~(1 << 8); //选择 step_clk = osc_clk = 24MHz CCM->CCSR |= (1 << 2); //选择 pll1_sw_clk = step_clk = 24MHz t = CCM_ANALOG->PLL_ARM; t &= ~(3 << 14); //选择24MHz做为时钟源 t |= (1 << 13); //使能时钟 t &= ~(0x7F << 0); //先将低七位清零 t |= (88 << 0); //DIV_SELECT = 88 CCM_ANALOG->PLL_ARM = t; CCM->CACRR |= (1 << 0); //设置PLL1输出二分频 CCM->CCSR &= ~(1 << 2); //选择 pll1_sw_clk = pll1_main_clk //配置 PLL2 的四路PFD:PFD0 PFD1 PFD2 PFD3 t = CCM_ANALOG->PFD_528; //配置PLL2的四路PFD输出指定频率时钟并且使能时钟 t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24)); t &= ~((1 << 7) | (1 << 15) | (1 << 23) | (1 << 31)); //启用分数分频器 t |= ((27 << 0) | (16 << 8) | (24 << 16) | (32 << 24)); CCM_ANALOG->PFD_528 = t; //配置 PLL3 的四路PFD:PFD0 PFD1 PFD2 PFD3 t = CCM_ANALOG->PFD_480; //配置PLL3的四路PFD输出指定频率时钟并且使能时钟 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; //配置三个根时钟 //AHB_CLK_ROOT t = CCM->CBCMR; t &= ~(3 << 18); t |= (1 << 18); //选择PFD2 CCM->CBCMR = t; t = CCM->CBCDR; t &= ~(1 << 25); //选择PLL2 t &= ~((7 << 10) | (3 << 8)); //三分频输出 t |= (2 << 10); t |= (1 << 8); //配置IPG_CLK_ROOT = 132/2 =66MHz CCM->CBCDR = t; //PERCLK_CLK_ROOT t = CCM->CSCMR1; t &= ~(1 << 6); //选择 IPG_CLK_ROOT,配置PERCLK_CLK_ROOT = IPG_CLK_ROOT t &= ~(0x3F << 0); //选择一分频,PERCLK_CLK_ROOT = IPG_CLK_ROOT = 66MHz CCM->CSCMR1 = t; //使能时钟 enable_clocks(); }