分类: LINUX
2013-05-25 12:21:14
驱动数字电路运转的是时钟信号,时序电路都需要一个外部时钟信号来驱动,完成计时,同步,计数,时序控制等各种功能。像CPU也是时序信号驱动来完成各种运算的,而且像ARM带的模块大部分都与时序有关,因此理解时钟信号对于底层编程非常重要。
一、时钟信号的源头--晶振
数字电路的时钟信号的来源哪里呢?主流的设计方案是使用时钟这种外部器件来产生稳定的电流波形。这是性价比最高的一个方案,晶振是一块水晶加一些电路的小器材,但是它只需要输入很小的电流就可以持续输出稳定时钟波形。
晶振在数字电路的作用就是提供一个基准时间,数字电路都是按照时序进行工作的,在某个时刻专门完成特定的任务,因此几乎每个电路都有会接外部时钟信号的管脚。如果某个时钟信号发生混乱,整个电路就工作不正常了。在一个整体设备里,如开发板或PC主板,所有电路通常共享一个晶振,便于各部分保持同步。有些通讯系统的基频和射频使用不同的晶振,而通过电子调整频率的方法保持同步。
二、系统主频--内部时钟频率
一般晶振称为外部时钟频率,它需要把信号引入数字电路给CPU和其他模块使用,局限于材料的物理特性,一般的晶振的频率并不是太高。如S5PC100上的晶振频率一般是12MHZ,而对应的CPU需要时钟信号高达667MHZ,或者更高。这个时候,需要把较低外部时钟信号增加到CPU可以接收的频率。这称为倍频。S5PC100的主频最高可达到800MHZ。
锁相环电路
倍频的功能是由一种特殊电路--锁相环电路完成的。锁相环电路(Phase-Locked Loop,缩写PLL),PLL基本上是一个闭环的反馈控制系统,它使PLL的输出可以与一个参考信号保持固定的相位关系。PLL在电路的作用之一是起到倍频的作用。即可以输出系统时钟的固定倍数频率。
因为在ARM CPU启动后,最开始必须做的事情就是配置倍频的比率。这样当输入外部时钟频率一定的情况下,按照倍频的比例,就可以得到CPU的频率。在嵌入式CPU里面,一个系统可能出于不同的母的需要不同的频率运行。低频运算速度慢但是省电,高频运算速度快但耗能大。
每个CPU都有一个最高频,如果强行配置成高于设计频率的速度运行,就是人们称的超频。有可能加速CPU老化,运行时散热增加的问题。
一般为了保险,软件开发都会配成产商给的几种标准频率。
三、设备频率
在SOC上,除了CPU内核以外,在一个物理芯片上,还有一些其他模块,以S5PC100为例,它带了I2C,UART,USB等多个模块,这一些模块通过AMBA总线与CPU内核相连,这一些模块同样需要时钟信号来驱动。
但是ARM的主频信号相对于这一些模块来说,频率显得过高。这个时候像S5PC100中内核会提供两种较低频率的时钟信号。HCLK和PCLK两种时钟信号给设备使用。
divider 分频器
但是对一些低频模块,PCLK的频率仍然显得过高,这时需要模块自己使用分频器(divider)来把频率进一步降低。降到多少值一般取决于软件的需求,因此各个模块的分频参数一般都是可以调整的。因此初始化模块时候,软件做的一件重要事情就是设置分频参数。
Prescaler 预分频因子
有一些模块,如果需要编程来设定分频的比率,通常是用Prescaler即预分频因子这个参数来设定分频后的值,假设输入频率Fin,分频后输出的频率是Fout,而三者有如下关系
Fout = Fin/(Prescaler + 1)
在某一些模块里,分频后的频率仍然是太高,可能需要再次分频,这时分频的参数一般 称为divider value,这样公式变成
Fout = Fin / (Prescaler + 1) / divider
以PWM的定时器的参数为例,它的设置就有如下几个值
以供电系统为例来给这种系统时钟打一个比方
晶振就是发电站,它通过PLL倍频后变成高压电,给CPU传输使用,而模块又使用分频器把高压电降下来给自己使用。
四、S5PC100时钟分析
S5PC100的ARM核心是cortex-A8,核心主频为667MHZ,最高可达800MHZ。
S5PC100的时钟信号可以由外部晶振(XXTI)和外部晶振(XusbXTI),两种方式输入时钟信号。它是由跳线OM[0]决定的,这一位为0 时,由XXTI提供,为1时由XusbXTI提供。
由于外部晶振提供的时钟频率只有12MHZ, 需要通过PLL来倍频。S5PC100提供了4个PLL,分别为:APLL,MPLL,HPLL,EPLL。
APLL给ARM CORE用的,可以产生50MHZ ~ 2GHZ
MPLL主要给系统总线使用,可以产生 12MHZ ~ 600MHZ
EPLL 主要给一些需要特殊频率设备使用(audio),12MHZ ~ 600MHZ
HPLL 主要给HDMI使用,可以产生27MHZ ~ 600MHZ
S5PC100 的时钟系统比较复杂,给一个模块提供的时钟,可以由多条路径提供,我们在实际操作的时候,只需要选择其中的一条路径即可。
这里以配置S5PC100的核ARMCLK为例,讲解S5PC100时钟配置的套路。
下图是从S5PC100的DATASHEET中截取的部分时钟框图。
由上图中红色线标识所示:
图中MUX为多路复用器,如MUXAPLL,将其值设为0,选择 FINAPLL输入(没有倍频),将其值设为1选择FOUTAPLL(APLL倍频)输入。DIV为分频器。
ARMCLK的时钟可以配置由XXTI输入->APLL倍频->DIVAPLL分频->DIVARM分频->ARMCLK。
以此类推,如果想得到HCLK1的时钟,可以由哪几种选择呢?读者可以自己思考。
下面我们以一个实例来配置ARM核的时钟和总线的时钟,配置的具体时钟频率由下图所示。
S5PC100的时钟系统分为三个域:D0,D1,D2。不同的域给不同的模块提供时钟。在这里我们只关心D0域和D1域。
如上图所示,现在想把ARMCLK配置成667MHZ,HCLK0配置成166MHZ,PCLK0配置成83MHZ,HCKL1配置成133MHZ,PCLK1配置成66MHZ。
第一步:选择路径
XXTI>APLL->DIVAPLL->DIVARM->ARMCLK
XXTI->APLL->DIVAPLL->DIVARM->DIVD0_BUS->HCLK0
XXTI->APLL->DIVAPLL->DIVARM->DIVD0_BUS->DIVPCLK0->PCLK0
关于HCLK1和PCLK1的时钟路径在选择的时候就都有两条:
其中一条是APLL倍频之后,然后由DIVAM分频后提供的。在这里我们不选择这一条路径。为什么呢?在S5PC100的datasheet中明确说明,DIVAM最高能承受的时钟信号是667MHZ,图中也有标明。由于我们给ARMCLK的时钟是667MHZ,在达到之前还需要通过分频器分频,所以APLL倍频后的时钟频率在这里是要大于667MHZ的。
XXTI->MPLL->DIVD1_BUS->HCLK1
XXTI->MPLL->DIVD1_BUS->DIVPCLK->PCLK1
第二步:配置相关的寄存器(倍频器(PLLCON),分频器(DIV),时钟源选择器(MUX))
在这里我说一下ARMCLK,HCLK0,PCLK0的配置方法,大家可以自己按照datasheet配置一下HCLK1,PCLK1。
XXTI>APLL->DIVAPLL->DIVARM->ARMCLK
XXT1的输入时钟是12MHZ,首先要配置APLL。查看datasheet:
(1)PLL需要屏蔽一段时间,以达到输出稳定的倍频时钟。这个和你开车一样,并不是一下就可以提到你想要的车速,需要一段时间的国度,才可以达到你想要的车速。
需要屏蔽的时间从手册上可以知道,如果输入的是12MHZ,需要屏蔽300us,也就是将PLL_MASKTIME设置成0xE10。
(2)设置倍频值
PLL的倍频值由MDIV,PDIV,SDIV这三个倍频因子决定,即给他们不同的值我们得到不同的频率。他们之间有个换算规则,APLL的换算规则如下:
在这里我们将APLL配置成输出1332MHZ的时钟频率,需要将SDIV的值设为0,PDIV的值设为3,MDIV的值设为333。
主意最后还要将APLL使能哦,即将第31位设为1。
为了简化开发,一般会有如下固定值给开发人员来设定:
(3)设置分频值
ARMCLK需要通过DIVAPLL和DIVARM两个分频器分频后得到,手册上说明这两个分频器必须有一个的分频值要大于1。
从手册找到设置分频器的寄存器,如下图所示:
DIVAPLL的值设为1,DIVARM的值设为0,DIVD0_BUS的值设为3,DIVPCLK0的值设为1 即可得到:
ARMCLK = 1332 / 2 = 666MHZ
HCLK0 = 666 / 4 = 166MHZ
PCLK0 = 166 / 2 = 83MHZ
最后给出配置的实验代码:
system_clock_init:
ldr r0,=S5PC100_CLOCK_BASE
/*set clock divider*/
ldr r1,=(1 << 0) + (0 << 4) + (3 << 8) + ( 1 << 12) + ( 1 << 16);
str r1,[r0,#0x0300]
ldr r1,=(1 << 12) + (1 << 16);
str r1,[r0,#0x0304]
/*set lock time*/
ldr r1,=S5PC100_PLL_MASKTIME
str r1,[r0,#0x0000]
str r1,[r0,#0x0004]
str r1,[r0,#0x0008]
str r1,[r0,#0x000c]
@APLL_CON ~1334MHZ
@SDIV 0, PDIV 3, MDIV 333 1332MHZ
@ldr r1,=(APLL_MDIV << 16) + (APLL_PDIV << 8) + (APLL_SDIV << 0)
ldr r1,=(1 << 31) + (333 << 16) + (3 << 8) + ( 0 << 0)
str r1,[r0,#0x0100]
@MPLL_CON ~600MHZ
@SDIV 1, PDIV 3,MDIV 133 266MHZ
@ldr r1,=(MPLL_MDIV << 16) + (MPLL_PDIV << 8) + (MPLL_SDIV << 0)
ldr r1,=(1 << 31) + (133 << 16) + (3 << 8) + (1 << 0)
str r1,[r0,#0x0104]
/*Set Source Clock*/
ldr r1,=0x11 @APLL_SEL 1,MPLL_SEL 1
str r1,[r0,#0x0200] @CLK_SRC0
/*wait at least 200us to stablize all clock*/
ldr r2,=0x10000
1: subs r2,r2,#1
bne 1b
mov pc,lr
stop:
b stop
最后顺便提一下,有些人总是在思考一个问题”我的模块具体属于哪一个时钟域呢”。这个问题手册上是有说明的,可以通过 CLK_GATE寄存器知道,例如S5PC100的PWM模块,它的时钟是由谁提供的呢?
从上图可以知道,它是由D1时钟域的PCLK提供的。
如何知道自己的时钟是否配置正确了,可以通过示波器去检测,也可以通过流水灯实验观察流水灯的速度。这里我们将ARMCLK的时钟设为了667MHZ,此时观看一下流水灯的速度,将ARMCLK时钟域调低一点(怎么调呀,设对应分频器的值就可以了哦),观看流水灯的速度,可以看到明显变慢。
最后附上整个实验代码:
clock.rar