I.MX6U 的系统主频为 528MHz,但是默认情况下内部 boot rom 会将 I.MX6U 的主频设置为396MHz
I.MX6U-ALPHA 开发板的系统时钟来源于两部分: 32.768KHz 和24MHz 的晶振,其中 32.768KHz 晶振是 I.MX6U 的 RTC 时钟源, 24MHz 晶振是 I.MX6U 内核和其它外设的时钟源
时钟树:
时钟树一共有三部分: CLOCK_SWITCHER、 CLOCK ROOT GENERATOR 和SYSTEM CLOCKS。CLOCK_SWITCHER 是 7 路 PLL 和8 路 PFD,SYSTEM CLOCKS 是芯片外设时钟,CLOCK ROOT GENERATOR 从 7 路PLL 和 8 路 PFD 中选择合适的时钟源经过分频给外设使用。
注:中间的每一条不同颜色的线表示来自不同的时钟源,结合左边图标注释分析,比如,黄色的标志表示选择时钟源寄存器的位设置,红色的标志表示时钟源分频系数寄存器位的设置。
为了方便生成时钟,阿尔法从24MHz晶振生出来7路PLL。PLL2和PLL3又生成4路的PDF0-3。
ARM_PLL(PLL1):供 ARM 内核使用的, ARM 内核时钟就是由此 PLL生成的,此 PLL 通过编程的方式最高可倍频到 1.3GHz
528_PLL(PLL2):也叫做 System_PLL,PLL2 是固定的 22 倍频,PLL2 =24MHz * 22 = 528MHz,PLL2又分为4路PFD: PLL2_PFD0~PLL2_PFD3。这 4 路 PFD 和 528_PLL
共同作为内部系统总线的时钟源和其它很多外设的根时钟源USB1_PLL(PLL3):主要用于 USBPHY,PLL3是固定的20倍分频,USB1_PLL=24MHz *20=480MHz
AUDIO_PLL(PLL4):用于音频相关的外设
VIDEO_PLL(PLL5):用于显示相关的外设
ENET_PLL(PLL6):用于生成网络所需的时钟,PLL6固定为20+5/6倍频, ENET_PLL=24MHz * (20+5/6)= 500MHz
USB2_PLL(PLL7):用于USB2PHY,PLL7固定为20倍频,因此PLL7=480MHz
时钟树配置外设时钟:(ESAI外设为例)
(1)这是时钟源选择器,有4个可选的时钟源: PLL4、 PLL5、 PLL3_PFD2 和 pll3_sw_clk 。 具 体 选 择 哪 一 路 作 为 ESAI 的 时 钟 源 是 由 寄 存 器 CCM->CSCMR2 的ESAI_CLK_SEL 位来决定的(正上方黄色标志部分)
(2)时钟分频器,分频值由寄存器 CCM_CS1CDR 的 ESAI_CLK_PRED来确定的,可设置 1~8 分频(浅蓝色是3bit分配器, 图标)
(3)和2一样的作用,经过两次分频得到ESAI_CLK_ROOT外设时钟源
目的:将ARM 内核时钟设置为528Mhz
(1)内核时钟源来自于 PLL1,然后经过一次2分频得到系统时钟(后面的灰色的分频器是static divider,不进行分频),所以要设置系统时钟为528MHz, 需要修改PLL1的值为528 * 2 = 1056MHz < 1.3GHz,PLL1 的频率通过寄存器 CCM_ANALOG_PLL_ARMn 来设置
CCM_CACRR 寄存器(分频)
ARM_PODF只用了第3位,设置为2分频,寄存器写入1即可
CCM_ANALOG_PLL_ARMn(频率)
13位:时钟输出使能,1:使能PLL1输出;0:关闭PLL1输出
DIV_SELECT: 设置 PLL1 的输出频率,PLL1 CLK = Fin *div_seclec/2.0, Fin=24MHz
可写入范围:54~108,所以PLL1 要输出 1056MHz , div_select 要设置为 88
(2)但是在修改 PLL1 时钟频率的时候需要先将内核时钟源改为其他的时钟源,因为一般默认选择PLL1提供时基并且修改时必须要有时钟给系统提供时基。
pll1_sw_clk 是 PLL1的输出频率,时钟来源:pll1_main_clk 和 step_clk,由寄存器 CCM_CCSR 的PLL1_SW_CLK_SEL 位决定
而step_clk 的时钟源可以来自osc_clk和secondary_clk,一般选择 osc_clk(24MHz 的晶振),由寄存器 CCM_CCSR的STEP_SEL位决定
/* 1.1、判断当前ARM内核是使用的那个时钟源启动的,正常情况下ARM内核是由pll1_sw_clk驱动的,而
* pll1_sw_clk有两个来源:pll1_main_clk和tep_clk。
* 如果我们要让ARM内核跑到528M的话那必须选择pll1_main_clk作为pll1的时钟源。
* 如果我们要修改pll1_main_clk时钟的话就必须先将pll1_sw_clk从pll1_main_clk切换到step_clk,
* 当修改完pll1_main_clk以后在将pll1_sw_clk切换回pll1_main_clk。而step_clk的时钟源可以选择
* 板子上的24MHz晶振。
*/
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 */
}
/* 1.2、设置pll1_main_clk为528MHz
* 因为pll1_sw_clk进ARM内核的时候会被二分频!
* 配置CCM_ANLOG->PLL_ARM寄存器
* bit13: 1 使能时钟输出
* bit[6:0]: 88, 由公式:Fout = Fin * div_select / 2.0,528=24*div_select/2.0,
* 得出:div_select= 88
*/
CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F); /* 配置pll1_main_clk=528MHz */
CCM->CCSR &= ~(1 << 2); /* 将pll_sw_clk时钟重新切换回pll1_main_clk */
CCM->CACRR = 1; /* ARM内核时钟为pll1_sw_clk=1056/2=528Mhz */
首先判断ARM内核使用的时钟源,然后设置寄存器 CCSR 的 STEP_SEL 位,设置 step_clk 的时钟源为 24M 的晶振,再设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,设置 pll1_sw_clk 的时钟源为step_clk=24MHz,将 I.MX6U 的主频先设置为 24MHz,直接来自于外部的24M 晶振
时钟源修改完成后,可以修改PLL1的频率为1056MHz,CCM_ANALOG_PLL_ARMn低6位写入88并使能13位即可。设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,重新将 pll1_sw_clk 的时钟源切换回pll1_main_clk,切换回来以后的 pll1_sw_clk 就等于 1056MHz。最后设置寄存器CCM_CACRR 的 ARM_PODF 为 2 分频, MX6U 的内核主频就为1056/2=528MHz
设置系统时钟后还要设置其他的PLL和PFD时钟,主要设置 PLL2 和 PLL3 的4 路 PFD
8路FPD频率表:
设置 PLL2 的 4路 PFD 频率,使用到的寄存器是 CCM_ANALOG_PFD_528n
刚好分为四组,每组 8 个 bit,分别对应PFD0~PFD3,PFDx_FRAC: PLL2_PFDx 的分频数
频率计算公式:PLL2_PFDx = 528*18/PFDx_FRAC
若要设置 PLL2_PFD0 的频率为 352MHz,则PFD0_FRAC=528*18/352=27,其他的同理。
设 置 PLL3的4 路 PFD 的 频 率 , 使 用 到 的 寄 存 器 是CCM_ANALOG_PFD_480n
频率计算公式:PLL3_PFDX=480*18/PFDX_FRAC(X=0~3),除了公式不一样其他都是一样的。
uint8_t reg = 0;
/* 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 */
技巧:将8-13设置为16,其余位不受影响
GPIO->DR &= ~(0X00003F00) /* 将相应的位清0*/
GPIO->DR |= 16 << 8 /*从第8位写入16*/
7 路 PLL 和 8 路 PFD 设置完成以后还需要设置 AHB_CLK_ROOT 和 IPG_CLK_ROOT的时钟
外设根时钟频率表:
将AHB_CLK_ROOT、IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 分 别 设 置 为 132MHz 、 66MHz 、 66MHz
(1)先选择 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
(2)选择 periph_clk 的时钟源,由寄存器 CCM_CBCDR 的 PERIPH_CLK_SEL位与 PLL_bypass_en2 组成的或来选择。当 CCM_CBCDR 的 PERIPH_CLK_SEL 位为 0 的时候periph_clk=pr_periph_clk=396MHz
(3)设置 AHB_CLK_ROOT 的分频值,由CBCDR 的 AHB_PODF 位决定,可以设置 1~8 分频,如果想要 AHB_CLK_ROOT=132MHz 的话就应该设置为 3 分频: 396/3=132MHz。虽然默认 4 分频,但是MX6U 的内部 boot rom 将其改为了 3 分频
(4)设置 IPG_CLK_ROOT 的分频值,IPG_CLK_ROOT 时钟源是 AHB_CLK_ROOT,要想IPG_CLK_ROOT= 66MHz 的话就应该设置2 分频: 132/2=66MHz
(5)设置 PERCLK_CLK_ROOT 时钟频率
PERCLK_CLK_ROOT 来 源 有 两 种 : OSC(24MHz) 和IPG_CLK_ROOT,由寄存器 CCM_CSCMR1 的 PERCLK_CLK_SEL 位来决定。如果为 0 选择PERCLK_CLK_ROOT 的 时 钟 源 是 IPG_CLK_ROOT=66MHz 。 可 以 通 过 寄 存 器CCM_CSCMR1 的 PERCLK_PODF 位来设置分频,如果要设置 PERCLK_CLK_ROOT 为 66MHz的话就要设置为 1 分频
注:在修改如下时钟选择器或者分频器的时候会引起与 MMDC 的握手发生
mmdc_podf
periph_clk_sel
periph2_clk_sel
arm_podf
ahb_podf
发生握手信号以后需要等待握手完成,寄存器 CCM_CDHIPR 中保存着握手信号是否完成,如果相应的位为 1 的话就表示握手没有完成,如果为 0 的话就表示握手完成。
/* 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));/* 等待握手完成 */
/* 修改AHB_PODF位的时候需要先禁止AHB_CLK_ROOT的输出,但是
* 没有找到关闭AHB_CLK_ROOT输出的的寄存器,所以就没法设置。
* 下面设置AHB_PODF的代码仅供学习参考不能直接拿来使用!!
* 内部boot rom将AHB_PODF设置为了3分频,即使我们不设置AHB_PODF,
* AHB_ROOT_CLK也依旧等于396/3=132Mhz。
*/
#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分频 */
二、时钟初始代码
/*
* @description : 初始化系统时钟,设置系统时钟为792Mhz,并且设置PLL2和PLL3各个
PFD时钟,所有的时钟频率均按照I.MX6U官方手册推荐的值.
* @param : 无
* @return : 无
*/
void imx6u_clkinit(void)
{
unsigned int reg = 0;
/* 1.1、判断当前ARM内核是使用的那个时钟源启动的,正常情况下ARM内核是由pll1_sw_clk驱动的,而
* pll1_sw_clk有两个来源:pll1_main_clk和tep_clk。
* 如果我们要让ARM内核跑到528M的话那必须选择pll1_main_clk作为pll1的时钟源。
* 如果我们要修改pll1_main_clk时钟的话就必须先将pll1_sw_clk从pll1_main_clk切换到step_clk,
* 当修改完pll1_main_clk以后在将pll1_sw_clk切换回pll1_main_clk。而step_clk的时钟源可以选择
* 板子上的24MHz晶振。
*/
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 */
}
/* 1.2、设置pll1_main_clk为528MHz
* 因为pll1_sw_clk进ARM内核的时候会被二分频!
* 配置CCM_ANLOG->PLL_ARM寄存器
* bit13: 1 使能时钟输出
* bit[6:0]: 88, 由公式:Fout = Fin * div_select / 2.0,528=24*div_select/2.0,
* 得出:div_select= 88
*/
CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F); /* 配置pll1_main_clk=528MHz */
CCM->CCSR &= ~(1 << 2); /* 将pll_sw_clk时钟重新切换回pll1_main_clk */
CCM->CACRR = 1; /* ARM内核时钟为pll1_sw_clk=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));/* 等待握手完成 */
/* 修改AHB_PODF位的时候需要先禁止AHB_CLK_ROOT的输出,但是
* 没有找到关闭AHB_CLK_ROOT输出的的寄存器,所以就没法设置。
* 下面设置AHB_PODF的代码仅供学习参考不能直接拿来使用!!
* 内部boot rom将AHB_PODF设置为了3分频,即使我们不设置AHB_PODF,
* AHB_ROOT_CLK也依旧等于396/3=132Mhz。
*/
#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分频 */
}
小结:或与非的应用
(1)读取寄存器的值
/*读取GPIO的DR寄存器的第pin为的值 */
(GPIO->DR) >> pin) & 0x1 /*先将该为右移pin位,然后&上0x1得到该位的值*/
(2)写入寄存器的值:&= (清0) , |= (置1)
/*分别将DR寄存器的pin位清0和置1*/
GPIO->DR &= ~(1 << pin); /*清0*/
GPIO->DR |= (1 << pin); /*置1 */
/*将8-13设置为16,其余位不受影响*/
GPIO->DR &= ~(0X00003F00) /* 将8-13相应的位清0*/
GPIO->DR |= 16 << 8 /*从第8位写入16*/