ARM裸机开发:主频与时钟

Posted JeckXu666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARM裸机开发:主频与时钟相关的知识,希望对你有一定的参考价值。

ARM裸机开发:主频与时钟

本章了解一下 IMX 的系统时钟主频配置原理

一、时钟系统

1.1 外部时钟电路

开发板时钟电路

IMX 的时钟电路和 STM32 没有多大区别,一个 24M 的晶振作为系统内核和外设的时钟电路,另外一个 32.768K 的晶振电路作为系统 RTC 时钟电路

1.2 7路PLL时钟源

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

图上7路 PLL 功能如下:

PLL功能
ARM_PLL(PLL1)此路 PLL 是供 ARM 内核使用的,ARM 内核时钟就是由此 PLL 生成的,此 PLL 通过编程的方式最高可倍频到 1.3GHz
528_PLL(PLL2)此路是 System_PLL,固定的 22 倍频,不可编程修改,PLL 时钟=24MHz * 22 = 528MHz;此 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
USB2_PLL(PLL7)此路PLL是给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 分频

1.3 时钟树概览

先看一张时钟树劝退图:

我们在里面可以看到两部分内容:左边的 CLOCK SWITCHER (PLL 时钟源)和右边的 CLOCK ROOT GENERATOR (时钟生成树)、SYSTEM CLOCKS (外设时钟);左边部分就是上面分析的 PLL 时钟源,CLOCK ROOT GENERATOR 则是对时钟源进行进一步处理,分频到合适的值给外设时钟使用,其中主要的部分就是 CLOCK ROOT GENERATOR 这部分电路,这部分电路主要用到以下几个逻辑电路:

  • 时钟源选择器:用于选择时钟输入

  • 时钟分频器:用于时钟分频

我们通过配置时钟选择器和分频器,使PLL时钟源和外设时钟关联起来,达到可以正常使用的功能

二、时钟配置

时钟配置主要分为三个部分的时钟配置:

  • 内核时钟设置
  • PFD时钟设置
  • AHB、IPG 和 PERCLK 根时钟设置

2.1 内核时钟设置

先看一下时钟源到内核时钟的时钟路线:

其中 PLL1 经过分频器1和2后输出到ARM内核时钟上,但注意,其中灰色的那个分频器并没有作用,所以只要配置第一个就行,如我们要是需要498M的主频,只需要将时钟源配置为996M,然后配置分频器为2分频,这样出来的就是498M的主频时钟,所以我们主频时钟配置需要配置两个寄存器

  • CCM_CACRR 的 ARM_PODF 位设置分频
  • 寄存器 CCM_ANALOG_PLL_ARMn 设置 PLL1 频率

2.1.1 CCM_CACRR寄存器

该寄存器以及 ARM_PODF 位与分频的对应关系如下:

ARM_PODF 位,可以设置为 0~7,分别对应 1~8 分频,要设置 2分频就是给 001就行

2.1.2 CCM_ANALOG_PLL_ARMn 寄存器

**ENABLE :**用于使能时钟输出

DIV_SELECT: 此位设置 PLL1 的输出频率,可设置范围为:54~108,PLL1 CLK = Fin * div_seclec/2.0,因为Fin=24MHz,所以如果 PLL1 要输出 1056MHz 的话,div_select 就要设置为 88

注意!我们在配置时钟频率时必须先切换时钟源为其他时钟源,不然会出问题,先看一下内核时钟源有哪些:

看图中我们有 2、3 两个时钟选择器来控制时钟源的选择,其中选择器2是由寄存器 CCM_CCSR 的 PLL1_SW_CLK_SEL 位决定 pll1_sw_clk 是选择 pll1_main_clk 还是 step_clk;选择器3是由寄存器 CCM_CCSR 的 STEP_SEL 位 来决定 step_clk 是选择 osc_clk 还是secondary_clk;CCM_CCSR 寄存器如下:

所以综合上面,修改主频步骤如下:

  • 设置寄存器 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

2.2 PFD时钟设置

