以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考内容
一、SoC时钟系统的简介
1、时钟的含义与作用
时钟是同步工作系统的同步节拍。SoC内部有很多器件,比如CPU、串口、DRAM控制器、GPIO等内部外设,这些器件要彼此协同工作,需要一个同步的时钟系统来指挥,即SoC的时钟系统。
2、获取时钟信号的途径
一般有以下三种途径获得时钟信号:
(1)外部直接输入时钟信号。SoC有个引脚用来输入外部时钟信号。这种方式用得很少。
(2)外部晶振+内部时钟发生器。大部分低频单片机使用这种方式产生时钟信号。
(3)外部晶振 + 内部时钟发生器 + 内部PLL产生高频时钟 + 内部分频器分频。
3、时钟和系统性能的关系
一般情况下SoC的时钟频率可以人为编程控制,而SoC时钟频率的高低对系统性能有很大影响。S5PV210建议工作频率800MHz~1.2GHz,通常设置为1GHz。如果设置大于1.2GHz则叫超频,这时候系统性能会提升,但是发热也会增大,影响系统稳定性。
4、时钟和外设编程的关联
每个外设工作都需要一定频率的时钟信号,这些时钟信号都是由时钟系统提供的。可以通过编程来控制时钟系统的工作模式,程序员可以为每个外设指定时钟来源、时钟分频系统。
5、时钟和功耗控制的关系
SoC中各种设备工作时,时钟频率越高,其功耗越大、发热越大、越不稳定,需要外部的散热条件越苛刻。SoC内部有很多外设,为了降低功耗,这些外设不用的时候最好关掉。外设的开与关是通过时钟来设置的,当我们给某个外设断掉时钟,则这个外设就不会工作。
二、S5PV210的时钟系统简介
此部分内容在用户手册的section 02_system:3 CLOCK CONTROLLER 。
1、三个时钟域
S5PV210有很多内部外设,它们的工作时钟速率差异很大,因此有必要按照时钟频率的高低,将整个内部的时钟划分为以下三个域(把高速的放一起,相对低速的放一起):
(1)MSYS(main system)
这个域主要为CPU、DRAM控制器(DMC0与DMC1)、IRAM与IROM等模块提供时钟。
(2)DSYS(display system)
这个域主要为音视频编解码相关的模块提供时钟。
(3)PSYS(peripheral system)
这个域主要为内部外设提供时钟,比如GPIO、I2C、UART、WDT、SD接口、USB等模块。
2、时钟来源
在上面第一节中我们讲过,时钟来源有三种方式,而S5PV210的时钟来源方式是:外部晶振 + 内部时钟发生器 + 内部PLL产生高频时钟 + 内部分频器分频。
有人可能会问,为什么不直接采用高频晶振产生高频信号给CPU?主要是因为芯片外部电路不适宜使用高频率,因为传导辐射比较难控制;另外高频晶振价格昂贵。
为什么内部先高频然后再分频?主要是因为SoC内部有很多模块都需要时钟,各模块所需要的时钟频率不同,无法统一供应。因此设计思路是先 PLL得到一个最高的频率(1GHz或1.2GHz),然后各外设根据自己想要的频率来设置自己的分频器。
接下来将详细分析S5PV210的时钟来源。
(1)S5PV210时钟体系框图
S5PV210时钟体系框图位于用户手册第361与362页,即下面的两张图。
第一张图从左到右依次完成了原始时钟生成、PLL倍频得到高频时钟、初次分频得到各总线时钟。第二张图是从各中间时钟(第一张图中某个步骤生成的时钟)得到各外设自己使用的时钟(实际就是个别外设自己再额外分频的设置)。可见第一张图是理解整个时钟体系的关键,第二种图是进一步分析各外设时钟来源的关键。
(2)四个晶振
S5PV210外部有4个晶振接口,设计板子时可以根据需要来决定选取哪个晶振。接了晶振之后上电,相应的模块就能产生振荡,产生原始时钟。
(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 |
(4)MUX开关
MUX 开关是一个多路选择开关,其实就是一个或门。我们可以通过设置某个寄存器的某几个 bit 位,来决定选取哪条通道。具体设置细节见下面第三节的第3点内容。另外我们通过分析这个 MUX 开关,可以知道右侧的时钟是从左侧哪条路过来的,从而知道右侧的时钟大小是多少。
(5)DIV分频器
DIV 分频器是一个硬件设备,可以对左边的频率进行n分频后输出到右边。我们可以通过设置某个寄存器的某些bit位,来设置分频器的分频系数。比如左边进来的时钟是80MHz,如果分频系统设置为8,则分频器右边输出的时钟频率为10MHz。具体设置细节见下面第三节的第4点内容。
3、默认的时钟典型值
当X210开发板刚上电时,“外部晶振+内部时钟发生器” 产生24MHz的时钟,供给ARMCLK,这时系统的主频就是24MHz,运行非常慢。IROM代码执行时,第6步会初始化时钟系统(见上图),从而给系统各模块提供默认的时钟频率,这些时钟频率是三星推荐的S5PV210各模块工作性能和稳定性最佳的频率,如下表所示:
时钟域 | 时钟信号 | 典型频率 | 描述 |
MSYS | ARMCLK | 1000 MHz | CPU工作的时钟,即所谓的主频 |
HCLK_MSYS | 200 MHz | MSYS域的高频时钟 | |
PCLK_MSYS | 100 MHz | MSYS域的低频时钟 | |
HCLK_IMEM | 100 MHz | 给iROM和iRAM(合称iMEM)使用的时钟 | |
SCLK_DMC0 | |||
DSYS | HCLK_DSYS | 166 MHz | DSYS域的高频时钟 |
PCLK_DSYS | 83 MHz | DSYS域的低频时钟 | |
PSYS | HCLK_PSYS | 133 MHz | PSYS域的高频时钟 |
PCLK_PSYS | 66 MHz | PSYS域的低频时钟 | |
SCLK_ONENAND | 133 MHz, 166 MHz |
这里的默认,是指已经将相应的设置写死在IROM中的代码里,IROM代码开始执行时,就会产生上面这些默认的时钟典型值。我们这里之所以还进行讨论与设置,只不过是为了学习如何设置时钟而已;另外在 uboot 中,对时钟部分也进行了初始化,其实也是多此一举。
为了学习,我们后续将根据这些推荐的时钟频率,重新设置多路选择开关、锁相环、分频器的值。
三、时钟设置有关的寄存器
这部分内容在用户手册第367页,有很多寄存器,其中最重要的寄存器有3类:
- xPLL_CONn 寄存器:决定PLL倍频到多少。
- CLK_SRCn 寄存器:决定MUX开关选择哪一条路线。
- CLK_DIVn 寄存器:决定分频多少。
1、xPLL_LOCK寄存器(控制锁定周期)
(1)寄存器的作用
锁相环PLL倍频需要一定时间才能达到相应的频率。
xPLL_LOCK寄存器,主要用来控制锁相环PLL锁定周期。
其设置一般为默认值,官方推荐值为0xFFF,因此我们设置为0xFFFF。
(2)寄存器的地址
(3)寄存器的设置
2、xPLL_CONn寄存器(PLL的 开关、设置倍频系数)
(1)寄存器的作用
这些寄存器用来打开或关闭PLL电路,设置倍频参数,查看PLL锁定状态等。
(2)寄存器的地址
(3)寄存器的设置
以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。
对于锁相环ALL,S5PV210推荐的倍频输出情况如下:
(4)各锁相环推荐输出值
下面是各锁相环推荐的输入输出值,可见输入都是24MHz(这是“外部晶振+内部时钟发生器” 产生的24MHz的时钟),输出由MDIV、PDIV、SDIV这个内容决定。
另外各个锁相环还有其他的输入输出情况,这里不再列出。
3、CLK_SRCn寄存器(选择MUX哪路)、CLK_SRC_MASKn寄存器(使能MUX开关)
(1)寄存器的作用
这里的n=0,1,2,3,4,5,6。
CLK_SRCn寄存器,主要用来设置MUX开关(多路选择开关),即选择哪一个作为输入。
CLK_SRC_MASKn寄存器,用来决定 MUX 开关是否使能。如果没有使能,信号不能从MUX开关输出。不过默认都是使能的。
(2)寄存器的设置
以CLK_SRC0寄存器为例,各bit的含义如下。
注意,我们在编写代码的时候,一般首先要关闭4个PLL后面的MUX开关,设置好相应寄存器以后再把这4个MUX开关打开。
4、CLK_DIVn寄存器(设置分频系数)
(1)寄存器的作用
CLK_DIVn寄存器,主要用来设置各个分频器的值。
(2)寄存器的设置
以CLK_DIV0寄存器为例,各bit的含义如下。
比如将CLK_DIV0寄存器的内容设置为0x14131440,分析得知这个值表示的意思如下。
首先0x14131440=0b0001_0100_0001_0011_0001_0100_0100_0000
- PCLK_PSYS = HCLK_PSYS / (1+1)
- HCLK_PSYS = MOUT_PSYS / (4+1)
- PCLK_DSYS = HCLK_DSYS / (1+1)
- HCLK_DSYS = MOUT_DSYS / (3+1)
- PCLK_MSYS = HCLK_MSYS / (1+1)
- HCLK_MSYS = ARMCLK / (4+1)
- ……
- ARMCLK = MOUT_MSYS / (0+1)
5、CLK_GATE_x寄存器
类似于CLK_SRC_MASK,对时钟进行开关控制。
6、CLK_DIV_STATn、CLK_MUX_STATn寄存器
这两类状态位寄存器,用来查看DIV和MUX的状态:已经完成、在进行中。
四、代码实战
这里的代码实践,是指初始化时钟系统,即按照S5PV210推荐的各时钟典型值来进行时钟系统的初始化。初始化完成后,各时钟产生与典型值一样频率的时钟信号。
1、编程步骤
第1步:在MUL开关处选择不使用PLL,让外部24MHz原始时钟直接过去。
第2步:设置锁相环锁定时间。默认值为0x0FFF,保险起见我们设置为0xFFFF。
第3步:设置分频系统,决定(由PLL出来的)最高时钟如何分频得到各个分时钟。
第4步:设置PLL,主要是设置PLL的倍频系统,决定由输入端24MHz的原始频率可以得到多大的输出频率。我们按照默认设置值设置输出为ARMCLK为1GHz。
第5步:打开PLL。前面4步已经设置好了所有的开关和分频系数,本步骤打开PLL后PLL开始工作,锁定频率后输出,然后经过分频得到各个频率。
2、代码实践
注意,下面的代码只设置了APLL和MPLL两个锁相环的倍频,其他两个锁相环没有管。
(1)主流程文件start.S文件
#define WTCON 0xE2700000
#define SVC_STACK 0xd0037d80
.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
// 第1步:关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2步:初始化时钟
bl clock_init
// 第3步:设置SVC栈
ldr sp, =SVC_STACK
// 第4步:开/关icache
mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中
//bic r0, r0, #(1<<12) // bit12 置0 关icache
orr r0, r0, #(1<<12) // bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;
// 从这里之后就可以开始调用C程序了
bl led_blink // led_blink是C语言实现的一个函数
// 汇编最后的这个死循环不能丢
b .
(2)clock_init.c文件(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;
}
或者用汇编实现时钟系统的初始化:clock_init.S文件
// 时钟控制器基地址
#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