imx6ull-裸机学习实验7——主频和时钟配置实验

发布于:2025-07-10 ⋅ 阅读:(24) ⋅ 点赞:(0)

目录

前言

时钟系统

硬件原理图

7路PLL时钟源

时钟树

内核时钟设置

主频设置

PFD时钟设置

​编辑设置PLL2

设置PLL3

AHB、 IPG 和 PERCLK 根时钟设置

注意事项:

实验程序编写

clk.c

main.c


前言

在学习实验1-5中我们都没有涉及到时钟和主频的配置,那么默认的工作频率为396MHz。这一讲实验就让我们学习正点原子开发板:I.MX6U 的时钟系统,学习如何配置 I.MX6U 的系统时钟和其他的外设时钟,使其工作频率为 528MHz,其他的外设时钟源都工作在 NXP 推荐的频率。

时钟系统

已知,默认情况下内部 boot rom 会将 I.MX6U 的主频设置为 396MHz。

那么怎么修改成我们需要的值呢?来查阅硬件原理图和参考手册吧~

硬件原理图

笔者在底板原理图中没有找到对应的系统时钟部分,借用正点原子官方的图:

系统时钟来源于两部分: 32.768KHz 和24MHz 的晶振。

  • 32.768KHz 晶振是 I.MX6U 的 RTC 时钟源,
  • 24MHz 晶振是 I.MX6U 内核和其它外设的时钟源

7路PLL时钟源

不同的外设时钟源不同, NXP 将这些外设的时钟源进行了分组,一共有 7 组,都是从 24MHz 晶振 PLL 而来的,因此也叫做 7 组 PLL:


来看看正点原子的详细分析:

 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 分出了 4 路 PFD,分别为: PLL2_PFD0~PLL2_PFD3,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。

4. USB2_PLL(PLL7),是给USB2PHY 使用的。同样的,此路PLL固定为20倍频,因此也是480MHz。

5. ENET_PLL(PLL6),此路 PLL 固定为 20+5/6 倍频,用于生成网络所需的时钟。

        因此 ENET_PLL=24MHz * (20+5/6) = 500MHz。可以在此 PLL 的基础上生成 25/50/100/125MHz的网络时钟。
6. VIDEO_PLL(PLL5), 此路 PLL 用于显示相关的外设,比如 LCD。

        此路 PLL 的倍频可以调整, PLL 的输出范围在 650MHz~1300MHz。此路 PLL 在最终输出的时候还可以进行分频,可选 1/2/4/8/16 分频。

7. AUDIO_PLL(PLL4),此路 PLL 用于音频相关的外设。

        此路 PLL 的倍频可以调整, PLL的输出范围同样也是 650MHz~1300MHz,此路 PLL 在最终输出的时候也可以进行分频,可选1/2/4 分频。

时钟树

在“《IMX6ULL 参考手册》Chapter 18 Clock Controller Module (CCM)”的 18.3 小节给出了 I.MX6U详细的时钟树图,先来看一下局部:

最上面显示整个时钟图分为3部分:

  • LOCK_SWITCHER:7 路 PLL 和8 路 PFD
  • CLOCK ROOT GENERATOR:负责从 7 路PLL 和 8 路 PFD 中选择合适的时钟源给外设使用
  • SYSTEM CLOCKS:芯片外设

看不懂怎么办?以ESAI 这个外设为例:

①、此部分是时钟源选择器, 可以看见左边有4种颜色的线打结了,往线的跟寻找过去:

可以看出:ESAI 有 4 个可选的时钟源: PLL4、 PLL5、 PLL3_PFD2 和pll3_sw_clk 。
具 体 选 择 哪 一 路 作 为 ESAI 的 时 钟 源 ,是 由 寄 存 器 CCM->CSCMR2 的ESAI_CLK_SEL 位来决定的:


 

②、是 ESAI 时钟的前级分频,分频值由寄存器 CCM_CS1CDR 的 ESAI_CLK_PRED来确定的,可设置 1~8 分频.


假如现在 PLL4=650MHz,我们选择 PLL4 作为 ESAI 时钟,前级分频选择 2 分频,那么此时的时钟就是 650/2=325MHz. 配置寄存器CCM_CS1CDR 的 bit9-11, 就是ESAI_CLK_PRED:


③、也是一个分频器,对②中输出的时钟进一步分频,分频值由寄存器CCM_CS1CDR 的 ESAI_CLK_PODF 来决定,可设置 1~8 分频。

假如我们设置为 8 分频的话,经过此分频器以后的时钟就是 325/8=40.625MHz。因此最终进入到 ESAI 外设的时钟就是40.625MHz。配置寄存器CCM_CS1CDR 的 bit25-27, 就是ESAI_CLK_PODF

内核时钟设置

主频设置

我们将 I.MX6U 的主频设置为 528MHz,来查看时钟树的结构:

  • 内核时钟源来自于 PLL1,假如此时 PLL1 为 1056MHz。PLL1 的频率可以通过寄存器 CCM_ANALOG_PLL_ARMn 来设置:

