2440时钟设置浅谈(带程序)-gududesiling-ChinaUnix博客 http://blog.chinaunix.net/uid-25100840-id-271075.html
这篇我自己写的关于2440时钟控制的的理解,这下面的代码是我在一个“ram9之家论坛”(在这里宣传一下,呵呵)上貌似是一个叫做wi100sh的博主贴子那里下来的代码,相当经典(不过其中大部分都是友善官方的源码,只是翻译了一下),在这里面我就一步一步谈谈对这个裸机源码初始化时钟的理解,对于裸机的朋友,这是个相当不错的程序。
一.首先,我们看到#include "2440lib.h"#include "2440slib.h" 其实还需要2440init.s(不过这个文件在工程目录中引用了),这是裸机程序基本上必须的三个启动文件,,我也不是很清楚实在那里引用了。不过这不影响我们写裸机程序,这些太底层,还没到那个地步。
我们看这个Main()开始,从这里面,我们看到红字注释的地方,开始调用初始化时钟,函数在下面我列了出来
/**************************************************************
4*4 Key Scan
**************************************************************/
#define GLOBAL_CLK 1 //相当于定义了FCLK,HCLK,PCLK,UCLK
#include "def.h"
#include "option.h"
#include "2440addr.h"
#include "2440lib.h"
#include "2440slib.h"
#include "mmu.h"
#include "RGB.h"
extern void InitAllClock(void);
extern void Test_Touchpanel(void);
extern void TFT_LCD_Init(void);
int Main()
{//
InitAllClock(); //初始化时钟,由这个地方来调用初始化时钟函数这篇就从这开始
MMU_Init(); //初始化MMU
TFT_LCD_Init();
Test_Touchpanel();
//return 0;
while(1)
{
if(Uart_GetKey() == ESC_KEY)
{
Lcd_ClearScr(White);
}
}
}
二.以下是初始化时钟的一个文件,在这里,基本上把时钟设置好了,不过用户可以在这里面修改,很多都是默认值,
#include "def.h"
#include "2440addr.h"
/******************************************************************************Function name: ChangeClockDivider,设置分频,并写进并确定了rCLKDIVN的值,从而也确定了HCLK,PCLK的值
** Descriptions: 设置时钟寄存器CLKDIVN的分频比寄存器值,包括HDIVN和PDIVN
** Input: hdivn_val,pdivn_val分别对应寄存器HDIVN[2:1]和PDIVN[0]的值
//因为2440和2410不一样,重要的一点是hdivn(HCLK的分频位),2440是2位,2410是一位,也就确定了,2410只能2分频,而2440正常就能够4分频,如果加上rCAMDIVN的话,就能6分频,8分频。多说一点,因为2440//的是2位,如果为00,01的话,不用考虑rCAMDIVN,如果是10,11,时就要考虑了,看最后的图
***************************************************************/
static U32 cpu_freq;//系统总线的时钟频率
static U32 UPLL;
static void ChangeClockDivider(int hdivn_val,int pdivn_val) //static限定该函数为内部函数!
{
int hdivn,pdivn; //hdivn代替HDIVN=CLKDIVN[2:1], pdiv代替PDIVN=CLKDIVN[0]
hdivn=2; //初始化数值(默认值,呵呵),
pdivn=1;
switch(hdivn_val)
{
case 11:hdivn=0;break; //11表示FCLK:HCLK=1:1
case 12:hdivn=1;break; //12表示HCLK=FCLK/2 ,
case 13:hdivn=3;break; //13表示HCLK=FCLK/3
case 16: //16表示HCLK=FCLK/6
hdivn=3;
rCAMDIVN=(rCAMDIVN&~(3<<8))|(1<<8); //HDIVN=3且CAMDIVN[8]=1,则HCLK=FCLK/6
break;
case 14:hdivn=2;break; //14表示HCLK=FCLK/4 (这个就是默认值了,不过一般也是传的这个值,这个时钟文件中就是设置的这个值)
case 18: //18表示HCLK=FCLK/8
hdivn=2;
rCAMDIVN=(rCAMDIVN&~(3<<8))|(1<<9); //HDIVN=2且CAMDIVN[9]=1,则HCLK=FCLK/8
break; //16,18是分别是4分频,8分频,用到摄像头的时候用的,最后的图片
default:
hdivn=2;break; //设置默认值HCLK=FCLK/4
}
switch(pdivn_val) //变量pdivn_val的值就是寄存器PDIVN的值
{
case 11:pdivn=0;break; //11表示PCLK=HCLK
case 12:pdivn=1;break; //12表示PCLK=HCLK/2 //这个也是默认的值
}
rCLKDIVN=(hdivn<<1)|pdivn; //变量hdivn和pdivn的值分别是CLKDIVN寄存器中HDIVN和PDIVN的值
}
/******************************************************************************
** Function name: ChangeMPllValue
** Descriptions: 设置寄存器MPLLCON的值,确定下来FCLK的时钟值
** Input: mdiv,pdiv,sdiv 分别对应寄存器MPLLCON中的MDIV[19:12],PDIV[9:4],SDIV[1:0]
***************************************************************/
void ChangeMPllValue(int mdiv,int pdiv,int sdiv)
{
rMPLLCON=(mdiv<<12)|(pdiv<<4)|sdiv; //给寄存器MDIV,PDIV,SDIV赋值
}
/******************************************************************************
** Function name:cal_cup_bus_clk
** Descriptions: 读取寄存器的值并用来计算CPU总线的时钟频率,FCLK,HCLK,PCLK(就是得到3个数值,因为这三个数值是全局变量) 计算公式FCLK=MPLL=(2*(MDIV+8)*FIN)/((PDIV+2)*(1<<sdiv)) //FIN是系统输入的晶振频率12MHZ
** Input: 无
***************************************************************/
static void cal_cpu_bus_clk(void) //static关键词申明该函数为内部函数!
{
U32 val;
U8 m,p,s;
//从寄存器MPLLCON中提取MDIV,PDIV,SDIV的值,从而计算FCLK的值,就是MPLLout
val=rMPLLCON; //注意,之前在调用函数中已经设置好了rMPLLCON的值
m=(val>>12)&0xff; //m=MDIV=MPLLCON[19:12] 8位取值
p=(val>>4)&0x3f; //p=PDIV=MPLLCON[9:4] 6位取值
s=val&3; //s=SDIV=MPLLCON[1:0] 低2位取值
//(m+8)*FIN*2 不要超出32位数! 最大为0x100000000
FCLK=((m+8)*(FIN/100)*2)/((p+2)*(1<<s))*100; //(1<<s)< span="" style="word-wrap: break-word;">表示第s位置1,也就是2^s
//MPLL=(2*(MDIV+8)*FIN)/((PDIV+2)*(2^s)) FIN=12000000,从寄存器CLKDIVN中提取出HDIVN和PDIVN,准备进行FCLK:HCLK:PCLK分频比的设置
val=rCLKDIVN; //在调用函数里面已经设置了rCLKDIVN的值
m=(val>>1)&3; //m=CLKDIVN[2:1]=HDIVN
p=val&1; //p=CLKDIVN[1]=PDIVN
//CLKDIVN[3]=DIVN_UPLL
//从寄存器CAMDIVN中提取出CAMDIVN[9:8]
val=rCAMDIVN; //在调用函数里面已经设置了rCAMDIVN的值
s=val>>8; //s[1:0]=CAMDIVN[9:8]=HCLK4_HALF,HCLK3_HALF
//s[4]=CAMDIVN[12]=DVS_EN,如果DVS_EN=1,则CPU时钟为HCLK,否则为FCLK
switch (m) //这里的m=HDIVN,对FCLK和HCLK进行分频设置
{
case 0:
HCLK=FCLK;
break;
case 1:
HCLK=FCLK>>1; //HCLK=FCLK/2
break;
case 2:
if(s&2) //如果s[1]=1,也即CAMDIVN[9]=HCLK4_HALF=1
HCLK=FCLK>>3; //HCLK=FCLK/8
else
HCLK=FCLK>>2; //HCLK=FCLK/4
break;
case 3:
if(s&1) //如果s[0]=1,也即CAMDIVN[8]=HCLK3_HALF=1
HCLK=FCLK/6; //HCLK=FCLK/6
else
HCLK=FCLK/3; //HCLK=FCLK/3
break;
}
if(p) //这里p=PDIVN,对HCLK和PCLK进行分频设置
PCLK=HCLK>>1; //PDIVN=1,则PCLK=HCLK/2;否则PCLK=HCLK
else
PCLK=HCLK;
if(s&0x10) //如果s[4]=1,也即CAMDIVN[12]=DVS_EN=1
cpu_freq=HCLK; //s3c2440手册中CAMDIVN[12],也表示CPU总线是快速总线模式
else
cpu_freq=FCLK; //CPU总线频率,也表示CPU总线是异步模式
//设置UPLLCON寄存器,UPLLCON是用来设置usb的时钟的,还没学到, 将来学到了,再解释(和MPLLCON原理一样,不过不用*2)
val=rUPLLCON; //调用函数里面已经设置了rUPLLCON的值(在目前的程序还没有设置,应该是0)
m=(val>>12)&0xff; //m=MDIV=UPLLCON[19:12] 8位
p=(val>>4)&0x3f; //p=PDIV=UPLLCON[9:4] 6位
s=val&3; //s=SDIV=UPLLCON[1:0] 2位
UPLL=((m+8)*FIN)/((p+2)*(1<<s)); //公式UPLL=((MDIV+8)*FIN)/((PDIV+2)*(2^s)) FIN为输入时钟频率
UCLK=(rCLKDIVN&8)?(UPLL>>1):UPLL; //如果CLKDIVN[3]=1,则UPLL需要分频
}
/******************************************************************************
** Function name: InitAllClock
** Descriptions: 初始化时钟,包括FCLK,HCLK,PCLK,UPLL,cpu_freq(系统时钟)
** 变量说明: i=0时FCLK=200MHZ,HCLK=FCLK/2=100MHZ,PCLK由pdivn_val来确定,pdivn_val=1,则PCLK=HCLK/2
i=1时 FCLK=300MHZ,HCLK=FCLK/3=100MHZ,PCLK由pdivn_val来确定,pdivn_val=1,则PCLK=HCLK/2
i=2时 FCLK=400MHZ,HCLK=FCLK/4=100MHZ,PCLK由pdivn_val来确定,pdivn_val=1,则PCLK=HCLK/2
i=3时 FCLK=440MHZ,HCLK=FCLK/4=110MHZ,PCLK由pdivn_val来确定,pdivn_val=1,则PCLK=HCLK/2
*************************************************************************/
void InitAllClock()
{
U8 i;
U32 mpll_val;
int mdiv,pdiv,sdiv; //mdiv,pdiv,sdiv分别对应寄存器MPLLCON中的MDIV,PDIV,SDIV
int hdivn_val,pdivn_val; //hdivn_val,pdivn_val分别对应寄存器CLKDIVN中的HDIVN和PDIVN
i=2; //设定i的初始值 don't use 100M!==>因为200M<=FCLK<=600M !!!
//因为这个地方i=2,导致下面的设置,系统cpu是时钟为400Mhz,如果我们想要其他的频率的话,就要改变 / /这个i值了,具体多大这在函数的开头说明了
switch(i)
{
//FCLK=MPLLout=(2*(MDIV+8)*FIN/100)/((PDIV+2)*(1<
// hdivn_val这个参数是用来设置HCLK的(12,代表FCLK:HCLK=1:2,,13:代表1:3终于明白了,这也真是为难了友善的技术师门,呵呵)
case 0: //FCLK=200M
hdivn_val=12; //hdivn_val=HDIVN,12表示HCLK=FCLK/2
mpll_val=(92<<12)|(4<<4)|(1); //FCLK=200M,
break;
case 1: //FCLK=300M
hdivn_val=13;
mpll_val=(67<<12)|(1<<4)|(1);
break;
case 2: //FCLK=400M
hdivn_val=14;
mpll_val=(92<<12)|(1<<4)|(1);
break;
case 3: //FCLK=440M
hdivn_val=14;
mpll_val=(102<<12)|(1<<4)|(1);
break;
default: //FCLK=400M
hdivn_val=14;
mpll_val=(92<<12)|(1<<4)|(1);
break;
}
mdiv=(mpll_val>>12)&0xff; //实际这个地方感觉多此一举,不过也是为了让用户修改方便吧(指的是上面)
pdiv=(mpll_val>>4)&0x3f;
sdiv=(mpll_val)&3;
//init FCLK=400M, so change MPLL first
ChangeMPllValue(mdiv,pdiv,sdiv);//初始化MPLLCON的值,这里默认是MDIV=92,PDIV=1,SDIV=1,上面的这个函数调用完后,就已经确认了FCLK的值了
pdivn_val=12; //表示PCLK=HCLK/2 设定的初值(即设置PCLK的分频
//数,这个是相对于HCLK而言的,即HCKL:PCLK=1:2,用于下面最用的设定值
//根据上面设置好的2个比例参数,来最终确定HCLK和PCLK的值(通过给寄
器赋值)//这一点要注意一下,如果想要使用摄像头的话,在这里,要最后设定
一下hdivn_val的值,要不,系统不会对//摄像头时钟赋值的,即不会对
CAMDIVN进行赋值,关于CAMDIVN对HCLK的影响,最后有一张图,不过大//
家也可以参考2440的技术手册。
ChangeClockDivider(hdivn_val, pdivn _val); //初始化寄存器CLKDIVN和CAMDIVN
//默认值hdivn_val(上面switch中得到的) =14,表示hdivn=2,HCLK=FCLK/4=100MHZ;第二个参数12表示pdivn=1,PCLK=HCLK/2=50MHZ
cal_cpu_bus_clk(); //调用这个函数的说明我改了一下,我认为这个函数,
是一个读取时钟函数
//因为时钟的设置在上面的2个重要函数中已经设置好了,而这个函数cal,主要
//是用来给这几个外部变量FCLK,HCLK,PCLK,UPLL,UCLK,cpu_freq,赋值用
//的,用来将来对这几个时钟值的读取,并作为外围芯片的时序参考
}
图片(摘自2440技术手册)
解释了这么多了,实际时钟控制就是,要根据自己需要的始终设置一下MPLLCON的值,而这个值的设定确实需要三个m,p,s(用来实现乘除,最终倍频),确定下来FCLK,然后又hdivn ,hdivn来设定三个时钟的分频比,就最终确定的三大时钟(不过对于2440,还要考虑一下CAMDIVN的影响)
这些弄了这么长时间,不过也确实学习了很多东西,让我对时钟不在迷惑,在裸机的道路上又迈进了一步,在这里再次谢谢“ram9之家论坛“和上面的博主wi100sh,是这些人的无私奉献,让我学习到了很多,因此,如果我学会了,也会无私的奉献给大家
最后,如果谁要是想要这些文件的源码,请联系我,qq:117838621
Arm9之家论坛网址:http://www.arm9home.net/read.php?tid=5808&page=3#51406,希望更多的人来那里讨论,也希望大家都不要放弃,一定要坚持下去。谢谢