八.主频及时钟配置
我们前面所有的试验,都是使用默认配置下单I.MX6U的默认配置,在默认配置下单工作频率是396MHz,但是I.MX6U系列的标准工作频率是528MHz,下面我们就来学习一下如何配置I.MX6U的系统时钟和其他外设时钟,使其工作频率为528MHz,其他外设时钟源都工作在恩智浦推荐的频率。
I.MX6U时钟系统简介
开篇讲过,I.MX6UL的系统主频为528MHz,但是默认情况下bootrom会将其工作频率设置为396MHz,为了设备能达到最大性能,要把主频设置到528MHz,其他外设也要设置到NXP的推荐参数。芯片参考手册第10章时钟电源管理和第18章时钟控制模型有详细的介绍
系统时钟来源
查一下核心板原理图,手酸关键字Hz,可以发现有两个时钟源:32.768KHz的Y1和24MHz的Y2。32.768KHz刚好对应32768,也就是2^15,经过15分频刚好得到一个1Hz的信号,用作RTC时钟信号,也就是图上的RTC_XTALI和RTC_XTALO。
另一组时钟是24MHz的Y2,接入XTALI和XTALO。这组时钟是I.MX6UL的内核和其他外设的时钟源,是我们要重点分析的。
7路PLL时钟源
I.MX6U的外设有很多,不同的外设需要用到不同的时钟源,NXP将这些外设根据不同的时钟源需求进行了分组,一共分了7组。这些时钟源是从24MHz到晶振通过7组PLL倍频出来的。根据参考手册第18章CCM上所述,这7组PLL是这么分配的
- PLL1 - ARM PLL (typical functional frequency )
- PLL2 - System PLL (functional frequency 528 MHz)
- PLL3 - USB1 PLL (functional frequency 480 MHz)
- PLL4 - Audio PLL
- PLL5 - Video PLL
- PLL6 - ENET PLL
- PLL7 - USB2 PLL (functional frequency 480 MHz)
根据描述,可以发现这7组PLL刚好是对于不同功能外设做的分组,当我们需要用到音频外设,可以只对控制音频的PLL进行初始化就可以了!具体功能 如下:
PLL1(ARM_PLL)
ARM内核使用,ARM内核时钟由此PLL生成,该PLL通过编程的方式可以倍频到1.3GHz。
PLL2(SYSTEM_PLL)
固定的22倍频,无法通过程序修改。此路时钟为24MHz*22=528MHz,所以这路PLL也被叫做528_PLL。虽然不能调整,但是PLL2还分出来4路PFD(Phase Fractional Dividers相位分频器):PLL2_PFD0~3,这4路PFD又提供了4组不同的主频供给其他频率的外设时钟。通常来说这四路PFD是I.MX6U内部系统总线的时钟源,晕公寓内部处理裸机单元、DDR接口、NAND接口等外设。
PLL3(USB1_PLL)
主要用于USB1PHY,固定20倍频,也就是480MHz,也提供了4组不同的PFD作为其他外设的时钟源。
PLL4(AUDIO_PLL)
用作音频外设的时钟,PLL倍频可调整,输出范围为650~1300MHz。并且输出时候可以进行进行1/2/4分频。
PLL5(VIDEO_PLL)
显示相关外设使用,PLL倍频可调整,输出范围为650~1300MHz。并且输出时候可以进行进行1/2/4/8/16分频。
PLL6(ENET_PLL)
网络使用,PLL固定为20+5/6倍频,;也就是500MHz,输出时刻进行分频后输出25/50/100/125MHz时钟。
PLL7(USB2_PLL)
和USB1_PLL一样,提供给USB2PHY使用,也是固定20倍频,即480MHz。
时钟树
这里引入一个时钟树的概念:所有的时钟都是基于24MHz,然后每个PLL就像树枝一样长在主干上,然后PDF就是主支上的分支,各个外设就像果实一样挂在每个PDF上,整个结构就像一个树一样
在参考手册第18章CCM上专门给了个这个数的图
并且从这个图中我们可以了解后面对各个主频进行配置时候需要改变哪个寄存器的值。图的左边是各个时钟源,中间是各种选择器(黄底字SEL为选择器,红色字PODF为分频器对应寄存器及位说明),右边对应各种外设。如果我们要使用那种外设,对应设置其时钟源,就从右向左设置每个选择器和分频器对应的寄存器就可以了。
举个简单的例子,从图里面截取一段,假如我们要使用SPDIF接口可以从左侧的选择器看出来可以选择4中主频(黑、绿、蓝、粉四个线对应了PLL4,PLL3_PFD2,PLL5和PLL3)以及前后两个分频器。我们只需在手册里查上面标注的寄存器的配置信息就可以
CDCDR[SPDIF0_CLK_SEL]
CDCDR[SPDIF0_CLK_PRED]
CDCDR[SPDIF0_CLK_PODR]
通过查询手册,我们可以得到寄存器CDCDR的定义
CDCDR里的三个配置决定了SPDIF的时钟源,我们只需按要求配置就可以了!
各个PLL时钟配置
系统主频配置
从时钟树上,我们可以看出来,系统主频是通过PLL1经过分频(实际工作只有第一个分频器,后面的domain divider分频其没有实际作用,不知道为什么)以后提供的,如果我们需要按要求将系统主频设置到528MHz,可以将PLL1设置到建议的996MHz然后把第一个分频器设置为2分频。就可以得到528MHz的时钟源。
PLL1的流程可以参考下图(18.5.1.5.1Clock Switcher截取)
最右边的Pll1_sw_clk相当于输出值时钟树的PLL1处,如果想要PLL1有996MHz的频率,就要下面几个参数
- PLL1设置位996MHz
- CCSR:pll1_sw_clk_sel的值为0,即GLITCHLESS选择器选择输出为pll1_main_clk。
这里要举个不太恰当的例子,如果我们要修改系统的主频,想起来以前陪同学去摩托罗拉以前修手机,我们把要修的手机给售后(相当于修改主频),售后是要给了我们一个临时手机供我们使用(这里要给系统设置个备份的时钟源),等手机修好了,把备用手机换回去拿到修好的手机。这个备份的时钟源就是VLITCHLESS MUX选择的step_clk。
整个流程如下:
切换备用时钟源:CCSR:step_sel置0,使step_clk为osc_clk=24MHzCCSR: pll1_sw_clk_sel置1,使pll1_sw_clk = step_clk=24MHz
修改主频及时钟树里的分频器,主频修改遵从下面的公式
其中DIV_SEL是我们要设置CCM_ANALOG_PLL_ARM寄存器的参数bit[6:0]。
按照上面的公式计算,Freq就是我们的OSC=24MHz,输入值需要1056,那么DIV_SEL = 1056*2/24=88,就是说CCM_ANALOG_PLL_ARM[DIV_SEL]值就是88。
要注意范围这里有个点要注意,NXP推荐的内核时钟是528MHz,但是可以超频到700MHz(实际值为696),如果要696MHz工作时,如果后面的分频器还是2分频,PLL输出就要到2*696=1392,DIV_SEL=1392*2/24=116就会超出给的设定范围(54-108)。想要把主频设置在696时就把后面CACRR分频器设置为1分频,PLL输出定为696,DIV_SEL值就是696*2/24=58,刚好在范围内。
设置完成后要有个使能要开启bit[13]
切换至主时钟源:修改CCSR:step_sel及CCSR: pll1_sw_clk_sel,使pll_sw_clk切换置pll1_main_clk
PFD配置
在修改完主频后,我们需要对各个PFD进行配置,具体参数可以按照时钟树上的值来设置。
PFD | NXP推荐频率 |
PLL2_PFD0 | 352MHz |
PLL2_PFD1 | 594MHz |
PLL2_PFD2 | 400MHz(实际为396MHz) |
PLL2_PFD3 | 297MHz |
PLL3_PFD0 | 720MHz |
PLL3_PFD1 | 540MHz |
PLL3_PFD2 | 508.2MHz |
PLL3_PFD3 | 454.7MHz |
PFD的设置是由两组寄存器决定的:CCM_ANALOG_PFD_480n和CCM_ANALOG_PFD_528n,480和528对应的就是PLL3和PLL2的主频,寄存器数据结构是一样的,我们这里用CCM_ANALOG_PFD_480n来讲解。
先看下寄存器的数据结构
寄存器被分了4组,每组8个bit,
每个PFD的设置方法都是一样的,以PFD0为例:
[7]PFD0_CLKGATE为使能位,为1的时候关闭PLL3_PFD0输出
[6]PFD0_STABLE 只读位,通过该bit可以判定PFD0是否稳定输出
[5:0]PFD0_FRAC分频设置,PFD0的输出频率=480*18/FRAC,如果我们西药将PLL3_PFD0输出设置为720MHz,FRAC = (480*18)/720=12,该组数据设置为12即可。这个数值计算完可以对照表格里的范围,应该在12-35之间。
其他时钟源设置
除了上述时钟源的设置,我们常用的外设(i2c,串口,看门狗什么的)还牵扯到两个时钟源:PERCLK_CLK_ROOT和IPG_CLK_ROOT。而由时钟树可以发现,这两个时钟源还涉及到另外一组时钟源:AHB_CLK_ROOT。所以要对这三个时钟源进行配置。
AHB_CLK_ROOT配置
根据NXP给的参数,AHB_CLK_ROOT的频率应该在6-132之间,我们设置为132MHz,
从时钟树看下AHB_CLK_ROOT给定的流程(加粗的线描述)
流程如下:
一般情况我们选择PLL2_PFD2作为输入(CBCMR[PRE_PERIPH_CLK_SEL],bit[19:18]不再截图了,值为01),
设置选择器CBCDR[PERIPH_CLK2_PODF] bit[25]=0,时钟频率为396MHz。修改时要等待CCDR和CDHIPR寄存器握手信号完成(后面的代码中参考教程中,并没有等CCDR的信号)
设置分频器CBCDR[AHB_PODF]bit[12:10],使AHB_CLK_ROOT满足132MHz,分频器值应为3分频,对应bit为010,这个设置完成后,按照手册上所述,要检查一个CDHIPR寄存器的握手信号(这个步骤会有问题,后面代码里会说明)
对应的CDHIPR寄存器
要在这里等待一下,
上面的流程参考手册有个图可以参考一下
IPG_CLK_ROOT设置
按照上面的流程图,在设置完AHB_CLK_ROOT后,在设置个CBCDR的分频器就可以设置好IPG_CLK_ROOT。按照需求我们要把这个时钟设置为66MHz,也就是2分频。
PERCLK_CLK_ROOT设置
看下时钟树
PERCLK_CLK_ROOT是经过前面的选择器可以选择使用24MHz的OSC也可以使用IPG_CLK_ROOT,通过CSCMR1[PERIPH_CLK_SEL](bit[6])选择(按照图中CBCMR好像少了个1,但从寄存器说明上应该是这个,从寄存器说明也应该是这个寄存器,并且后面的分频器也是这个寄存器控制的)。
代码结构
整个时钟初始化的函数就定义在bsp/clk/bsp_clk.c里,在头文件里声明好了以后可以直接在main.c里调用
#include "bsp_clk.h" 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; } /*时钟初始化*/ void imx6u_clkinit(void) { unsigned int reg = 0; //过程变量,用来周转参数 /*-------------------------主频设置-------------------------*/ /*初始化主频为528MHz*/ if((CCM->CCSR >> 2) &0x1 ==0) //确认当前时钟使用PLL1并切换至step_clk { CCM->CCSR &= ~(1<<8); //CCSR[bit8]清零,切换置step_clk = osc_clk = 24MHz CCM->CCSR |= 1<<2; //CCSR[2]:pll1_sw_clk_sel置1,PLL1 = step_clk } /* 设置pll1_main_clk=1056MHz */ CCM_ANALOG->PLL_ARM = ((88<<0) & 0x7f) | (1<<13); //CCM_ANALOG_PLL_ARM[DIV_SELECT](bit[6:0])=88,PLL为1056MHz \ 和0x7f进行与运算是比较保险。\ bit[13]置1,使能时钟输出 CCM->CACRR = 1; //设置CACRR[ARM_PODF]为2分频,使主频=PLL1/2 = 528MHz CCM->CCSR &= ~(1<<2); //切换至设置主时钟使PLL1_sw_clk = PLL1_main_clk=1056 /*-----------------------主频设置完毕-----------------------*/ /*-------------------------PFD设置-------------------------*/ /*设置PLL2的4路PFD,PLL2主频为528,计算公式为 主频=528*18/FRAC,即FRAC = 528*18/主频 先清零每个PFD对应的FRAC,再按下面要求设置 PLL2_FPD3=297MHz FRAC=32 PLL2_PFD2=396MHz FRAC=24 PLL2_PFD1=594MHz FRAC=16 PLL2_PFD0=352MHz FRAC=27 */ reg = CCM_ANALOG->PFD_528; //获取寄存器值传递给变量reg reg &= 0x3F3F3F; //清除原有的值 /* 按计算结果设置参数 */ reg |= 32<<24; reg |= 24<<16; reg |= 16<<8; reg |= 27<<0; CCM_ANALOG->PFD_528 = reg; //设置寄存器 /*设置PLL3的4路PFD,PLL3主频为420,计算公式为 主频=420*18/FRAC,即FRAC = 420*18/主频 先清零每个PFD对应的FRAC,再按下面要求设置 PLL3_FPD3=454.7MHz FRAC=19 PLL3_PFD2=508.2MHz FRAC=17 PLL2_PFD1=540MHz FRAC=16 PLL2_PFD0=720MHz FRAC=12 */ reg = CCM_ANALOG->PFD_480; reg &= 0x3F3F3F; //将寄存器清零 reg |= 19<<24; reg |= 17<<16; reg |= 16<<8; reg |= 12<<0; CCM_ANALOG->PFD_480 =reg; /*-----------------------PFD设置完毕-----------------------*/ /*-------------------------AHB设置-------------------------*/ /*设置AHB_CLK_ROOT=132MHz*/ CCM->CBCMR &= ~(3<<18); //CBCMR[pre_periph_clk_sel](bit[19:18])清零 CCM->CBCMR |= (1<<18); //CBCMR[pre_periph_clk_sel](bit[19:18])设置为1,选用PLL2_PDF2=396MHz CCM->CBCDR &= ~(1<<25); //CBCDR[periph_clk_sel](bit[25])设置为0,选用PLL2_PDF2。 while(CCM->CDHIPR & (1<<5));//等待握手信号 #if 0 /*这里需要关闭AHB_CLK_ROOT输出,否则会出错\ 但是没找到对应的寄存器,所以这段代如果不屏蔽掉码会出错\ 在内部bootrom中将AHB_PODF设置为3分频,即使我们不设置AHB_PODF,AHB_CLK_ROOT也是132MHz */ CCM->CBCDR &= ~(7<<10); //CBCDR[AHB_PODF](bit[12:10])清零,7=0111b,左移取反后讲指定bit位置清零 CCM->CBCDR |= 2<<10; //CBCDR[AHB_PODF](bit[12:10])数据为010,对应3分频 while(CCM->CDHIPR & (1<<1));//等待握手信号 #endif /*-------------------------AHB设置完毕-------------------------*/ /*--------------------------IPG设置--------------------------*/ /*IPG_CLK_ROOT主频设置为66MHz*/ CCM->CBCDR &= ~(3<<8); //CBCDR[ipg_podf](9:8)清零 CCM->CBCDR |= 1<<8; //CBCDR[ipg_podf](9:8)设置1,对应为2分频 /*-------------------------IPG设置完毕------------------------*/ /*--------------------------PERCLK设置--------------------------*/ /*PERCLK_CLK_ROOT主频设置为66MHz*/ CCM->CSCMR1 &= ~(1<<6); //CSCMR1[PERIPH_CLK_SEL](bit[6])置零,选择ipg时钟源 CCM->CSCMR1 &= ~(7<<0); //CSCMR1[PERIPH_PODF](bit[5:0])设置为000,对应1分频 /*-------------------------IPG设置完毕------------------------*/ }
程序里有一段注释掉了,原因就是备注里说的!但是整体是不影响使用的。
上面就是I.MX6U的时钟源管理,这个Soc仅仅比STM32高级一点点的,所以整个时钟树也比较简单,但是后面更高级的架构整个原理是相通的,理解了这个芯片的使用,也就知道了其他时钟源管理的流程了。