在寄存器 CCM_ANALOG_PLL_ARMn 中,我们关注bit13 和bit0-6:

  1. ENABLE: 时钟输出使能位,此位设置为 1 使能 PLL1 输出,如果设置为 0 的话就关闭 PLL1输出。
  2. DIV_SELECT: 此位设置 PLL1 的输出频率,可设置范围为: 54~108, PLL1 CLK = Fin * div_seclec/2.0, Fin=24MHz。如果 PLL1 要输出 1056MHz 的话, div_select 就要设置为 88。
  • 寄存器 CCM_CACRR 的 ARM_PODF 位对 PLL1 进行分频,可选择 1/2/4/8 分频,假如我们选择 2 分频,那么经过分频以后的时钟频率是 996/2=528MHz。

好了,现在我们已经知道怎么去配置寄存器,来修改PLL1的时钟频率,和设置PLL1的分频值了。但是还没有结束,在修改 PLL1 时钟频率的时候我们需要先将内核时钟源改为其他的时钟源, 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寄存器的bit8是STEP_SEL,为0时选择 osc_clk(24M);bit2为PLL1_SW_CLK_SEL,决定 pll1_sw_clk 是选择 pll1_main_clk 还是 step_clk。

综上所述,修改 I.MX6U 主频的步骤如下:

①、 设置寄存器 CCSRSTEP_SEL 位,设置 step_clk 的时钟源为 24M 的晶振。

②、设置寄存器 CCSRPLL1_SW_CLK_SEL 位,设置 pll1_sw_clk 的时钟源为step_clk=24MHz,通过这一步我们就将 I.MX6U 的主频先设置为 24MHz,直接来自于外部的24M 晶振。

③、设置寄存器 CCM_ANALOG_PLL_ARMn,将 pll1_main_clk(PLL1)设置为 1056MHz。

④、设置寄存器 CCSRPLL1_SW_CLK_SEL 位,重新将 pll1_sw_clk 的时钟源切换回pll1_main_clk,切换回来以后的 pll1_sw_clk 就等于 1056MHz。

⑤、最后设置寄存器 CCM_CACRRARM_PODF 为 2 分频, I.MX6U 的内核主频就为1056/2=528MHz。

PFD时钟设置

设置好主频以后我们还需要设置好其他的 PLL 和 PFD 时钟:

PLL2、 PLL3 和 PLL7 固定为 528MHz、 480MHz 和 480MHz,

PLL4~PLL6 都是针对特殊外设的,用到的时候再设置。

因此,接下来重点就是设置 PLL2 和 PLL3 的各自 4 路 PFD。


设置PLL2

先设置 PLL2 的 4 路 PFD 频率,用到寄存器是 CCM_ANALOG_PFD_528n,寄存器结构如图:

寄存器 CCM_ANALOG_PFD_528n 其实分为四组,分别对应PFD0~PFD3,每组 8 个 bit,我们就以 PFD0 为例,看一下如何设置 PLL2_PFD0 的频率。 PFD0对应的寄存器位如下:

  • PFD0_FRAC: PLL2_PFD0 的分频数, PLL2_PFD0 的计算公式为 528*18/PFD0_FRAC,此为 可 设 置 的 范 围 为 12~35 。 如 果 PLL2_PFD0 的 频 率 要 设 置 为 352MHz 的 话PFD0_FRAC=528*18/352=27。
  • PFD0_STABLE: 此位为只读位,可以通过读取此位判断 PLL2_PFD0 是否稳定。
  • PFD0_CLKGATE: PLL2_PFD0 输出使能位,为 1 的时候关闭 PLL2_PFD0 的输出,为 0 的时候使能输出。

频 率 计 算 公 式 都 是 528*18/PFDX_FRAC(X=1~3).

比如,要配置 PLL2_PFD1=594MHz 的 话 , PFD1_FRAC=16。

设置PLL3

PLL3_PFD0~PLL3_PFD3 这 4 路 PFD 的 频 率 , 使 用 到 的 寄 存 器 是CCM_ANALOG_PFD_480n,此寄存器结构如图:

可以看出,寄存器 CCM_ANALOG_PFD_480n 和 CCM_ANALOG_PFD_528n的结构是一模一样的。但频 率 计 算 公 式 不 同 , 比 如 PLL3_PFDX=480*18/PFDX_FRAC(X=0~3)

比如:PLL3_PFD0=720MHz 的话, 设置PFD0_FRAC=12;


AHB、 IPG 和 PERCLK 根时钟设置

7 路 PLL 和 8 路 PFD 设置完成以后最后还需要设置 AHB_CLK_ROOTIPG_CLK_ROOT的时钟, I.MX6U 外设根时钟可设置范围如图:


由表可知:AHB_CLK_ROOT 最高可以设置 132MHz, IPG_CLK_ROOT 和PERCLK_CLK_ROOT 最高可以设置66MHz。

