简述
所使用的芯片为NXP的I.MX6U。
硬件分析
原理图如下:
分为两个来源:
- 32.768KHz晶振(I.MX6U的RTC时钟源)
- 24MHz晶振(I.MX6U内核和外设的时钟源)
流程分析
PLL时时钟源
PLL(锁相环): 锁相回路(PLL:Phase-lockedloops)是一种利用反馈控制原理实现的频率及相位的同步技术,其作用是将电路输出的时钟与其外部的参考时钟保持同步。当参考时钟的频率或相位发生改变时,锁相回路会检测到这种变化,并且通过其内部的反馈系统来调节输出频率,直到两者重新同步,这种同步又称为“锁相”。
PFD: 鉴频鉴相器(phase frequencydetector)简称PFD,可以比较两种输入信号的相位误差及频率误差。鉴频鉴相器是异步电路,最早是由四颗触发器构成。逻辑电路可以判断二个信号中,哪一个比较早出现零交越,以及哪一个信号较常出现零交。用在锁相环的应用当中,就算是频率不同,也可以达到锁相的作用。总之,是PLL的一个关键部件,PLL包括PFD。
NXP将外设时钟源分为了7组,如下图是时钟生成图:
- ARM_PLL(PLL1),此路 PLL 是供 ARM 内核使用的, ARM内核时钟就是由此PLL生成的,此PLL通过编程的方式最高可倍频到 1.3GHz。
- 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 接口等等。
- USB1_PLL(PLL3),此路 PLL 主要用于 USBPHY,此 PLL 也有四路 PFD,为:PLL3_PFD0~PLL3_PFD3, USB1_PLL 是固定的 20 倍频,因此 USB1_PLL=24MHz *20=480MHz。USB1_PLL虽然主要用于USB1PHY,但是其和四路PFD同样也可以作为其他外设的根时钟源。
- **USB2_PLL(PLL7)**给USB2PHY使用的。同样的,此路PLL固定为20倍频,因此也是480MHz。
- ENET_PLL(PLL6),此路 PLL 固定为20+5/6倍频,因此ENET_PLL=24MHz*(20+5/6)=500MHz。此路PLL用于生成网络所需的时钟,可以在此 PLL 的基础上生成 25/50/100/125MHz的网络时钟。
- VIDEO_PLL(PLL5),此路 PLL 用于显示相关的外设,比如LCD,此路PLL的倍频可以调整,PLL的输出范围在650MHz~1300MHz。此路 PLL 在最终输出的时候还可以进行分频,可选 1/2/4/8/16 分频。
- AUDIO_PLL(PLL4),此路 PLL 用于音频相关的外设,此路PLL的倍频可以调整,PLL的输出范围同样也是650MHz~1300MHz,此路 PLL 在最终输出的时候也可以进行分频,可选1/2/4 分频。
时钟树
分为三个部分:
CLOCK_SWITCHER | CLOCK ROOT GENERATOR | SYSTEM CLOCKS |
---|---|---|
7路PLL和8路PFD(见上) | 负责从7路PLL和8路PFD中选择最合适的时钟源给外设用(相当于将左右进行“搭桥”) | 芯片外设 |
这么复杂的图如何去看?
图中大致分为两种部件,一种是橘色的梯形,一种是写着"/x"的方型图标。
- 橘色的梯形是时钟源选择器,左边的连线是可以选择的时钟源。
- "/x"的方型图标,是分频器。x并不表示分频的具体频数。
内核时钟设置
配置主频
如上图中时钟数中可以得:
- 内核时钟树源来自于PLL1,假如此时PLL1为996MHz。
- 通过寄存器 CCM_CACRR 的 ARM_PODF 位对 PLL1 进行分频,可选择 1/2/4/8 分频,此处2分频,频率也就是498MHz。
- 此处并没有进行2分频。
- 此时为主频为498MHz。
如果我们要设置内核主频为528MHz,那么PLL1的时钟就要设置为1056MHz。
- 查看手册可以通过寄存器 CCM_ANALOG_PLL_ARMn来配置PLL1的频率。如图寄存器数据手册:
- ENABLE: 时钟输出使能位,此位设置为 1 使能 PLL1 输出,如果设置为 0 的话就关闭 PLL1输出。
- DIV_SELECT: 此位设置 PLL1 的输出频率,可设置范围为: 54~108, PLL1 CLK=Fin*div_seclec/2.0,Fin=24MHz。如果PLL1 要输出 1056MHz 的话, div_select 就要设置为 88。
(在修改PLL1时钟频率的时候要先将内核时钟源改为其它的时钟源,可选时钟源如下)
- pll1_sw_clk 也就是 PLL1 的最终输出频率。
- 此处是一个选择器。选择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=1056MHz,此时就要先将 pll1_sw_clk 切换到 step_clk 上。等 pll1_main_clk 调整完成以后再切换回来。
- 此处也是一个选择器,选择 step_clk 的时钟源,由寄存器 CCM_CCSR 的 STEP_SEL 位来决定 step_clk 是选择 osc_clk 还是 secondary_clk。一般选择 osc_clk,也就是 24MHz 的晶振。
寄存器CCM_CCSR结构图如下:
寄存器CCM_CCSR只用到了STEP_SEL、 PLL1_SW_CLK_SEL 这两个位。
STEP_SEL用来选择step_clk时钟源
PLL1_SW_CLK_SEL用来选择p1ll_sw_clk时钟源
- 通过CCM_CACRR 寄存器进行2分频。 结构图如下:
只用ARM_PODF位,可设置07,分别对应18分频。
以上是主要的两个过程,全部过程如下:
- 设置寄存器 CCSR 的 STEP_SEL 位,设置 step_clk 的时钟源为 24M 的晶振。
- 设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,设置 pll1_sw_clk 的时钟源为step_clk=24MHz,通过这一步我们就将 I.MX6U 的主频先设置为 24MHz,直接来自于外部的24M 晶振。
- 设置寄存器 CCM_ANALOG_PLL_ARMn,将 pll1_main_clk(PLL1)设置为 1056MHz。
- 设置寄存器 CCSR 的 PLL1_SW_CLK_SEL位,重新将pll1_sw_clk的时钟源切换回pll1_main_clk,切换回来以后的pll1_sw_clk 就等于 1056MHz。
- 最后设置寄存器 CCM_CACRR 的 ARM_PODF 为 2 分频, I.MX6U 的内核主频就为1056/2=528MHz。
PFD时钟设置
PLL2、PLL3和PLL7都是固定值,而PLL4~PLL6是针对特殊外设的。
重点是PLL2和PLL3各自4路的PFD。
NXP推荐频率为:
PLL2用到寄存器是:CCM_ANALOG_PFD_528n。寄存器如下:
该寄存器分为4组,分为PFD0~3,每组8bit。
PDF0对应的寄存器位如下:
- PFD0_FRAC: PLL2_PFD0 的分频数, PLL2_PFD0 的计算公式为 52818/PFD0_FRAC,此为可设置的范围为 12~35 。 如果 PLL2_PFD0 的频率要设置为 352MHz 的话PFD0_FRAC=52818/352=27。
- PFD0_STABLE: 此位为只读位,可以通过读取此位判断 PLL2_PFD0 是否稳定。
- PFD0_CLKGATE: PLL2_PFD0 输出使能位,为 1 的时候关闭 PLL2_PFD0 的输出,为 0 的时候使能输出。
若不能取到推荐值,尽可能接近即可。
PLL3的结构也是这样的,只是频率计算公式不一样。PLL3的计算公式为:PLL3_PFDX=480*18/PFDX_FRAC(X=0~3)
AHB、 IPG 和 PERCLK 根时钟设置
要配置AHB_CLK_ROOT 和 IPG_CLK_ROOT的时钟。可设置范围如下:
根据上表,我们将AHB_CLK_ROOT、IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 频率都调到最高。
AHB_CLK_ROOT 和 IPG_CLK_ROOT 的涉及如下图:
- 此选择器用来选择 pre_periph_clk 的时钟源,可以选择 PLL2、 PLL2_PFD2、 PLL2_PFD0和 PLL2_PFD2/2。寄存器 CCM_CBCMR 的 PRE_PERIPH_CLK_SEL 位决定选择哪一个,默认选择 PLL2_PFD2,因此 pre_periph_clk=PLL2_PFD2=396MHz。
- 此选择器用来选择 periph_clk 的时钟源,由寄存器 CCM_CBCDR的PERIPH_CLK_SEL位与PLL_bypass_en2组成的或来选择。当 CCM_CBCDR 的 PERIPH_CLK_SEL 位为 0 的时候periph_clk=pr_periph_clk=396MHz。
- 通过 CBCDR 的 AHB_PODF 位来设置 AHB_CLK_ROOT 的分频值,可以设置 1~8 分频,如果想要 AHB_CLK_ROOT=132MHz 的话就应该设置为 3 分频: 396/3=132MHz。
- 通过 CBCDR 的 IPG_PODF 位来设置 IPG_CLK_ROOT 的分频值,可以设置 1~4 分频,IPG_CLK_ROOT 时钟源是 AHB_CLK_ROOT,要想 IPG_CLK_ROOT=66MHz 的话就应该设置2 分频: 132/2=66MHz。
设置PERCLK_CLK_ROOT 时钟频率。 结构图如下:
该时钟的来源有两个:
- OSC(24MHz)
- IPG_CLK_ROOT
由寄存器CCM_CSCMR1 的 PERCLK_CLK_SEL 位来决定。如果为0的话时钟源就是IPG_CLK_ROOT,为1的话时钟源就是OSC。
可以通过寄存器CCM_CSCMR1 的 PERCLK_PODF 位来设置分频。
上述CCM_CBCDR、 CCM_CBCMR 、 CCM_CSCMR1寄存器结构如下:
CCM_CBCDR
各位说明:
- PERIPH_CLK2_PODF: periph2 时钟分频,可设置 0~7,分别对应 1~8 分频。
- **PERIPH2_CLK_SEL:**选择 peripheral2的主时钟,如果为0的话选择PLL2,如果为1的话选择periph2_clk2_clk。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
- PERIPH_CLK_SEL: peripheral主时钟选择,如果为0的话选择PLL2,如果为1的话选择periph_clk2_clock。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
- AXI_PODF: axi 时钟分频,可设置 0~7,分别对应 1~8 分频。
- AHB_PODF: ahb 时钟分频,可设置07,分别对应18分频。修改此位会引起一次与MMDC的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
- IPG_PODF: ipg 时钟分频,可设置 0~3,分别对应 1~4 分频。
- AXI_ALT_CLK_SEL: axi_alt 时钟选择,为 0 的话选择 PLL2_PFD2,如果为 1 的话选择PLL3_PFD1。
- AXI_CLK_SEL: axi 时钟源选择,为 0 的话选择 periph_clk,为 1 的话选择 axi_alt 时钟。
- FABRIC_MMDC_PODF: fabric/mmdc 时钟分频设置,可设置 0~7,分别对应 1~8 分频。
- PERIPH2_CLK2_PODF: periph2_clk2 的时钟分频,可设置 0~7,分别对应 1~8 分频。
CCM_CBCMR
各位说明:
- LCDIF1_PODF: lcdif1 的时钟分频,可设置 0~7,分别对应 1~8 分频。
- PRE_PERIPH2_CLK_SEL: pre_periph2 时钟源选择, 00 选择 PLL2, 01 选择 PLL2_PFD2,10 选择 PLL2_PFD0, 11 选择 PLL4。
- PERIPH2_CLK2_SEL: periph2_clk2 时钟源选择为 0 的时候选择 pll3_sw_clk,为 1 的时候选择 OSC。
- PRE_PERIPH_CLK_SEL: pre_periph时钟源选择,00选择PLL2,01选择PLL2_PFD2,10选择PLL2_PFD0,11选择PLL2_PFD2/2。
- PERIPH_CLK2_SEL: peripheral_clk2 时钟源选择, 00 选择 pll3_sw_clk, 01 选择 osc_clk,10 选择 pll2_bypass_clk。
CCM_CSCMR1
- PERCLK_CK_SEL: perclk 时钟源选择,为 0 的话选择 ipg clk,为 1 的话选择 osc clk
- PERCLK_PODF: perclk 的时钟分频,可设置 0~7,分别对应 1~8 分频
- 在修改如下时钟选择器或者分频器的时候会引起与 MMDC 的握手发生:
- mmdc_podf
- periph_clk_sel
- periph2_clk_sel
- arm_podf
- ahb_podf
编写代码
#include "bsp_clk.h"
/*
* 使能I.MX6U所有外设时钟
*/
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;
}
/*
* 初始化系统时钟,设置系统时钟为528Mhz,并且设置PLL2和PLL3各个PFD时钟,所有的时钟频率均按照I.MX6U官方手册推荐的值.
*/
void imx6u_clkinit(void)
{
unsigned int reg = 0;
/* 1、设置ARM内核时钟为528MHz */
if((((CCM->CCSR) >> 2) & 0x1 ) == 0) // 当前pll1_sw_clk使用的pll1_main_clk
{
CCM->CCSR &= ~(1 << 8); // 配置step_clk时钟源为24MH OSC
CCM->CCSR |= (1 << 2); // 配置pll1_sw_clk时钟源为step_clk
}
CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F); // 配置pll1_main_clk=1056MHz
CCM->CCSR &= ~(1 << 2); // 将pll_sw_clk时钟重新切换回pll1_main_clk
CCM->CACRR = 1; // ARM内核时钟为pll1_sw_clk/2=1056/2=528Mhz
/* 2、设置PLL2(SYS PLL)各个PFD */
reg = CCM_ANALOG->PFD_528;
reg &= ~(0X3F3F3F3F); // 清除原来的设置
reg |= 32<<24; // PLL2_PFD3=528*18/32=297Mhz
reg |= 24<<16; // PLL2_PFD2=528*18/24=396Mhz(DDR使用的时钟,最大400Mhz)
reg |= 16<<8; // PLL2_PFD1=528*18/16=594Mhz
reg |= 27<<0; // PLL2_PFD0=528*18/27=352Mhz
CCM_ANALOG->PFD_528=reg; // 设置PLL2_PFD0~3
/* 3、设置PLL3(USB1)各个PFD */
reg = 0; // 清零
reg = CCM_ANALOG->PFD_480;
reg &= ~(0X3F3F3F3F); // 清除原来的设置
reg |= 19<<24; // PLL3_PFD3=480*18/19=454.74Mhz
reg |= 17<<16; // PLL3_PFD2=480*18/17=508.24Mhz
reg |= 16<<8; // PLL3_PFD1=480*18/16=540Mhz
reg |= 12<<0; // PLL3_PFD0=480*18/12=720Mhz
CCM_ANALOG->PFD_480=reg; // 设置PLL3_PFD0~3
/* 4、设置AHB时钟 最小6Mhz, 最大132Mhz (boot rom自动设置好了可以不用设置)*/
CCM->CBCMR &= ~(3 << 18); // 清除设置
CCM->CBCMR |= (1 << 18); // pre_periph_clk=PLL2_PFD2=396MHz
CCM->CBCDR &= ~(1 << 25); // periph_clk=pre_periph_clk=396MHz
while(CCM->CDHIPR & (1 << 5));// 等待握手完成
#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_CLK_ROOT最小3Mhz,最大66Mhz (boot rom自动设置好了可以不用设置)*/
CCM->CBCDR &= ~(3 << 8); // CBCDR的IPG_PODF清零
CCM->CBCDR |= 1 << 8; // IPG_PODF 2分频,IPG_CLK_ROOT=66MHz
/* 6、设置PERCLK_CLK_ROOT时钟 */
CCM->CSCMR1 &= ~(1 << 6); // PERCLK_CLK_ROOT时钟源为IPG
CCM->CSCMR1 &= ~(7 << 0); // PERCLK_PODF位清零,即1分频
}
主函数:
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
int main(void)
{
int i = 0;
int keyvalue = 0;
unsigned char led_state = OFF;
unsigned char beep_state = OFF;
imx6u_clkinit(); // 初始化系统时钟
clk_enable(); // 使能所有的时钟
led_init(); // 初始化led
while(1)
{
keyvalue = key_getvalue();
if(keyvalue)
{
switch ((keyvalue))
{
case KEY0_VALUE:
beep_state = !beep_state;
beep_switch(beep_state);
break;
}
}
i++;
if(i==50)
{
i = 0;
led_state = !led_state;
led_switch(LED0, led_state);
}
delay(10);
}
return 0;
}
编译烧录
结果
led闪烁更快。