设置好主频以后我们还需要设置好其他的 PLL 和 PFD 时钟,PLL2、PLL3 和 PLL7 固定为 528MHz、480MHz 和 480MHz,PLL4~PLL6 都是针对特殊外设 的,用到的时候再设置。因此,接下来重点就是设置 PLL2 和 PLL3 的各自 4 路 PFD,NXP 推 荐的这 8 路 PFD 频率如表:

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

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 的时候使能输出

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

PFD0_FRAC: PLL3_PFD0 的分频数,PLL3_PFD0 的计算公式为 480 * 18 / PFD0_FRAC

PFD0_STABLE: 此位为只读位,可以通过读取此位判断 PLL3_PFD0 是否稳定。

PFD0_CLKGATE: PLL3_PFD0 输出使能位,为 1 的时候关闭 PLL3_PFD0 的输出,为 0 的时候使能输出

2.3 AHB、IPG 和 PERCLK 根时钟设置

除了以上两个时钟,IMX还需要设置 AHB_CLK_ROOT 和 IPG_CLK_ROOT 的时钟,I.MX6U 外设根时钟可设置范围如图:

上图给出了大多数时钟的配置方式,下面按上图,将AHB_CLK_ROOT、 IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 分别设置为 132MHz 、 66MHz 、 66MHz;先看一下 AHB_CLK_ROOT 和 IPG_CLK_ROOT 的时钟树:

可以看到,时钟树中的时钟经过路径为:

  • 选择器1确定 pre_periph_clk 的时钟源,由寄存器 CCM_CBCMR 的 PRE_PERIPH_CLK_SEL 位决定选择哪一个;

  • 选择器 2 确定 periph_clk 时钟源,由寄存器 CCM_CBCDR 的 PERIPH_CLK_SEL 位与 PLL_bypass_en2 组成的或来选择;

  • 分频器 3 通过 CBCDR 的 AHB_PODF 位来设置 AHB_CLK_ROOT 的分频值

  • 分频器 4 通过 CBCDR 的 IPG_PODF 位来设置 IPG_CLK_ROOT 的分频值

除了上面两个,还有 PERCLK_CLK_ROOT 的时钟频率设置,其时钟结构图如图:

其时钟源可以来自 OSC(24MHz) 和 IPG_CLK_ROOT 两个时钟源,如果来自 OSC 的话,通过寄存器 CCM_CSCMR1 的 PERCLK_PODF 位来设置分频

以上三个时钟频率的配置主要依靠三个寄存器来配置:

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 时钟分频,可设置 0~7,分别对应 1~8 分频。修改此位会引起一次与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_SELperclk 时钟源选择,为 0 的话选择 ipg clk,为 1 的话选择 osc clk
PERCLK_PODFperclk 的时钟分频,可设置 0~7,分别对应 1~8 分频

注意:修改如下时钟选择器或者分频器的时候会引起与 MMDC 的握手发生

① mmdc_podf

② periph_clk_sel

③ periph2_clk_sel

④ arm_podf

⑤ ahb_podf

在寄存器 CCM_CDHIPR 中保存着握手信号是否完成, 如果相应的位为 1 的话就表示握手没有完成,如果为 0 的话就表示握手完成

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

三、配置代码

代码工程基于上一届的按键输入继续修改,主要就是修改 bsp_clk 模块内容,此处使用的正点原子的配置代码:

#include "bsp_clk.h"
#include "fsl_iomuxc.h"
#include "MCIMX6Y2.h"
void CLK_INIT(void)
{
    //寄存器缓冲位
	unsigned int reg = 0;
    //先切换输入时钟源
	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));/* 等待握手完成 */
		
	/* 修改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分频 */

}

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

代码只修改了 bsp_clk 模块文件的代码,所以 makefile 文件不需要修改直接编译下载:下载通过如下

这里只是修改了默认主频,表现起来的现象只是延时长度不同

以上是关于ARM裸机开发:主频与时钟的主要内容,如果未能解决你的问题,请参考以下文章

s3c2440裸机编程-时钟编程(配置时钟寄存器)

ARM的异常与裸机开发

S5PV210-arm-裸机-i2c

ARM裸机开发:C语言点亮LED

ARM裸机开发:蜂鸣器实验

ARM裸机开发:BSP工程管理