那我们就将AHB_CLK_ROOT、IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 分 别 设 置 为 132MHz 、 66MHz 、 66MHz 。

AHB_CLK_ROOT 和 IPG_CLK_ROOT 的设计如图:

①、此选择器用来选择 pre_periph_clk 的时钟源,可以选择 PLL2、 PLL2_PFD2、 PLL2_PFD0和 PLL2_PFD2/2。寄存器 CCM_CBCMRPRE_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。图中虽然写的是默认 4 分频,但是 I.MX6U 的内部 boot rom 将其改为了 3 分频!

④、通过 CBCDR 的 IPG_PODF 位来设置 IPG_CLK_ROOT 的分频值,可以设置 1~4 分频, IPG_CLK_ROOT 时钟源是 AHB_CLK_ROOT,要想 IPG_CLK_ROOT=66MHz 的话就应该设置2 分频: 132/2=66MHz。

翻阅手册查找CBCDR寄存器如图:

我们需要设置的位分别是:PERIPH_CLK_SEL、AHB_PODF、IPG_PODF

  • PERIPH_CLK_SEL: peripheral 主时钟选择,如果为 0 的话选择 PLL2,如果为 1 的话选择 periph_clk2_clock。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
  • HB_PODF: ahb 时钟分频,可设置 0~7,分别对应 1~8 分频。修改此位会引起一次与MMDC 的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
  • IPG_PODF: ipg 时钟分频,可设置 0~3,分别对应 1~4 分频。

最后要设置的就是 PERCLK_CLK_ROOT 时钟频率,其时钟结构图如图:

PERCLK_CLK_ROOT 来 源 有 两 种 : OSC(24MHz) 和IPG_CLK_ROOT,由寄存器 CCM_CSCMR1 的 PERCLK_CLK_SEL 位来决定:

可以看出也就是设置该寄存器的bit6,  如果为 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 的话就表示握手完成。

该寄存器保存着各种状态是否busy,如果关注的是与 MMDC 的握手是否busy,那么判断bit2即可:

另外在修改 arm_podf 和 ahb_podf 的时候需要先关闭其时钟输出,等修改完成以后再打开,否则的话可能会出现在修改完成以后没有时钟输出的问题。

实验程序编写

clk.c

修改bsp/bsp_clk.c文件,源码如下:

#include "bsp_clk.h"

/*
 * @description	: 使能I.MX6U所有外设时钟
 * @param 		: 无
 * @return 		: 无
 */
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;
}

/*
 * @description	: 初始化系统时钟,设置系统时钟为792Mhz,并且设置PLL2和PLL3各个
 				  PFD时钟,所有的时钟频率均按照I.MX6U官方手册推荐的值.
 * @param 		: 无
 * @return 		: 无
 */
void imx6u_clkinit(void)
{
	unsigned int reg = 0;
	/* 1、设置ARM内核时钟为792MHz */
	/* 1.1、判断当前ARM内核是使用的那个时钟源启动的,正常情况下ARM内核是由pll1_sw_clk驱动的,而
	 *      pll1_sw_clk有两个来源:pll1_main_clk和tep_clk。
	 *      如果我们要让ARM内核跑到792M的话那必须选择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为792MHz
	 *      因为pll1_sw_clk进ARM内核的时候会被二分频!
	 *      配置CCM_ANLOG->PLL_ARM寄存器
	 *      bit13: 1 使能时钟输出
	 *      bit[6:0]: 66, 由公式:Fout = Fin * div_select / 2.0,792=24*div_select/2.0,
	 *              		得出:div_select=    66 
	 */
	CCM_ANALOG->PLL_ARM = (1 << 13) | ((66 << 0) & 0X7F); 	/* 配置pll1_main_clk=792MHz */
	CCM->CCSR &= ~(1 << 2);									/* 将pll_sw_clk时钟重新切换回pll1_main_clk */
	CCM->CACRR = 0;											/* ARM内核时钟为pll1_sw_clk/1=792/1=792Mhz */

	/* 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分频 */
}

函数 clk_enable ,就是使能 I.MX6U 的所有外设时钟。

函数 imx6u_clkinit , 先设置系统主频为 528MHz,然后根据我们分析的 I.MX6U 时钟系统来设置 8 路 PFD,最后设置 AHB、 IPG 和 PERCLK。


main.c

修改 main.c 文件,在 main 函数里面调用 imx6u_clkinit 来初始化时钟。

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"

/*
 * @description	: main函数
 * @param 		: 无
 * @return 		: 无
 */
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 			*/
	beep_init();		/* 初始化beep	 		*/
	key_init();			/* 初始化key 			*/

	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;
}

终于万事俱备,只欠东风啦。将代码编译成功后,试试下载到开发板上呢~

本试验效果其实和学习实验6差不多,但是 LED 灯的闪烁频率相比起来要快一点。因为我们的主频从396MHz配置成了 528MHz,因此代码执行速度会变快,所以延时函数的运行就会加快。


网站公告

今日签到

点亮在社区的每一天
去签到