时钟设置学习

其实这里的时钟配置仅仅是学习作用,因为在开发板启动BL0段的时候iROM就已经初始化了时钟做好了各项设置的了。

学习的方法:

通过示例代码,结合数据手册,先看懂示例代码的数据设置方法,然后根据实际需要再自己进行配置。

学习的例子

S5PV210开发板的时钟控制器部分。
在这里插入图片描述
这些时钟管理单元(CMU)和电源管理单元都是通过开发板的SYSCON来进行管理控制的。
MSYS:main system
DSYS:display system
PSYS:peripherial system
建议最好从头看一遍介绍,在数据手册(353页)
在354页介绍了时钟的晶振来源
XXTI、XRTCXTI、XUSBXTI、XHDMIXTI
这些都是晶振来源的名字编号,例如:
在这里插入图片描述
通过这里,再结合板子原理图可以确定外部晶振是24MHz

所需要用到的其他中间频率,都是CMU利用上述的晶振来源来generate的。

结合代码进行分析

// 时钟控制器基地址
#define ELFIN_CLOCK_POWER_BASE		0xE0100000	

// 时钟相关的寄存器相对时钟控制器基地址的偏移值
#define APLL_LOCK_OFFSET		0x00		
#define MPLL_LOCK_OFFSET		0x08

#define APLL_CON0_OFFSET		0x100
#define APLL_CON1_OFFSET		0x104
#define MPLL_CON_OFFSET			0x108

#define CLK_SRC0_OFFSET			0x200
#define CLK_SRC1_OFFSET			0x204
#define CLK_SRC2_OFFSET			0x208
#define CLK_SRC3_OFFSET			0x20c
#define CLK_SRC4_OFFSET			0x210
#define CLK_SRC5_OFFSET			0x214
#define CLK_SRC6_OFFSET			0x218
#define CLK_SRC_MASK0_OFFSET	0x280
#define CLK_SRC_MASK1_OFFSET	0x284

#define CLK_DIV0_OFFSET			0x300
#define CLK_DIV1_OFFSET			0x304
#define CLK_DIV2_OFFSET			0x308
#define CLK_DIV3_OFFSET			0x30c
#define CLK_DIV4_OFFSET			0x310
#define CLK_DIV5_OFFSET			0x314
#define CLK_DIV6_OFFSET			0x318
#define CLK_DIV7_OFFSET			0x31c

#define CLK_DIV0_MASK			0x7fffffff

// 这些M、P、S的配置值都是查数据手册中典型时钟配置值的推荐配置得来的。
// 这些配置值是三星推荐的,因此工作最稳定。如果是自己随便瞎拼凑出来的那就要
// 经过严格测试,才能保证一定对。
#define APLL_MDIV      	 		0x7d		// 125
#define APLL_PDIV       		0x3
#define APLL_SDIV       		0x1

#define MPLL_MDIV				0x29b		// 667
#define MPLL_PDIV				0xc
#define MPLL_SDIV				0x1

#define set_pll(mdiv, pdiv, sdiv)	(1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VAL			set_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VAL			set_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)


