以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考内容
s5pv210——初始化时钟 - biaohc - 博客园
用户手册
一、SoC时钟系统
1、时钟的含义与作用
时钟是同步工作系统的同步节拍。
SoC内部有很多器件,比如CPU、串口、DRAM控制器、GPIO等内部外设,这些器件要彼此协同工作,需要一个同步的时钟系统来指挥,即SoC的时钟系统。
2、获取时钟信号的途径
一般有以下三种途径获得时钟信号:
(1)外部直接输入时钟信号。SoC有个引脚用来输入外部时钟信号。这种方式用得很少。
(2)外部晶振+内部时钟发生器。大部分低频单片机使用这种方式产生时钟信号。
(3)外部晶振+内部时钟发生器+内部PLL产生高频时钟+内部分频器分频。
S5PV210采用第三种方式。
为什么不直接采用高频晶振产生高频信号给CPU?主要是因为芯片外部电路不适宜使用高频率,因为传导辐射比较难控制;另外高频晶振价格昂贵。为什么内部先高频然后再分频?主要是因为SoC内部有很多部件都需要时钟,各部件所需要的时钟频率不同,无法统一供应。因此设计思路是先 PLL得到一个最高的频率,然后各外设根据自己想要的频率来设置自己的分频器。
3、时钟和系统性能的关系
一般情况下SoC的时钟频率可以人为编程控制,而SoC时钟频率的高低对系统性能有很大影响。S5PV210建议工作频率800MHz~1.2GHz,通常设置为1GHz。如果设置大于1.2GHz则叫超频,这时候系统性能会提升,但是发热也会增大,影响系统稳定性。
4、时钟和外设编程的关联
每个外设工作都需要一定频率的时钟信号,这些时钟信号都是由时钟系统提供的。可以通过编程来控制时钟系统的工作模式,程序员可以为每个外设指定时钟来源、时钟分频系统。
5、时钟和功耗控制的关系
SoC中各种设备工作时,时钟频率越高,其功耗越大、发热越大、越不稳定,需要外部的散热条件越苛刻。SoC内部有很多外设,为了降低功耗,这些外设不用的时候最好关掉。外设的开与关是通过时钟来设置的,当我们给某个外设断掉时钟,则这个外设就不会工作。
二、S5PV210的时钟系统
此部分内容在用户手册的section 02_system:3 CLOCK CONTROLLER 。
1、时钟域:MSYS、DSYS、PSYS
S5PV210的内部外设模块太多,这些模块的工作时钟速率差异很大,因此有必要按照时钟频率的高低,将整个内部的时钟划分为以下三个域。
(1)MSYS(main system)
这个域主要为CPU、3D、intc(中断)、DMC0、DMC1、IRAM、IROM提供时钟。
这个域主要包含以下时钟类型:
- ARMCLK:CPU工作的时钟,即所谓的主频,1000MHZ
- HCLK_IMEM:给iROM和iRAM(合称iMEM)使用的时钟,100MHZ
- HCLK_MSYS:MSYS域的高频时钟,200MHZ
- PCLK_MSYS:MSYS域的低频时钟,100MHz
(2)DSYS(display system)
这个域主要为音视频等设备提供时钟。
这个域主要包含以下时钟类型:
- HCLK_DSYS:DSYS域的高频时钟,166MHZ
- PCLK_DSYS:DSYS域的低频时钟,83MHz
(3)PSYS(peripheral system)
这个域主要为外设提供时钟,如GPIO、I2C、PWM、UART、WDT、SD接口、USB等
这个域主要包含以下时钟类型:
- HCLK_PSYS:PSYS域的高频时钟,133MHZ
- PCLK_PSYS:PSYS域的低频时钟,66MHz
- SCLK_ONENAND
分析可知,S5PV210内部外设都接在内部AMBA总线上面,而AMBA总线有1条高频分支叫AHB,有一条低频分支叫APB。各个时钟域的HCLK_XXX,就是XXX这个域中AHB总线的工作频率;各个时钟域的PCLK_XXX,就是XXX这个域中APB总线的工作频率。SoC内部的各个外设其实是挂在总线上工作的,也就是说这个外设的时钟来自于它所挂在的总线,比如串口挂在PSYS域下的APB总线上,因此串口的时钟来源是PCLK_PSYS。
2、时钟来源:晶振+时钟发生器+PLL+分频电路
(1)S5PV210时钟体系框图
S5PV210时钟体系框图位于用户手册第361与362页。
上面两张图之间是渐进的关系。第一张图从左到右依次完成了原始时钟生成、PLL倍频得到高频时钟、初次分频得到各总线时钟。第二张图是从各中间时钟(第一张图中某个步骤生成的时钟)得到各外设自己使用的时钟(实际就是个别外设自己再额外分频的设置)。可见第一张图是理解整个时钟体系的关键,第二种图是进一步分析各外设时钟来源的关键。
(2)MUX开关
MUX开关即多路选择开关,其实就是一个或门,对应某个寄存器的某几个bit位的设置,设置值决定了选取哪条通道。通过分析这个可以知道右边的时钟是从左边哪条路过来的,从而知道右边时钟是多少。见寄存器介绍的第3点。
(3)DIV分频器
DIV分频器,是一个硬件设备,可以对左边的频率进行n分频后输出到右边。分频器在编程时实际对应某个寄存器中的某几个bit位,我们可以通过设置这个寄存器的这些对应bit位来设置分频器的分频系数(比如左边进来的时钟是80MHz,分频系统设置为8,则分频器右边输出的时钟频率为10MHz)。见寄存器介绍的第4点。
(4)四个晶振
S5PV210外部有4个晶振接口,设计板子时可以根据需要来决定选取哪个晶振。接了晶振之后上电,相应的模块就能产生振荡,产生原始时钟。
晶振 描述 XRTCXTI
- 提供32.768KHz,RTC 使用
- XRTCXTI and XRTCXTO pins
XXTI
- CMU and PLL使用这个时钟,为APLL、MPLL、VPLL、EPLL提供时钟
- 推荐频率24MHz
- XXTI and XXTO pins
XUSBXTI
- 为APLL、MPLL、VPLL、EPLL、USB PHY提供时钟
- 推荐24MHz
- XUSBXTI and XUSBXTO pins
XHDMIXTI
- 27MHz,VPLL、HDMI PHY为TV解码器提供54MHz时钟
- XHDMIXTI and XHDMIXTO pins
(3)四个锁相环
原始时钟经过一系列的筛选开关进入相应的PLL电路,生成倍频后的高频时钟。
锁相环 描述 APLL APLL generates ARM core and MSYS clocks MPLL MPLL generates a system bus clock and special clocks VPLL VPLL generates clocks for video interface EPLL EPLL generates special clocks
3、默认的时钟典型值
当X210开发板刚上电时,外部晶振+内部时钟发生器产生24MHz的时钟,供给ARMCLK,这时系统的主频就是24MHz,运行非常慢。IROM代码执行时,第6步中初始化了时钟系统,从而给系统提供了一个默认的时钟频率。这个时钟频率是三星推荐的S5PV210工作性能和稳定性最佳的频率。
时钟信号 频率 freq(ARMCLK) 1000 MHz freq(HCLK_MSYS) 200 MHz freq(HCLK_IMEM) 100 MHz freq(PCLK_MSYS) 100 MHz freq(HCLK_DSYS) 166 MHz freq(PCLK_DSYS) 83 MHz freq(HCLK_PSYS) 133 MHz freq(PCLK_PSYS) 66 MHz freq(SCLK_ONENAND) 133 MHz, 166 MHz 各时钟的典型值,或者说推荐S5PV210使用的时钟频率,如上所示。我们后续将根据这些推荐的时钟频率,来设置多路选择开关(MUX)、锁相环(PLL)、分频器(DIV)的值。
三、时钟设置有关的寄存器
这部分内容在用户手册第367页。其中最重要的寄存器有3类:xPLL_CONn寄存器、CLK_SRCn寄存器、CLK_DIVn寄存器。其中xPLL_CONn寄存器决定PLL倍频到多少,CLK_SRCn寄存器决定走哪一条路,CLK_DIVn寄存器决定分频多少。
1、xPLL_LOCK寄存器
(1)寄存器的作用
锁相环PLL倍频需要一定时间才能达到相应的频率。xPLL_LOCK寄存器,主要用来控制锁相环PLL锁定周期,其设置一般为默认值,官方推荐值为0xFFF,我们设置为0xFFFF。
(2)寄存器的地址
(3)寄存器的设置
2、xPLL_CONn寄存器
(1)寄存器的作用
这些寄存器用来打开或关闭PLL电路,设置倍频参数,查看PLL锁定状态等。
(2)寄存器的地址
(3)寄存器的设置(以APLL_CON0寄存器为例)
APLL_CON0寄存器各bit的含义如下。
通过对APLL_CON0寄存器进行设置,可以设定锁相环APLL的倍频输出。
其计算公式为:FOUT = MDIV X FIN / (PDIV × 2^(SDIV)-1)。
比如锁相环的输入是24MHz,倍频后要输出1000MHZ,则1000=125*24 / (3*2^1-1)。
也就是说,MDIV要设为125,PDIV要设为3,SDIV要设为1。
对于锁相环APLL,其MDIV, PDIV, SDIV 的约束条件如下:
PDIV: 1 ≤ PDIV ≤ 63
MDIV: 64 ≤ MDIV ≤ 1023
SDIV: 1 ≤ SDIV ≤ 5
Fref (=FIN / PDIV): 1MHz ≤ Fref ≤ 12MHz
FVCO (=2 × MDIV × FIN / PDIV): 1000MHz ≤ FVCO ≤ 2060MHz对于锁相环ALL,S5PV210推荐的倍频输出情况如下:
3、CLK_SRCn寄存器、CLK_SRC_MASKn寄存器
(1)寄存器的作用
这里的n=0,1,2,3,4,5,6。
CLK_SRCn寄存器,主要用来设置MUX开关(多路选择开关),即选择哪一个作为输入。
CLK_SRC_MASKn寄存器,用来决定 MUX 开关是否使能。如果没有使能,信号不能从MUX开关输出。不过默认都是使能的。
(2)寄存器的设置(以CLK_SRC0寄存器为例)
CLK_SRC0寄存器各bit的含义如下。
注意,我们在编写代码的时候,一般首先要关闭4个PLL后面的MUX开关,设置好相应寄存器以后再把这4个MUX开关打开。
4、CLK_DIVn寄存器
(1)寄存器的作用
CLK_DIVn寄存器,主要用来设置各个分频器的值。
(2)寄存器的设置(以CLK_DIV0寄存器为例)
比如将CLK_DIV0寄存器的内容设置为0x14131440,分析得知这个值表示的意思如下。
首先0x14131440=0b0001_0100_0001_0011_0001_0100_0100_0000
- HCLK_PSYS = MOUT_PSYS / 5
- PCLK_PSYS = HCLK_PSYS / 2
- HCLK_DSYS = MOUT_DSYS / 4
- PCLK_DSYS = HCLK_DSYS / 2
- ·······
- HCLK_MSYS = ARMCLK / 5
- ARMCLK = MOUT_MSYS / 1
5、CLK_GATE_x寄存器
类似于CLK_SRC_MASK,对时钟进行开关控制。
6、CLK_DIV_STATn、CLK_MUX_STATn寄存器
这两类状态位寄存器,用来查看DIV和MUX的状态:已经完成、在进行中。
五、代码实战
这里的代码实践,是指初始化时钟系统,即按照S5PV210推荐的各时钟典型值来进行时钟系统的初始化。初始化完成后,各时钟产生与典型值一样频率的时钟信号。
编程步骤
第1步:在MUL开关处选择不使用PLL,让外部24MHz原始时钟直接过去。
第2步:设置锁相环锁定时间。默认值为0x0FFF,保险起见我们设置为0xFFFF。
第3步:设置分频系统,决定(由PLL出来的)最高时钟如何分频得到各个分时钟。
第4步:设置PLL,主要是设置PLL的倍频系统,决定由输入端24MHz的原始频率可以得到多大的输出频率。我们按照默认设置值设置输出为ARMCLK为1GHz。
第5步:打开PLL。前面4步已经设置好了所有的开关和分频系数,本步骤打开PLL后PLL开始工作,锁定频率后输出,然后经过分频得到各个频率。
注意,下面的代码只设置了APLL和MPLL两个锁相环的倍频,其他两个锁相环没有管。
主流程文件:
#define WTCON 0xE2700000 #define SVC_STACK 0xd0037d80.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了 _start:// 第1步:关看门狗(向WTCON的bit5写入0即可)ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:初始化时钟bl clock_init // 第3步:设置SVC栈ldr sp, =SVC_STACK// 第4步:开/关icachemrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中//bic r0, r0, #(1<<12) // bit12 置0 关icacheorr r0, r0, #(1<<12) // bit12 置1 开icachemcr p15,0,r0,c1,c0,0;// 从这里之后就可以开始调用C程序了bl led_blink // led_blink是C语言实现的一个函数// 汇编最后的这个死循环不能丢b .
C语言实现时钟系统的初始化:
#define _REG_APLL_LOCK *((unsigned int*)0xE0100000) #define _REG_MPLL_LOCK *((unsigned int*)0xE0100008) #define _REG_EPLL_LOCK *((unsigned int*)0xE0100010) #define _REG_VPLL_LOCK *((unsigned int*)0xE0100020) #define _REG_APLL_CON0 *((unsigned int*)0xE0100100) #define _REG_MPLL_CON *((unsigned int*)0xE0100108) #define _REG_CLK_SRC0 *((unsigned int*)0xE0100200) #define _REG_CLK_DIV0 *((unsigned int*)0xE0100300)#define APLL_SDIV (1) #define APLL_PDIV (3) #define APLL_MDIV (125) #define APLL_EN (1)#define MPLL_SDIV (1) #define MPLL_PDIV (12) #define MPLL_MDIV (667) #define MPLL_EN (1)void clock_init(void) {//第一步先关闭PLL的MUX开关//即设置MUX开关选择路线0,不选择锁相环的那路,也就相当于关闭PLL_REG_CLK_SRC0 = 0x0;//第二步设置LOCK时间,设置为默认值0x0FFF_REG_APLL_LOCK = 0x0FFF;_REG_MPLL_LOCK = 0x0FFF;_REG_EPLL_LOCK = 0x0FFF;_REG_VPLL_LOCK = 0x0FFF;//第三步设置DIV分频器的值 _REG_CLK_DIV0 = 0x14131400;//第四步设置APLL、MPLL的倍频值。_REG_APLL_CON0 = (APLL_EN<<31) | (APLL_MDIV<<16) | (APLL_PDIV<<8) | (APLL_SDIV<<0);_REG_MPLL_CON = (MPLL_EN<<31) | (MPLL_MDIV<<16) | (MPLL_PDIV<<8) | (MPLL_SDIV<<0);//第五步设置MUX开关,即设置MUX开关选择从PLL出来的那一路_REG_CLK_SRC0 = 0x1111; }
或者用汇编实现时钟系统的初始化:
// 时钟控制器基地址
#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 设置各种时钟开关,暂时不使用PLLldr 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, r2ldr r2, =0x14131440 orr r1, r1, r2str r1, [r0, #CLK_DIV0_OFFSET]// 4 设置PLL// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHzldr r1, =APLL_VAL str r1, [r0, #APLL_CON0_OFFSET]// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHzldr r1, =MPLL_VAL str r1, [r0, #MPLL_CON_OFFSET]// 5 设置各种时钟开关,使用PLLldr r1, [r0, #CLK_SRC0_OFFSET]ldr r2, =0x10001111orr r1, r1, r2str r1, [r0, #CLK_SRC0_OFFSET]mov pc, lr