标签:arm裸机
汇编实现时钟设置代码理解
下面的笔记是我在看《朱老师物联网大讲堂》(www.zhulaoshi.org)之后所做的笔记,只是大概根据自己看了视频与朱老师上课做的笔记而有的理解记录下来。
写了
有代码的,要把代码给理解完整。
朱老师的随堂程序是:clock.s
// 时钟控制器基地址
#define ELFIN_CLOCK_POWER_BASE0xE0100000
// 时钟相关的寄存器相对时钟控制器基地址的偏移值
#define APLL_LOCK_OFFSET0x00
#define MPLL_LOCK_OFFSET0x08
#define APLL_CON0_OFFSET0x100
#define APLL_CON1_OFFSET0x104
#define MPLL_CON_OFFSET0x108
#define CLK_SRC0_OFFSET0x200
#define CLK_SRC1_OFFSET0x204
#define CLK_SRC2_OFFSET0x208
#define CLK_SRC3_OFFSET0x20c
#define CLK_SRC4_OFFSET0x210
#define CLK_SRC5_OFFSET0x214
#define CLK_SRC6_OFFSET0x218
#define CLK_SRC_MASK0_OFFSET 0x280
#define CLK_SRC_MASK1_OFFSET 0x284
#define CLK_DIV0_OFFSET0x300
#define CLK_DIV1_OFFSET0x304
#define CLK_DIV2_OFFSET0x308
#define CLK_DIV3_OFFSET0x30c
#define CLK_DIV4_OFFSET0x310
#define CLK_DIV5_OFFSET0x314
#define CLK_DIV6_OFFSET0x318
#define CLK_DIV7_OFFSET0x31c
#define CLK_DIV0_MASK0x7fffffff
// 这些M、P、S的配置值都是查数据手册中典型时钟配置值的推荐配置得来的。
// 这些配置值是三星推荐的,因此工作最稳定。如果是自己随便瞎拼凑出来的那就要
// 经过严格测试,才能保证一定对。
#define APLL_MDIV 0x7d// 125
#define APLL_PDIV 0x3
#define APLL_SDIV 0x1
#define MPLL_MDIV0x29b// 667
#define MPLL_PDIV0xc
#define MPLL_SDIV0x1
#define set_pll(mdiv, pdiv, sdiv)(1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VALset_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VALset_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)
.global clock_init
clock_init:
ldrr0, =ELFIN_CLOCK_POWER_BASE
// 1 设置各种时钟开关,暂时不使用PLL
ldrr1, =0x0
// 芯片手册P378 寄存器CLK_SRC:Select clock source 0 (Main)
strr1, [r0, #CLK_SRC0_OFFSET]
// 2 设置锁定时间,使用默认值即可
// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
ldrr1,=0x0000FFFF
strr1,[r0, #APLL_LOCK_OFFSET]
str r1, [r0, #MPLL_LOCK_OFFSET]
// 3 设置分频
// 清bit[0~31]
ldr r1, [r0, #CLK_DIV0_OFFSET]
ldrr2, =CLK_DIV0_MASK
bicr1, r1, r2
ldrr2, =0x14131440
orrr1, r1, r2
strr1, [r0, #CLK_DIV0_OFFSET]
// 4 设置PLL
// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
ldrr1, = APLL_VAL
strr1, [r0, #APLL_CON0_OFFSET]
// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
ldrr1, = MPLL_VAL
strr1, [r0, #MPLL_CON_OFFSET]
// 5 设置各种时钟开关,使用PLL
ldrr1, [r0, #CLK_SRC0_OFFSET]
ldrr2, =0x10001111
orrr1, r1, r2
strr1, [r0, #CLK_SRC0_OFFSET]
movpc, lr
(下面就是对这一个代码的理解)
对clock.s这一个汇编代码的理解
这一段代码的第一部分:设置各种时钟开关,暂时不用PLL
在前面的课程里面有对于时钟框图的解释,像在文档的3.2的Figure3.3的左上角的部分有画着这么样的东西,初始时钟从左边过来,然后有两条路走,一条是通过PLL,另一条是直接连到一个MUX开关,所以我们设置初始时钟而且不用PLL的时候我们做的就是选MUX开关来达到只设置时钟不通过(绕过)PLL。这样子的话就要去设置寄存器控制选通位了。
代码:
ldrr0, =ELFIN_CLOCK_POWER_BASE
ldrr1, =0x0
strr1, [r0, #CLK_SRC0_OFFSET]// 芯片手册P378 寄存器CLK_SRC:Select clock source 0 (Main)
解释说到上面的代码:
上往下数第一句
这一句是把ELFIN_CLOCK_POWER_BASE这个值赋给r0寄存器,为什么要赋这一个值呢?
可以从这一段汇编函数开始的更前面看起,这一个“ELFIN_CLOCK_POWER_BASE”是一个宏定义就是相当把ELFIN_CLOCK_POWER_BASE与0xE0100000等值起来了(就是用到ELFIN_CLOCK_POWER_BASE的时候就是相当于在用0xE0100000这个值)。
为什么要把这个值在前面给宏定义出来呢?
从数据手册的3.7 REGISTER DESCRIPTION看到关于这个时钟的寄存器地址都是从0xE0100000这个地址开始,这个就是基地址,其他的地址都是相对着这一个地址偏移多少的量,通过一个计算,就是用基址地址加上偏移的量就可以找到我们想要设置的寄存器了,
如何通过上述所说的来给目标寄存器写东西呢?
代码的第三句有一个CLK_POWER_BASE,这个东西就是偏移量啦,但这是一堆字母没看到量啊,其实就是想上面的ELFIN_CLOCK_POWER_BASE一样,在前面已经给宏定义一个值了。这一个值是0x200,因为我们要写的寄存器是距离0xE0100000有0x200,我们就依据这个东西来找寄存器,并且写入东西。写入的值是0x0.就是这一个寄存器全部写零。 为什么写入0xE0100200这一个寄存器呢?
这个地址所对应的寄存器的名称是:CLK_SRC0,首先去看一下这个寄存器是干嘛的,在手册的378页有这个寄存的介绍与如何设置(3.7.3.1 Clock Source Control Registers)。为什么要设置这个寄存器,首先就要看我们刚开始的目的是什么,我们想干嘛。我们是想“设置各种时钟开关,暂时不用PLL”,那么我们怎么去实现呢,那就要看一下,哪一个寄存器能够满足我们的要求了。看一下这一寄存器的功能是,数据手册上面写的什么Clock Source Control,就是时钟源控制的寄存器,我想的是这一个寄存器就是控制时钟源的来源,从图上面看的是设置晶振与时钟发生器产生的时钟是否是要经过PLL,这样子就可以满足我们的目的要求了,我们的目的就是不要PLL,晶振与时钟产生多少的频率就向右传多 少频率,那么我们就是要使它不使用经过PLL的频率好。
为什么传入0x0:
从手册上面看到这一个寄存器的初始值(默认值都是选择0的)就是都是刚开始默认不用PLL倍频过后的程序。这个的话与上面的目标相符。这里重点看这几个:
1、VPLL_SEL
2、EPLL_SEL
3、MPLL_SEL
4、APLL_SEL
这几个控制位都是赋值零的。就是都选择FINOUT,这么选,貌似是因为s5pv210这个板子上面只焊了一个时钟发生器。这个可供其他使用了,这样子的话都选择了0,那么就是都不经过PLL的了,这样的话就是不倍频了。
代码最后一段:
这个是按照变址寻址来做的,就是把r1的值写入到r0+CLK_SRC0_OFFSET这一个地址里面去(因为是统一编址,这个是个寄存器地址)。
到此,设置各种时钟开关,暂时不用PLL的问题解决了。
第二步:设置锁定时间,使用默认值即可。
为什么要设置锁定时间,我的认为,因为锁相环(PLL)想要初始时钟(24MHz)在瞬间完成倍频到1G是不可能的,它需要低频率在PLL里面回环转,一次次频率的升高来达到1G这样子的话就是是需要时间的,所以等一会儿,好了就可以了,在文档(3.7.2 PLL CONTROL REGISTERS)上面是说当输入的频率频率改变或者分频的值改变是得锁定时间。锁定时间的长短基于PLL的源时钟,用PLL_LOCK这一个寄存器去设置,
ldrr1,=0x0000FFFF
strr1,[r0, #APLL_LOCK_OFFSET]
str r1, [r0, #MPLL_LOCK_OFFSET]
为什么写入0x0000FFFF
这个寄存器只有低十六位可以用,最大值就是0x0000FFFF,值设置越大,锁定的时间就越就久。默认值是0x00000FFF,设置这一个最大值也没事,也就是隔久一点而已。时间超过刚好值的话那时候已经是好了。
有两个寄存器,就是锁好两个APLL和MPLL两个倍频器。就是这样。
第三步:设置分频
设置分频系统,由它决定给左边的分多少倍从而得到右边的频率,说得清楚一些就是左边的频率除以一个分频器可以接受的值,然后得到的值输出到右边。我们要做的就是设置分频系数,说清楚些就是设置那一个除数。设置的寄存器是CLK_DIV
如何设置呢?
视频上面用说的方式给清楚地说出了如何去设置,在代码中那个值是什么意思了。
直接用代码中的设置值来分析:
ldr r2, =0x14131440
给r2所指代的寄存器写入0x14131440,从文档的3.7.4.1 Clock Divider Control Register这一部分看。所写入的数据的第一个1,是30:28位的,在关于这一个寄存器的设置描述栏中写的是公式PCLK_PSYS = HCLK_PSYS / (PCLK_PSYS_RATIO + 1),我们写的1,那就相当于给PCLK_PSYS_RATIO的值赋了一个1,这样子的话下面的除数就是2.因为1+1等于2,PCLK_PSYS和HCLK_PSYS要从文档上面3.4 CLOCK GENERATION的那个3.3图中找,可以找到在右下角的PSYS域中的HCLK_PSYS和PCLK_PSYS,描述栏的公式意思是PCLK_PSYS的时钟频率是通过HCLK_PSYS的频率除以2得到的。
代码的理解:
ldr r1, [r0, #CLK_DIV0_OFFSET]
ldrr2, =CLK_DIV0_MASK
bicr1, r1, r2
ldrr2, =0x14131440
orrr1, r1, r2
strr1, [r0, #CLK_DIV0_OFFSET]
//忽然发现我理解不了,方法是往哪个寄存器写东西?为什么往那里写,为什么写//0x14131440?(漫长)
第四步:设置PLL
这个地方也是要学会看文档。(可见有会看文档的功力也是很重要的)
讲的是APLL和MPLL,这两个PLL我们可以在文档的361页可以看到图。
要对它做什么?
对他设置合适的参数,然后lock,然后等待输出。
怎么设置参数:
找文档,现在设置的是APLL,找啊找,在文档的372,上面有个APLL_CON0,看这一页的寄存器位的介绍,首先要看一个公式:
FOUT = MDIV X FIN / (PDIV × 2^(SDIV-1))
这个公式是与寄存位的介绍配合起来看的,想知道在公式上面的变量怎么设置看寄存器位的介绍表中的MDIV[25:16]、PDIV[13:8]、SDIV[2:0]这几位。这几个很重要。
公式中的MDIV值就是设置寄存器中的25:26位的值,FIN的值是左边的频率进PLL的值,比如在文档上面“3.4 CLOCK GENERATION”部分的那个图中看左上角的APLL地方的左边写着有PLL这就是FIN。FOUT同理可以理解。PDIV就是我们设置的PDIV(所在寄存器的位是13:8)的值,SDIV是就是设置的SDIV所在寄存器的位是(2:0)的值。
例:在文档中给定的初始值是:MDIV是0xc8(对应的10进制是200),PDIV是0x3(对应的10进制是3),SDIV是0x1(对应的10进制是1),对于APLL的话FIN是24MHz把这些值代入上式得FOUT=24*200/3*2^0=1600,这是文档上面一个想不到的东西,因为我们要设置的是1GHz,但是按照这一个初值来设置的话就变成了1.6GHz了。在程序中的数值与上述的数值不一样的是MDIV的值,这个值应该设置成0x7d(对应的10进制是125)。关于这些经典值可以到文档的357页的3.3.1RECOMMENDED PLL PMS VALUE FOR APLL可以得到三星给的推荐值。
但是在程序中计算FOUT的值有点让人眼前一新(自己没有碰到过的,在朱老师的C高级里面有讲位运算):
#define set_pll(mdiv, pdiv, sdiv)(1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VALset_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VALset_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)
下面要讲的是MPLL:
MPLL的设置方法与APLL是一样的,只是公式不一样而已。
第五步:设置各种时钟开关,使用PLL
从代码来看:
ldrr1, [r0, #CLK_SRC0_OFFSET]
ldrr2, =0x10001111
orrr1, r1, r2
strr1, [r0, #CLK_SRC0_OFFSET]
这里要理解的是0x10001111的问题,在CLK_SRC0寄存器中写入这一个值,为什么要写入这一个值。
这里又要配合着文档上面的《3.7.3.1 Clock Source Control Registers》这一部分来查看开了使用了哪一个东西,之后去配合《3.4 CLOCK GENERATION》这一部分的图后,结合之前学过的xPLL与DIV的使用原理,可以算出在CLK_SRC0之后,频率经过什么样的路径,最后到达使用的部件的频率是多少。
刚刚才入ARM裸机这水,还不深,其中肯定会有疏忽和错误,如果看了这篇笔记发现了错误,请指出,谢谢。
标签:arm裸机
原文地址:http://10899496.blog.51cto.com/10889496/1726631