前言
首先,我们应该知道一点,mini2440开发板在没有开启时钟前,整个开发板全靠一个12MHz的外部晶振提供频率来工作运行的,也就是说CPU、内存、UART、ADC等所有需要用到时钟频率的硬件都工作在12MHz下,而S3C2440A可以正常工作在400MHz下,可想而知两者速度相差会有多大了。如果CPU工作在12MHz频率下,开发板的使用效率非常低,所有依赖系统时钟工作的硬件,其工作效率也很低,比如,我们电脑里面经常提到的超频,超频就是让CPU工作在更高的频率下,让电脑运算速度更快,虽然频率是越高越好,但是由于硬件特性决定了任何一个设备都不可能无止境的超频,电脑超频时要考虑到CPU或主板发热过大,烧坏的危险,同样开发板的主板上的外设和CPU也有一个频率限度,ARM920T内核的S3C2440的最高正常工作频率如下:
● FCLK:400MHz
● HCLK:100MHz
● PCLK:50MHz
那么咱们怎样让CPU工作在400MHz下,运行的速度大为提高呢?(本段主要是别的老师的话,嘿嘿,借用没事,只要吸收成自己的知识就行了)
S3C2440有关的时钟种类
总体来说,与S3C2440处理器有关的时钟主要有4种:Fin、FCLK、HCLK、PCLK。
● Fin:外部输入的晶振频率。
● FCLK:用于CPU核。 由Fin得来
● HCLK:用在与AHB总线互连的设备(如存储控制器、LCD控制器、NAND、中断控制器、DMA等)上。 由FCLK得来
● PCLK:用在与APB总线互连的低速设备(如定时器、UART、ADC等)上。 由FCLK得来
为什么需要不同种类的时钟呢?
由于不同的硬件外设工作时需要的额定频率不同,所以需要产生不同种类的时钟频率。也就是说,对于一些需要时钟工作的硬件,如果切断其时钟源,就不会再工作了,从而达到低功耗的目的,这也是便携嵌入式设备的一个特点。
时钟源:开发板外部时钟频率太高容易受到外界环境的干扰,同时为了降低成本,通常开发板的外部晶振时钟频率都很低,mini2440开发板就用用1个12MHz的晶振来提供时钟源。但是S3C2440处理器内部工作频率较高,这就需要用锁相环(PLL)来实现倍频功能。
锁相环PLL
锁相环是实现倍频功能的,说白了就是将12MHz成倍的增加,达到实际所需频率。虽然锁相环有很多指标,咱们完全可以将其理解为一个时钟变换电路,低频晶振输入即可得到处理器所使用的较高频率的时钟。
S3C2440里有两个PLL:MPLL和UPLL。MPLL用来产生FCLK、HCLK、PCLK的高频工作时钟,UPLL用来为USB提供工作频率。下图为Fin通过MPLL产生FCLK、HCLK、FCLK的框图。
上图还有两个控制寄存器(MPLLCON和CLKDIVN),分别用于控制分频比。
● MPLLCON控制FCLK和Fin的比例关系
● CLKDIVN控制FCLK、HCLK和PCLK之间的比例关系
Fin通过UPLL产生USB设备正常工作所需要的时钟频率,工作原理与上面的MPLL类似。
系统时钟初始化
这一节很重要啊!!!一定要好好理解,明白系统时钟初始化的流程。
系统上电后,S3C2440处理器会自动锁存OM3和OM2引脚的电平值,这两个引脚用于选择外部时钟输入方式,如下表所示。你可以从我们的mini2440开发板的电路图看到,开发板上的OM3和OM2均接地,即OM[3:2]=00。所以,时钟源为外部晶振。
模式 OM[3:2] |
MPLL状态 |
UPLL状态 |
主时钟源 |
USB 时钟源 |
00 |
开启 |
开启 |
晶振 |
晶振 |
01 |
开启 |
开启 |
晶振 |
外部时钟 |
10 |
开启 |
开启 |
外部时钟 |
晶振 |
11 |
开启 |
开启 |
外部时钟 |
外部时钟 |
注意:虽然MPLL在复位后就开启,MPLL输出(Mpll)并没有作为系统时钟,直到软件写入有效值来设置MPLLCON寄存器。在设置此值之前,是将外部晶振或外部时钟源提供的时钟直接作为系统时钟。即使用户不想改变MPLLCON寄存器的默认值,用户也应当写入与之相同的值到MPLLCON寄存器中。
咱们再分析上图1,系统时钟初始化流程如下:
①系统刚上电几毫秒后,FCLK等于外部晶振(OSC)的时钟频率,即FCLK=Fin;
②当复位信号nRESET恢复高电平后,锁相环按照寄存器MPLLCON和CLKDIVN设定的倍频比例开始生成所需要的时钟频率。从图1可以看到,从锁相环开始工作到输出新的稳定的频率值需要一定的时间(Lock Time,也叫锁相环的捕获时间),经过这段时间后,锁相环输出新的频率值,这时FCLK等于锁相环的输出。寄存器LOCKTIME中的值对应着图1中的Lock Time,初始化时一般将其设为0xffffff,这是S3C2440数据手册上给出的默认值,一般按照这个值初始化LOCKTIME寄存器即可满足要求。
③经过一段时间后,锁相环PLL输出新的时钟频率。
FCLK、HCLK、PCLK与Fin的关系
相信你看完上面的关于初始化的,应该大概懂了初始化流程,但可能还是不知道怎么产生对应的MCLK、PCLK和FCLK。
那么,如何控制锁相环PLL的输出频率呢?S3C2440处理器内部有两个寄存器:MPLLCON寄存器控制FCLK与Fin的比例关系,CLKDIVN寄存器控制FCLK和HCLK、PCLK的比例关系,咱也可以笼统的用下图表示这四者的关系
(1)Fin得到FCLK
对于S3C2440,Fin与FCLK之间的关系为:FCLK=(2*m*Fin) / (p*2^s)。 多说一句,对于S3C2410,Fin与FCLK之间的关系为:FCLK=(m*Fin) / (p*2^s)。 这也是在U-boot移植时需要注意的一点。
在上式中,m=MDIV+8,p=PDIV+2,s=SDIV。其中,MDIV、PDIV和SDIV是MPLLCON寄存器中的数据,如图2所示
注意:在上图2中,可以看到最下面的NOTE用来提醒读者注意,在系统初始化阶段,如果UPLL和MPLL都需要设置,应该先初始化UPLL(USB时钟),然后等待大约7个nop指令(控指令)后,再初始化MPLL。
现在咱们应该有个大概的意识了,设置MPLLCON中相应的位,就可以通过Fin获得FCLK了。但是这家伙还得一步步的算MDIV、PDIV、SDIV的值,太麻烦了,对于我这懒人,这样不好不好。那么怎么得到这三者的值呢?
虽然PLL给用户提供了灵活变换系统时钟的功能,但是,并不是任意的时钟下处理器都能正常工作,基于此种原因,为了照顾我等懒人,官方给出了系统时钟配置参考,如图3所示。
下面以第5行为例讲解,假设外部晶振输入为12MHz,MDIV=127,PDIV=1,SDIV=1,则m=127+8=135,,p=2+2=4,s=1。则FCLK=(2*2*135)/(4*2^1)=405MHz。
小技巧:由上面的分析可以看到,MDIV、PDIV和SDIV都是占用了MPLLCON的连续某几位,如PDIV是MPLLCON寄存器的第4~9位。因此,初始化时可以使用移位指令来实现对MPLLCON的初始化。
其实咱们经常用的就是FCLK=400MHz和FCLK=200MHz,你只管记住这两个对应的值就行了。
例如,已知系统外部晶振输入为12MHz,要求FCLK输出200MHz,经过计算可以得到MDIV=92,PDIV=4,SDIV=1。
对于ARM汇编,可以用下面的指令进行初始化。
M_MDIV EQU 92 ;Fin=12MHz,Fout=200MHz
M_PDIV EQU 4
M_SDIV EQU 1 ;S3C2440
ldr r0,=MPLLCON ;将MPLLCON寄存器的地址值暂存于r0
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;将设置MPLLCON的值暂存于r1
str r1,[r0] ;将r1寄存器的值赋给MPLLCON寄存器
对于C语言,可以用下面的指令进行初始化
MPLLCON=((92<<12)|(4<<4)|(1<<0)); //貌似C语言很简单呢
如果要求FCLK输出400MHz,那么相对应的值MDIV=92,PDIV=1,SDIV=1。这两组值就是经常用到的。
(2)FCLK得到HCLK、PCLK
上一步通过Fin得到了FCLK,下面就可以通过FCLK获得相应的PCLK、HCLK了。主要就是设置CLKDIVN寄存器,来实现FCLK、HCLK、PCLK之间的分频比。
在CLKDIVN寄存器中,HDIVN用于控制FCLK和PCLK的比例关系,PDIVN主要用于控制HCLK和PCLK的比例关系,如图4所示:
● 当CLKDIV_VAL=0 时,FCLK:HCLK:PCLK=1:1:1。
● 当CLKDIV_VAL=1 时,FCLK:HCLK:PCLK=1:1:2。
● 当CLKDIV_VAL=2 时,FCLK:HCLK:PCLK=1:2:2。
● 当CLKDIV_VAL=3 时,FCLK:HCLK:PCLK=1:2:4。
● 当CLKDIV_VAL=4 时,FCLK:HCLK:PCLK=1:4:4。
● 当CLKDIV_VAL=5 时,FCLK:HCLK:PCLK=1:4:8。
● 当CLKDIV_VAL=6 时,FCLK:HCLK:PCLK=1:3:3。
● 当CLKDIV_VAL=7 时,FCLK:HCLK:PCLK=1:3:6。
有的人可能会问,CLKDIV_VAL是什么?其实就是一个宏定义,方便直观,因为CLKDIVN的DIVN_UPLL、HDIVN、PDIVN是连续的低4个位,所以根据你要设置的分频
比,一起赋值个CLKDIVN就可以了,不用再移位什么的了。
还是拿上面举例,已知系统外部晶振输入为12MHz,要求FCLK=200MHz,HCLK=100MHz,PCLK=50MHz,即FCLK:HCLK:PCLK=1:2:4,则CLKDIV_VAL=3。
对于ARM汇编,可以用下面的指令进行初始化。
M_MDIV EQU 92 ;Fin=12MHz,Fout=200MHz
M_PDIV EQU 4
M_SDIV EQU 1 ;S3C2440
CLKDIV_VAL EQU 3
ldr r0,=CLKDIVN ;将CLKDIVN寄存器的地址值暂存于r0
ldr r1,=CLKDIV_VAL ;将CLKDIV_VAL暂存于r1
ldr r1,[r0] ;将CLKDIV_VAL赋给CLKDIVN
ldr r0,=MPLLCON ;将MPLLCON寄存器的地址值暂存于r0
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;将设置MPLLCON的值暂存于r1
str r1,[r0] ;将r1寄存器的值赋给MPLLCON寄存器
时钟初始化实验
下面是完整的时钟初始化代码
ARM汇编版本:
;Fin=12MHz,FCLK=400MHz,HCLK=100MHz,PCLK=50MHz
;时钟相关寄存器地址宏定义
LOCKTIME EQU 0x4c000000
MPLLCON EQU 0x4c000004
CLKDIVN EQU 0x4c000014
;设置值宏定义
M_MDIV EQU 92 ;Fin=12MHz,Fout=400MHz
M_PDIV EQU 1
M_SDIV EQU 1 ;S3C2440
CLKDIV_VAL EQU 5 ;FCLK:HCLK:PCLK=1:4:8
Clock_Init ;时钟初始化代码,Clock_Init为标号
ldr r0,=LOCKTIME ;设置变频锁定时间
ldr r1,=0x00ffffff
str r1,[r0]
ldr r0,=CLKDIVN ;设置分频比FCLK:HCLK:PCLK=1:4:8
ldr r1,=CLKDIV_VAL
str r1,[r0]
;注意如果HDIVN设置为非0,CPU的总线模式要进行改变,默认情况下FCLK=HCLK,CPU工作在快速总线模式(fast bus mode)下,
;HDIVN设置为非0后,FCLK与HCLK不再相等,要将CPU改为异步总线模式(asynchronous bus mod)下,如下面代码所示 mrc p15,0,r1,c1,c0,0 ;修改CPU总线模式 为异步总线模式
orr r1,r1,#0xc0000000
mcr p15,0,r1,c1,c0,0
ldr r0,=MPLLCON ;FCLK=400MHz
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV)
str r1,[r0]
mov pc,lr ;函数调用子程序返回
C语言版本:
#define MPLL_400MHz ((92<<12)|(1<<4)|(1<<0))
#define CLKDIV_VAL 5
void Clock_Init(void)
{
//设置变频锁定时间
LOCKTIME=0x00ffffff;
//设置分频比FCLK:HCLK:PCLK=1:4:8
CLKDIVN=CLKDIV_VAL;
//修改CPU总线模式,由于修改CPU总线模式时要使用mrc指令,因此只能使用C语言嵌入汇编方式来实现。
__asm__(
"mrc p15,0,r1,c1,c0,0\n"
"orr r1,r1,#0xc0000000\n"
"mcr p15,0,r1,c1,c0,0\n"
);
MPLLCON=MPLL_400MHz;
}
UPLL设置
前边咱们都将UPLL的设置略过了,下面讲讲它吧。已经说过了,S3C2440有两个锁相环PLL,其中UPLL就是提供USB设备正常工作所需要的时钟频率的,工作原理其实与MPLL的一样,它是直接通过设置UPLL得到的,如下图:
它的计算公式为:USB设备工作频率=(m*Fin) / (p*2^s)。其中m、s、p与MPLL的一模一样!以下是S3C2440的数据手册截图
下面就举个例子讲讲怎么设置UPLL吧!
已知Fin=12MHz,设置FCLK=400MHz、HCLK=100MHz、PCLK=50MHz、UCLK=48MHz
ARM汇编:
;时钟寄存器
LOCKTIME EQU 0x4c000000
CLKDIVN EQU 0x4c000014
MPLLCON EQU 0x4c000004
UPLLCON EQU 0x4c000008
;FCLK=400MHz、HCLK=100MHz、PCLK=50MHz
M_MDIV EQU 92
M_PDIV EQU 1
M_SDIV EQU 1
CLKDIV_VAL EQU 5 ;分频比
;UCLK=48MHz
U_MDIV EQU 56
U_PDIV EQU 2
U_SDIV EQU 2
;设置锁定时间
ldr r0,=LOCKTIME
ldr r1,=0x00ffffff
str r1,[r0]
;设置分频比
ldr r0,=CLKDIVN
ldr r1,=CLKDIV_VAL
str r1,[r0]
;修改总线模式
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000
mcr p15,0,r0,c1,c0,0
;配置UPLL
ldr r0,=UPLLCON
;Fin=12.0MHz,UCLK=48MHz
ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)
str r1,[r0]
nop ;先设置UPLL,间隔7个空指令后,再设置MPLL
nop
nop
nop
nop
nop
nop
;配置MPLL
ldr r0,=MPLLCON
;Fin=12.0MHz,FCLK=400MHz
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV)
str r1,[r0]
总结
至此,关于S3C2440的时钟已经整理完了,它是学习下一节定时器知识的前提,希望对你有所帮助!
补充说明:我纠正自己说的一些错误,我说的以上关于初始化时钟时的嵌入汇编__asm__有错误,它可以在ADS下实现,但是在MDK中格式不对,我还没弄明白怎么用,等着弄明白了再回来补充,只是格式不对哈,原理其实都是对的。
另外,我还想说,其实咱们要初始化时钟,很简单的做法就是修改一下S3C2440.s中的一行代码就行了,也就是将CLOCK_SETUP EQU 0 修改为 CLOCK_SETUP EQU 1,如图:
这样,系统默认的初始化后的时钟为FCLK=300MHz、HCLK=100MHz、PCLK=50MHz。你可以从上图的MPLLCON_Val看出。