.global clock_init
clock_init:
	ldr	r0, =ELFIN_CLOCK_POWER_BASE
	
	// 1 设置各种时钟开关,暂时不使用PLL
	ldr	r1, =0x0
	// 芯片手册P378 寄存器CLK_SRC:Select clock source 0 (Main)
	str	r1, [r0, #CLK_SRC0_OFFSET]				

	// 2 设置锁定时间,使用默认值即可
	// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
	ldr	r1,	=0x0000FFFF					
	str	r1,	[r0, #APLL_LOCK_OFFSET]				
	str r1, [r0, #MPLL_LOCK_OFFSET]	 				

	// 3 设置分频
	// 清bit[0~31]
	ldr r1, [r0, #CLK_DIV0_OFFSET]					
	ldr	r2, =CLK_DIV0_MASK					
	bic	r1, r1, r2
	ldr	r2, =0x14131440						
	orr	r1, r1, r2
	str	r1, [r0, #CLK_DIV0_OFFSET]

	// 4 设置PLL
	// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
	ldr	r1, =APLL_VAL						
	str	r1, [r0, #APLL_CON0_OFFSET]
	// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
	ldr	r1, =MPLL_VAL						
	str	r1, [r0, #MPLL_CON_OFFSET]

	// 5 设置各种时钟开关,使用PLL
	ldr	r1, [r0, #CLK_SRC0_OFFSET]
	ldr	r2, =0x10001111
	orr	r1, r1, r2
	str	r1, [r0, #CLK_SRC0_OFFSET]

	mov	pc, lr

从clock_init开始

第一步设置各种时钟开关,暂时不启用PLL
PLL是锁相环的意思,用来倍频的。
clock generation 图
在这里插入图片描述
有疑问:
根据上面的时钟发生图可以看到直接启用APLL不行吗?为什么代码前面要先暂时不用呢?

因为一方面PLL是需要经过一定的locking period才可以产生稳定的频率,目前只有上面推荐的24MHz晶振可以用,通过用这个24MHz晶振来对APLL进行配置,以及对后面的MUX开关(选择开关)还有DIV(分频开关)做好相应的配置之后,再打开APLL,然后就有稳定的需要的各种工作频率(时钟)
第一段:

	ldr	r0, =ELFIN_CLOCK_POWER_BASE //基地址上面是有的
					//这里是设置基地址,结合下面的偏移量pffset进行动作。
	// 1 设置各种时钟开关,暂时不使用PLL
	ldr	r1, =0x0
	// 芯片手册P378 寄存器CLK_SRC:Select clock source 0 (Main)
	str	r1, [r0, #CLK_SRC0_OFFSET]	
	//这句话把所有的CLK_SRC0都置零了

选择CLK_SRC0的话根据数据手册(378页)可以看到这是要配置MUX_PSYS、MUX_DSYS、MUX_MSYS、MUXAPLL等等,与上面的clock generation相对应。
全部置零的话也就是相对应的MUX开关是0,可以看出是先利用FINPLL的24MHz进行配置工作的。
为什么FINPLL是24MHz呢?
在355页讲到
在这里插入图片描述

他们都是利用FINPLL作为input的,然后现在只有外部的24MHz能用,所以FINPLL就是24MHz
关于FINPLL结点前面,看clock generation 图
可以推出是通过XOM来选择0或者1,来选择是XXTI还是XUSBXTI来提供初始晶振来源,反正都是推荐24MHz的。

第二段:

// 2 设置锁定时间,使用默认值即可
	// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
	ldr	r1,	=0x0000FFFF //r1寄存器是用来放一个数来置位用的					
	str	r1,	[r0, #APLL_LOCK_OFFSET]	 //用r1里的数值给APLL_LOCK寄存器置位			
	str r1, [r0, #MPLL_LOCK_OFFSET]	  //用r1里的数值给MPLL_LOCK寄存器置位			
	

上面有提及到一个locking period的概念,PLL是需要一定的锁定周期才能稳定输出的,数据手册里面显示的Reset Value 默认值是0X00000FFF,这里设置为0x0000FFFF的话就就更加保险。
在这里插入图片描述
第二点,控制寄存器的时候依旧是基地址+偏移量的使用方法r0存了基地址,然后再+上对应寄存器的偏移量。

第三段:

// 3 设置分频
	// 清bit[0~31]
	ldr r1, [r0, #CLK_DIV0_OFFSET]					
	ldr	r2, =CLK_DIV0_MASK					
	bic	r1, r1, r2
	ldr	r2, =0x14131440						
	orr	r1, r1, r2
	str	r1, [r0, #CLK_DIV0_OFFSET]

通过第一句计算基地址+偏移量可以查阅数据手册得r1里存的是被操作的CLK_DIV0寄存器
( Clock Divider Control Register (CLK_DIV0, R/W, Address = 0xE010_0300))
其实第三步设置分频的话也可以查找相对应的DIV寄存器。
r2 = CLK_DIV0_MASK ,查看上面宏定义得知是0x7fffffff
r1寄存器有0-31共32个位。
通过bic r1,r1,r2就把这32位都清零(除了第31位不用清,不知道为什么)
例如:

BIC R0,R0,#0b1011 ; 该指令清除 R0 中的位 0、1、和 3,其余的位保持不变。

清零之后再load新的值给r2,然后通过位或运算给r1赋值,从而配置CLK_DIV0寄存器
(位或,有0有1就是1,就是置位。)
新的值0x14131400要转换成为二进制的0101,然后代入数据手册里面, 根据位数来计算相对应的值。
在这里插入图片描述
然后根据公式:
PCLK_PSYS = HCLK_PSYS / (PCLK_PSYS_RATIO + 1)
HCLK_PSYS = MOUT_PSYS / (HCLK_PSYS_RATIO + 1)
PCLK_DSYS = HCLK_DSYS / (PCLK_DSYS_RATIO + 1)
HCLK_DSYS = MOUT_DSYS / (HCLK_DSYS_RATIO + 1)
PCLK_MSYS = HCLK_MSYS / (PCLK_MSYS_RATIO + 1)
HCLK_MSYS = ARMCLK / (HCLK_MSYS_RATIO+ 1)
SCLKA2M = SCLKAPLL / (A2M_RATIO + 1)
ARMCLK = MOUT_MSYS / (APLL_RATIO + 1)
把数值代入可得:
PCLK_PSYS = HCLK_PSYS / 2
HCLK_PSYS = MOUT_PSYS / 5
PCLK_DSYS = HCLK_DSYS /2
HCLK_DSYS = MOUT_DSYS / 4
PCLK_MSYS = HCLK_MSYS / 2
HCLK_MSYS = ARMCLK / 5
SCLKA2M = SCLKAPLL /5
ARMCLK = MOUT_MSYS / 1

再通过这些公式结合clock generation图来看
在这里插入图片描述

所有DIV都设置了合适的n值。当APLL可以交付使用的时候会产生1000MHz的晶振,
由此可得
ARMCLK = 1000MHz
HCLK_MSYS = 200MHz
PCLK-MSYS = 100MHz
刚好与手册给的推荐值相对应
在这里插入图片描述
设置好了之后最后一句str r1, [r0, #CLK_DIV0_OFFSET],把设置好的值真正的填充在CLK_DIV0寄存器上,完成分频的设置

第四段

// 4 设置PLL
	// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
	ldr	r1, =APLL_VAL						
	str	r1, [r0, #APLL_CON0_OFFSET]
	// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
	ldr	r1, =MPLL_VAL						
	str	r1, [r0, #MPLL_CON_OFFSET]

第一句 ldr r1 = APLL_VAL
在宏定义里面查看得:
#define APLL_MDIV 0x7d // 125
#define APLL_PDIV 0x3
#define APLL_SDIV 0x1

#define MPLL_MDIV 0x29b // 667
#define MPLL_PDIV 0xc
#define MPLL_SDIV 0x1

#define set_pll(mdiv, pdiv, sdiv) (1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VAL set_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VAL set_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)

这个APLL_VAL是一个宏,set_pll也是一个宏
set_pll的作用就是把1左移31位,即使得第32位置1,同样把mdiv左移16位,也就是0x7d左移16位,其他同理。最终得到APLL_VAL的值。
下一句通过APLL_VAL给r0+基地址(APLL_CON0寄存器)置位,查看对应的寄存器设置:在这里插入图片描述
bit 31置位1,表示PLL控制使能,0x7d、0x3、0x1分别是给MDIV、PDIV、SDIV置位,转换成对应的十进制可得:
M = 125
P = 3
S = 1
得到M、P、S这三个值有什么用呢?

在这里插入图片描述
在APLL_CON0寄存器下面有这样的公式。回顾一下,PLL没启动之前是通过外部24MHz晶振工作的,所以FIN = 24MHz,然后上面计算得到的M、P、S三个值分别对应MDIV、 PDIV、SDIV。

同理计算MPLL的
先在数据手册查看MPLL_CON0寄存器
M对应0x29b = 667
P对应0xc = 12
S对应1 = 1
在这里插入图片描述
注意:每个PLL的计算公式不尽相同,所以要从每个寄存器下面找到相应的计算公式。
数据代入上式可得:

小总结:
通过对照手册,可以知道相应的代码做了哪些操作。
其实这些M、P、S值官方是有给予推荐值的,在这些推荐值的情况下工作效果会更加好。在(357-359页)有RECOMMENDED PLL PMS VALUE FOR APLL、MPLL、EPLL、VPLL
所以可以通过这些表格查找需要的Target FOUT值,把这些都设置好之后PLL的各项准备工作已经做好了。

第五段:

// 5 设置各种时钟开关,使用PLL
	ldr	r1, [r0, #CLK_SRC0_OFFSET] 
	ldr	r2, =0x10001111
	orr	r1, r1, r2
	str	r1, [r0, #CLK_SRC0_OFFSET]

第一句 ldr r1, [r0, #CLK_SRC0_OFFSET]
结合查宏定义得知这句话是操作CLK_SRC0寄存器
第二第三第四局通过或运算给CLK_SRC0寄存器置位
在这里插入图片描述
按这样的置位方法,得到开了时钟的是:
控制MUXFLASH的是HCLK_PSYS
控制MUX_PSYS的是SCLK_MPLL
控制MUX_DSYS的是SCLKMPLL
控制MUX_MSYS的是SCLKAPLL
控制MUXVPLL的是FOUTVPLL
控制MUXEPLL的是FOUTEPLL
控制MUXMPLL的是FOUTMPLL
控制MUXAPLL的是FOUTAPLL
文字讲的话可能表达得不怎么明确,结合clock generation 图来看
在这里插入图片描述
结合上图,其实第五段代码的作用就是设置每个mux开关。因为第四步做好了各种配置之后,前面的APLL、MPLL、EPLL、VPLL就已经可以工作了,第五步把mux开关打开,决定各种输出时钟是从哪里来的。打开开关后就有各种时钟可以用了。

最后一句
mov pc, lr 作用是从子程序返回的意思。
因为这个clock.s文件是被Start.s文件所调用的,这里进行完时钟初始化之后就要返回上一级程序继续工作。

最后的最后:
课程的最后用了C语言对上述的汇编语言进行了编写。是比汇编简洁很多。
感觉重点是在宏定义

#define REG_CLK_SRC0	(ELFIN_CLOCK_POWER_BASE + CLK_SRC0_OFFSET)
#define REG_APLL_LOCK	(ELFIN_CLOCK_POWER_BASE + APLL_LOCK_OFFSET)
#define REG_MPLL_LOCK	(ELFIN_CLOCK_POWER_BASE + MPLL_LOCK_OFFSET)
#define REG_CLK_DIV0	(ELFIN_CLOCK_POWER_BASE + CLK_DIV0_OFFSET)
#define REG_APLL_CON0	(ELFIN_CLOCK_POWER_BASE + APLL_CON0_OFFSET)
#define REG_MPLL_CON	(ELFIN_CLOCK_POWER_BASE + MPLL_CON_OFFSET)

#define rREG_CLK_SRC0	(*(volatile unsigned int *)REG_CLK_SRC0)
#define rREG_APLL_LOCK	(*(volatile unsigned int *)REG_APLL_LOCK)
#define rREG_MPLL_LOCK	(*(volatile unsigned int *)REG_MPLL_LOCK)
#define rREG_CLK_DIV0	(*(volatile unsigned int *)REG_CLK_DIV0)
#define rREG_APLL_CON0	(*(volatile unsigned int *)REG_APLL_CON0)
#define rREG_MPLL_CON	(*(volatile unsigned int *)REG_MPLL_CON)

*(volatile unsigned int *)REG_CLK_SRC0 这个解引用的C语言技巧还是很值得学习的。

心得

终于自己完完全全复述了一遍,真的有点累,回看之后发现原来也就这么点知识,一下子就看完了,但是在编写记录的过程中确实使得印象加深了不少。

发布了38 篇原创文章 · 获赞 1 · 访问量 1028

猜你喜欢

转载自blog.csdn.net/qq_40897531/article/details/104350625